View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.directory.api.ldap.model.entry;
20  
21  
22  import java.io.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  import java.util.Arrays;
26  import java.util.Comparator;
27  
28  import org.apache.directory.api.i18n.I18n;
29  import org.apache.directory.api.ldap.model.exception.LdapException;
30  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
31  import org.apache.directory.api.ldap.model.schema.AttributeType;
32  import org.apache.directory.api.ldap.model.schema.LdapComparator;
33  import org.apache.directory.api.ldap.model.schema.MatchingRule;
34  import org.apache.directory.api.ldap.model.schema.Normalizer;
35  import org.apache.directory.api.ldap.model.schema.comparators.ByteArrayComparator;
36  import org.apache.directory.api.util.Strings;
37  
38  
39  /**
40   * A server side schema aware wrapper around a binary attribute value.
41   * This value wrapper uses schema information to syntax check values,
42   * and to compare them for equality and ordering.  It caches results
43   * and invalidates them when the wrapped value changes.
44   *
45   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
46   */
47  public class BinaryValue extends AbstractValue<byte[]>
48  {
49      /** Used for serialization */
50      public static final long serialVersionUID = 2L;
51  
52  
53      /**
54       * Creates a BinaryValue without an initial wrapped value.
55       *
56       * @param attributeType the schema type associated with this BinaryValue
57       */
58      /* No protection */BinaryValue( AttributeType attributeType )
59      {
60          if ( attributeType != null )
61          {
62              // We must have a Syntax
63              if ( attributeType.getSyntax() == null )
64              {
65                  throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
66              }
67  
68              if ( attributeType.getSyntax().isHumanReadable() )
69              {
70                  LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
71              }
72  
73              this.attributeType = attributeType;
74          }
75      }
76  
77  
78      /**
79       * Creates a BinaryValue with an initial wrapped binary value.
80       *
81       * @param value the binary value to wrap which may be null, or a zero length byte array
82       */
83      public BinaryValue( byte[] value )
84      {
85          if ( value != null )
86          {
87              this.wrappedValue = new byte[value.length];
88              this.normalizedValue = new byte[value.length];
89              System.arraycopy( value, 0, this.wrappedValue, 0, value.length );
90              System.arraycopy( value, 0, this.normalizedValue, 0, value.length );
91          }
92          else
93          {
94              this.wrappedValue = null;
95              this.normalizedValue = null;
96          }
97      }
98  
99  
100     /**
101      * Creates a BinaryValue with an initial wrapped binary value.
102      *
103      * @param attributeType the schema type associated with this BinaryValue
104      * @param value the binary value to wrap which may be null, or a zero length byte array
105      * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 
106      * to the schema
107      */
108     public BinaryValue( AttributeType attributeType, byte[] value ) throws LdapInvalidAttributeValueException
109     {
110         this( value );
111         apply( attributeType );
112     }
113 
114 
115     /**
116      * Gets a direct reference to the normalized representation for the
117      * wrapped value of this ServerValue wrapper. Implementations will most
118      * likely leverage the attributeType this value is associated with to
119      * determine how to properly normalize the wrapped value.
120      *
121      * @return the normalized version of the wrapped value
122      */
123     public byte[] getNormValue()
124     {
125         if ( isNull() )
126         {
127             return null;
128         }
129 
130         byte[] copy = new byte[normalizedValue.length];
131         System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
132         return copy;
133     }
134 
135 
136     /**
137      *
138      * @see ServerValue#compareTo(Value)
139      */
140     public int compareTo( Value<byte[]> value )
141     {
142         if ( isNull() )
143         {
144             if ( ( value == null ) || value.isNull() )
145             {
146                 return 0;
147             }
148             else
149             {
150                 return -1;
151             }
152         }
153         else
154         {
155             if ( ( value == null ) || value.isNull() )
156             {
157                 return 1;
158             }
159         }
160 
161         BinaryValue binaryValue = ( BinaryValue ) value;
162 
163         if ( attributeType != null )
164         {
165             try
166             {
167                 LdapComparator<byte[]> comparator = getLdapComparator();
168 
169                 if ( comparator != null )
170                 {
171                     return comparator
172                         .compare( getNormReference(), binaryValue.getNormReference() );
173                 }
174                 else
175                 {
176                     return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue
177                         .getNormReference() );
178                 }
179             }
180             catch ( LdapException e )
181             {
182                 String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
183                 LOG.error( msg, e );
184                 throw new IllegalStateException( msg, e );
185             }
186         }
187         else
188         {
189             return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() );
190         }
191     }
192 
193 
194     // -----------------------------------------------------------------------
195     // Object Methods
196     // -----------------------------------------------------------------------
197     /**
198      * @see Object#hashCode()
199      * @return the instance's hashcode 
200      */
201     public int hashCode()
202     {
203         if ( h == 0 )
204         {
205             // return zero if the value is null so only one null value can be
206             // stored in an attribute - the string version does the same
207             if ( isNull() )
208             {
209                 return 0;
210             }
211 
212             byte[] normalizedValue = getNormReference();
213             h = Arrays.hashCode( normalizedValue );
214         }
215 
216         return h;
217     }
218 
219 
220     /**
221      * Checks to see if this BinaryValue equals the supplied object.
222      *
223      * This equals implementation overrides the BinaryValue implementation which
224      * is not schema aware.
225      */
226     public boolean equals( Object obj )
227     {
228         if ( this == obj )
229         {
230             return true;
231         }
232 
233         if ( !( obj instanceof BinaryValue ) )
234         {
235             return false;
236         }
237 
238         BinaryValue other = ( BinaryValue ) obj;
239 
240         // First check if we have an attrbuteType.
241         if ( attributeType != null )
242         {
243             // yes : check for the other value
244             if ( other.attributeType != null )
245             {
246                 if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
247                 {
248                     // Both AttributeType have the same OID, we can assume they are 
249                     // equals. We don't check any further, because the unicity of OID
250                     // makes it unlikely that the two AT are different.
251                     // The values may be both null
252                     if ( isNull() )
253                     {
254                         return other.isNull();
255                     }
256 
257                     // Shortcut : if we have an AT for both the values, check the 
258                     // already normalized values
259                     if ( Arrays.equals( wrappedValue, other.wrappedValue ) )
260                     {
261                         return true;
262                     }
263 
264                     // We have an AttributeType, we use the associated comparator
265                     try
266                     {
267                         Comparator<byte[]> comparator = getLdapComparator();
268 
269                         // Compare normalized values
270                         if ( comparator == null )
271                         {
272                             return Arrays.equals( getNormReference(), other.getNormReference() );
273                         }
274                         else
275                         {
276                             return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
277                         }
278                     }
279                     catch ( LdapException ne )
280                     {
281                         return false;
282                     }
283                 }
284                 else
285                 {
286                     // We can't compare two values when the two ATs are different
287                     return false;
288                 }
289             }
290             else
291             {
292                 // We only have one AT : we will assume that both values are for the 
293                 // same AT.
294                 // The values may be both null
295                 if ( isNull() )
296                 {
297                     return other.isNull();
298                 }
299 
300                 // We have an AttributeType on the base value, we need to use its comparator
301                 try
302                 {
303                     Comparator<byte[]> comparator = getLdapComparator();
304 
305                     // Compare normalized values. We have to normalized the other value,
306                     // as it has no AT
307                     MatchingRule equality = getAttributeType().getEquality();
308 
309                     if ( equality == null )
310                     {
311                         // No matching rule : compare the raw values
312                         return Arrays.equals( getNormReference(), other.getNormReference() );
313                     }
314 
315                     Normalizer normalizer = equality.getNormalizer();
316 
317                     BinaryValue otherValue = ( BinaryValue ) normalizer.normalize( other );
318 
319                     if ( comparator == null )
320                     {
321                         return Arrays.equals( getNormReference(), otherValue.getNormReference() );
322                     }
323                     else
324                     {
325                         return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
326                     }
327                 }
328                 catch ( LdapException ne )
329                 {
330                     return false;
331                 }
332             }
333         }
334         else
335         {
336             // No : check for the other value
337             if ( other.attributeType != null )
338             {
339                 // We only have one AT : we will assume that both values are for the 
340                 // same AT.
341                 // The values may be both null
342                 if ( isNull() )
343                 {
344                     return other.isNull();
345                 }
346 
347                 try
348                 {
349                     Comparator<byte[]> comparator = other.getLdapComparator();
350 
351                     // Compare normalized values. We have to normalized the other value,
352                     // as it has no AT
353                     MatchingRule equality = other.getAttributeType().getEquality();
354 
355                     if ( equality == null )
356                     {
357                         // No matching rule : compare the raw values
358                         return Arrays.equals( getNormReference(), other.getNormReference() );
359                     }
360 
361                     Normalizer normalizer = equality.getNormalizer();
362 
363                     BinaryValue thisValue = ( BinaryValue ) normalizer.normalize( this );
364 
365                     if ( comparator == null )
366                     {
367                         return Arrays.equals( thisValue.getNormReference(), other.getNormReference() );
368                     }
369                     else
370                     {
371                         return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
372                     }
373                 }
374                 catch ( LdapException ne )
375                 {
376                     return false;
377                 }
378             }
379             else
380             {
381                 // The values may be both null
382                 if ( isNull() )
383                 {
384                     return other.isNull();
385                 }
386 
387                 // Now check the normalized values
388                 return Arrays.equals( getNormReference(), other.getNormReference() );
389             }
390         }
391     }
392 
393 
394     // -----------------------------------------------------------------------
395     // Cloneable methods
396     // -----------------------------------------------------------------------
397     /**
398      * {@inheritDoc}
399      */
400     public BinaryValue clone()
401     {
402         BinaryValue clone = ( BinaryValue ) super.clone();
403 
404         // We have to copy the byte[], they are just referenced by suoer.clone()
405         if ( normalizedValue != null )
406         {
407             clone.normalizedValue = new byte[normalizedValue.length];
408             System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
409         }
410 
411         if ( wrappedValue != null )
412         {
413             clone.wrappedValue = new byte[wrappedValue.length];
414             System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length );
415         }
416 
417         return clone;
418     }
419 
420 
421     /**
422      * {@inheritDoc}
423      */
424     public byte[] getValue()
425     {
426         if ( wrappedValue == null )
427         {
428             return null;
429         }
430 
431         final byte[] copy = new byte[wrappedValue.length];
432         System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length );
433 
434         return copy;
435     }
436 
437 
438     /**
439      * Tells if the current value is Human Readable
440      * 
441      * @return <code>true</code> if the value is HR, <code>false</code> otherwise
442      */
443     public boolean isHumanReadable()
444     {
445         return false;
446     }
447 
448 
449     /**
450      * @return The length of the interned value
451      */
452     public int length()
453     {
454         return wrappedValue != null ? wrappedValue.length : 0;
455     }
456 
457 
458     /**
459      * Get the wrapped value as a byte[]. This method returns a copy of 
460      * the wrapped byte[].
461      * 
462      * @return the wrapped value as a byte[]
463      */
464     public byte[] getBytes()
465     {
466         return getValue();
467     }
468 
469 
470     /**
471      * Get the wrapped value as a String.
472      *
473      * @return the wrapped value as a String
474      */
475     public String getString()
476     {
477         return Strings.utf8ToString( wrappedValue );
478     }
479 
480 
481     /**
482      * Deserialize a BinaryValue. It will return a new BinaryValue instance.
483      * 
484      * @param attributeType The AttributeType associated with the Value. Can be null
485      * @param in The input stream
486      * @return A new StringValue instance
487      * @throws IOException If the stream can't be read
488      * @throws ClassNotFoundException If we can't instanciate a BinaryValue
489      */
490     public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
491     {
492         BinaryValue value = new BinaryValue( ( AttributeType ) null );
493         value.readExternal( in );
494 
495         return value;
496     }
497 
498 
499     /**
500      * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance.
501      * 
502      * @param attributeType The AttributeType associated with the Value. Can be null
503      * @param in The input stream
504      * @return A new StringValue instance
505      * @throws IOException If the stream can't be read
506      * @throws ClassNotFoundException If we can't instanciate a BinaryValue
507      */
508     public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
509         ClassNotFoundException
510     {
511         BinaryValue value = new BinaryValue( attributeType );
512         value.readExternal( in );
513 
514         return value;
515     }
516 
517 
518     /**
519      * {@inheritDoc}
520      */
521     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
522     {
523         // Read the BINARY flag
524         boolean isHR = in.readBoolean();
525 
526         if ( isHR )
527         {
528             throw new IOException( "The serialized value is not a Binary value" );
529         }
530         // Read the wrapped value, if it's not null
531         int wrappedLength = in.readInt();
532 
533         if ( wrappedLength >= 0 )
534         {
535             wrappedValue = new byte[wrappedLength];
536 
537             in.readFully( wrappedValue );
538         }
539 
540         // Read the isNormalized flag
541         boolean normalized = in.readBoolean();
542 
543         if ( normalized )
544         {
545             int normalizedLength = in.readInt();
546 
547             if ( normalizedLength >= 0 )
548             {
549                 normalizedValue = new byte[normalizedLength];
550 
551                 in.readFully( normalizedValue );
552             }
553         }
554         else
555         {
556             if ( attributeType != null )
557             {
558                 try
559                 {
560                     normalizedValue = attributeType.getEquality().getNormalizer().normalize( this ).getBytes();
561                     MatchingRule equality = attributeType.getEquality();
562 
563                     if ( equality == null )
564                     {
565                         if ( wrappedLength >= 0 )
566                         {
567                             normalizedValue = new byte[wrappedLength];
568 
569                             System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
570                         }
571                     }
572                     else
573                     {
574                         Normalizer normalizer = equality.getNormalizer();
575 
576                         if ( normalizer != null )
577                         {
578                             normalizedValue = normalizer.normalize( this ).getBytes();
579                         }
580                         else
581                         {
582                             if ( wrappedLength >= 0 )
583                             {
584                                 normalizedValue = new byte[wrappedLength];
585 
586                                 System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
587                             }
588                         }
589                     }
590                 }
591                 catch ( LdapException le )
592                 {
593                     // Copy the wrappedValue into the normalizedValue
594                     if ( wrappedLength >= 0 )
595                     {
596                         normalizedValue = new byte[wrappedLength];
597 
598                         System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
599                     }
600                 }
601             }
602             else
603             {
604                 // Copy the wrappedValue into the normalizedValue
605                 if ( wrappedLength >= 0 )
606                 {
607                     normalizedValue = new byte[wrappedLength];
608 
609                     System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
610                 }
611             }
612         }
613 
614         // The hashCoe
615         h = in.readInt();
616     }
617 
618 
619     /**
620      * {@inheritDoc}
621      */
622     public void writeExternal( ObjectOutput out ) throws IOException
623     {
624         // Write the BINARY flag
625         out.writeBoolean( BINARY );
626 
627         // Write the wrapped value, if it's not null
628         if ( wrappedValue != null )
629         {
630             out.writeInt( wrappedValue.length );
631 
632             if ( wrappedValue.length > 0 )
633             {
634                 out.write( wrappedValue, 0, wrappedValue.length );
635             }
636         }
637         else
638         {
639             out.writeInt( -1 );
640         }
641 
642         // Write the isNormalized flag
643         if ( attributeType != null )
644         {
645             out.writeBoolean( true );
646 
647             // Write the normalized value, if not null
648             if ( normalizedValue != null )
649             {
650                 out.writeInt( normalizedValue.length );
651 
652                 if ( normalizedValue.length > 0 )
653                 {
654                     out.write( normalizedValue, 0, normalizedValue.length );
655                 }
656             }
657             else
658             {
659                 out.writeInt( -1 );
660             }
661         }
662         else
663         {
664             out.writeBoolean( false );
665         }
666 
667         // The hashCode
668         out.writeInt( h );
669 
670         out.flush();
671     }
672 
673 
674     /**
675      * Dumps binary in hex with label.
676      *
677      * @see Object#toString()
678      */
679     public String toString()
680     {
681         if ( wrappedValue == null )
682         {
683             return "null";
684         }
685         else if ( wrappedValue.length > 16 )
686         {
687             // Just dump the first 16 bytes...
688             byte[] copy = new byte[16];
689 
690             System.arraycopy( wrappedValue, 0, copy, 0, 16 );
691 
692             return Strings.dumpBytes( copy ) + "...";
693         }
694         else
695         {
696             return Strings.dumpBytes( wrappedValue );
697         }
698     }
699 }