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.server.core.entry;
20  
21  
22  import java.io.Externalizable;
23  import java.io.IOException;
24  import java.io.ObjectInput;
25  import java.io.ObjectOutput;
26  import java.util.Arrays;
27  import java.util.Comparator;
28  
29  import javax.naming.NamingException;
30  
31  import org.apache.directory.shared.ldap.NotImplementedException;
32  import org.apache.directory.shared.ldap.entry.Value;
33  import org.apache.directory.shared.ldap.entry.client.ClientBinaryValue;
34  import org.apache.directory.shared.ldap.schema.AttributeType;
35  import org.apache.directory.shared.ldap.schema.ByteArrayComparator;
36  import org.apache.directory.shared.ldap.schema.MatchingRule;
37  import org.apache.directory.shared.ldap.schema.Normalizer;
38  import org.apache.directory.shared.ldap.util.StringTools;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  
43  /**
44   * A server side schema aware wrapper around a binary attribute value.
45   * This value wrapper uses schema information to syntax check values,
46   * and to compare them for equality and ordering.  It caches results
47   * and invalidates them when the wrapped value changes.
48   *
49   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
50   * @version $Rev$, $Date$
51   */
52  public class ServerBinaryValue extends ClientBinaryValue
53  {
54      /** Used for serialization */
55      private static final long serialVersionUID = 2L;
56      
57      /** logger for reporting errors that might not be handled properly upstream */
58      private static final Logger LOG = LoggerFactory.getLogger( ServerBinaryValue.class );
59  
60      /** used to dynamically lookup the attributeType when/if deserializing */
61      //@SuppressWarnings ( { "FieldCanBeLocal", "UnusedDeclaration" } )
62      //private final String oid;
63  
64      /** reference to the attributeType which is not serialized */
65      private transient AttributeType attributeType;
66  
67      /** A flag set if the normalized data is different from the wrapped data */
68      private transient boolean same;
69  
70  
71      // -----------------------------------------------------------------------
72      // utility methods
73      // -----------------------------------------------------------------------
74      /**
75       * Utility method to get some logs if an assert fails
76       */
77      protected String logAssert( String message )
78      {
79          LOG.error(  message );
80          return message;
81      }
82  
83      
84      /**
85       *  Check the attributeType member. It should not be null, 
86       *  and it should contains a syntax.
87       */
88      protected String checkAttributeType( AttributeType attributeType )
89      {
90          try
91          {
92              if ( attributeType == null )
93              {
94                  return "The AttributeType parameter should not be null";
95              }
96              
97              if ( attributeType.getSyntax() == null )
98              {
99                  return "There is no Syntax associated with this attributeType";
100             }
101 
102             return null;
103         }
104         catch ( NamingException ne )
105         {
106             return "This AttributeType is incorrect";
107         }
108     }
109 
110     
111     // -----------------------------------------------------------------------
112     // Constructors
113     // -----------------------------------------------------------------------
114     /**
115      * Creates a ServerBinaryValue without an initial wrapped value.
116      *
117      * @param attributeType the schema type associated with this ServerBinaryValue
118      */
119     public ServerBinaryValue( AttributeType attributeType )
120     {
121         super();
122         
123         if ( attributeType == null )
124         {
125             throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
126         }
127 
128         try
129         {
130             if ( attributeType.getSyntax() == null )
131             {
132                 throw new IllegalArgumentException( "There is no Syntax associated with this attributeType" );
133             }
134 
135             if ( attributeType.getSyntax().isHumanReadable() )
136             {
137                 LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
138             }
139         }
140         catch( NamingException e )
141         {
142             LOG.error( "Failed to resolve syntax for attributeType {}", attributeType, e );
143         }
144 
145         this.attributeType = attributeType;
146     }
147 
148 
149     /**
150      * Creates a ServerBinaryValue with an initial wrapped binary value.
151      *
152      * @param attributeType the schema type associated with this ServerBinaryValue
153      * @param wrapped the binary value to wrap which may be null, or a zero length byte array
154      */
155     public ServerBinaryValue( AttributeType attributeType, byte[] wrapped )
156     {
157         this( attributeType );
158         this.wrapped = wrapped;
159     }
160 
161 
162     /**
163      * Creates a ServerStringValue with an initial wrapped String value and
164      * a normalized value.
165      *
166      * @param attributeType the schema type associated with this ServerStringValue
167      * @param wrapped the value to wrap which can be null
168      * @param normalizedValue the normalized value
169      */
170     /** No protection */ 
171     ServerBinaryValue( AttributeType attributeType, byte[] wrapped, byte[] normalizedValue, boolean same, boolean valid )
172     {
173         super( wrapped );
174         this.normalized = true;
175         this.attributeType = attributeType;
176         this.normalizedValue = normalizedValue;
177         this.valid = valid;
178         this.same = same;
179         //this.oid = attributeType.getOid();
180     }
181 
182 
183     // -----------------------------------------------------------------------
184     // ServerValue<byte[]> Methods
185     // -----------------------------------------------------------------------
186     public void normalize() throws NamingException
187     {
188         if ( isNormalized() )
189         {
190             // Bypass the normalization if it has already been done. 
191             return;
192         }
193         
194         if ( getReference() != null )
195         {
196             Normalizer normalizer = getNormalizer();
197     
198             if ( normalizer == null )
199             {
200                 normalizedValue = getCopy();
201                 setNormalized( false );
202             }
203             else
204             {
205                 normalizedValue = ( byte[] ) normalizer.normalize( getCopy() );
206                 setNormalized( true );
207             }
208             
209             if ( Arrays.equals( super.getReference(), normalizedValue ) )
210             {
211                 same = true;
212             }
213             else
214             {
215                 same = false;
216             }
217         }
218         else
219         {
220             normalizedValue = null;
221             same = true;
222             setNormalized( false );
223         }
224     }
225 
226     
227     /**
228      * Gets the normalized (cannonical) representation for the wrapped string.
229      * If the wrapped String is null, null is returned, otherwise the normalized
230      * form is returned.  If no the normalizedValue is null, then this method
231      * will attempt to generate it from the wrapped value: repeated calls to
232      * this method do not unnecessarily normalize the wrapped value.  Only changes
233      * to the wrapped value result in attempts to normalize the wrapped value.
234      *
235      * @return a reference to the normalized version of the wrapped value
236      */
237     public byte[] getNormalizedValueReference()
238     {
239         if ( isNull() )
240         {
241             return null;
242         }
243 
244         if ( !isNormalized() )
245         {
246             try
247             {
248                 normalize();
249             }
250             catch ( NamingException ne )
251             {
252                 String message = "Cannot normalize the value :" + ne.getMessage();
253                 LOG.warn( message );
254                 normalized = false;
255             }
256         }
257 
258         return normalizedValue;
259     }
260 
261 
262     /**
263      * Gets the normalized (canonical) representation for the wrapped byte[].
264      * If the wrapped byte[] is null, null is returned, otherwise the normalized
265      * form is returned.  If no the normalizedValue is null, then this method
266      * will attempt to generate it from the wrapped value: repeated calls to
267      * this method do not unnecessarily normalize the wrapped value.  Only changes
268      * to the wrapped value result in attempts to normalize the wrapped value.
269      *
270      * @return gets the normalized value
271      */
272     public byte[] getNormalizedValue() 
273     {
274         if ( isNull() )
275         {
276             return null;
277         }
278 
279         if ( !normalized )
280         {
281             try
282             {
283                 normalize();
284             }
285             catch ( NamingException ne )
286             {
287                 String message = "Cannot normalize the value :" + ne.getMessage();
288                 LOG.warn( message );
289                 normalized = false;
290             }
291         }
292 
293         return normalizedValue;
294     }
295 
296 
297     /**
298      * Gets a direct reference to the normalized representation for the
299      * wrapped value of this ServerValue wrapper. Implementations will most
300      * likely leverage the attributeType this value is associated with to
301      * determine how to properly normalize the wrapped value.
302      *
303      * @return the normalized version of the wrapped value
304      */
305     public byte[] getNormalizedValueCopy()
306     {
307         if ( isNull() )
308         {
309             return null;
310         }
311 
312         if ( normalizedValue == null )
313         {
314             try
315             {
316                 normalize();
317             }
318             catch ( NamingException ne )
319             {
320                 String message = "Cannot normalize the value :" + ne.getMessage();
321                 LOG.warn( message );
322                 normalized = false;
323             }
324         }
325 
326         if ( normalizedValue != null )
327         {
328             byte[] copy = new byte[ normalizedValue.length ];
329             System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
330             return copy;
331         }
332         else
333         {
334             return null;
335         }
336     }
337 
338 
339     /**
340      * Uses the syntaxChecker associated with the attributeType to check if the
341      * value is valid.  Repeated calls to this method do not attempt to re-check
342      * the syntax of the wrapped value every time if the wrapped value does not
343      * change. Syntax checks only result on the first check, and when the wrapped
344      * value changes.
345      *
346      * @see Value#isValid()
347      */
348     public final boolean isValid()
349     {
350         if ( valid != null )
351         {
352             return valid;
353         }
354 
355         try
356         {
357             valid = attributeType.getSyntax().getSyntaxChecker().isValidSyntax( getReference() );
358         }
359         catch ( NamingException ne )
360         {
361             String message = "Cannot check the syntax : " + ne.getMessage();
362             LOG.error( message );
363             valid = false;
364         }
365         
366         return valid;
367     }
368 
369     
370     /**
371      * @return Tells if the wrapped value and the normalized value are the same 
372      */
373     public final boolean isSame()
374     {
375         return same;
376     }
377     
378 
379     /**
380      *
381      * @see Value#compareTo(Value)
382      * @throws IllegalStateException on failures to extract the comparator, or the
383      * normalizers needed to perform the required comparisons based on the schema
384      */
385     public int compareTo( Value<byte[]> value )
386     {
387         if ( isNull() )
388         {
389             if ( ( value == null ) || value.isNull() )
390             {
391                 return 0;
392             }
393             else
394             {
395                 return -1;
396             }
397         }
398         else
399         {
400             if ( ( value == null ) || value.isNull() ) 
401             {
402                 return 1;
403             }
404         }
405 
406         if ( value instanceof ServerBinaryValue )
407         {
408             ServerBinaryValue binaryValue = ( ServerBinaryValue ) value;
409 
410             try
411             {
412                 Comparator<? super Value<byte[]>> comparator = getComparator();
413                 
414                 if ( comparator != null )
415                 {
416                     return getComparator().compare( getNormalizedValueReference(), binaryValue.getNormalizedValueReference() );
417                 }
418                 else
419                 {
420                     return ByteArrayComparator.INSTANCE.compare( getNormalizedValueReference(), 
421                         binaryValue.getNormalizedValueReference() );
422                 }
423             }
424             catch ( NamingException e )
425             {
426                 String msg = "Failed to compare normalized values for " + Arrays.toString( getReference() )
427                         + " and " + value;
428                 LOG.error( msg, e );
429                 throw new IllegalStateException( msg, e );
430             }
431         }
432 
433         String message = "I don't really know how to compare anything other " +
434         "than ServerBinaryValues at this point in time.";
435         LOG.error( message );
436         throw new NotImplementedException( message );
437     }
438 
439 
440     /**
441      * Get the associated AttributeType
442      * @return The AttributeType
443      */
444     public AttributeType getAttributeType()
445     {
446         return attributeType;
447     }
448 
449 
450     /**
451      * Check if the value is stored into an instance of the given 
452      * AttributeType, or one of its ascendant.
453      * 
454      * For instance, if the Value is associated with a CommonName,
455      * checking for Name will match.
456      * 
457      * @param attributeType The AttributeType we are looking at
458      * @return <code>true</code> if the value is associated with the given
459      * attributeType or one of its ascendant
460      */
461     public boolean instanceOf( AttributeType attributeType ) throws NamingException
462     {
463         if ( this.attributeType.equals( attributeType ) )
464         {
465             return true;
466         }
467 
468         return this.attributeType.isDescentantOf( attributeType );
469     }
470 
471 
472     // -----------------------------------------------------------------------
473     // Object Methods
474     // -----------------------------------------------------------------------
475     /**
476      * @see Object#hashCode()
477      * @return the instance's hash code 
478      */
479     public int hashCode()
480     {
481         // return zero if the value is null so only one null value can be
482         // stored in an attribute - the string version does the same
483         if ( isNull() )
484         {
485             return 0;
486         }
487 
488         return Arrays.hashCode( getNormalizedValueReference() );
489     }
490 
491 
492     /**
493      * Checks to see if this ServerBinaryValue equals the supplied object.
494      *
495      * This equals implementation overrides the BinaryValue implementation which
496      * is not schema aware.
497      * @throws IllegalStateException on failures to extract the comparator, or the
498      * normalizers needed to perform the required comparisons based on the schema
499      */
500     public boolean equals( Object obj )
501     {
502         if ( this == obj )
503         {
504             return true;
505         }
506 
507         if ( ! ( obj instanceof ServerBinaryValue ) )
508         {
509             return false;
510         }
511 
512         ServerBinaryValue other = ( ServerBinaryValue ) obj;
513         
514         if ( !attributeType.equals( other.attributeType ) )
515         {
516             return false;
517         }
518         
519         if ( isNull() )
520         {
521             return other.isNull();
522         }
523 
524         // Shortcut : if the values are equals, no need to compare
525         // the normalized values
526         if ( Arrays.equals( wrapped, other.get() ) )
527         {
528             return true;
529         }
530         else
531         {
532             try
533             {
534                 Comparator<byte[]> comparator = getComparator();
535 
536                 // Compare normalized values
537                 if ( comparator == null )
538                 {
539                     return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
540                 }
541                 else
542                 {
543                     return comparator.compare( getNormalizedValueReference(), other.getNormalizedValueReference() ) == 0;
544                 }
545             }
546             catch ( NamingException ne )
547             {
548                 return false;
549             }
550         }
551     }
552 
553 
554     // -----------------------------------------------------------------------
555     // Private Helper Methods (might be put into abstract base class)
556     // -----------------------------------------------------------------------
557     /**
558      * Find a matchingRule to use for normalization and comparison.  If an equality
559      * matchingRule cannot be found it checks to see if other matchingRules are
560      * available: SUBSTR, and ORDERING.  If a matchingRule cannot be found null is
561      * returned.
562      *
563      * @return a matchingRule or null if one cannot be found for the attributeType
564      * @throws NamingException if resolution of schema entities fail
565      */
566     private MatchingRule getMatchingRule() throws NamingException
567     {
568         MatchingRule mr = attributeType.getEquality();
569 
570         if ( mr == null )
571         {
572             mr = attributeType.getOrdering();
573         }
574 
575         if ( mr == null )
576         {
577             mr = attributeType.getSubstr();
578         }
579 
580         return mr;
581     }
582 
583 
584     /**
585      * Gets a normalizer using getMatchingRule() to resolve the matchingRule
586      * that the normalizer is extracted from.
587      *
588      * @return a normalizer associated with the attributeType or null if one cannot be found
589      * @throws NamingException if resolution of schema entities fail
590      */
591     private Normalizer getNormalizer() throws NamingException
592     {
593         MatchingRule mr = getMatchingRule();
594 
595         if ( mr == null )
596         {
597             return null;
598         }
599 
600         return mr.getNormalizer();
601     }
602 
603 
604     /**
605      * Gets a comparator using getMatchingRule() to resolve the matching
606      * that the comparator is extracted from.
607      *
608      * @return a comparator associated with the attributeType or null if one cannot be found
609      * @throws NamingException if resolution of schema entities fail
610      */
611     private Comparator getComparator() throws NamingException
612     {
613         MatchingRule mr = getMatchingRule();
614 
615         if ( mr == null )
616         {
617             return null;
618         }
619 
620         return mr.getComparator();
621     }
622     
623     
624     /**
625      * @return a copy of the current value
626      */
627     public ServerBinaryValue clone()
628     {
629         ServerBinaryValue clone = (ServerBinaryValue)super.clone();
630         
631         if ( normalizedValue != null )
632         {
633             clone.normalizedValue = new byte[ normalizedValue.length ];
634             System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
635         }
636         
637         return clone;
638     }
639 
640 
641     /**
642      * @see Externalizable#writeExternal(ObjectOutput)
643      * 
644      * We can't use this method for a ServerBinaryValue, as we have to feed the value
645      * with an AttributeType object
646      */ 
647     public void writeExternal( ObjectOutput out ) throws IOException
648     {
649         throw new IllegalStateException( "Cannot use standard serialization for a ServerStringValue" );
650     }
651     
652     
653     /**
654      * We will write the value and the normalized value, only
655      * if the normalized value is different.
656      * 
657      * If the value is empty, a flag is written at the beginning with 
658      * the value true, otherwise, a false is written.
659      * 
660      * The data will be stored following this structure :
661      *  [length] the wrapped length. Can be -1, if wrapped is null
662      *  [value length]
663      *  [UP value] if not empty
664      *  [normalized] (will be false if the value can't be normalized)
665      *  [same] (a flag set to true if the normalized value equals the UP value)
666      *  [Norm value] (the normalized value if different from the UP value, and not empty)
667      *  
668      *  @param out the buffer in which we will stored the serialized form of the value
669      *  @throws IOException if we can't write into the buffer
670      */
671     public void serialize( ObjectOutput out ) throws IOException
672     {
673         if ( wrapped != null )
674         {
675             // write a the wrapped length
676             out.writeInt( wrapped.length );
677             
678             // Write the data if not empty
679             if ( wrapped.length > 0 )
680             {
681                 // The data
682                 out.write( wrapped );
683                 
684                 // Normalize the data
685                 try
686                 {
687                     normalize();
688                     
689                     if ( !normalized )
690                     {
691                         // We may not have a normalizer. Just get out
692                         // after having writen the flag
693                         out.writeBoolean( false );
694                     }
695                     else
696                     {
697                         // Write a flag indicating that the data has been normalized
698                         out.writeBoolean( true );
699                         
700                         if ( Arrays.equals( getReference(), normalizedValue ) )
701                         {
702                             // Write the 'same = true' flag
703                             out.writeBoolean( true );
704                         }
705                         else
706                         {
707                             // Write the 'same = false' flag
708                             out.writeBoolean( false );
709                             
710                             // Write the normalized value length
711                             out.write( normalizedValue.length );
712                             
713                             if ( normalizedValue.length > 0 )
714                             {
715                                 // Write the normalized value if not empty
716                                 out.write( normalizedValue );
717                             }
718                         }
719                     }
720                 }
721                 catch ( NamingException ne )
722                 {
723                     // The value can't be normalized, we don't write the 
724                     // normalized value.
725                     normalizedValue = null;
726                     out.writeBoolean( false );
727                 }
728             }
729         }
730         else
731         {
732             // Write -1 indicating that the value is null
733             out.writeInt( -1 );
734         }
735     }
736 
737     
738     /**
739      * @see Externalizable#readExternal(ObjectInput)
740      * 
741      * We can't use this method for a ServerBinaryValue, as we have to feed the value
742      * with an AttributeType object
743      */
744     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
745     {
746         throw new IllegalStateException( "Cannot use standard serialization for a ServerStringValue" );
747     }
748     
749 
750     /**
751      * 
752      * Deserialize a ServerBinaryValue. 
753      *
754      * @param in the buffer containing the bytes with the serialized value
755      * @throws IOException 
756      * @throws ClassNotFoundException
757      */
758     public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
759     {
760         // The UP value length
761         int wrappedLength = in.readInt();
762         
763         if ( wrappedLength == -1 )
764         {
765             // If the value is null, the length will be set to -1
766             same = true;
767             wrapped = null;
768         }
769         else if ( wrappedLength == 0 )
770         {
771              wrapped = StringTools.EMPTY_BYTES;
772              same = true;
773              normalized = true;
774              normalizedValue = wrapped;
775         }
776         else
777         {
778             wrapped = new byte[wrappedLength];
779             
780             // Read the data
781             in.readFully( wrapped );
782             
783             // Check if we have a normalized value
784             normalized = in.readBoolean();
785             
786             if ( normalized )
787             {
788                 // Read the 'same' flag
789                 same = in.readBoolean();
790                 
791                 if ( !same )
792                 {
793                     // Read the normalizedvalue length
794                     int normalizedLength = in.readInt();
795                 
796                     if ( normalizedLength > 0 )
797                     {
798                         normalizedValue = new byte[normalizedLength];
799                         
800                         // Read the normalized value
801                         in.read( normalizedValue, 0, normalizedLength );
802                     }
803                     else
804                     {
805                         normalizedValue = StringTools.EMPTY_BYTES;
806                     }
807                 }
808                 else
809                 {
810                     normalizedValue = new byte[wrappedLength];
811                     System.arraycopy( wrapped, 0, normalizedValue, 0, wrappedLength );
812                 }
813             }
814         }
815     }
816 }