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 user provided 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 user provided 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 user provided 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.upValue = new byte[value.length];
88              this.normalizedValue = new byte[value.length];
89              System.arraycopy( value, 0, this.upValue, 0, value.length );
90              System.arraycopy( value, 0, this.normalizedValue, 0, value.length );
91          }
92          else
93          {
94              this.upValue = null;
95              this.normalizedValue = null;
96          }
97      }
98  
99  
100     /**
101      * Creates a BinaryValue with an initial user provided 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      * user provided 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 user provided value.
120      *
121      * @return the normalized version of the user provided value
122      */
123     @Override
124     public byte[] getNormValue()
125     {
126         if ( isNull() )
127         {
128             return null;
129         }
130 
131         byte[] copy = new byte[normalizedValue.length];
132         System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
133         return copy;
134     }
135 
136 
137     /**
138      * Compare the current value with a provided one
139      *
140      * @param value The value we want to compare to
141      * @return -1 if the current is below the provided one, 1 if it's above, 0 if they are equal
142      */
143     @Override
144     public int compareTo( Value<byte[]> value )
145     {
146         if ( isNull() )
147         {
148             if ( ( value == null ) || value.isNull() )
149             {
150                 return 0;
151             }
152             else
153             {
154                 return -1;
155             }
156         }
157         else
158         {
159             if ( ( value == null ) || value.isNull() )
160             {
161                 return 1;
162             }
163         }
164 
165         BinaryValue binaryValue = ( BinaryValue ) value;
166 
167         if ( attributeType != null )
168         {
169             try
170             {
171                 LdapComparator<byte[]> comparator = getLdapComparator();
172 
173                 if ( comparator != null )
174                 {
175                     return comparator
176                         .compare( getNormReference(), binaryValue.getNormReference() );
177                 }
178                 else
179                 {
180                     return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue
181                         .getNormReference() );
182                 }
183             }
184             catch ( LdapException e )
185             {
186                 String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
187                 LOG.error( msg, e );
188                 throw new IllegalStateException( msg, e );
189             }
190         }
191         else
192         {
193             return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() );
194         }
195     }
196 
197 
198     // -----------------------------------------------------------------------
199     // Object Methods
200     // -----------------------------------------------------------------------
201     /**
202      * @see Object#hashCode()
203      * @return the instance's hashcode 
204      */
205     @Override
206     public int hashCode()
207     {
208         if ( h == 0 )
209         {
210             // return zero if the value is null so only one null value can be
211             // stored in an attribute - the string version does the same
212             if ( isNull() )
213             {
214                 return 0;
215             }
216 
217             byte[] normalizedValue = getNormReference();
218             h = Arrays.hashCode( normalizedValue );
219         }
220 
221         return h;
222     }
223 
224 
225     /**
226      * Checks to see if this BinaryValue equals the supplied object.
227      *
228      * This equals implementation overrides the BinaryValue implementation which
229      * is not schema aware.
230      */
231     @Override
232     public boolean equals( Object obj )
233     {
234         if ( this == obj )
235         {
236             return true;
237         }
238 
239         if ( !( obj instanceof BinaryValue ) )
240         {
241             return false;
242         }
243 
244         BinaryValue other = ( BinaryValue ) obj;
245 
246         // First check if we have an attrbuteType.
247         if ( attributeType != null )
248         {
249             // yes : check for the other value
250             if ( other.attributeType != null )
251             {
252                 if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
253                 {
254                     // Both AttributeType have the same OID, we can assume they are 
255                     // equals. We don't check any further, because the unicity of OID
256                     // makes it unlikely that the two AT are different.
257                     // The values may be both null
258                     if ( isNull() )
259                     {
260                         return other.isNull();
261                     }
262 
263                     // Shortcut : if we have an AT for both the values, check the 
264                     // already normalized values
265                     if ( Arrays.equals( upValue, other.upValue ) )
266                     {
267                         return true;
268                     }
269 
270                     // We have an AttributeType, we use the associated comparator
271                     try
272                     {
273                         Comparator<byte[]> comparator = getLdapComparator();
274 
275                         // Compare normalized values
276                         if ( comparator == null )
277                         {
278                             return Arrays.equals( getNormReference(), other.getNormReference() );
279                         }
280                         else
281                         {
282                             return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
283                         }
284                     }
285                     catch ( LdapException ne )
286                     {
287                         return false;
288                     }
289                 }
290                 else
291                 {
292                     // We can't compare two values when the two ATs are different
293                     return false;
294                 }
295             }
296             else
297             {
298                 // We only have one AT : we will assume that both values are for the 
299                 // same AT.
300                 // The values may be both null
301                 if ( isNull() )
302                 {
303                     return other.isNull();
304                 }
305 
306                 // We have an AttributeType on the base value, we need to use its comparator
307                 try
308                 {
309                     Comparator<byte[]> comparator = getLdapComparator();
310 
311                     // Compare normalized values. We have to normalized the other value,
312                     // as it has no AT
313                     MatchingRule equality = getAttributeType().getEquality();
314 
315                     if ( equality == null )
316                     {
317                         // No matching rule : compare the raw values
318                         return Arrays.equals( getNormReference(), other.getNormReference() );
319                     }
320 
321                     Normalizer normalizer = equality.getNormalizer();
322 
323                     BinaryValue otherValue = ( BinaryValue ) normalizer.normalize( other );
324 
325                     if ( comparator == null )
326                     {
327                         return Arrays.equals( getNormReference(), otherValue.getNormReference() );
328                     }
329                     else
330                     {
331                         return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
332                     }
333                 }
334                 catch ( LdapException ne )
335                 {
336                     return false;
337                 }
338             }
339         }
340         else
341         {
342             // No : check for the other value
343             if ( other.attributeType != null )
344             {
345                 // We only have one AT : we will assume that both values are for the 
346                 // same AT.
347                 // The values may be both null
348                 if ( isNull() )
349                 {
350                     return other.isNull();
351                 }
352 
353                 try
354                 {
355                     Comparator<byte[]> comparator = other.getLdapComparator();
356 
357                     // Compare normalized values. We have to normalized the other value,
358                     // as it has no AT
359                     MatchingRule equality = other.getAttributeType().getEquality();
360 
361                     if ( equality == null )
362                     {
363                         // No matching rule : compare the raw values
364                         return Arrays.equals( getNormReference(), other.getNormReference() );
365                     }
366 
367                     Normalizer normalizer = equality.getNormalizer();
368 
369                     BinaryValue thisValue = ( BinaryValue ) normalizer.normalize( this );
370 
371                     if ( comparator == null )
372                     {
373                         return Arrays.equals( thisValue.getNormReference(), other.getNormReference() );
374                     }
375                     else
376                     {
377                         return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
378                     }
379                 }
380                 catch ( LdapException ne )
381                 {
382                     return false;
383                 }
384             }
385             else
386             {
387                 // The values may be both null
388                 if ( isNull() )
389                 {
390                     return other.isNull();
391                 }
392 
393                 // Now check the normalized values
394                 return Arrays.equals( getNormReference(), other.getNormReference() );
395             }
396         }
397     }
398 
399 
400     // -----------------------------------------------------------------------
401     // Cloneable methods
402     // -----------------------------------------------------------------------
403     /**
404      * {@inheritDoc}
405      */
406     @Override
407     public BinaryValue clone()
408     {
409         BinaryValue clone = ( BinaryValue ) super.clone();
410 
411         // We have to copy the byte[], they are just referenced by suoer.clone()
412         if ( normalizedValue != null )
413         {
414             clone.normalizedValue = new byte[normalizedValue.length];
415             System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
416         }
417 
418         if ( upValue != null )
419         {
420             clone.upValue = new byte[upValue.length];
421             System.arraycopy( upValue, 0, clone.upValue, 0, upValue.length );
422         }
423 
424         return clone;
425     }
426 
427 
428     /**
429      * {@inheritDoc}
430      */
431     @Override
432     public byte[] getValue()
433     {
434         if ( upValue == null )
435         {
436             return null;
437         }
438 
439         final byte[] copy = new byte[upValue.length];
440         System.arraycopy( upValue, 0, copy, 0, upValue.length );
441 
442         return copy;
443     }
444 
445 
446     /**
447      * Tells if the current value is Human Readable
448      * 
449      * @return <code>true</code> if the value is HR, <code>false</code> otherwise
450      */
451     @Override
452     public boolean isHumanReadable()
453     {
454         return false;
455     }
456 
457 
458     /**
459      * @return The length of the interned value
460      */
461     @Override
462     public int length()
463     {
464         return upValue != null ? upValue.length : 0;
465     }
466 
467 
468     /**
469      * Get the user provided value as a byte[]. This method returns a copy of 
470      * the user provided byte[].
471      * 
472      * @return the user provided value as a byte[]
473      */
474     @Override
475     public byte[] getBytes()
476     {
477         return getValue();
478     }
479 
480 
481     /**
482      * Get the user provided value as a String.
483      *
484      * @return the user provided value as a String
485      */
486     @Override
487     public String getString()
488     {
489         return Strings.utf8ToString( upValue );
490     }
491 
492 
493     /**
494      * Deserialize a BinaryValue. It will return a new BinaryValue instance.
495      * 
496      * @param in The input stream
497      * @return A new StringValue instance
498      * @throws IOException If the stream can't be read
499      * @throws ClassNotFoundException If we can't instanciate a BinaryValue
500      */
501     public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
502     {
503         BinaryValue value = new BinaryValue( ( AttributeType ) null );
504         value.readExternal( in );
505 
506         return value;
507     }
508 
509 
510     /**
511      * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance.
512      * 
513      * @param attributeType The AttributeType associated with the Value. Can be null
514      * @param in The input stream
515      * @return A new StringValue instance
516      * @throws IOException If the stream can't be read
517      * @throws ClassNotFoundException If we can't instanciate a BinaryValue
518      */
519     public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
520         ClassNotFoundException
521     {
522         BinaryValue value = new BinaryValue( attributeType );
523         value.readExternal( in );
524 
525         return value;
526     }
527 
528 
529     /**
530      * {@inheritDoc}
531      */
532     @Override
533     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
534     {
535         // Read the BINARY flag
536         boolean isHR = in.readBoolean();
537 
538         if ( isHR )
539         {
540             throw new IOException( "The serialized value is not a Binary value" );
541         }
542         // Read the user provided value, if it's not null
543         int upLength = in.readInt();
544 
545         if ( upLength >= 0 )
546         {
547             upValue = new byte[upLength];
548 
549             in.readFully( upValue );
550         }
551 
552         // Read the isNormalized flag
553         boolean normalized = in.readBoolean();
554 
555         if ( normalized )
556         {
557             int normalizedLength = in.readInt();
558 
559             if ( normalizedLength >= 0 )
560             {
561                 normalizedValue = new byte[normalizedLength];
562 
563                 in.readFully( normalizedValue );
564             }
565         }
566         else
567         {
568             if ( attributeType != null )
569             {
570                 try
571                 {
572                     normalizedValue = attributeType.getEquality().getNormalizer().normalize( this ).getBytes();
573                     MatchingRule equality = attributeType.getEquality();
574 
575                     if ( equality == null )
576                     {
577                         if ( upLength >= 0 )
578                         {
579                             normalizedValue = new byte[upLength];
580 
581                             System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
582                         }
583                     }
584                     else
585                     {
586                         Normalizer normalizer = equality.getNormalizer();
587 
588                         if ( normalizer != null )
589                         {
590                             normalizedValue = normalizer.normalize( this ).getBytes();
591                         }
592                         else
593                         {
594                             if ( upLength >= 0 )
595                             {
596                                 normalizedValue = new byte[upLength];
597 
598                                 System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
599                             }
600                         }
601                     }
602                 }
603                 catch ( LdapException le )
604                 {
605                     // Copy the upValue into the normalizedValue
606                     if ( upLength >= 0 )
607                     {
608                         normalizedValue = new byte[upLength];
609 
610                         System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
611                     }
612                 }
613             }
614             else
615             {
616                 // Copy the upValue into the normalizedValue
617                 if ( upLength >= 0 )
618                 {
619                     normalizedValue = new byte[upLength];
620 
621                     System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
622                 }
623             }
624         }
625 
626         // The hashCoe
627         h = in.readInt();
628     }
629 
630 
631     /**
632      * {@inheritDoc}
633      */
634     @Override
635     public void writeExternal( ObjectOutput out ) throws IOException
636     {
637         // Write the BINARY flag
638         out.writeBoolean( BINARY );
639 
640         // Write the user provided value, if it's not null
641         if ( upValue != null )
642         {
643             out.writeInt( upValue.length );
644 
645             if ( upValue.length > 0 )
646             {
647                 out.write( upValue, 0, upValue.length );
648             }
649         }
650         else
651         {
652             out.writeInt( -1 );
653         }
654 
655         // Write the isNormalized flag
656         if ( attributeType != null )
657         {
658             out.writeBoolean( true );
659 
660             // Write the normalized value, if not null
661             if ( normalizedValue != null )
662             {
663                 out.writeInt( normalizedValue.length );
664 
665                 if ( normalizedValue.length > 0 )
666                 {
667                     out.write( normalizedValue, 0, normalizedValue.length );
668                 }
669             }
670             else
671             {
672                 out.writeInt( -1 );
673             }
674         }
675         else
676         {
677             out.writeBoolean( false );
678         }
679 
680         // The hashCode
681         out.writeInt( h );
682 
683         out.flush();
684     }
685 
686 
687     /**
688      * Dumps binary in hex with label.
689      *
690      * @see Object#toString()
691      */
692     @Override
693     public String toString()
694     {
695         if ( upValue == null )
696         {
697             return "null";
698         }
699         else if ( upValue.length > 16 )
700         {
701             // Just dump the first 16 bytes...
702             byte[] copy = new byte[16];
703 
704             System.arraycopy( upValue, 0, copy, 0, 16 );
705 
706             return Strings.dumpBytes( copy ) + "...";
707         }
708         else
709         {
710             return Strings.dumpBytes( upValue );
711         }
712     }
713 }