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.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  
26  import javax.naming.NamingException;
27  import javax.naming.directory.InvalidAttributeValueException;
28  
29  import org.apache.directory.shared.asn1.primitives.OID;
30  import org.apache.directory.shared.ldap.entry.EntryAttribute;
31  import org.apache.directory.shared.ldap.entry.Value;
32  import org.apache.directory.shared.ldap.entry.client.ClientBinaryValue;
33  import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
34  import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
35  import org.apache.directory.shared.ldap.schema.AttributeType;
36  import org.apache.directory.shared.ldap.util.StringTools;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  
41  /**
42   * A server side entry attribute aware of schema.
43   *
44   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
45   * @version $Rev$, $Date$
46   */
47  public final class DefaultServerAttribute extends DefaultClientAttribute implements ServerAttribute
48  {
49      public static final long serialVersionUID = 1L;
50      
51      /** logger for reporting errors that might not be handled properly upstream */
52      private static final Logger LOG = LoggerFactory.getLogger( DefaultServerAttribute.class );
53      
54      /** The associated AttributeType */
55      private AttributeType attributeType;
56      
57      
58      //-----------------------------------------------------------------------
59      // utility methods
60      //-----------------------------------------------------------------------
61      /**
62       * Private helper method used to set an UpId from an attributeType
63       * 
64       * @param at The attributeType for which we want the upID
65       * @return the ID of the given attributeType
66       */
67      private String getUpId( AttributeType at )
68      {
69          String atUpId = at.getName();
70          
71          if ( atUpId == null )
72          {
73              atUpId = at.getOid();
74          }
75          
76          return atUpId;
77      }
78      
79      
80      //-------------------------------------------------------------------------
81      // Constructors
82      //-------------------------------------------------------------------------
83      /**
84       * 
85       * Creates a new instance of DefaultServerAttribute, by copying
86       * another attribute, which can be a ClientAttribute. If the other
87       * attribute is a ServerAttribute, it will be copied.
88       *
89       * @param attributeType The attribute's type 
90       * @param attribute The attribute to be copied
91       */
92      public DefaultServerAttribute( AttributeType attributeType, EntryAttribute attribute )
93      {
94          // Copy the common values. isHR is only available on a ServerAttribute 
95          this.attributeType = attributeType;
96          this.id = attribute.getId();
97          this.upId = attribute.getUpId();
98  
99          if ( attribute instanceof ServerAttribute )
100         {
101             isHR = attribute.isHR();
102 
103             // Copy all the values
104             for ( Value<?> value:attribute )
105             {
106                 add( value.clone() );
107             }
108         }
109         else
110         {
111             
112             try
113             {
114                 isHR = attributeType.getSyntax().isHumanReadable();
115             }
116             catch ( NamingException ne )
117             {
118                 // Do nothing : the syntax should always exist ...
119             }
120             
121 
122             // Copy all the values
123             for ( Value<?> clientValue:attribute )
124             {
125                 Value<?> serverValue = null; 
126 
127                 // We have to convert the value first
128                 if ( clientValue instanceof ClientStringValue )
129                 {
130                     if ( isHR )
131                     {
132                         serverValue = new ServerStringValue( attributeType, (String)clientValue.get() );
133                     }
134                     else
135                     {
136                         // We have to convert the value to a binary value first
137                         serverValue = new ServerBinaryValue( attributeType, 
138                             StringTools.getBytesUtf8( (String)clientValue.get() ) );
139                     }
140                 }
141                 else if ( clientValue instanceof ClientBinaryValue )
142                 {
143                     if ( isHR )
144                     {
145                         // We have to convert the value to a String value first
146                         serverValue = new ServerStringValue( attributeType, 
147                             StringTools.utf8ToString( (byte[])clientValue.get() ) );
148                     }
149                     else
150                     {
151                         serverValue = new ServerBinaryValue( attributeType, (byte[])clientValue.get() );
152                     }
153                 }
154 
155                 add( serverValue );
156             }
157         }
158     }
159     
160     
161     // maybe have some additional convenience constructors which take
162     // an initial value as a string or a byte[]
163     /**
164      * Create a new instance of a EntryAttribute, without ID nor value.
165      * 
166      * @param attributeType the attributeType for the empty attribute added into the entry
167      */
168     public DefaultServerAttribute( AttributeType attributeType )
169     {
170         if ( attributeType == null )
171         {
172             throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
173         }
174         
175         setAttributeType( attributeType );
176     }
177 
178 
179     /**
180      * Create a new instance of a EntryAttribute, without value.
181      * 
182      * @param upId the ID for the added attributeType
183      * @param attributeType the added AttributeType
184      */
185     public DefaultServerAttribute( String upId, AttributeType attributeType )
186     {
187         if ( attributeType == null ) 
188         {
189             String message = "The AttributeType parameter should not be null";
190             LOG.error( message );
191             throw new IllegalArgumentException( message );
192         }
193 
194         setAttributeType( attributeType );
195         setUpId( upId );
196     }
197 
198 
199     /**
200      * Doc me more!
201      *
202      * If the value does not correspond to the same attributeType, then it's
203      * wrapped value is copied into a new Value which uses the specified
204      * attributeType.
205      *
206      * @param attributeType the attribute type according to the schema
207      * @param vals an initial set of values for this attribute
208      */
209     public DefaultServerAttribute( AttributeType attributeType, Value<?>... vals )
210     {
211         this( null, attributeType, vals );
212     }
213 
214 
215     /**
216      * Doc me more!
217      *
218      * If the value does not correspond to the same attributeType, then it's
219      * wrapped value is copied into a new Value which uses the specified
220      * attributeType.
221      * 
222      * Otherwise, the value is stored, but as a reference. It's not a copy.
223      *
224      * @param upId the ID of the added attribute
225      * @param attributeType the attribute type according to the schema
226      * @param vals an initial set of values for this attribute
227      */
228     public DefaultServerAttribute( String upId, AttributeType attributeType, Value<?>... vals )
229     {
230         if ( attributeType == null )
231         {
232             throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
233         }
234         
235         setAttributeType( attributeType );
236         setUpId( upId, attributeType );
237         add( vals );
238     }
239 
240 
241     /**
242      * Create a new instance of a EntryAttribute, without ID but with some values.
243      * 
244      * @param attributeType The attributeType added on creation
245      * @param vals The added value for this attribute
246      */
247     public DefaultServerAttribute( AttributeType attributeType, String... vals )
248     {
249         this( null, attributeType, vals );
250     }
251 
252 
253     /**
254      * Create a new instance of a EntryAttribute.
255      * 
256      * @param upId the ID for the added attribute
257      * @param attributeType The attributeType added on creation
258      * @param vals the added values for this attribute
259      */
260     public DefaultServerAttribute( String upId, AttributeType attributeType, String... vals )
261     {
262         if ( attributeType == null )
263         {
264             throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
265         }
266 
267         setAttributeType( attributeType );
268         add( vals );
269         setUpId( upId, attributeType );
270     }
271 
272 
273     /**
274      * Create a new instance of a EntryAttribute, with some byte[] values.
275      * 
276      * @param attributeType The attributeType added on creation
277      * @param vals The value for the added attribute
278      */
279     public DefaultServerAttribute( AttributeType attributeType, byte[]... vals )
280     {
281         this( null, attributeType, vals );
282     }
283 
284 
285     /**
286      * Create a new instance of a EntryAttribute, with some byte[] values.
287      * 
288      * @param upId the ID for the added attribute
289      * @param attributeType the AttributeType to be added
290      * @param vals the values for the added attribute
291      */
292     public DefaultServerAttribute( String upId, AttributeType attributeType, byte[]... vals )
293     {
294         if ( attributeType == null )
295         {
296             throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
297         }
298 
299         setAttributeType( attributeType );
300         add( vals );
301         setUpId( upId, attributeType );
302     }
303     
304     
305     //-------------------------------------------------------------------------
306     // API
307     //-------------------------------------------------------------------------
308     /**
309      * <p>
310      * Adds some values to this attribute. If the new values are already present in
311      * the attribute values, the method has no effect.
312      * </p>
313      * <p>
314      * The new values are added at the end of list of values.
315      * </p>
316      * <p>
317      * This method returns the number of values that were added.
318      * </p>
319      * <p>
320      * If the value's type is different from the attribute's type,
321      * the value is not added.
322      * </p>
323      * It's the responsibility of the caller to check if the stored
324      * values are consistent with the attribute's type.
325      * <p>
326      *
327      * @param vals some new values to be added which may be null
328      * @return the number of added values, or 0 if none has been added
329      */
330     public int add( byte[]... vals )
331     {
332         if ( !isHR )
333         {
334             int nbAdded = 0;
335             
336             for ( byte[] val:vals )
337             {
338                 Value<?> value = new ServerBinaryValue( attributeType, val );
339                 
340                 try
341                 {
342                     value.normalize();
343                 }
344                 catch( NamingException ne )
345                 {
346                     // The value can't be normalized : we don't add it.
347                     LOG.error( "The value '" + val + "' can't be normalized, it hasn't been added" );
348                     return 0;
349                 }
350                 
351                 if ( add( value ) != 0 )
352                 {
353                     nbAdded++;
354                 }
355                 else
356                 {
357                     LOG.error( "The value '" + val + "' is incorrect, it hasn't been added" );
358                 }
359             }
360             
361             return nbAdded;
362         }
363         else
364         {
365             // We can't add Binary values into a String serverAttribute
366             return 0;
367         }
368     }    
369 
370 
371     /**
372      * <p>
373      * Adds some values to this attribute. If the new values are already present in
374      * the attribute values, the method has no effect.
375      * </p>
376      * <p>
377      * The new values are added at the end of list of values.
378      * </p>
379      * <p>
380      * This method returns the number of values that were added.
381      * </p>
382      * If the value's type is different from the attribute's type,
383      * the value is not added.
384      *
385      * @param vals some new values to be added which may be null
386      * @return the number of added values, or 0 if none has been added
387      */
388     public int add( String... vals )
389     {
390         if ( isHR )
391         {
392             int nbAdded = 0;
393             
394             for ( String val:vals )
395             {
396                 if ( add( new ServerStringValue( attributeType, val ) ) != 0 )
397                 {
398                     nbAdded++;
399                 }
400                 else
401                 {
402                     LOG.error( "The value '" + val + "' is incorrect, it hasn't been added" );
403                 }
404             }
405             
406             return nbAdded;
407         }
408         else
409         {
410             // We can't add String values into a Binary serverAttribute
411             return 0;
412         }
413     }    
414 
415 
416     /**
417      * @see EntryAttribute#add(org.apache.directory.shared.ldap.entry.Value...)
418      * 
419      * @return the number of added values into this attribute
420      */
421     public int add( Value<?>... vals )
422     {
423         int nbAdded = 0;
424         
425         for ( Value<?> val:vals )
426         {
427             try
428             {
429                 if ( attributeType.getSyntax().isHumanReadable() )
430                 {
431                     if ( val == null )
432                     {
433                         Value<String> nullSV = new ServerStringValue( attributeType, (String)null );
434                         
435                         if ( !values.contains( nullSV ) )
436                         {
437                             values.add( nullSV );
438                             nbAdded++;
439                         }
440                     }
441                     else if ( val instanceof ServerStringValue )
442                     {
443                         if ( !values.contains( val ) )
444                         {
445                             if ( values.add( val ) )
446                             {
447                                 nbAdded++;
448                             }
449                         }
450                     }
451                     else if ( val instanceof ClientStringValue )
452                     {
453                         // If we get a Client value, convert it to a Server value first 
454                         Value<String> serverStringValue = new ServerStringValue( attributeType, (String)val.get() ); 
455                         
456                         if ( !values.contains( serverStringValue ) )
457                         {
458                             if ( values.add( serverStringValue ) )
459                             {
460                                 nbAdded++;
461                             }
462                         }
463                     }
464                     else
465                     {
466                         String message = "The value must be a String, as its AttributeType is H/R";
467                         LOG.error( message );
468                     }
469                 }
470                 else
471                 {
472                     if ( val == null )
473                     {
474                         Value<byte[]> nullSV = new ServerBinaryValue( attributeType, (byte[])null );
475                         
476                         if ( !values.contains( nullSV ) )
477                         {
478                             values.add( nullSV );
479                             nbAdded++;
480                         }
481                     }
482                     else if ( ( val instanceof ClientBinaryValue ) )
483                     {
484                         Value<byte[]> serverBinaryValue = new ServerBinaryValue( attributeType, (byte[])val.get() ); 
485                         
486                         if ( !values.contains( serverBinaryValue ) )
487                         {
488                             if ( values.add( serverBinaryValue ) )
489                             {
490                                 nbAdded++;
491                             }
492                         }
493                     }
494                     else if ( val instanceof ServerBinaryValue )
495                     {
496                         if ( !values.contains( val ) )
497                         {
498                             if ( values.add( val ) )
499                             {
500                                 nbAdded++;
501                             }
502                         }
503                     }
504                     else
505                     {
506                         String message = "The value must be a byte[], as its AttributeType is not H/R";
507                         LOG.error( message );
508                     }
509                 }
510             }
511             catch ( NamingException ne )
512             {
513                 String message = "Error while adding value '" + val.toString() +"' : " + ne.getMessage();
514                 LOG.error( message );
515             }
516         }
517         
518         return nbAdded;
519     }
520 
521 
522     /**
523      * Remove all the values from this attribute type, including a 
524      * null value. 
525      */
526     public void clear()
527     {
528         values.clear();
529     }
530 
531 
532     /**
533      * <p>
534      * Indicates whether all the specified values are attribute's values. If
535      * at least one value is not an attribute's value, this method will return 
536      * <code>false</code>
537      * </p>
538      * <p>
539      * If the Attribute is HR, this method will returns <code>false</code>
540      * </p>
541      *
542      * @param vals the values
543      * @return true if this attribute contains all the values, otherwise false
544      */
545     public boolean contains( byte[]... vals )
546     {
547         if ( !isHR )
548         {
549             // Iterate through all the values, and quit if we 
550             // don't find one in the values
551             for ( byte[] val:vals )
552             {
553                 ServerBinaryValue value = new ServerBinaryValue( attributeType, val );
554                 
555                 try
556                 {
557                     value.normalize();
558                 }
559                 catch ( NamingException ne )
560                 {
561                     return false;
562                 }
563                 
564                 if ( !values.contains( value ) )
565                 {
566                     return false;
567                 }
568             }
569             
570             return true;
571         }
572         else
573         {
574             return false;
575         }
576     }
577     
578     
579     /**
580      * <p>
581      * Indicates whether all the specified values are attribute's values. If
582      * at least one value is not an attribute's value, this method will return 
583      * <code>false</code>
584      * </p>
585      * <p>
586      * If the Attribute is not HR, this method will returns <code>false</code>
587      * </p>
588      *
589      * @param vals the values
590      * @return true if this attribute contains all the values, otherwise false
591      */
592     public boolean contains( String... vals )
593     {
594         if ( isHR )
595         {
596             // Iterate through all the values, and quit if we 
597             // don't find one in the values
598             for ( String val:vals )
599             {
600                 ServerStringValue value = new ServerStringValue( attributeType, val );
601                 
602                 if ( !values.contains( value ) )
603                 {
604                     return false;
605                 }
606             }
607             
608             return true;
609         }
610         else
611         {
612             return false;
613         }
614     }
615     
616     
617     /**
618      * <p>
619      * Indicates whether the specified values are some of the attribute's values.
620      * </p>
621      * <p>
622      * If the Attribute is HR, te metho will only accept String Values. Otherwise, 
623      * it will only accept Binary values.
624      * </p>
625      *
626      * @param vals the values
627      * @return true if this attribute contains all the values, otherwise false
628      */
629     public boolean contains( Value<?>... vals )
630     {
631         // Iterate through all the values, and quit if we 
632         // don't find one in the values. We have to separate the check
633         // depending on the isHR flag value.
634         if ( isHR )
635         {
636             for ( Value<?> val:vals )
637             {
638                 if ( val instanceof ServerStringValue )
639                 {
640                     if ( !values.contains( val ) )
641                     {
642                         return false;
643                     }
644                 }
645                 else if ( val instanceof ClientStringValue )
646                 {
647                     ServerStringValue serverValue = new ServerStringValue( attributeType, (String)val.get() );
648                     
649                     if ( !values.contains( serverValue ) )
650                     {
651                         return false;
652                     }
653                 }
654                 else
655                 {
656                     // Not a String value
657                     return false;
658                 }
659             }
660         }
661         else
662         {
663             for ( Value<?> val:vals )
664             {
665                 if ( val instanceof ClientBinaryValue )
666                 {
667                     if ( !values.contains( val ) )
668                     {
669                         return false;
670                     }
671                 }
672                 else
673                 {
674                     // Not a Binary value
675                     return false;
676                 }
677             }
678         }
679         
680         return true;
681     }
682 
683 
684     /**
685      * Get the attribute type associated with this ServerAttribute.
686      *
687      * @return the attributeType associated with this entry attribute
688      */
689     public AttributeType getAttributeType()
690     {
691         return attributeType;
692     }
693     
694     
695     /**
696      * <p>
697      * Check if the current attribute type is of the expected attributeType
698      * </p>
699      * <p>
700      * This method won't tell if the current attribute is a descendant of 
701      * the attributeType. For instance, the "CN" serverAttribute will return
702      * false if we ask if it's an instance of "Name". 
703      * </p> 
704      *
705      * @param attributeId The AttributeType ID to check
706      * @return True if the current attribute is of the expected attributeType
707      * @throws InvalidAttributeValueException If there is no AttributeType
708      */
709     public boolean instanceOf( String attributeId ) throws InvalidAttributeValueException
710     {
711         String trimmedId = StringTools.trim( attributeId );
712         
713         if ( StringTools.isEmpty( trimmedId ) )
714         {
715             return false;
716         }
717         
718         String normId = StringTools.lowerCaseAscii( trimmedId );
719         
720         for ( String name:attributeType.getNamesRef() )
721         {
722             if ( normId.equalsIgnoreCase( name ) )
723             {
724                 return true;
725             }
726         }
727         
728         return normId.equalsIgnoreCase( attributeType.getOid() );
729     }
730     
731 
732     /**
733      * <p>
734      * Checks to see if this attribute is valid along with the values it contains.
735      * </p>
736      * <p>
737      * An attribute is valid if :
738      * <li>All of its values are valid with respect to the attributeType's syntax checker</li>
739      * <li>If the attributeType is SINGLE-VALUE, then no more than a value should be present</li>
740      *</p>
741      * @return true if the attribute and it's values are valid, false otherwise
742      * @throws NamingException if there is a failure to check syntaxes of values
743      */
744     public boolean isValid() throws NamingException
745     {
746         // First check if the attribute has more than one value
747         // if the attribute is supposed to be SINGLE_VALUE
748         if ( attributeType.isSingleValue() && ( values.size() > 1 ) )
749         {
750             return false;
751         }
752 
753         for ( Value<?> value : values )
754         {
755             if ( ! value.isValid() )
756             {
757                 return false;
758             }
759         }
760 
761         return true;
762     }
763 
764 
765     /**
766      * @see EntryAttribute#remove(byte[]...)
767      * 
768      * @return <code>true</code> if all the values shave been removed from this attribute
769      */
770     public boolean remove( byte[]... vals )
771     {
772         if ( isHR ) 
773         {
774             return false;
775         }
776         
777         boolean removed = true;
778         
779         for ( byte[] val:vals )
780         {
781             ServerBinaryValue value = new ServerBinaryValue( attributeType, val );
782             removed &= values.remove( value );
783         }
784         
785         return removed;
786     }
787 
788 
789     /**
790      * @see EntryAttribute#remove(String...)
791      * 
792      * @return <code>true</code> if all the values shave been removed from this attribute
793      */
794     public boolean remove( String... vals )
795     {
796         if ( !isHR )
797         {
798             return false;
799         }
800         
801         boolean removed = true;
802         
803         for ( String val:vals )
804         {
805             ServerStringValue value = new ServerStringValue( attributeType, val );
806             removed &= values.remove( value );
807         }
808         
809         return removed;
810     }
811 
812 
813     /**
814      * @see EntryAttribute#remove(org.apache.directory.shared.ldap.entry.Value...)
815      * 
816      * @return <code>true</code> if all the values shave been removed from this attribute
817      */
818     public boolean remove( Value<?>... vals )
819     {
820         boolean removed = true;
821         
822         // Loop through all the values to remove. If one of
823         // them is not present, the method will return false.
824         // As the attribute may be HR or not, we have two separated treatments
825         if ( isHR )
826         {
827             for ( Value<?> val:vals )
828             {
829                 if ( val instanceof ClientStringValue )
830                 {
831                     ServerStringValue ssv = new ServerStringValue( attributeType, (String)val.get() );
832                     removed &= values.remove( ssv );
833                 }
834                 else if ( val instanceof ServerStringValue )
835                 {
836                     removed &= values.remove( val );
837                 }
838                 else
839                 {
840                     removed = false;
841                 }
842             }
843         }
844         else
845         {
846             for ( Value<?> val:vals )
847             {
848                 if ( val instanceof ClientBinaryValue )
849                 {
850                     ServerBinaryValue sbv = new ServerBinaryValue( attributeType, (byte[])val.get() );
851                     removed &= values.remove( sbv );
852                 }
853                 else if ( val instanceof ServerBinaryValue )
854                 {
855                     removed &= values.remove( val );
856                 }
857                 else
858                 {
859                     removed = false;
860                 }
861             }
862         }
863         
864         return removed;
865     }
866 
867 
868     
869     /**
870      * <p>
871      * Set the attribute type associated with this ServerAttribute.
872      * </p>
873      * <p>
874      * The current attributeType will be replaced. It is the responsibility of
875      * the caller to insure that the existing values are compatible with the new
876      * AttributeType
877      * </p>
878      *
879      * @param attributeType the attributeType associated with this entry attribute
880      */
881     public void setAttributeType( AttributeType attributeType )
882     {
883         if ( attributeType == null )
884         {
885             throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
886         }
887 
888         this.attributeType = attributeType;
889         setUpId( null, attributeType );
890         
891         try
892         {
893             if ( attributeType.getSyntax().isHumanReadable() )
894             {
895                 isHR = true;
896             }
897             else
898             {
899                 isHR = false;
900             }
901         }
902         catch ( NamingException ne )
903         {
904             // If we have an exception while trying to get the Syntax for this attribute
905             // just set it as Binary
906             isHR = false;
907         }
908     }
909     
910     
911     /**
912      * <p>
913      * Overload the ClientAttribte isHR method : we can't change this flag
914      * for a ServerAttribute, as the HR is already set using the AttributeType.
915      * Set the attribute to Human Readable or to Binary. 
916      * </p>
917      * 
918      * @param isHR <code>true</code> for a Human Readable attribute, 
919      * <code>false</code> for a Binary attribute.
920      */
921     public void setHR( boolean isHR )
922     {
923         // Do nothing...
924     }
925 
926     
927     /**
928      * <p>
929      * Overload the {@link DefaultClientAttribute#setId(String)} method.
930      * </p>
931      * <p>
932      * As the attributeType has already been set, we have to be sure that the 
933      * argument is compatible with the attributeType's name. 
934      * </p>
935      * <p>
936      * If the given ID is not compatible with the attributeType's possible
937      * names, the previously loaded ID will be kept.
938      * </p>
939      *
940      * @param id The attribute ID
941      */
942     public void setId( String id )
943     {
944         if ( !StringTools.isEmpty( StringTools.trim( id  ) ) )
945         {
946             if ( attributeType.getName() == null )
947             {
948                 // If the name is null, then we may have to store an OID
949                 if ( OID.isOID( id )  && attributeType.getOid().equals( id ) )
950                 {
951                     // Everything is fine, store the upId.
952                     // This should not happen...
953                     super.setId( id );
954                 }
955             }
956             else
957             {
958                 // We have at least one name. Check that the normalized upId
959                 // is one of those names. Otherwise, the upId may be an OID too.
960                 // In this case, it must be equals to the attributeType OID.
961                 String normId = StringTools.lowerCaseAscii( StringTools.trim( id ) );
962                 
963                 for ( String atName:attributeType.getNamesRef() )
964                 {
965                     if ( atName.equalsIgnoreCase( normId ) )
966                     {
967                         // Found ! We can store the upId and get out
968                         super.setId( normId );
969                         return;
970                     }
971                 }
972                 
973                 // Last case, the UpId is an OID
974                 if ( OID.isOID( normId ) && attributeType.getOid().equals( normId ) )
975                 {
976                     // We have an OID : stores it
977                     super.setUpId( normId );
978                 }
979                 else
980                 {
981                     // The id is incorrect : this is not allowed 
982                     throw new IllegalArgumentException( "The ID '" + id + "'is incompatible with the AttributeType's id '" + 
983                         attributeType.getName() + "'" );
984                 }
985             }
986         }
987         else
988         {
989             throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" +
990             " value when trimmed" );
991         }
992     }
993     
994     
995     /**
996      * <p>
997      * Overload the {@link DefaultClientAttribute#setUpId(String)} method.
998      * </p>
999      * <p>
1000      * As the attributeType has already been set, we have to be sure that the 
1001      * argument is compatible with the attributeType's name. 
1002      * </p>
1003      * <p>
1004      * If the given ID is not compatible with the attributeType's possible
1005      * names, the previously loaded ID will be kept.
1006      * </p>
1007      *
1008      * @param upId The attribute ID
1009      */
1010     public void setUpId( String upId )
1011     {
1012         if ( !StringTools.isEmpty( StringTools.trim( upId  ) ) )
1013         {
1014             if ( attributeType.getName() == null )
1015             {
1016                 // If the name is null, then we may have to store an OID
1017                 if ( OID.isOID( upId )  && attributeType.getOid().equals( upId ) )
1018                 {
1019                     // Everything is fine, store the upId.
1020                     // This should not happen...
1021                     super.setUpId( upId );
1022                     
1023                 }
1024             }
1025             else
1026             {
1027                 // We have at least one name. Check that the normalized upId
1028                 // is one of those names. Otherwise, the upId may be an OID too.
1029                 // In this case, it must be equals to the attributeType OID.
1030                 String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) );
1031                 
1032                 for ( String atId:attributeType.getNamesRef() )
1033                 {
1034                     if ( atId.equalsIgnoreCase( normUpId ) )
1035                     {
1036                         // Found ! We can store the upId and get out
1037                         super.setUpId( upId );
1038                         return;
1039                     }
1040                 }
1041                 
1042                 // Last case, the UpId is an OID
1043                 if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) )
1044                 {
1045                     // We have an OID : stores it
1046                     super.setUpId( upId );
1047                 }
1048             }
1049         }
1050     }
1051     
1052     
1053     /**
1054      * <p>
1055      * Set the user provided ID. If we have none, the upId is assigned
1056      * the attributetype's name. If it does not have any name, we will
1057      * use the OID.
1058      * </p>
1059      * <p>
1060      * If we have an upId and an AttributeType, they must be compatible. :
1061      *  - if the upId is an OID, it must be the AttributeType's OID
1062      *  - otherwise, its normalized form must be equals to ones of
1063      *  the attributeType's names.
1064      * </p>
1065      * <p>
1066      * In any case, the ATtributeType will be changed. The caller is responsible for
1067      * the present values to be compatoble with the new AttributeType.
1068      * </p>
1069      *
1070      * @param upId The attribute ID
1071      * @param attributeType The associated attributeType
1072      */
1073     public void setUpId( String upId, AttributeType attributeType )
1074     {
1075         if ( StringTools.isEmpty( StringTools.trim( upId  ) ) )
1076         {
1077             super.setUpId( getUpId( attributeType ) );
1078             this.attributeType = attributeType;
1079         }
1080         else
1081         {
1082             String name = attributeType.getName();
1083             
1084             if ( name == null )
1085             {
1086                 // If the name is null, then we may have to store an OID
1087                 if ( OID.isOID( upId )  && attributeType.getOid().equals( upId ) )
1088                 {
1089                     //  Everything is fine, store the upId. 
1090                     super.setUpId( upId );
1091                     this.attributeType = attributeType;
1092                 }
1093                 else
1094                 {
1095                     // We have a difference or the upId is not a valid OID :
1096                     // we will use the attributeTypeOID in this case.
1097                     LOG.warn( "The upID ({}) is not an OID or is different from the AttributeType OID({})",
1098                         upId, attributeType.getOid() );
1099                     super.setUpId( attributeType.getOid() );
1100                     this.attributeType = attributeType;
1101                 }
1102             }
1103             else
1104             {
1105                 // We have at least one name. Check that the normalized upId
1106                 // is one of those names. Otherwise, the upId may be an OID too.
1107                 // In this case, it must be equals to the attributeType OID.
1108                 String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) );
1109                 
1110                 for ( String atId:attributeType.getNamesRef() )
1111                 {
1112                     if ( atId.equalsIgnoreCase( normUpId ) )
1113                     {
1114                         // Found ! We can store the upId and get out
1115                         super.setUpId( upId );
1116                         this.attributeType = attributeType;
1117                         return;
1118                     }
1119                 }
1120     
1121                 // UpId was not found in names. It should be an OID, or if not, we 
1122                 // will use the AttributeType name.
1123                 if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) )
1124                 {
1125                     // We have an OID : stores it
1126                     super.setUpId( upId );
1127                     this.attributeType = attributeType;
1128                 }
1129                 else
1130                 {
1131                     String message = "The upID (" + upId + ") is not an OID or is different from the AttributeType OID (" + 
1132                                         attributeType.getOid() + ")";
1133                     // Not a valid OID : use the AttributeTypes OID name instead
1134                     LOG.error( message );
1135                     throw new IllegalArgumentException( message );
1136                 }
1137             }
1138         }
1139     }
1140 
1141 
1142     /**
1143      * Convert the ServerAttribute to a ClientAttribute
1144      *
1145      * @return An instance of ClientAttribute
1146      */
1147     public EntryAttribute toClientAttribute()
1148     {
1149         // Create the new EntryAttribute
1150         EntryAttribute clientAttribute = new DefaultClientAttribute( upId );
1151         
1152         // Copy the values
1153         for ( Value<?> value:this )
1154         {
1155             Value<?> clientValue = null;
1156             
1157             if ( value instanceof ServerStringValue )
1158             {
1159                 clientValue = new ClientStringValue( (String)value.get() );
1160             }
1161             else
1162             {
1163                 clientValue = new ClientBinaryValue( (byte[])value.get() );
1164             }
1165             
1166             clientAttribute.add( clientValue );
1167         }
1168         
1169         return clientAttribute;
1170     }
1171 
1172 
1173     //-------------------------------------------------------------------------
1174     // Serialization methods
1175     //-------------------------------------------------------------------------
1176     
1177     /**
1178      * @see java.io.Externalizable#writeExternal(ObjectOutput)
1179      * 
1180      * We can't use this method for a ServerAttribute, as we have to feed the value
1181      * with an AttributeType object
1182      */
1183     public void writeExternal( ObjectOutput out ) throws IOException
1184     {
1185         throw new IllegalStateException( "Cannot use standard serialization for a ServerAttribute" );
1186     }
1187     
1188     
1189     /**
1190      * @see Externalizable#writeExternal(ObjectOutput)
1191      * <p>
1192      * 
1193      * This is the place where we serialize attributes, and all theirs
1194      * elements. 
1195      * 
1196      * The inner structure is the same as the client attribute, but we can't call
1197      * it as we won't be able to serialize the serverValues
1198      * 
1199      */
1200     public void serialize( ObjectOutput out ) throws IOException
1201     {
1202         // Write the UPId (the id will be deduced from the upID)
1203         out.writeUTF( upId );
1204         
1205         // Write the HR flag, if not null
1206         if ( isHR != null )
1207         {
1208             out.writeBoolean( true );
1209             out.writeBoolean( isHR );
1210         }
1211         else
1212         {
1213             out.writeBoolean( false );
1214         }
1215         
1216         // Write the number of values
1217         out.writeInt( size() );
1218         
1219         if ( size() > 0 ) 
1220         {
1221             // Write each value
1222             for ( Value<?> value:values )
1223             {
1224                 // Write the value, using the correct method
1225                 if ( value instanceof ServerStringValue )
1226                 {
1227                     ((ServerStringValue)value).serialize( out );
1228                 }
1229                 else
1230                 {
1231                     ((ServerBinaryValue)value).serialize( out );
1232                 }
1233             }
1234         }
1235     }
1236 
1237     
1238     /**
1239      * @see java.io.Externalizable#readExternal(ObjectInput)
1240      * 
1241      * We can't use this method for a ServerAttribute, as we have to feed the value
1242      * with an AttributeType object
1243      */
1244     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1245     {
1246         throw new IllegalStateException( "Cannot use standard serialization for a ServerAttribute" );
1247     }
1248     
1249     
1250     /**
1251      * @see Externalizable#readExternal(ObjectInput)
1252      */
1253     public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
1254     {
1255         // Read the ID and the UPId
1256         upId = in.readUTF();
1257         
1258         // Compute the id
1259         setUpId( upId );
1260         
1261         // Read the HR flag, if not null
1262         if ( in.readBoolean() )
1263         {
1264             isHR = in.readBoolean();
1265         }
1266 
1267         // Read the number of values
1268         int nbValues = in.readInt();
1269 
1270         if ( nbValues > 0 )
1271         {
1272             for ( int i = 0; i < nbValues; i++ )
1273             {
1274                 Value<?> value = null;
1275                 
1276                 if ( isHR )
1277                 {
1278                     value  = new ServerStringValue( attributeType );
1279                     ((ServerStringValue)value).deserialize( in );
1280                 }
1281                 else
1282                 {
1283                     value  = new ServerBinaryValue( attributeType );
1284                     ((ServerBinaryValue)value).deserialize( in );
1285                 }
1286                 
1287                 try
1288                 {
1289                     value.normalize();
1290                 }
1291                 catch ( NamingException ne )
1292                 {
1293                     // Do nothing...
1294                 }
1295                     
1296                 values.add( value );
1297             }
1298         }
1299     }
1300     
1301     
1302     //-------------------------------------------------------------------------
1303     // Overloaded Object class methods
1304     //-------------------------------------------------------------------------
1305     /**
1306      * Clone an attribute. All the element are duplicated, so a modification on
1307      * the original object won't affect the cloned object, as a modification
1308      * on the cloned object has no impact on the original object
1309      * 
1310      * @return a clone of the current attribute
1311      */
1312     public ServerAttribute clone()
1313     {
1314         // clone the structure by cloner the inherited class
1315         ServerAttribute clone = (ServerAttribute)super.clone();
1316         
1317         // We are done !
1318         return clone;
1319     }
1320 
1321 
1322     /**
1323      * @see Object#equals(Object)
1324      * 
1325      * @return <code>true</code> if the two objects are equal
1326      */
1327     public boolean equals( Object obj )
1328     {
1329         if ( obj == this )
1330         {
1331             return true;
1332         }
1333         
1334         if ( ! (obj instanceof ServerAttribute ) )
1335         {
1336             return false;
1337         }
1338         
1339         ServerAttribute other = (ServerAttribute)obj;
1340         
1341         if ( !attributeType.equals( other.getAttributeType() ) )
1342         {
1343             return false;
1344         }
1345         
1346         if ( values.size() != other.size() )
1347         {
1348             return false;
1349         }
1350         
1351         for ( Value<?> val:values )
1352         {
1353             if ( ! other.contains( val ) )
1354             {
1355                 return false;
1356             }
1357         }
1358         
1359         return true;
1360     }
1361     
1362     
1363     /**
1364      * The hashCode is based on the id, the isHR flag and 
1365      * on the internal values.
1366      *  
1367      * @see Object#hashCode()
1368      * 
1369      * @return the instance's hash code 
1370      */
1371     public int hashCode()
1372     {
1373         int h = super.hashCode();
1374         
1375         if ( attributeType != null )
1376         {
1377             h = h*17 + attributeType.hashCode();
1378         }
1379         
1380         return h;
1381     }
1382     
1383     
1384     /**
1385      * @see Object#toString()
1386      * 
1387      * @return A String representation of this instance
1388      */
1389     public String toString()
1390     {
1391         StringBuilder sb = new StringBuilder();
1392         
1393         if ( ( values != null ) && ( values.size() != 0 ) )
1394         {
1395             for ( Value<?> value:values )
1396             {
1397                 sb.append( "    " ).append( upId ).append( ": " );
1398                 
1399                 if ( value.isNull() )
1400                 {
1401                     sb.append( "''" );
1402                 }
1403                 else
1404                 {
1405                     sb.append( value );
1406                 }
1407                 
1408                 sb.append( '\n' );
1409             }
1410         }
1411         else
1412         {
1413             sb.append( "    " ).append( upId ).append( ": (null)\n" );
1414         }
1415         
1416         return sb.toString();
1417     }
1418 }