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