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