001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.directory.shared.ldap.entry.client;
020
021
022 import java.io.Externalizable;
023 import java.io.IOException;
024 import java.io.ObjectInput;
025 import java.io.ObjectOutput;
026 import java.util.Arrays;
027
028 import javax.naming.NamingException;
029
030 import org.apache.directory.shared.i18n.I18n;
031 import org.apache.directory.shared.ldap.NotImplementedException;
032 import org.apache.directory.shared.ldap.entry.AbstractValue;
033 import org.apache.directory.shared.ldap.entry.Value;
034 import org.apache.directory.shared.ldap.schema.Normalizer;
035 import org.apache.directory.shared.ldap.schema.comparators.ByteArrayComparator;
036 import org.apache.directory.shared.ldap.util.StringTools;
037 import org.slf4j.Logger;
038 import org.slf4j.LoggerFactory;
039
040
041 /**
042 * A server side schema aware wrapper around a binary attribute value.
043 * This value wrapper uses schema information to syntax check values,
044 * and to compare them for equality and ordering. It caches results
045 * and invalidates them when the wrapped value changes.
046 *
047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
048 * @version $Rev$, $Date$
049 */
050 public class ClientBinaryValue extends AbstractValue<byte[]>
051 {
052 /** Used for serialization */
053 private static final long serialVersionUID = 2L;
054
055 /** logger for reporting errors that might not be handled properly upstream */
056 private static final Logger LOG = LoggerFactory.getLogger( ClientBinaryValue.class );
057
058
059 /**
060 * Creates a ServerBinaryValue without an initial wrapped value.
061 *
062 * @param attributeType the schema type associated with this ServerBinaryValue
063 */
064 public ClientBinaryValue()
065 {
066 wrapped = null;
067 normalized = false;
068 valid = null;
069 normalizedValue = null;
070 }
071
072
073 /**
074 * Creates a ServerBinaryValue with an initial wrapped binary value.
075 *
076 * @param attributeType the schema type associated with this ServerBinaryValue
077 * @param wrapped the binary value to wrap which may be null, or a zero length byte array
078 */
079 public ClientBinaryValue( byte[] wrapped )
080 {
081 if ( wrapped != null )
082 {
083 this.wrapped = new byte[ wrapped.length ];
084 System.arraycopy( wrapped, 0, this.wrapped, 0, wrapped.length );
085 }
086 else
087 {
088 this.wrapped = null;
089 }
090
091 normalized = false;
092 valid = null;
093 normalizedValue = null;
094 }
095
096
097 // -----------------------------------------------------------------------
098 // Value<String> Methods
099 // -----------------------------------------------------------------------
100 /**
101 * Reset the value
102 */
103 public void clear()
104 {
105 wrapped = null;
106 normalized = false;
107 normalizedValue = null;
108 valid = null;
109 }
110
111
112
113
114 /*
115 * Sets the wrapped binary value. Has the side effect of setting the
116 * normalizedValue and the valid flags to null if the wrapped value is
117 * different than what is already set. These cached values must be
118 * recomputed to be correct with different values.
119 *
120 * @see ServerValue#set(Object)
121 */
122 public final void set( byte[] wrapped )
123 {
124 // Why should we invalidate the normalized value if it's we're setting the
125 // wrapper to it's current value?
126 byte[] value = getReference();
127
128 if ( value != null )
129 {
130 if ( Arrays.equals( wrapped, value ) )
131 {
132 return;
133 }
134 }
135
136 normalizedValue = null;
137 normalized = false;
138 valid = null;
139
140 if ( wrapped == null )
141 {
142 this.wrapped = null;
143 }
144 else
145 {
146 this.wrapped = new byte[ wrapped.length ];
147 System.arraycopy( wrapped, 0, this.wrapped, 0, wrapped.length );
148 }
149 }
150
151
152 // -----------------------------------------------------------------------
153 // ServerValue<String> Methods
154 // -----------------------------------------------------------------------
155 /**
156 * Gets a direct reference to the normalized representation for the
157 * wrapped value of this ServerValue wrapper. Implementations will most
158 * likely leverage the attributeType this value is associated with to
159 * determine how to properly normalize the wrapped value.
160 *
161 * @return the normalized version of the wrapped value
162 * @throws NamingException if schema entity resolution fails or normalization fails
163 */
164 public byte[] getNormalizedValueCopy()
165 {
166 if ( normalizedValue == null )
167 {
168 return null;
169 }
170
171 byte[] copy = new byte[ normalizedValue.length ];
172 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
173 return copy;
174 }
175
176
177 /**
178 * Normalize the value. For a client String value, applies the given normalizer.
179 *
180 * It supposes that the client has access to the schema in order to select the
181 * appropriate normalizer.
182 *
183 * @param Normalizer The normalizer to apply to the value
184 * @exception NamingException If the value cannot be normalized
185 */
186 public final void normalize( Normalizer normalizer ) throws NamingException
187 {
188 if ( normalizer != null )
189 {
190 if ( wrapped == null )
191 {
192 normalizedValue = wrapped;
193 normalized = true;
194 same = true;
195 }
196 else
197 {
198 normalizedValue = normalizer.normalize( this ).getBytes();
199 normalized = true;
200 same = Arrays.equals( wrapped, normalizedValue );
201 }
202 }
203 }
204
205
206 /**
207 *
208 * @see ServerValue#compareTo(ServerValue)
209 * @throws IllegalStateException on failures to extract the comparator, or the
210 * normalizers needed to perform the required comparisons based on the schema
211 */
212 public int compareTo( Value<byte[]> value )
213 {
214 if ( isNull() )
215 {
216 if ( ( value == null ) || value.isNull() )
217 {
218 return 0;
219 }
220 else
221 {
222 return -1;
223 }
224 }
225 else
226 {
227 if ( ( value == null ) || value.isNull() )
228 {
229 return 1;
230 }
231 }
232
233 if ( value instanceof ClientBinaryValue )
234 {
235 ClientBinaryValue binaryValue = ( ClientBinaryValue ) value;
236
237 return new ByteArrayComparator( null ).compare( getNormalizedValue(), binaryValue.getNormalizedValue() );
238 }
239
240 String message = I18n.err( I18n.ERR_04127 );
241 LOG.error( message );
242 throw new NotImplementedException( message );
243 }
244
245
246 // -----------------------------------------------------------------------
247 // Object Methods
248 // -----------------------------------------------------------------------
249
250
251 /**
252 * @see Object#hashCode()
253 * @return the instance's hashcode
254 */
255 public int hashCode()
256 {
257 // return zero if the value is null so only one null value can be
258 // stored in an attribute - the string version does the same
259 if ( isNull() )
260 {
261 return 0;
262 }
263
264 return Arrays.hashCode( getNormalizedValueReference() );
265 }
266
267
268 /**
269 * Checks to see if this ServerBinaryValue equals the supplied object.
270 *
271 * This equals implementation overrides the BinaryValue implementation which
272 * is not schema aware.
273 * @throws IllegalStateException on failures to extract the comparator, or the
274 * normalizers needed to perform the required comparisons based on the schema
275 */
276 public boolean equals( Object obj )
277 {
278 if ( this == obj )
279 {
280 return true;
281 }
282
283 if ( ! ( obj instanceof ClientBinaryValue ) )
284 {
285 return false;
286 }
287
288 ClientBinaryValue other = ( ClientBinaryValue ) obj;
289
290 if ( isNull() )
291 {
292 return other.isNull();
293 }
294
295 // now unlike regular values we have to compare the normalized values
296 return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
297 }
298
299
300 // -----------------------------------------------------------------------
301 // Private Helper Methods (might be put into abstract base class)
302 // -----------------------------------------------------------------------
303 /**
304 * @return a copy of the current value
305 */
306 public ClientBinaryValue clone()
307 {
308 ClientBinaryValue clone = (ClientBinaryValue)super.clone();
309
310 if ( normalizedValue != null )
311 {
312 clone.normalizedValue = new byte[ normalizedValue.length ];
313 System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
314 }
315
316 if ( wrapped != null )
317 {
318 clone.wrapped = new byte[ wrapped.length ];
319 System.arraycopy( wrapped, 0, clone.wrapped, 0, wrapped.length );
320 }
321
322 return clone;
323 }
324
325
326 /**
327 * Gets a copy of the binary value.
328 *
329 * @return a copy of the binary value
330 */
331 public byte[] getCopy()
332 {
333 if ( wrapped == null )
334 {
335 return null;
336 }
337
338
339 final byte[] copy = new byte[ wrapped.length ];
340 System.arraycopy( wrapped, 0, copy, 0, wrapped.length );
341 return copy;
342 }
343
344
345 /**
346 * Tells if the current value is Binary or String
347 *
348 * @return <code>true</code> if the value is Binary, <code>false</code> otherwise
349 */
350 public boolean isBinary()
351 {
352 return true;
353 }
354
355
356 /**
357 * @return The length of the interned value
358 */
359 public int length()
360 {
361 return wrapped != null ? wrapped.length : 0;
362 }
363
364
365 /**
366 * Get the wrapped value as a byte[]. This method returns a copy of
367 * the wrapped byte[].
368 *
369 * @return the wrapped value as a byte[]
370 */
371 public byte[] getBytes()
372 {
373 return getCopy();
374 }
375
376
377 /**
378 * Get the wrapped value as a String.
379 *
380 * @return the wrapped value as a String
381 */
382 public String getString()
383 {
384 return StringTools.utf8ToString( wrapped );
385 }
386
387
388 /**
389 * @see Externalizable#readExternal(ObjectInput)
390 */
391 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
392 {
393 // Read the wrapped value, if it's not null
394 int wrappedLength = in.readInt();
395
396 if ( wrappedLength >= 0 )
397 {
398 wrapped = new byte[wrappedLength];
399
400 if ( wrappedLength > 0 )
401 {
402 in.read( wrapped );
403 }
404 }
405
406 // Read the isNormalized flag
407 normalized = in.readBoolean();
408
409 if ( normalized )
410 {
411 int normalizedLength = in.readInt();
412
413 if ( normalizedLength >= 0 )
414 {
415 normalizedValue = new byte[normalizedLength];
416
417 if ( normalizedLength > 0 )
418 {
419 in.read( normalizedValue );
420 }
421 }
422 }
423 }
424
425
426 /**
427 * @see Externalizable#writeExternal(ObjectOutput)
428 */
429 public void writeExternal( ObjectOutput out ) throws IOException
430 {
431 // Write the wrapped value, if it's not null
432 if ( wrapped != null )
433 {
434 out.writeInt( wrapped.length );
435
436 if ( wrapped.length > 0 )
437 {
438 out.write( wrapped, 0, wrapped.length );
439 }
440 }
441 else
442 {
443 out.writeInt( -1 );
444 }
445
446 // Write the isNormalized flag
447 if ( normalized )
448 {
449 out.writeBoolean( true );
450
451 // Write the normalized value, if not null
452 if ( normalizedValue != null )
453 {
454 out.writeInt( normalizedValue.length );
455
456 if ( normalizedValue.length > 0 )
457 {
458 out.write( normalizedValue, 0, normalizedValue.length );
459 }
460 }
461 else
462 {
463 out.writeInt( -1 );
464 }
465 }
466 else
467 {
468 out.writeBoolean( false );
469 }
470 }
471
472
473 /**
474 * Dumps binary in hex with label.
475 *
476 * @see Object#toString()
477 */
478 public String toString()
479 {
480 if ( wrapped == null )
481 {
482 return "null";
483 }
484 else if ( wrapped.length > 16 )
485 {
486 // Just dump the first 16 bytes...
487 byte[] copy = new byte[16];
488
489 System.arraycopy( wrapped, 0, copy, 0, 16 );
490
491 return "'" + StringTools.dumpBytes( copy ) + "...'";
492 }
493 else
494 {
495 return "'" + StringTools.dumpBytes( wrapped ) + "'";
496 }
497 }
498 }