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.Comparator;
26  
27  import org.apache.directory.api.i18n.I18n;
28  import org.apache.directory.api.ldap.model.exception.LdapException;
29  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
30  import org.apache.directory.api.ldap.model.schema.AttributeType;
31  import org.apache.directory.api.ldap.model.schema.MatchingRule;
32  import org.apache.directory.api.ldap.model.schema.Normalizer;
33  import org.apache.directory.api.util.Serialize;
34  import org.apache.directory.api.util.Strings;
35  import org.apache.directory.api.util.exception.NotImplementedException;
36  
37  
38  /**
39   * A server side schema aware wrapper around a String attribute value.
40   * This value wrapper uses schema information to syntax check values,
41   * and to compare them for equality and ordering.  It caches results
42   * and invalidates them when the user provided value changes.
43   *
44   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
45   */
46  public class StringValue extends AbstractValue<String>
47  {
48      /** Used for serialization */
49      private static final long serialVersionUID = 2L;
50  
51      /** The UTF-8 bytes for this value */
52      private byte[] bytes;
53  
54  
55      // -----------------------------------------------------------------------
56      // Constructors
57      // -----------------------------------------------------------------------
58      /**
59       * Creates a StringValue without an initial user provided value.
60       *
61       * @param attributeType the schema attribute type associated with this StringValue
62       */
63      public StringValue( AttributeType attributeType )
64      {
65          if ( attributeType != null )
66          {
67              // We must have a Syntax
68              if ( attributeType.getSyntax() == null )
69              {
70                  throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
71              }
72  
73              if ( !attributeType.getSyntax().isHumanReadable() )
74              {
75                  LOG.warn( "Treating a value of a binary attribute {} as a String: "
76                      + "\nthis could cause data corruption!", attributeType.getName() );
77              }
78  
79              this.attributeType = attributeType;
80          }
81      }
82  
83  
84      /**
85       * Creates a StringValue with an initial user provided String value.
86       *
87       * @param value the value to wrap which can be null
88       */
89      public StringValue( String value )
90      {
91          this.upValue = value;
92          this.normalizedValue = value;
93          bytes = Strings.getBytesUtf8( value );
94      }
95  
96  
97      /**
98       * Creates a StringValue with an initial user provided String value and a normalized value.
99       *
100      * @param value the user provided value to wrap which can be null
101      * @param normalizedValue the normalized value to wrap which can be null
102      */
103     public StringValue( String value, String normalizedValue )
104     {
105         this.upValue = value;
106         this.normalizedValue = normalizedValue;
107         bytes = Strings.getBytesUtf8( normalizedValue );
108     }
109 
110 
111     /**
112      * Creates a schema aware StringValue with an initial user provided String value.
113      *
114      * @param attributeType the schema type associated with this StringValue
115      * @param value the value to wrap
116      * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
117      * to the schema
118      */
119     public StringValue( AttributeType attributeType, String value ) throws LdapInvalidAttributeValueException
120     {
121         this( value, value );
122         apply( attributeType );
123     }
124 
125 
126     /**
127      * Creates a schema aware StringValue with an initial user provided String value.
128      *
129      * @param attributeType the schema type associated with this StringValue
130      * @param value the value to wrap
131      * @param normValue The normalized form to store
132      * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
133      * to the schema
134      */
135     public StringValue( AttributeType attributeType, String value, String normValue ) throws LdapInvalidAttributeValueException
136     {
137         this( value, normValue );
138         apply( attributeType );
139     }
140 
141 
142     // -----------------------------------------------------------------------
143     // Value<String> Methods
144     // -----------------------------------------------------------------------
145     /**
146      * {@inheritDoc}
147      */
148     @Override
149     public String getValue()
150     {
151         // The String is immutable, we can safely return the internal
152         // object without copying it.
153         return upValue;
154     }
155 
156 
157     /**
158      * {@inheritDoc}
159      */
160     @Override
161     public String getNormValue()
162     {
163         return normalizedValue;
164     }
165 
166 
167     // -----------------------------------------------------------------------
168     // Comparable<String> Methods
169     // -----------------------------------------------------------------------
170     /**
171      * Compare the current value with a given value
172      * @param value The Value to compare to
173      * @return -1 if the current value is below the given value, 1 if it's abobe, 0 if it's equal
174      * @throws IllegalStateException on failures to extract the comparator, or the
175      * normalizers needed to perform the required comparisons based on the schema
176      */
177     @Override
178     public int compareTo( Value<String> value )
179     {
180         if ( isNull() )
181         {
182             if ( ( value == null ) || value.isNull() )
183             {
184                 return 0;
185             }
186             else
187             {
188                 return -1;
189             }
190         }
191         else if ( ( value == null ) || value.isNull() )
192         {
193             return 1;
194         }
195 
196         if ( !( value instanceof StringValue ) )
197         {
198             String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
199             LOG.error( message );
200             throw new NotImplementedException( message );
201         }
202 
203         StringValue stringValue = ( StringValue ) value;
204 
205         if ( attributeType != null )
206         {
207             if ( stringValue.getAttributeType() == null )
208             {
209                 return getNormValue().compareTo( stringValue.getNormValue() );
210             }
211             else
212             {
213                 if ( !attributeType.equals( stringValue.getAttributeType() ) )
214                 {
215                     String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
216                     LOG.error( message );
217                     throw new NotImplementedException( message );
218                 }
219             }
220         }
221         else
222         {
223             return getNormValue().compareTo( stringValue.getNormValue() );
224         }
225 
226         try
227         {
228             return getLdapComparator().compare( getNormValue(), stringValue.getNormValue() );
229         }
230         catch ( LdapException e )
231         {
232             String msg = I18n.err( I18n.ERR_04443, this, value );
233             LOG.error( msg, e );
234             throw new IllegalStateException( msg, e );
235         }
236     }
237 
238 
239     // -----------------------------------------------------------------------
240     // Cloneable methods
241     // -----------------------------------------------------------------------
242     /**
243      * {@inheritDoc}
244      */
245     @Override
246     public StringValue clone()
247     {
248         return ( StringValue ) super.clone();
249     }
250 
251 
252     // -----------------------------------------------------------------------
253     // Object Methods
254     // -----------------------------------------------------------------------
255     /**
256      * @see Object#hashCode()
257      * @return the instance's hashcode
258      */
259     @Override
260     public int hashCode()
261     {
262         if ( h == 0 )
263         {
264             // return zero if the value is null so only one null value can be
265             // stored in an attribute - the binary version does the same
266             if ( isNull() )
267             {
268                 return 0;
269             }
270 
271             // If the normalized value is null, will default to user provided
272             // which cannot be null at this point.
273             // If the normalized value is null, will default to user provided
274             // which cannot be null at this point.
275             String normalized = getNormValue();
276 
277             if ( normalized != null )
278             {
279                 h = normalized.hashCode();
280             }
281             else
282             {
283                 h = 17;
284             }
285         }
286 
287         return h;
288     }
289 
290 
291     /**
292      * Two StringValue are equals if their normalized values are equal
293      * 
294      * @see Object#equals(Object)
295      */
296     @Override
297     public boolean equals( Object obj )
298     {
299         if ( this == obj )
300         {
301             return true;
302         }
303 
304         if ( !( obj instanceof StringValue ) )
305         {
306             return false;
307         }
308 
309         StringValue other = ( StringValue ) obj;
310 
311         // First check if we have an attrbuteType.
312         if ( attributeType != null )
313         {
314             // yes : check for the other value
315             if ( other.attributeType != null )
316             {
317                 if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
318                 {
319                     // Both AttributeType have the same OID, we can assume they are 
320                     // equals. We don't check any further, because the unicity of OID
321                     // makes it unlikely that the two AT are different.
322                     // The values may be both null
323                     if ( isNull() )
324                     {
325                         return other.isNull();
326                     }
327 
328                     // Shortcut : if we have an AT for both the values, check the 
329                     // already normalized values
330                     if ( upValue.equals( other.upValue ) )
331                     {
332                         return true;
333                     }
334 
335                     // We have an AttributeType, we use the associated comparator
336                     try
337                     {
338                         Comparator<String> comparator = getLdapComparator();
339 
340                         // Compare normalized values
341                         if ( comparator == null )
342                         {
343                             return getNormReference().equals( other.getNormReference() );
344                         }
345                         else
346                         {
347                             return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
348                         }
349                     }
350                     catch ( LdapException ne )
351                     {
352                         return false;
353                     }
354                 }
355                 else
356                 {
357                     // We can't compare two values when the two ATs are different
358                     return false;
359                 }
360             }
361             else
362             {
363                 // We only have one AT : we will assume that both values are for the 
364                 // same AT.
365                 // The values may be both null
366                 if ( isNull() )
367                 {
368                     return other.isNull();
369                 }
370 
371                 // We have an AttributeType on the base value, we need to use its comparator
372                 try
373                 {
374                     Comparator<String> comparator = getLdapComparator();
375 
376                     // Compare normalized values. We have to normalized the other value,
377                     // as it has no AT
378                     MatchingRule equality = getAttributeType().getEquality();
379 
380                     if ( equality == null )
381                     {
382                         // No matching rule : compare the raw values
383                         return getNormReference().equals( other.getNormReference() );
384                     }
385 
386                     Normalizer normalizer = equality.getNormalizer();
387 
388                     StringValue otherValue = ( StringValue ) normalizer.normalize( other );
389 
390                     if ( comparator == null )
391                     {
392                         return getNormReference().equals( otherValue.getNormReference() );
393                     }
394                     else
395                     {
396                         return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
397                     }
398                 }
399                 catch ( LdapException ne )
400                 {
401                     return false;
402                 }
403             }
404         }
405         else
406         {
407             // No : check for the other value
408             if ( other.attributeType != null )
409             {
410                 // We only have one AT : we will assume that both values are for the 
411                 // same AT.
412                 // The values may be both null
413                 if ( isNull() )
414                 {
415                     return other.isNull();
416                 }
417 
418                 try
419                 {
420                     Comparator<String> comparator = other.getLdapComparator();
421 
422                     // Compare normalized values. We have to normalized the other value,
423                     // as it has no AT
424                     MatchingRule equality = other.getAttributeType().getEquality();
425 
426                     if ( equality == null )
427                     {
428                         // No matching rule : compare the raw values
429                         return getNormReference().equals( other.getNormReference() );
430                     }
431 
432                     Normalizer normalizer = equality.getNormalizer();
433 
434                     StringValue thisValue = ( StringValue ) normalizer.normalize( this );
435 
436                     if ( comparator == null )
437                     {
438                         return thisValue.getNormReference().equals( other.getNormReference() );
439                     }
440                     else
441                     {
442                         return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
443                     }
444                 }
445                 catch ( LdapException ne )
446                 {
447                     return false;
448                 }
449             }
450             else
451             {
452                 // The values may be both null
453                 if ( isNull() )
454                 {
455                     return other.isNull();
456                 }
457 
458                 // Now check the normalized values
459                 return getNormReference().equals( other.getNormReference() );
460             }
461         }
462     }
463 
464 
465     /**
466      * {@inheritDoc}
467      */
468     @Override
469     public boolean isHumanReadable()
470     {
471         return true;
472     }
473 
474 
475     /**
476      * @return The length of the interned value
477      */
478     @Override
479     public int length()
480     {
481         return upValue != null ? upValue.length() : 0;
482     }
483 
484 
485     /**
486      * Get the user provided value as a byte[].
487      * @return the user provided value as a byte[]
488      */
489     @Override
490     public byte[] getBytes()
491     {
492         return bytes;
493     }
494 
495 
496     /**
497      * Get the user provided value as a String.
498      *
499      * @return the user provided value as a String
500      */
501     @Override
502     public String getString()
503     {
504         return upValue != null ? upValue : "";
505     }
506 
507 
508     /**
509      * Deserialize a StringValue. It will return a new StringValue instance.
510      * 
511      * @param in The input stream
512      * @return A new StringValue instance
513      * @throws IOException If the stream can't be read
514      * @throws ClassNotFoundException If we can't instanciate a StringValue
515      */
516     public static StringValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
517     {
518         StringValue value = new StringValue( ( AttributeType ) null );
519         value.readExternal( in );
520 
521         return value;
522     }
523 
524 
525     /**
526      * Deserialize a schemaAware StringValue. It will return a new StringValue instance.
527      * 
528      * @param attributeType The AttributeType associated with the Value. Can be null
529      * @param in The input stream
530      * @return A new StringValue instance
531      * @throws IOException If the stream can't be read
532      * @throws ClassNotFoundException If we can't instanciate a StringValue
533      */
534     public static StringValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
535         ClassNotFoundException
536     {
537         StringValue value = new StringValue( attributeType );
538         value.readExternal( in );
539 
540         return value;
541     }
542 
543 
544     /**
545      * {@inheritDoc}
546      */
547     @Override
548     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
549     {
550         // Read the STRING flag
551         boolean isHR = in.readBoolean();
552 
553         if ( !isHR )
554         {
555             throw new IOException( "The serialized value is not a String value" );
556         }
557 
558         // Read the user provided value, if it's not null
559         if ( in.readBoolean() )
560         {
561             upValue = in.readUTF();
562             bytes = Strings.getBytesUtf8( upValue );
563         }
564 
565         // Read the isNormalized flag
566         boolean normalized = in.readBoolean();
567 
568         if ( normalized )
569         {
570             // Read the normalized value, if not null
571             if ( in.readBoolean() )
572             {
573                 normalizedValue = in.readUTF();
574             }
575         }
576         else
577         {
578             if ( attributeType != null )
579             {
580                 try
581                 {
582                     MatchingRule equality = attributeType.getEquality();
583 
584                     if ( equality == null )
585                     {
586                         normalizedValue = upValue;
587                     }
588                     else
589                     {
590                         Normalizer normalizer = equality.getNormalizer();
591 
592                         if ( normalizer != null )
593                         {
594                             normalizedValue = normalizer.normalize( upValue );
595                         }
596                         else
597                         {
598                             normalizedValue = upValue;
599                         }
600                     }
601                 }
602                 catch ( LdapException le )
603                 {
604                     normalizedValue = upValue;
605                 }
606             }
607             else
608             {
609                 normalizedValue = upValue;
610             }
611         }
612 
613         // The hashCoe
614         h = in.readInt();
615     }
616 
617 
618     /**
619      * Serialize the StringValue into a buffer at the given position.
620      * 
621      * @param buffer The buffer which will contain the serialized StringValue
622      * @param pos The position in the buffer for the serialized value
623      * @return The new position in the buffer
624      */
625     public int serialize( byte[] buffer, int pos )
626     {
627         // Compute the length
628         // The value type, the user provided value presence flag,
629         // the normalizedValue presence flag and the hash length.
630         int length = 1 + 1 + 1 + 4;
631 
632         byte[] upValueBytes = null;
633         byte[] normalizedValueBytes = null;
634 
635         if ( upValue != null )
636         {
637             upValueBytes = Strings.getBytesUtf8( upValue );
638             length += 4 + upValueBytes.length;
639         }
640 
641         if ( attributeType != null )
642         {
643             if ( normalizedValue != null )
644             {
645                 normalizedValueBytes = Strings.getBytesUtf8( normalizedValue );
646                 length += 1 + 4 + normalizedValueBytes.length;
647             }
648             else
649             {
650                 length += 1;
651             }
652         }
653 
654         // Check that we will be able to store the data in the buffer
655         if ( buffer.length - pos < length )
656         {
657             throw new ArrayIndexOutOfBoundsException();
658         }
659 
660         // The STRING flag
661         buffer[pos] = Serialize.TRUE;
662         pos++;
663 
664         // Write the user provided value, if it's not null
665         if ( upValue != null )
666         {
667             buffer[pos++] = Serialize.TRUE;
668             pos = Serialize.serialize( upValueBytes, buffer, pos );
669         }
670         else
671         {
672             buffer[pos++] = Serialize.FALSE;
673         }
674 
675         // Write the isNormalized flag
676         if ( attributeType != null )
677         {
678             // This flag is present to tell that we have a normalized value different
679             // from the upValue
680 
681             buffer[pos++] = Serialize.TRUE;
682 
683             // Write the normalized value, if not null
684             if ( normalizedValue != null )
685             {
686                 buffer[pos++] = Serialize.TRUE;
687                 pos = Serialize.serialize( normalizedValueBytes, buffer, pos );
688             }
689             else
690             {
691                 buffer[pos++] = Serialize.FALSE;
692             }
693         }
694         else
695         {
696             // No normalized value
697             buffer[pos++] = Serialize.FALSE;
698         }
699 
700         // Write the hashCode
701         pos = Serialize.serialize( h, buffer, pos );
702 
703         return pos;
704     }
705 
706 
707     /**
708      * Deserialize a StringValue from a byte[], starting at a given position
709      * 
710      * @param buffer The buffer containing the StringValue
711      * @param pos The position in the buffer
712      * @return The new position
713      * @throws IOException If the serialized value is not a StringValue
714      */
715     public int deserialize( byte[] buffer, int pos ) throws IOException
716     {
717         if ( ( pos < 0 ) || ( pos >= buffer.length ) )
718         {
719             throw new ArrayIndexOutOfBoundsException();
720         }
721 
722         // Read the STRING flag
723         boolean isHR = Serialize.deserializeBoolean( buffer, pos );
724         pos++;
725 
726         if ( !isHR )
727         {
728             throw new IOException( "The serialized value is not a String value" );
729         }
730 
731         // Read the user provided value, if it's not null
732         boolean hasWrappedValue = Serialize.deserializeBoolean( buffer, pos );
733         pos++;
734 
735         if ( hasWrappedValue )
736         {
737             byte[] upValueBytes = Serialize.deserializeBytes( buffer, pos );
738             pos += 4 + upValueBytes.length;
739             upValue = Strings.utf8ToString( upValueBytes );
740         }
741 
742         // Read the isNormalized flag
743         boolean hasAttributeType = Serialize.deserializeBoolean( buffer, pos );
744         pos++;
745 
746         if ( hasAttributeType )
747         {
748             // Read the normalized value, if not null
749             boolean hasNormalizedValue = Serialize.deserializeBoolean( buffer, pos );
750             pos++;
751 
752             if ( hasNormalizedValue )
753             {
754                 byte[] normalizedValueBytes = Serialize.deserializeBytes( buffer, pos );
755                 pos += 4 + normalizedValueBytes.length;
756                 normalizedValue = Strings.utf8ToString( normalizedValueBytes );
757             }
758         }
759         else
760         {
761             if ( attributeType != null )
762             {
763                 try
764                 {
765                     MatchingRule equality = attributeType.getEquality();
766 
767                     if ( equality == null )
768                     {
769                         normalizedValue = upValue;
770                     }
771                     else
772                     {
773                         Normalizer normalizer = equality.getNormalizer();
774 
775                         if ( normalizer != null )
776                         {
777                             normalizedValue = normalizer.normalize( upValue );
778                         }
779                         else
780                         {
781                             normalizedValue = upValue;
782                         }
783                     }
784                 }
785                 catch ( LdapException le )
786                 {
787                     normalizedValue = upValue;
788                 }
789             }
790             else
791             {
792                 normalizedValue = upValue;
793             }
794         }
795 
796         // The hashCode
797         h = Serialize.deserializeInt( buffer, pos );
798         pos += 4;
799 
800         return pos;
801     }
802 
803 
804     /**
805      * {@inheritDoc}
806      */
807     @Override
808     public void writeExternal( ObjectOutput out ) throws IOException
809     {
810         // Write a boolean for the HR flag
811         out.writeBoolean( STRING );
812 
813         // Write the user provided value, if it's not null
814         if ( upValue != null )
815         {
816             out.writeBoolean( true );
817             out.writeUTF( upValue );
818         }
819         else
820         {
821             out.writeBoolean( false );
822         }
823 
824         // Write the isNormalized flag
825         if ( attributeType != null )
826         {
827             // This flag is present to tell that we have a normalized value different
828             // from the upValue
829             out.writeBoolean( true );
830 
831             // Write the normalized value, if not null
832             if ( normalizedValue != null )
833             {
834                 out.writeBoolean( true );
835                 out.writeUTF( normalizedValue );
836             }
837             else
838             {
839                 out.writeBoolean( false );
840             }
841         }
842         else
843         {
844             // No normalized value
845             out.writeBoolean( false );
846         }
847 
848         // Write the hashCode
849         out.writeInt( h );
850 
851         // and flush the data
852         out.flush();
853     }
854 
855 
856     /**
857      * @see Object#toString()
858      */
859     @Override
860     public String toString()
861     {
862         return upValue == null ? "null" : upValue;
863     }
864 }