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 }