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.ldap.NotImplementedException;
031 import org.apache.directory.shared.ldap.entry.AbstractValue;
032 import org.apache.directory.shared.ldap.entry.Value;
033 import org.apache.directory.shared.ldap.schema.Normalizer;
034 import org.apache.directory.shared.ldap.schema.comparators.ByteArrayComparator;
035 import org.apache.directory.shared.ldap.util.StringTools;
036 import org.slf4j.Logger;
037 import org.slf4j.LoggerFactory;
038
039
040 /**
041 * A server side schema aware wrapper around a binary attribute value.
042 * This value wrapper uses schema information to syntax check values,
043 * and to compare them for equality and ordering. It caches results
044 * and invalidates them when the wrapped value changes.
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 * @version $Rev$, $Date$
048 */
049 public class ClientBinaryValue extends AbstractValue<byte[]>
050 {
051 /** Used for serialization */
052 private static final long serialVersionUID = 2L;
053
054 /** logger for reporting errors that might not be handled properly upstream */
055 private static final Logger LOG = LoggerFactory.getLogger( ClientBinaryValue.class );
056
057
058 /**
059 * Creates a ServerBinaryValue without an initial wrapped value.
060 *
061 * @param attributeType the schema type associated with this ServerBinaryValue
062 */
063 public ClientBinaryValue()
064 {
065 wrapped = null;
066 normalized = false;
067 valid = null;
068 normalizedValue = null;
069 }
070
071
072 /**
073 * Creates a ServerBinaryValue with an initial wrapped binary value.
074 *
075 * @param attributeType the schema type associated with this ServerBinaryValue
076 * @param wrapped the binary value to wrap which may be null, or a zero length byte array
077 */
078 public ClientBinaryValue( byte[] wrapped )
079 {
080 if ( wrapped != null )
081 {
082 this.wrapped = new byte[ wrapped.length ];
083 System.arraycopy( wrapped, 0, this.wrapped, 0, wrapped.length );
084 }
085 else
086 {
087 this.wrapped = null;
088 }
089
090 normalized = false;
091 valid = null;
092 normalizedValue = null;
093 }
094
095
096 // -----------------------------------------------------------------------
097 // Value<String> Methods
098 // -----------------------------------------------------------------------
099 /**
100 * Reset the value
101 */
102 public void clear()
103 {
104 wrapped = null;
105 normalized = false;
106 normalizedValue = null;
107 valid = null;
108 }
109
110
111
112
113 /*
114 * Sets the wrapped binary value. Has the side effect of setting the
115 * normalizedValue and the valid flags to null if the wrapped value is
116 * different than what is already set. These cached values must be
117 * recomputed to be correct with different values.
118 *
119 * @see ServerValue#set(Object)
120 */
121 public final void set( byte[] wrapped )
122 {
123 // Why should we invalidate the normalized value if it's we're setting the
124 // wrapper to it's current value?
125 byte[] value = getReference();
126
127 if ( value != null )
128 {
129 if ( Arrays.equals( wrapped, value ) )
130 {
131 return;
132 }
133 }
134
135 normalizedValue = null;
136 normalized = false;
137 valid = null;
138
139 if ( wrapped == null )
140 {
141 this.wrapped = null;
142 }
143 else
144 {
145 this.wrapped = new byte[ wrapped.length ];
146 System.arraycopy( wrapped, 0, this.wrapped, 0, wrapped.length );
147 }
148 }
149
150
151 // -----------------------------------------------------------------------
152 // ServerValue<String> Methods
153 // -----------------------------------------------------------------------
154 /**
155 * Gets a direct reference to the normalized representation for the
156 * wrapped value of this ServerValue wrapper. Implementations will most
157 * likely leverage the attributeType this value is associated with to
158 * determine how to properly normalize the wrapped value.
159 *
160 * @return the normalized version of the wrapped value
161 * @throws NamingException if schema entity resolution fails or normalization fails
162 */
163 public byte[] getNormalizedValueCopy()
164 {
165 if ( normalizedValue == null )
166 {
167 return null;
168 }
169
170 byte[] copy = new byte[ normalizedValue.length ];
171 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
172 return copy;
173 }
174
175
176 /**
177 * Normalize the value. For a client String value, applies the given normalizer.
178 *
179 * It supposes that the client has access to the schema in order to select the
180 * appropriate normalizer.
181 *
182 * @param Normalizer The normalizer to apply to the value
183 * @exception NamingException If the value cannot be normalized
184 */
185 public final void normalize( Normalizer normalizer ) throws NamingException
186 {
187 if ( normalizer != null )
188 {
189 if ( wrapped == null )
190 {
191 normalizedValue = wrapped;
192 normalized = true;
193 same = true;
194 }
195 else
196 {
197 normalizedValue = normalizer.normalize( this ).getBytes();
198 normalized = true;
199 same = Arrays.equals( wrapped, normalizedValue );
200 }
201 }
202 }
203
204
205 /**
206 *
207 * @see ServerValue#compareTo(ServerValue)
208 * @throws IllegalStateException on failures to extract the comparator, or the
209 * normalizers needed to perform the required comparisons based on the schema
210 */
211 public int compareTo( Value<byte[]> value )
212 {
213 if ( isNull() )
214 {
215 if ( ( value == null ) || value.isNull() )
216 {
217 return 0;
218 }
219 else
220 {
221 return -1;
222 }
223 }
224 else
225 {
226 if ( ( value == null ) || value.isNull() )
227 {
228 return 1;
229 }
230 }
231
232 if ( value instanceof ClientBinaryValue )
233 {
234 ClientBinaryValue binaryValue = ( ClientBinaryValue ) value;
235
236 return ByteArrayComparator.INSTANCE.compare( getNormalizedValue(), binaryValue.getNormalizedValue() );
237 }
238
239 String message = "I don't really know how to compare anything other " +
240 "than ServerBinaryValues at this point in time.";
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 }