001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.directory.shared.ldap.entry.client;
020
021
022 import java.io.IOException;
023 import java.io.ObjectInput;
024 import java.io.ObjectOutput;
025 import java.util.Iterator;
026 import java.util.LinkedHashSet;
027 import java.util.List;
028 import java.util.Set;
029
030 import org.apache.directory.shared.ldap.exception.LdapException;
031 import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
032
033 import org.apache.directory.shared.asn1.primitives.OID;
034 import org.apache.directory.shared.i18n.I18n;
035 import org.apache.directory.shared.ldap.entry.BinaryValue;
036 import org.apache.directory.shared.ldap.entry.StringValue;
037 import org.apache.directory.shared.ldap.entry.EntryAttribute;
038 import org.apache.directory.shared.ldap.entry.Value;
039 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
040 import org.apache.directory.shared.ldap.schema.AttributeType;
041 import org.apache.directory.shared.ldap.schema.SyntaxChecker;
042 import org.apache.directory.shared.ldap.util.StringTools;
043 import org.slf4j.Logger;
044 import org.slf4j.LoggerFactory;
045
046
047 /**
048 * A client side entry attribute. The client is not aware of the schema,
049 * so we can't tell if the stored value will be String or Binary. We will
050 * default to Binary.<p>
051 * To define the kind of data stored, the client must set the isHR flag.
052 *
053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054 * @version $Rev$, $Date$
055 */
056 public class DefaultClientAttribute implements EntryAttribute
057 {
058 /** logger for reporting errors that might not be handled properly upstream */
059 private static final Logger LOG = LoggerFactory.getLogger( DefaultClientAttribute.class );
060
061 /** The associated AttributeType */
062 protected AttributeType attributeType;
063
064 /** The set of contained values */
065 protected Set<Value<?>> values = new LinkedHashSet<Value<?>>();
066
067 /** The User provided ID */
068 protected String upId;
069
070 /** The normalized ID (will be the OID if we have a AttributeType) */
071 protected String id;
072
073 /** Tells if the attribute is Human Readable or not. When not set,
074 * this flag is null. */
075 protected Boolean isHR;
076
077
078 // maybe have some additional convenience constructors which take
079 // an initial value as a string or a byte[]
080 /**
081 * Create a new instance of a EntryAttribute, without ID nor value.
082 */
083 public DefaultClientAttribute()
084 {
085 }
086
087
088 /**
089 * Create a new instance of a EntryAttribute, without value.
090 */
091 public DefaultClientAttribute( String upId )
092 {
093 setUpId( upId );
094 }
095
096
097 /**
098 * If the value does not correspond to the same attributeType, then it's
099 * wrapped value is copied into a new ClientValue which uses the specified
100 * attributeType.
101 *
102 * Otherwise, the value is stored, but as a reference. It's not a copy.
103 *
104 * @param upId
105 * @param attributeType the attribute type according to the schema
106 * @param vals an initial set of values for this attribute
107 */
108 public DefaultClientAttribute( String upId, Value<?>... vals )
109 {
110 // The value can be null, this is a valid value.
111 if ( vals[0] == null )
112 {
113 add( new StringValue() );
114 }
115 else
116 {
117 for ( Value<?> val:vals )
118 {
119 if ( ( val instanceof StringValue ) || ( val.isBinary() ) )
120 {
121 add( val );
122 }
123 else
124 {
125 String message = I18n.err( I18n.ERR_04129, val.getClass().getName() );
126 LOG.error( message );
127 throw new IllegalStateException( message );
128 }
129 }
130 }
131
132 setUpId( upId );
133 }
134
135
136 /**
137 * Create a new instance of a EntryAttribute.
138 */
139 public DefaultClientAttribute( String upId, String... vals )
140 {
141 add( vals );
142 setUpId( upId );
143 }
144
145
146 /**
147 * Create a new instance of a EntryAttribute, with some byte[] values.
148 */
149 public DefaultClientAttribute( String upId, byte[]... vals )
150 {
151 add( vals );
152 setUpId( upId );
153 }
154
155
156 /**
157 * <p>
158 * Get the byte[] value, if and only if the value is known to be Binary,
159 * otherwise a InvalidAttributeValueException will be thrown
160 * </p>
161 * <p>
162 * Note that this method returns the first value only.
163 * </p>
164 *
165 * @return The value as a byte[]
166 * @throws LdapInvalidAttributeValueException If the value is a String
167 */
168 public byte[] getBytes() throws LdapInvalidAttributeValueException
169 {
170 Value<?> value = get();
171
172 if ( value.isBinary() )
173 {
174 return value.getBytes();
175 }
176 else
177 {
178 String message = I18n.err( I18n.ERR_04130 );
179 LOG.error( message );
180 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
181 }
182 }
183
184
185 /**
186 * <p>
187 * Get the String value, if and only if the value is known to be a String,
188 * otherwise a InvalidAttributeValueException will be thrown
189 * </p>
190 * <p>
191 * Note that this method returns the first value only.
192 * </p>
193 *
194 * @return The value as a String
195 * @throws LdapInvalidAttributeValueException If the value is a byte[]
196 */
197 public String getString() throws LdapInvalidAttributeValueException
198 {
199 Value<?> value = get();
200
201 if ( value instanceof StringValue )
202 {
203 return value.getString();
204 }
205 else
206 {
207 String message = I18n.err( I18n.ERR_04131 );
208 LOG.error( message );
209 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
210 }
211 }
212
213
214 /**
215 * Get's the attribute identifier. Its value is the same than the
216 * user provided ID.
217 *
218 * @return the attribute's identifier
219 */
220 public String getId()
221 {
222 return id;
223 }
224
225
226 /**
227 * <p>
228 * Set the attribute to Human Readable or to Binary.
229 * </p>
230 * @param isHR <code>true</code> for a Human Readable attribute,
231 * <code>false</code> for a Binary attribute.
232 */
233 public void setHR( boolean isHR )
234 {
235 this.isHR = isHR;
236 //TODO : deal with the values, we may have to convert them.
237 }
238
239
240 /**
241 * Set the normalized ID. The ID will be lowercased, and spaces
242 * will be trimmed.
243 *
244 * @param id The attribute ID
245 * @throws IllegalArgumentException If the ID is empty or null or
246 * resolve to an empty value after being trimmed
247 */
248 public void setId( String id )
249 {
250 this.id = StringTools.trim( StringTools.lowerCaseAscii( id ) );
251
252 if ( this.id.length() == 0 )
253 {
254 this.id = null;
255 throw new IllegalArgumentException( I18n.err( I18n.ERR_04132 ) );
256 }
257 }
258
259
260 /**
261 * Get's the user provided identifier for this entry. This is the value
262 * that will be used as the identifier for the attribute within the
263 * entry. If this is a commonName attribute for example and the user
264 * provides "COMMONname" instead when adding the entry then this is
265 * the format the user will have that entry returned by the directory
266 * server. To do so we store this value as it was given and track it
267 * in the attribute using this property.
268 *
269 * @return the user provided identifier for this attribute
270 */
271 public String getUpId()
272 {
273 return upId;
274 }
275
276
277 /**
278 * Set the user provided ID. It will also set the ID, normalizing
279 * the upId (removing spaces before and after, and lowercasing it)<br>
280 * <br>
281 * If the Attribute already has an AttributeType, then the upId must
282 * be either the AttributeType name, or OID
283 *
284 * @param upId The attribute ID
285 * @throws IllegalArgumentException If the ID is empty or null or
286 * resolve to an empty value after being trimmed
287 */
288 public void setUpId( String upId )
289 {
290 setUpId( upId, null );
291 }
292
293
294 /**
295 * Check that the upId is either a name or the OID of a given AT
296 */
297 private boolean areCompatible( String id, AttributeType attributeType )
298 {
299 // First, get rid of the options, if any
300 int optPos = id.indexOf( ";" );
301 String idNoOption = id;
302
303 if ( optPos != -1 )
304 {
305 idNoOption = id.substring( 0, optPos );
306 }
307
308 // Check that we find the ID in the AT names
309 for ( String name : attributeType.getNames() )
310 {
311 if ( name.equalsIgnoreCase( idNoOption ) )
312 {
313 return true;
314 }
315 }
316
317 // Not found in names, check the OID
318 if ( OID.isOID( id ) && attributeType.getOid().equals( id ) )
319 {
320 return true;
321 }
322
323 return false;
324 }
325
326
327 /**
328 * <p>
329 * Set the user provided ID. If we have none, the upId is assigned
330 * the attributetype's name. If it does not have any name, we will
331 * use the OID.
332 * </p>
333 * <p>
334 * If we have an upId and an AttributeType, they must be compatible. :
335 * - if the upId is an OID, it must be the AttributeType's OID
336 * - otherwise, its normalized form must be equals to ones of
337 * the attributeType's names.
338 * </p>
339 * <p>
340 * In any case, the ATtributeType will be changed. The caller is responsible for
341 * the present values to be compatoble with the new AttributeType.
342 * </p>
343 *
344 * @param upId The attribute ID
345 * @param attributeType The associated attributeType
346 */
347 public void setUpId( String upId, AttributeType attributeType )
348 {
349 String trimmed = StringTools.trim( upId );
350
351 if ( StringTools.isEmpty( trimmed ) && ( attributeType == null ) )
352 {
353 throw new IllegalArgumentException( "Cannot set a null ID with a null AttributeType" );
354 }
355
356 String id = StringTools.toLowerCase( trimmed );
357
358 if ( attributeType == null )
359 {
360 if ( this.attributeType == null )
361 {
362 this.upId = upId;
363 this.id = id;
364 return;
365 }
366 else
367 {
368 if ( areCompatible( id, this.attributeType ) )
369 {
370 this.upId = upId;
371 this.id = id;
372 return;
373 }
374 else
375 {
376 return;
377 }
378 }
379 }
380
381 if ( StringTools.isEmpty( id ) )
382 {
383 this.attributeType = attributeType;
384 this.upId = attributeType.getName();
385 this.id = StringTools.trim( this.upId );
386 return;
387 }
388
389 if ( areCompatible( id, attributeType ) )
390 {
391 this.upId = upId;
392 this.id = id;
393 this.attributeType = attributeType;
394 return;
395 }
396
397 throw new IllegalArgumentException( "ID '" + id + "' and AttributeType '" + attributeType.getName() + "' are not compatible " );
398 }
399
400
401 /**
402 * <p>
403 * Tells if the attribute is Human Readable.
404 * </p>
405 * <p>This flag is set by the caller, or implicitly when adding String
406 * values into an attribute which is not yet declared as Binary.
407 * </p>
408 * @return
409 */
410 public boolean isHR()
411 {
412 return isHR != null ? isHR : false;
413 }
414
415
416 /**
417 * Checks to see if this attribute is valid along with the values it contains.
418 *
419 * @return true if the attribute and it's values are valid, false otherwise
420 * @throws LdapException if there is a failure to check syntaxes of values
421 */
422 public boolean isValid() throws LdapException
423 {
424 for ( Value<?> value:values )
425 {
426 if ( !value.isValid() )
427 {
428 return false;
429 }
430 }
431
432 return true;
433 }
434
435
436 /**
437 * Checks to see if this attribute is valid along with the values it contains.
438 *
439 * @return true if the attribute and it's values are valid, false otherwise
440 * @throws LdapException if there is a failure to check syntaxes of values
441 */
442 public boolean isValid( SyntaxChecker checker ) throws LdapException
443 {
444 for ( Value<?> value : values )
445 {
446 if ( !value.isValid( checker ) )
447 {
448 return false;
449 }
450 }
451
452 return true;
453 }
454
455
456 /**
457 * Adds some values to this attribute. If the new values are already present in
458 * the attribute values, the method has no effect.
459 * <p>
460 * The new values are added at the end of list of values.
461 * </p>
462 * <p>
463 * This method returns the number of values that were added.
464 * </p>
465 * <p>
466 * If the value's type is different from the attribute's type,
467 * a conversion is done. For instance, if we try to set some
468 * StringValue into a Binary attribute, we just store the UTF-8
469 * byte array encoding for this StringValue.
470 * </p>
471 * <p>
472 * If we try to store some BinaryValue in a HR attribute, we try to
473 * convert those BinaryValue assuming they represent an UTF-8 encoded
474 * String. Of course, if it's not the case, the stored value will
475 * be incorrect.
476 * </p>
477 * <p>
478 * It's the responsibility of the caller to check if the stored
479 * values are consistent with the attribute's type.
480 * </p>
481 * <p>
482 * The caller can set the HR flag in order to enforce a type for
483 * the current attribute, otherwise this type will be set while
484 * adding the first value, using the value's type to set the flag.
485 * </p>
486 * <p>
487 * <b>Note : </b>If the entry contains no value, and the unique added value
488 * is a null length value, then this value will be considered as
489 * a binary value.
490 * </p>
491 * @param val some new values to be added which may be null
492 * @return the number of added values, or 0 if none has been added
493 */
494 public int add( Value<?>... vals )
495 {
496 int nbAdded = 0;
497 BinaryValue nullBinaryValue = null;
498 StringValue nullStringValue = null;
499 boolean nullValueAdded = false;
500
501 for ( Value<?> val:vals )
502 {
503 if ( val == null )
504 {
505 // We have a null value. If the HR flag is not set, we will consider
506 // that the attribute is not HR. We may change this later
507 if ( isHR == null )
508 {
509 // This is the first value. Add both types, as we
510 // don't know yet the attribute type's, but we may
511 // know later if we add some new value.
512 // We have to do that because we are using a Set,
513 // and we can't remove the first element of the Set.
514 nullBinaryValue = new BinaryValue( (byte[])null );
515 nullStringValue = new StringValue( (String)null );
516
517 values.add( nullBinaryValue );
518 values.add( nullStringValue );
519 nullValueAdded = true;
520 nbAdded++;
521 }
522 else if ( !isHR )
523 {
524 // The attribute type is binary.
525 nullBinaryValue = new BinaryValue( (byte[])null );
526
527 // Don't add a value if it already exists.
528 if ( !values.contains( nullBinaryValue ) )
529 {
530 values.add( nullBinaryValue );
531 nbAdded++;
532 }
533
534 }
535 else
536 {
537 // The attribute is HR
538 nullStringValue = new StringValue( (String)null );
539
540 // Don't add a value if it already exists.
541 if ( !values.contains( nullStringValue ) )
542 {
543 values.add( nullStringValue );
544 }
545 }
546 }
547 else
548 {
549 // Let's check the value type.
550 if ( val instanceof StringValue )
551 {
552 // We have a String value
553 if ( isHR == null )
554 {
555 // The attribute type will be set to HR
556 isHR = true;
557 values.add( val );
558 nbAdded++;
559 }
560 else if ( !isHR )
561 {
562 // The attributeType is binary, convert the
563 // value to a BinaryValue
564 BinaryValue bv = new BinaryValue( val.getBytes() );
565
566 if ( !contains( bv ) )
567 {
568 values.add( bv );
569 nbAdded++;
570 }
571 }
572 else
573 {
574 // The attributeType is HR, simply add the value
575 if ( !contains( val ) )
576 {
577 values.add( val );
578 nbAdded++;
579 }
580 }
581 }
582 else
583 {
584 // We have a Binary value
585 if ( isHR == null )
586 {
587 // The attribute type will be set to binary
588 isHR = false;
589 values.add( val );
590 nbAdded++;
591 }
592 else if ( !isHR )
593 {
594 // The attributeType is not HR, simply add the value if it does not already exist
595 if ( !contains( val ) )
596 {
597 values.add( val );
598 nbAdded++;
599 }
600 }
601 else
602 {
603 // The attribute Type is HR, convert the
604 // value to a StringValue
605 StringValue sv = new StringValue( val.getString() );
606
607 if ( !contains( sv ) )
608 {
609 values.add( sv );
610 nbAdded++;
611 }
612 }
613 }
614 }
615 }
616
617 // Last, not least, if a nullValue has been added, and if other
618 // values are all String, we have to keep the correct nullValue,
619 // and to remove the other
620 if ( nullValueAdded )
621 {
622 if ( isHR )
623 {
624 // Remove the Binary value
625 values.remove( nullBinaryValue );
626 }
627 else
628 {
629 // Remove the String value
630 values.remove( nullStringValue );
631 }
632 }
633
634 return nbAdded;
635 }
636
637
638 /**
639 * @see EntryAttribute#add(String...)
640 */
641 public int add( String... vals )
642 {
643 int nbAdded = 0;
644
645 // First, if the isHR flag is not set, we assume that the
646 // attribute is HR, because we are asked to add some strings.
647 if ( isHR == null )
648 {
649 isHR = true;
650 }
651
652 // Check the attribute type.
653 if ( isHR )
654 {
655 for ( String val:vals )
656 {
657 // Call the add(Value) method, if not already present
658 if ( !contains( val ) )
659 {
660 if ( add( new StringValue( val ) ) == 1 )
661 {
662 nbAdded++;
663 }
664 }
665 }
666 }
667 else
668 {
669 // The attribute is binary. Transform the String to byte[]
670 for ( String val:vals )
671 {
672 byte[] valBytes = null;
673
674 if ( val != null )
675 {
676 valBytes = StringTools.getBytesUtf8( val );
677 }
678
679 // Now call the add(Value) method
680 if ( add( new BinaryValue( valBytes ) ) == 1 )
681 {
682 nbAdded++;
683 }
684 }
685 }
686
687 return nbAdded;
688 }
689
690
691 /**
692 * Adds some values to this attribute. If the new values are already present in
693 * the attribute values, the method has no effect.
694 * <p>
695 * The new values are added at the end of list of values.
696 * </p>
697 * <p>
698 * This method returns the number of values that were added.
699 * </p>
700 * If the value's type is different from the attribute's type,
701 * a conversion is done. For instance, if we try to set some String
702 * into a Binary attribute, we just store the UTF-8 byte array
703 * encoding for this String.
704 * If we try to store some byte[] in a HR attribute, we try to
705 * convert those byte[] assuming they represent an UTF-8 encoded
706 * String. Of course, if it's not the case, the stored value will
707 * be incorrect.
708 * <br>
709 * It's the responsibility of the caller to check if the stored
710 * values are consistent with the attribute's type.
711 * <br>
712 * The caller can set the HR flag in order to enforce a type for
713 * the current attribute, otherwise this type will be set while
714 * adding the first value, using the value's type to set the flag.
715 *
716 * @param val some new values to be added which may be null
717 * @return the number of added values, or 0 if none has been added
718 */
719 public int add( byte[]... vals )
720 {
721 int nbAdded = 0;
722
723 // First, if the isHR flag is not set, we assume that the
724 // attribute is not HR, because we are asked to add some byte[].
725 if ( isHR == null )
726 {
727 isHR = false;
728 }
729
730 // Check the attribute type.
731 if ( isHR )
732 {
733 // The attribute is HR. Transform the byte[] to String
734 for ( byte[] val:vals )
735 {
736 String valString = null;
737
738 if ( val != null )
739 {
740 valString = StringTools.utf8ToString( val );
741 }
742
743 // Now call the add(Value) method, if not already present
744 if ( !contains( val ) )
745 {
746 if ( add( new StringValue( valString ) ) == 1 )
747 {
748 nbAdded++;
749 }
750 }
751 }
752 }
753 else
754 {
755 for ( byte[] val:vals )
756 {
757 if ( add( new BinaryValue( val ) ) == 1 )
758 {
759 nbAdded++;
760 }
761 }
762 }
763
764 return nbAdded;
765 }
766
767
768 /**
769 * Remove all the values from this attribute.
770 */
771 public void clear()
772 {
773 values.clear();
774 }
775
776
777 /**
778 * <p>
779 * Indicates whether the specified values are some of the attribute's values.
780 * </p>
781 * <p>
782 * If the Attribute is HR, the binary values will be converted to String before
783 * being checked.
784 * </p>
785 *
786 * @param vals the values
787 * @return true if this attribute contains all the values, otherwise false
788 */
789 public boolean contains( Value<?>... vals )
790 {
791 if ( isHR == null )
792 {
793 // If this flag is null, then there is no values.
794 return false;
795 }
796
797 if ( isHR )
798 {
799 // Iterate through all the values, convert the Binary values
800 // to String values, and quit id any of the values is not
801 // contained in the object
802 for ( Value<?> val:vals )
803 {
804 if ( val instanceof StringValue )
805 {
806 if ( !values.contains( val ) )
807 {
808 return false;
809 }
810 }
811 else
812 {
813 byte[] binaryVal = val.getBytes();
814
815 // We have to convert the binary value to a String
816 if ( ! values.contains( new StringValue( StringTools.utf8ToString( binaryVal ) ) ) )
817 {
818 return false;
819 }
820 }
821 }
822 }
823 else
824 {
825 // Iterate through all the values, convert the String values
826 // to binary values, and quit id any of the values is not
827 // contained in the object
828 for ( Value<?> val:vals )
829 {
830 if ( val.isBinary() )
831 {
832 if ( !values.contains( val ) )
833 {
834 return false;
835 }
836 }
837 else
838 {
839 String stringVal = val.getString();
840
841 // We have to convert the binary value to a String
842 if ( ! values.contains( new BinaryValue( StringTools.getBytesUtf8( stringVal ) ) ) )
843 {
844 return false;
845 }
846 }
847 }
848 }
849
850 return true;
851 }
852
853
854 /**
855 * <p>
856 * Indicates whether the specified values are some of the attribute's values.
857 * </p>
858 * <p>
859 * If the Attribute is not HR, the values will be converted to byte[]
860 * </p>
861 *
862 * @param vals the values
863 * @return true if this attribute contains all the values, otherwise false
864 */
865 public boolean contains( String... vals )
866 {
867 if ( isHR == null )
868 {
869 // If this flag is null, then there is no values.
870 return false;
871 }
872
873 if ( isHR )
874 {
875 // Iterate through all the values, and quit if we
876 // don't find one in the values
877 for ( String val:vals )
878 {
879 if ( !contains( new StringValue( val ) ) )
880 {
881 return false;
882 }
883 }
884 }
885 else
886 {
887 // As the attribute type is binary, we have to convert
888 // the values before checking for them in the values
889 // Iterate through all the values, and quit if we
890 // don't find one in the values
891 for ( String val:vals )
892 {
893 byte[] binaryVal = StringTools.getBytesUtf8( val );
894
895 if ( !contains( new BinaryValue( binaryVal ) ) )
896 {
897 return false;
898 }
899 }
900 }
901
902 return true;
903 }
904
905
906 /**
907 * <p>
908 * Indicates whether the specified values are some of the attribute's values.
909 * </p>
910 * <p>
911 * If the Attribute is HR, the values will be converted to String
912 * </p>
913 *
914 * @param vals the values
915 * @return true if this attribute contains all the values, otherwise false
916 */
917 public boolean contains( byte[]... vals )
918 {
919 if ( isHR == null )
920 {
921 // If this flag is null, then there is no values.
922 return false;
923 }
924
925 if ( !isHR )
926 {
927 // Iterate through all the values, and quit if we
928 // don't find one in the values
929 for ( byte[] val:vals )
930 {
931 if ( !contains( new BinaryValue( val ) ) )
932 {
933 return false;
934 }
935 }
936 }
937 else
938 {
939 // As the attribute type is String, we have to convert
940 // the values before checking for them in the values
941 // Iterate through all the values, and quit if we
942 // don't find one in the values
943 for ( byte[] val:vals )
944 {
945 String stringVal = StringTools.utf8ToString( val );
946
947 if ( !contains( new StringValue( stringVal ) ) )
948 {
949 return false;
950 }
951 }
952 }
953
954 return true;
955 }
956
957
958 /**
959 * @see EntryAttribute#contains(Object...)
960 */
961 public boolean contains( Object... vals )
962 {
963 boolean isHR = true;
964 boolean seen = false;
965
966 // Iterate through all the values, and quit if we
967 // don't find one in the values
968 for ( Object val:vals )
969 {
970 if ( ( val instanceof String ) )
971 {
972 if ( !seen )
973 {
974 isHR = true;
975 seen = true;
976 }
977
978 if ( isHR )
979 {
980 if ( !contains( (String)val ) )
981 {
982 return false;
983 }
984 }
985 else
986 {
987 return false;
988 }
989 }
990 else
991 {
992 if ( !seen )
993 {
994 isHR = false;
995 seen = true;
996 }
997
998 if ( !isHR )
999 {
1000 if ( !contains( (byte[])val ) )
1001 {
1002 return false;
1003 }
1004 }
1005 else
1006 {
1007 return false;
1008 }
1009 }
1010 }
1011
1012 return true;
1013 }
1014
1015
1016 /**
1017 * <p>
1018 * Get the first value of this attribute. If there is none,
1019 * null is returned.
1020 * </p>
1021 * <p>
1022 * Note : even if we are storing values into a Set, one can assume
1023 * the values are ordered following the insertion order.
1024 * </p>
1025 * <p>
1026 * This method is meant to be used if the attribute hold only one value.
1027 * </p>
1028 *
1029 * @return The first value for this attribute.
1030 */
1031 public Value<?> get()
1032 {
1033 if ( values.isEmpty() )
1034 {
1035 return null;
1036 }
1037
1038 return values.iterator().next();
1039 }
1040
1041
1042 /**
1043 * <p>
1044 * Get the nth value of this attribute. If there is none,
1045 * null is returned.
1046 * </p>
1047 * <p>
1048 * Note : even if we are storing values into a Set, one can assume
1049 * the values are ordered following the insertion order.
1050 * </p>
1051 * <p>
1052 *
1053 * @param i the index of the value to get
1054 * @return The nth value for this attribute.
1055 */
1056 public Value<?> get( int i )
1057 {
1058 if ( values.size() < i )
1059 {
1060 return null;
1061 }
1062 else
1063 {
1064 int n = 0;
1065
1066 for ( Value<?> value:values )
1067 {
1068 if ( n == i )
1069 {
1070 return value;
1071 }
1072
1073 n++;
1074 }
1075 }
1076
1077 // fallback to
1078 return null;
1079 }
1080
1081
1082 /**
1083 * Returns an iterator over all the attribute's values.
1084 * <p>
1085 * The effect on the returned enumeration of adding or removing values of
1086 * the attribute is not specified.
1087 * </p>
1088 * <p>
1089 * This method will throw any <code>LdapException</code> that occurs.
1090 * </p>
1091 *
1092 * @return an enumeration of all values of the attribute
1093 */
1094 public Iterator<Value<?>> getAll()
1095 {
1096 return iterator();
1097 }
1098
1099
1100 /**
1101 * Retrieves the number of values in this attribute.
1102 *
1103 * @return the number of values in this attribute, including any values
1104 * wrapping a null value if there is one
1105 */
1106 public int size()
1107 {
1108 return values.size();
1109 }
1110
1111
1112 /**
1113 * <p>
1114 * Removes all the values that are equal to the given values.
1115 * </p>
1116 * <p>
1117 * Returns true if all the values are removed.
1118 * </p>
1119 * <p>
1120 * If the attribute type is HR and some value which are not String, we
1121 * will convert the values first (same thing for a non-HR attribute).
1122 * </p>
1123 *
1124 * @param vals the values to be removed
1125 * @return true if all the values are removed, otherwise false
1126 */
1127 public boolean remove( Value<?>... vals )
1128 {
1129 if ( ( isHR == null ) || ( values.size() == 0 ) )
1130 {
1131 // Trying to remove a value from an empty list will fail
1132 return false;
1133 }
1134
1135 boolean removed = true;
1136
1137 if ( isHR )
1138 {
1139 for ( Value<?> val:vals )
1140 {
1141 if ( val instanceof StringValue )
1142 {
1143 removed &= values.remove( val );
1144 }
1145 else
1146 {
1147 // Convert the binary value to a string value
1148 byte[] binaryVal = val.getBytes();
1149 removed &= values.remove( new StringValue( StringTools.utf8ToString( binaryVal ) ) );
1150 }
1151 }
1152 }
1153 else
1154 {
1155 for ( Value<?> val:vals )
1156 {
1157 removed &= values.remove( val );
1158 }
1159 }
1160
1161 return removed;
1162 }
1163
1164
1165 /**
1166 * <p>
1167 * Removes all the values that are equal to the given values.
1168 * </p>
1169 * <p>
1170 * Returns true if all the values are removed.
1171 * </p>
1172 * <p>
1173 * If the attribute type is HR, then the values will be first converted
1174 * to String
1175 * </p>
1176 *
1177 * @param vals the values to be removed
1178 * @return true if all the values are removed, otherwise false
1179 */
1180 public boolean remove( byte[]... vals )
1181 {
1182 if ( ( isHR == null ) || ( values.size() == 0 ) )
1183 {
1184 // Trying to remove a value from an empty list will fail
1185 return false;
1186 }
1187
1188 boolean removed = true;
1189
1190 if ( !isHR )
1191 {
1192 // The attribute type is not HR, we can directly process the values
1193 for ( byte[] val:vals )
1194 {
1195 BinaryValue value = new BinaryValue( val );
1196 removed &= values.remove( value );
1197 }
1198 }
1199 else
1200 {
1201 // The attribute type is String, we have to convert the values
1202 // to String before removing them
1203 for ( byte[] val:vals )
1204 {
1205 StringValue value = new StringValue( StringTools.utf8ToString( val ) );
1206 removed &= values.remove( value );
1207 }
1208 }
1209
1210 return removed;
1211 }
1212
1213
1214 /**
1215 * Removes all the values that are equal to the given values.
1216 * <p>
1217 * Returns true if all the values are removed.
1218 * </p>
1219 * <p>
1220 * If the attribute type is not HR, then the values will be first converted
1221 * to byte[]
1222 * </p>
1223 *
1224 * @param vals the values to be removed
1225 * @return true if all the values are removed, otherwise false
1226 */
1227 public boolean remove( String... vals )
1228 {
1229 if ( ( isHR == null ) || ( values.size() == 0 ) )
1230 {
1231 // Trying to remove a value from an empty list will fail
1232 return false;
1233 }
1234
1235 boolean removed = true;
1236
1237 if ( isHR )
1238 {
1239 // The attribute type is HR, we can directly process the values
1240 for ( String val:vals )
1241 {
1242 StringValue value = new StringValue( val );
1243 removed &= values.remove( value );
1244 }
1245 }
1246 else
1247 {
1248 // The attribute type is binary, we have to convert the values
1249 // to byte[] before removing them
1250 for ( String val:vals )
1251 {
1252 BinaryValue value = new BinaryValue( StringTools.getBytesUtf8( val ) );
1253 removed &= values.remove( value );
1254 }
1255 }
1256
1257 return removed;
1258 }
1259
1260
1261 /**
1262 * An iterator on top of the stored values.
1263 *
1264 * @return an iterator over the stored values.
1265 */
1266 public Iterator<Value<?>> iterator()
1267 {
1268 return values.iterator();
1269 }
1270
1271
1272 /**
1273 * Puts some values to this attribute.
1274 * <p>
1275 * The new values will replace the previous values.
1276 * </p>
1277 * <p>
1278 * This method returns the number of values that were put.
1279 * </p>
1280 *
1281 * @param val some values to be put which may be null
1282 * @return the number of added values, or 0 if none has been added
1283 */
1284 public int put( String... vals )
1285 {
1286 values.clear();
1287 return add( vals );
1288 }
1289
1290
1291 /**
1292 * Puts some values to this attribute.
1293 * <p>
1294 * The new values will replace the previous values.
1295 * </p>
1296 * <p>
1297 * This method returns the number of values that were put.
1298 * </p>
1299 *
1300 * @param val some values to be put which may be null
1301 * @return the number of added values, or 0 if none has been added
1302 */
1303 public int put( byte[]... vals )
1304 {
1305 values.clear();
1306 return add( vals );
1307 }
1308
1309
1310 /**
1311 * Puts some values to this attribute.
1312 * <p>
1313 * The new values are replace the previous values.
1314 * </p>
1315 * <p>
1316 * This method returns the number of values that were put.
1317 * </p>
1318 *
1319 * @param val some values to be put which may be null
1320 * @return the number of added values, or 0 if none has been added
1321 */
1322 public int put( Value<?>... vals )
1323 {
1324 values.clear();
1325 return add( vals );
1326 }
1327
1328
1329 /**
1330 * <p>
1331 * Puts a list of values into this attribute.
1332 * </p>
1333 * <p>
1334 * The new values will replace the previous values.
1335 * </p>
1336 * <p>
1337 * This method returns the number of values that were put.
1338 * </p>
1339 *
1340 * @param vals the values to be put
1341 * @return the number of added values, or 0 if none has been added
1342 */
1343 public int put( List<Value<?>> vals )
1344 {
1345 values.clear();
1346
1347 // Transform the List to an array
1348 Value<?>[] valArray = new Value<?>[vals.size()];
1349 return add( vals.toArray( valArray ) );
1350 }
1351
1352
1353
1354 /**
1355 * Get the attribute type associated with this ServerAttribute.
1356 *
1357 * @return the attributeType associated with this entry attribute
1358 */
1359 public AttributeType getAttributeType()
1360 {
1361 return attributeType;
1362 }
1363
1364
1365 /**
1366 * <p>
1367 * Set the attribute type associated with this ServerAttribute.
1368 * </p>
1369 * <p>
1370 * The current attributeType will be replaced. It is the responsibility of
1371 * the caller to insure that the existing values are compatible with the new
1372 * AttributeType
1373 * </p>
1374 *
1375 * @param attributeType the attributeType associated with this entry attribute
1376 */
1377 public void setAttributeType( AttributeType attributeType )
1378 {
1379 if ( attributeType == null )
1380 {
1381 throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
1382 }
1383
1384 this.attributeType = attributeType;
1385 setUpId( null, attributeType );
1386
1387 if ( attributeType.getSyntax().isHumanReadable() )
1388 {
1389 isHR = true;
1390 }
1391 else
1392 {
1393 isHR = false;
1394 }
1395 }
1396
1397
1398 /**
1399 * <p>
1400 * Check if the current attribute type is of the expected attributeType
1401 * </p>
1402 * <p>
1403 * This method won't tell if the current attribute is a descendant of
1404 * the attributeType. For instance, the "CN" serverAttribute will return
1405 * false if we ask if it's an instance of "Name".
1406 * </p>
1407 *
1408 * @param attributeId The AttributeType ID to check
1409 * @return True if the current attribute is of the expected attributeType
1410 * @throws LdapInvalidAttributeValueException If there is no AttributeType
1411 */
1412 public boolean instanceOf( String attributeId ) throws LdapInvalidAttributeValueException
1413 {
1414 String trimmedId = StringTools.trim( attributeId );
1415
1416 if ( StringTools.isEmpty( trimmedId ) )
1417 {
1418 return false;
1419 }
1420
1421 String normId = StringTools.lowerCaseAscii( trimmedId );
1422
1423 for ( String name:attributeType.getNames() )
1424 {
1425 if ( normId.equalsIgnoreCase( name ) )
1426 {
1427 return true;
1428 }
1429 }
1430
1431 return normId.equalsIgnoreCase( attributeType.getOid() );
1432 }
1433
1434
1435 /**
1436 * Convert the ServerAttribute to a ClientAttribute
1437 *
1438 * @return An instance of ClientAttribute
1439 */
1440 public EntryAttribute toClientAttribute()
1441 {
1442 // Create the new EntryAttribute
1443 EntryAttribute clientAttribute = new DefaultClientAttribute( upId );
1444
1445 // Copy the values
1446 for ( Value<?> value:this )
1447 {
1448 Value<?> clientValue = null;
1449
1450 if ( value instanceof StringValue )
1451 {
1452 clientValue = new StringValue( value.getString() );
1453 }
1454 else
1455 {
1456 clientValue = new BinaryValue( value.getBytes() );
1457 }
1458
1459 clientAttribute.add( clientValue );
1460 }
1461
1462 return clientAttribute;
1463 }
1464
1465
1466 //-------------------------------------------------------------------------
1467 // Overloaded Object classes
1468 //-------------------------------------------------------------------------
1469 /**
1470 * The hashCode is based on the id, the isHR flag and
1471 * on the internal values.
1472 *
1473 * @see Object#hashCode()
1474 * @return the instance's hashcode
1475 */
1476 public int hashCode()
1477 {
1478 int h = 37;
1479
1480 if ( isHR != null )
1481 {
1482 h = h*17 + isHR.hashCode();
1483 }
1484
1485 if ( id != null )
1486 {
1487 h = h*17 + id.hashCode();
1488 }
1489
1490 for ( Value<?> value:values )
1491 {
1492 h = h*17 + value.hashCode();
1493 }
1494
1495 return h;
1496 }
1497
1498
1499 /**
1500 * @see Object#equals(Object)
1501 */
1502 public boolean equals( Object obj )
1503 {
1504 if ( obj == this )
1505 {
1506 return true;
1507 }
1508
1509 if ( ! (obj instanceof EntryAttribute ) )
1510 {
1511 return false;
1512 }
1513
1514 EntryAttribute other = (EntryAttribute)obj;
1515
1516 if ( id == null )
1517 {
1518 if ( other.getId() != null )
1519 {
1520 return false;
1521 }
1522 }
1523 else
1524 {
1525 if ( other.getId() == null )
1526 {
1527 return false;
1528 }
1529 else
1530 {
1531 if ( !id.equals( other.getId() ) )
1532 {
1533 return false;
1534 }
1535 }
1536 }
1537
1538 if ( isHR() != other.isHR() )
1539 {
1540 return false;
1541 }
1542
1543 if ( values.size() != other.size() )
1544 {
1545 return false;
1546 }
1547
1548 for ( Value<?> val:values )
1549 {
1550 if ( ! other.contains( val ) )
1551 {
1552 return false;
1553 }
1554 }
1555
1556 return true;
1557 }
1558
1559
1560 /**
1561 * @see Cloneable#clone()
1562 */
1563 public EntryAttribute clone()
1564 {
1565 try
1566 {
1567 DefaultClientAttribute attribute = (DefaultClientAttribute)super.clone();
1568
1569 attribute.values = new LinkedHashSet<Value<?>>( values.size() );
1570
1571 for ( Value<?> value:values )
1572 {
1573 attribute.values.add( value.clone() );
1574 }
1575
1576 return attribute;
1577 }
1578 catch ( CloneNotSupportedException cnse )
1579 {
1580 return null;
1581 }
1582 }
1583
1584
1585 /**
1586 * @see Object#toString()
1587 */
1588 public String toString()
1589 {
1590 StringBuilder sb = new StringBuilder();
1591
1592 if ( ( values != null ) && ( values.size() != 0 ) )
1593 {
1594 for ( Value<?> value:values )
1595 {
1596 sb.append( " " ).append( upId ).append( ": " );
1597
1598 if ( value.isNull() )
1599 {
1600 sb.append( "''" );
1601 }
1602 else
1603 {
1604 sb.append( value );
1605 }
1606
1607 sb.append( '\n' );
1608 }
1609 }
1610 else
1611 {
1612 sb.append( " " ).append( upId ).append( ": (null)\n" );
1613 }
1614
1615 return sb.toString();
1616 }
1617
1618
1619 /**
1620 * @see Externalizable#writeExternal(ObjectOutput)
1621 * <p>
1622 *
1623 * This is the place where we serialize attributes, and all theirs
1624 * elements.
1625 *
1626 * The inner structure is :
1627 *
1628 */
1629 public void writeExternal( ObjectOutput out ) throws IOException
1630 {
1631 // Write the UPId (the id will be deduced from the upID)
1632 out.writeUTF( upId );
1633
1634 // Write the HR flag, if not null
1635 if ( isHR != null )
1636 {
1637 out.writeBoolean( true );
1638 out.writeBoolean( isHR );
1639 }
1640 else
1641 {
1642 out.writeBoolean( false );
1643 }
1644
1645 // Write the number of values
1646 out.writeInt( size() );
1647
1648 if ( size() > 0 )
1649 {
1650 // Write each value
1651 for ( Value<?> value:values )
1652 {
1653 // Write the value
1654 out.writeObject( value );
1655 }
1656 }
1657
1658 out.flush();
1659 }
1660
1661
1662 /**
1663 * @see Externalizable#readExternal(ObjectInput)
1664 */
1665 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1666 {
1667 // Read the ID and the UPId
1668 upId = in.readUTF();
1669
1670 // Compute the id
1671 setUpId( upId );
1672
1673 // Read the HR flag, if not null
1674 if ( in.readBoolean() )
1675 {
1676 isHR = in.readBoolean();
1677 }
1678
1679 // Read the number of values
1680 int nbValues = in.readInt();
1681
1682 if ( nbValues > 0 )
1683 {
1684 for ( int i = 0; i < nbValues; i++ )
1685 {
1686 values.add( (Value<?>)in.readObject() );
1687 }
1688 }
1689 }
1690 }