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 }