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;
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 import java.util.Comparator;
028
029 import org.apache.directory.shared.ldap.exception.LdapException;
030
031 import org.apache.directory.shared.i18n.I18n;
032 import org.apache.directory.shared.ldap.schema.AttributeType;
033 import org.apache.directory.shared.ldap.schema.LdapComparator;
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 BinaryValue extends AbstractValue<byte[]>
051 {
052 /** Used for serialization */
053 protected static final long serialVersionUID = 2L;
054
055 /** logger for reporting errors that might not be handled properly upstream */
056 protected static final Logger LOG = LoggerFactory.getLogger( BinaryValue.class );
057
058 /**
059 * Creates a BinaryValue without an initial wrapped value.
060 *
061 * @param attributeType the schema type associated with this BinaryValue
062 */
063 public BinaryValue()
064 {
065 wrappedValue = null;
066 normalized = false;
067 valid = null;
068 normalizedValue = null;
069 }
070
071
072 /**
073 * Creates a BinaryValue without an initial wrapped value.
074 *
075 * @param attributeType the schema type associated with this BinaryValue
076 */
077 public BinaryValue( AttributeType attributeType )
078 {
079 if ( attributeType == null )
080 {
081 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
082 }
083
084 if ( attributeType.getSyntax() == null )
085 {
086 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
087 }
088
089 if ( attributeType.getSyntax().isHumanReadable() )
090 {
091 LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
092 }
093
094 this.attributeType = attributeType;
095 }
096
097
098 /**
099 * Creates a BinaryValue with an initial wrapped binary value.
100 *
101 * @param attributeType the schema type associated with this BinaryValue
102 * @param value the binary value to wrap which may be null, or a zero length byte array
103 */
104 public BinaryValue( byte[] value )
105 {
106 if ( value != null )
107 {
108 this.wrappedValue = new byte[ value.length ];
109 System.arraycopy( value, 0, this.wrappedValue, 0, value.length );
110 }
111 else
112 {
113 this.wrappedValue = null;
114 }
115
116 normalized = false;
117 valid = null;
118 normalizedValue = null;
119 }
120
121
122 /**
123 * Creates a BinaryValue with an initial wrapped binary value.
124 *
125 * @param attributeType the schema type associated with this BinaryValue
126 * @param value the binary value to wrap which may be null, or a zero length byte array
127 */
128 public BinaryValue( AttributeType attributeType, byte[] value )
129 {
130 this( attributeType );
131 this.wrappedValue = value;
132 }
133
134
135 /**
136 * Gets a direct reference to the normalized representation for the
137 * wrapped value of this ServerValue wrapper. Implementations will most
138 * likely leverage the attributeType this value is associated with to
139 * determine how to properly normalize the wrapped value.
140 *
141 * @return the normalized version of the wrapped value
142 * @throws LdapException if schema entity resolution fails or normalization fails
143 */
144 public byte[] getNormalizedValueCopy()
145 {
146 if ( isNull() )
147 {
148 return null;
149 }
150
151 if ( !normalized )
152 {
153 try
154 {
155 normalize();
156 }
157 catch ( LdapException ne )
158 {
159 String message = "Cannot normalize the value :" + ne.getLocalizedMessage();
160 LOG.warn( message );
161 normalized = false;
162 }
163 }
164
165 if ( normalizedValue != null )
166 {
167 byte[] copy = new byte[ normalizedValue.length ];
168 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
169 return copy;
170 }
171 else
172 {
173 return StringTools.EMPTY_BYTES;
174 }
175 }
176
177
178 /**
179 * Gets the normalized (canonical) representation for the wrapped string.
180 * If the wrapped String is null, null is returned, otherwise the normalized
181 * form is returned. If no the normalizedValue is null, then this method
182 * will attempt to generate it from the wrapped value: repeated calls to
183 * this method do not unnecessarily normalize the wrapped value. Only changes
184 * to the wrapped value result in attempts to normalize the wrapped value.
185 *
186 * @return a reference to the normalized version of the wrapped value
187 */
188 public byte[] getNormalizedValueReference()
189 {
190 if ( isNull() )
191 {
192 return null;
193 }
194
195 if ( !isNormalized() )
196 {
197 try
198 {
199 normalize();
200 }
201 catch ( LdapException ne )
202 {
203 String message = "Cannot normalize the value :" + ne.getLocalizedMessage();
204 LOG.warn( message );
205 normalized = false;
206 }
207 }
208
209 if ( normalizedValue != null )
210 {
211 return normalizedValue;
212 }
213 else
214 {
215 return wrappedValue;
216 }
217 }
218
219
220 /**
221 * Gets the normalized (canonical) representation for the wrapped byte[].
222 * If the wrapped byte[] is null, null is returned, otherwise the normalized
223 * form is returned. If the normalizedValue is null, then this method
224 * will attempt to generate it from the wrapped value: repeated calls to
225 * this method do not unnecessarily normalize the wrapped value. Only changes
226 * to the wrapped value result in attempts to normalize the wrapped value.
227 *
228 * @return gets the normalized value
229 */
230 public byte[] getNormalizedValue()
231 {
232 return getNormalizedValueCopy();
233 }
234
235
236 /**
237 * Normalize the value. For a client String value, applies the given normalizer.
238 *
239 * It supposes that the client has access to the schema in order to select the
240 * appropriate normalizer.
241 *
242 * @param Normalizer The normalizer to apply to the value
243 * @exception LdapException If the value cannot be normalized
244 */
245 public final void normalize( Normalizer normalizer ) throws LdapException
246 {
247 if ( normalizer != null )
248 {
249 if ( wrappedValue == null )
250 {
251 normalizedValue = wrappedValue;
252 normalized = true;
253 same = true;
254 }
255 else
256 {
257 normalizedValue = normalizer.normalize( this ).getBytes();
258 normalized = true;
259 same = Arrays.equals( wrappedValue, normalizedValue );
260 }
261 }
262 else
263 {
264 normalizedValue = wrappedValue;
265 normalized = false;
266 same = true;
267 }
268 }
269
270
271 /**
272 * {@inheritDoc}
273 */
274 public void normalize() throws LdapException
275 {
276 if ( isNormalized() )
277 {
278 // Bypass the normalization if it has already been done.
279 return;
280 }
281
282 if ( attributeType != null )
283 {
284 Normalizer normalizer = getNormalizer();
285 normalize( normalizer );
286 }
287 else
288 {
289 normalizedValue = wrappedValue;
290 normalized = true;
291 same = true;
292 }
293 }
294
295
296 /**
297 *
298 * @see ServerValue#compareTo(ServerValue)
299 * @throws IllegalStateException on failures to extract the comparator, or the
300 * normalizers needed to perform the required comparisons based on the schema
301 */
302 public int compareTo( Value<byte[]> value )
303 {
304 if ( isNull() )
305 {
306 if ( ( value == null ) || value.isNull() )
307 {
308 return 0;
309 }
310 else
311 {
312 return -1;
313 }
314 }
315 else
316 {
317 if ( ( value == null ) || value.isNull() )
318 {
319 return 1;
320 }
321 }
322
323 BinaryValue binaryValue = ( BinaryValue ) value;
324
325 if ( attributeType != null )
326 {
327 try
328 {
329 LdapComparator<byte[]> comparator = getLdapComparator();
330
331 if ( comparator != null )
332 {
333 return comparator
334 .compare( getNormalizedValueReference(), binaryValue.getNormalizedValueReference() );
335 }
336 else
337 {
338 return new ByteArrayComparator( null ).compare( getNormalizedValueReference(), binaryValue
339 .getNormalizedValueReference() );
340 }
341 }
342 catch ( LdapException e )
343 {
344 String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
345 LOG.error( msg, e );
346 throw new IllegalStateException( msg, e );
347 }
348 }
349 else
350 {
351 return new ByteArrayComparator( null ).compare( getNormalizedValue(), binaryValue.getNormalizedValue() );
352 }
353 }
354
355
356 // -----------------------------------------------------------------------
357 // Object Methods
358 // -----------------------------------------------------------------------
359
360
361 /**
362 * @see Object#hashCode()
363 * @return the instance's hashcode
364 */
365 public int hashCode()
366 {
367 // return zero if the value is null so only one null value can be
368 // stored in an attribute - the string version does the same
369 if ( isNull() )
370 {
371 return 0;
372 }
373
374 byte[] normalizedValue = getNormalizedValueReference();
375 int h = Arrays.hashCode( normalizedValue );
376
377 return h;
378 }
379
380
381 /**
382 * Checks to see if this BinaryValue equals the supplied object.
383 *
384 * This equals implementation overrides the BinaryValue implementation which
385 * is not schema aware.
386 * @throws IllegalStateException on failures to extract the comparator, or the
387 * normalizers needed to perform the required comparisons based on the schema
388 */
389 public boolean equals( Object obj )
390 {
391 if ( this == obj )
392 {
393 return true;
394 }
395
396 if ( ! ( obj instanceof BinaryValue ) )
397 {
398 return false;
399 }
400
401 BinaryValue other = ( BinaryValue ) obj;
402
403 if ( isNull() )
404 {
405 return other.isNull();
406 }
407
408 // If we have an attributeType, it must be equal
409 // We should also use the comparator if we have an AT
410 if ( attributeType != null )
411 {
412 if ( other.attributeType != null )
413 {
414 if ( !attributeType.equals( other.attributeType ) )
415 {
416 return false;
417 }
418 }
419 else
420 {
421 other.attributeType = attributeType;
422 }
423 }
424 else if ( other.attributeType != null )
425 {
426 attributeType = other.attributeType;
427 }
428
429 // Shortcut : if the values are equals, no need to compare
430 // the normalized values
431 if ( Arrays.equals( wrappedValue, other.wrappedValue ) )
432 {
433 return true;
434 }
435
436 if ( attributeType != null )
437 {
438 // We have an AttributeType, we eed to use the comparator
439 try
440 {
441 Comparator<byte[]> comparator = (Comparator<byte[]>)getLdapComparator();
442
443 // Compare normalized values
444 if ( comparator == null )
445 {
446 return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
447 }
448 else
449 {
450 return comparator.compare( getNormalizedValueReference(), other.getNormalizedValueReference() ) == 0;
451 }
452 }
453 catch ( LdapException ne )
454 {
455 return false;
456 }
457
458 }
459 else
460 {
461 // now unlike regular values we have to compare the normalized values
462 return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
463 }
464 }
465
466
467 // -----------------------------------------------------------------------
468 // Private Helper Methods (might be put into abstract base class)
469 // -----------------------------------------------------------------------
470 /**
471 * @return a copy of the current value
472 */
473 public BinaryValue clone()
474 {
475 BinaryValue clone = (BinaryValue)super.clone();
476
477 if ( normalizedValue != null )
478 {
479 clone.normalizedValue = new byte[ normalizedValue.length ];
480 System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
481 }
482
483 if ( wrappedValue != null )
484 {
485 clone.wrappedValue = new byte[ wrappedValue.length ];
486 System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length );
487 }
488
489 return clone;
490 }
491
492
493 /**
494 * {@inheritDoc}
495 */
496 public byte[] get()
497 {
498 if ( wrappedValue == null )
499 {
500 return null;
501 }
502
503 final byte[] copy = new byte[ wrappedValue.length ];
504 System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length );
505
506 return copy;
507 }
508
509
510 /**
511 * Tells if the current value is Binary or String
512 *
513 * @return <code>true</code> if the value is Binary, <code>false</code> otherwise
514 */
515 public boolean isBinary()
516 {
517 return true;
518 }
519
520
521 /**
522 * @return The length of the interned value
523 */
524 public int length()
525 {
526 return wrappedValue != null ? wrappedValue.length : 0;
527 }
528
529
530 /**
531 * Get the wrapped value as a byte[]. This method returns a copy of
532 * the wrapped byte[].
533 *
534 * @return the wrapped value as a byte[]
535 */
536 public byte[] getBytes()
537 {
538 return get();
539 }
540
541
542 /**
543 * Get the wrapped value as a String.
544 *
545 * @return the wrapped value as a String
546 */
547 public String getString()
548 {
549 return StringTools.utf8ToString( wrappedValue );
550 }
551
552
553 /**
554 * @see Externalizable#readExternal(ObjectInput)
555 */
556 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
557 {
558 // Read the wrapped value, if it's not null
559 int wrappedLength = in.readInt();
560
561 if ( wrappedLength >= 0 )
562 {
563 wrappedValue = new byte[wrappedLength];
564
565 if ( wrappedLength > 0 )
566 {
567 in.read( wrappedValue );
568 }
569 }
570
571 // Read the isNormalized flag
572 normalized = in.readBoolean();
573
574 if ( normalized )
575 {
576 int normalizedLength = in.readInt();
577
578 if ( normalizedLength >= 0 )
579 {
580 normalizedValue = new byte[normalizedLength];
581
582 if ( normalizedLength > 0 )
583 {
584 in.read( normalizedValue );
585 }
586 }
587 }
588 }
589
590
591 /**
592 * @see Externalizable#writeExternal(ObjectOutput)
593 */
594 public void writeExternal( ObjectOutput out ) throws IOException
595 {
596 // Write the wrapped value, if it's not null
597 if ( wrappedValue != null )
598 {
599 out.writeInt( wrappedValue.length );
600
601 if ( wrappedValue.length > 0 )
602 {
603 out.write( wrappedValue, 0, wrappedValue.length );
604 }
605 }
606 else
607 {
608 out.writeInt( -1 );
609 }
610
611 // Write the isNormalized flag
612 if ( normalized )
613 {
614 out.writeBoolean( true );
615
616 // Write the normalized value, if not null
617 if ( normalizedValue != null )
618 {
619 out.writeInt( normalizedValue.length );
620
621 if ( normalizedValue.length > 0 )
622 {
623 out.write( normalizedValue, 0, normalizedValue.length );
624 }
625 }
626 else
627 {
628 out.writeInt( -1 );
629 }
630 }
631 else
632 {
633 out.writeBoolean( false );
634 }
635 }
636
637
638 /**
639 * We will write the value and the normalized value, only
640 * if the normalized value is different.
641 *
642 * If the value is empty, a flag is written at the beginning with
643 * the value true, otherwise, a false is written.
644 *
645 * The data will be stored following this structure :
646 * [length] the wrapped length. Can be -1, if wrapped is null
647 * [value length]
648 * [UP value] if not empty
649 * [normalized] (will be false if the value can't be normalized)
650 * [same] (a flag set to true if the normalized value equals the UP value)
651 * [Norm value] (the normalized value if different from the UP value, and not empty)
652 *
653 * @param out the buffer in which we will stored the serialized form of the value
654 * @throws IOException if we can't write into the buffer
655 */
656 public void serialize( ObjectOutput out ) throws IOException
657 {
658 if ( wrappedValue != null )
659 {
660 // write a the wrapped length
661 out.writeInt( wrappedValue.length );
662
663 // Write the data if not empty
664 if ( wrappedValue.length > 0 )
665 {
666 // The data
667 out.write( wrappedValue );
668
669 // Normalize the data
670 try
671 {
672 normalize();
673
674 if ( !normalized )
675 {
676 // We may not have a normalizer. Just get out
677 // after having writen the flag
678 out.writeBoolean( false );
679 }
680 else
681 {
682 // Write a flag indicating that the data has been normalized
683 out.writeBoolean( true );
684
685 if ( Arrays.equals( getReference(), normalizedValue ) )
686 {
687 // Write the 'same = true' flag
688 out.writeBoolean( true );
689 }
690 else
691 {
692 // Write the 'same = false' flag
693 out.writeBoolean( false );
694
695 // Write the normalized value length
696 out.writeInt( normalizedValue.length );
697
698 if ( normalizedValue.length > 0 )
699 {
700 // Write the normalized value if not empty
701 out.write( normalizedValue );
702 }
703 }
704 }
705 }
706 catch ( LdapException ne )
707 {
708 // The value can't be normalized, we don't write the
709 // normalized value.
710 normalizedValue = null;
711 out.writeBoolean( false );
712 }
713 }
714 }
715 else
716 {
717 // Write -1 indicating that the value is null
718 out.writeInt( -1 );
719 }
720 }
721
722
723 /**
724 *
725 * Deserialize a BinaryValue.
726 *
727 * @param in the buffer containing the bytes with the serialized value
728 * @throws IOException
729 * @throws ClassNotFoundException
730 */
731 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
732 {
733 // The UP value length
734 int wrappedLength = in.readInt();
735
736 if ( wrappedLength == -1 )
737 {
738 // If the value is null, the length will be set to -1
739 same = true;
740 wrappedValue = null;
741 }
742 else if ( wrappedLength == 0 )
743 {
744 wrappedValue = StringTools.EMPTY_BYTES;
745 same = true;
746 normalized = true;
747 normalizedValue = wrappedValue;
748 }
749 else
750 {
751 wrappedValue = new byte[wrappedLength];
752
753 // Read the data
754 in.readFully( wrappedValue );
755
756 // Check if we have a normalized value
757 normalized = in.readBoolean();
758
759 if ( normalized )
760 {
761 // Read the 'same' flag
762 same = in.readBoolean();
763
764 if ( !same )
765 {
766 // Read the normalizedvalue length
767 int normalizedLength = in.readInt();
768
769 if ( normalizedLength > 0 )
770 {
771 normalizedValue = new byte[normalizedLength];
772
773 // Read the normalized value
774 in.read( normalizedValue, 0, normalizedLength );
775 }
776 else
777 {
778 normalizedValue = StringTools.EMPTY_BYTES;
779 }
780 }
781 else
782 {
783 normalizedValue = new byte[wrappedLength];
784 System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
785 }
786 }
787 }
788 }
789
790
791 /**
792 * Dumps binary in hex with label.
793 *
794 * @see Object#toString()
795 */
796 public String toString()
797 {
798 if ( wrappedValue == null )
799 {
800 return "null";
801 }
802 else if ( wrappedValue.length > 16 )
803 {
804 // Just dump the first 16 bytes...
805 byte[] copy = new byte[16];
806
807 System.arraycopy( wrappedValue, 0, copy, 0, 16 );
808
809 return "'" + StringTools.dumpBytes( copy ) + "...'";
810 }
811 else
812 {
813 return "'" + StringTools.dumpBytes( wrappedValue ) + "'";
814 }
815 }
816 }