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 */ 020 package org.apache.directory.shared.ldap.util; 021 022 023 import java.text.ParseException; 024 import java.util.Arrays; 025 import java.util.Iterator; 026 import java.util.List; 027 028 import javax.naming.NamingEnumeration; 029 import javax.naming.NamingException; 030 import javax.naming.directory.Attribute; 031 import javax.naming.directory.Attributes; 032 import javax.naming.directory.BasicAttribute; 033 import javax.naming.directory.BasicAttributes; 034 import javax.naming.directory.InvalidAttributeIdentifierException; 035 036 import org.apache.directory.shared.i18n.I18n; 037 import org.apache.directory.shared.ldap.entry.Entry; 038 import org.apache.directory.shared.ldap.entry.EntryAttribute; 039 import org.apache.directory.shared.ldap.entry.Modification; 040 import org.apache.directory.shared.ldap.entry.Value; 041 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute; 042 import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry; 043 import org.apache.directory.shared.ldap.exception.LdapException; 044 import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException; 045 import org.apache.directory.shared.ldap.name.DN; 046 import org.apache.directory.shared.ldap.schema.AttributeType; 047 import org.apache.directory.shared.ldap.schema.MatchingRule; 048 import org.apache.directory.shared.ldap.schema.Normalizer; 049 import org.apache.directory.shared.ldap.schema.normalizers.NoOpNormalizer; 050 051 052 /** 053 * A set of utility fuctions for working with Attributes. 054 * 055 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 056 * @version $Rev: 923893 $ 057 */ 058 public class AttributeUtils 059 { 060 /** 061 * Correctly removes an attribute from an entry using it's attributeType information. 062 * 063 * @param type the attributeType of the attribute to remove 064 * @param entry the entry to remove the attribute from 065 * @return the Attribute that is removed 066 */ 067 public static Attribute removeAttribute( AttributeType type, Attributes entry ) 068 { 069 Attribute attr = entry.get( type.getOid() ); 070 071 if ( attr == null ) 072 { 073 List<String> aliases = type.getNames(); 074 075 for ( String alias : aliases ) 076 { 077 attr = entry.get( alias ); 078 079 if ( attr != null ) 080 { 081 return entry.remove( attr.getID() ); 082 } 083 } 084 } 085 086 if ( attr == null ) 087 { 088 return null; 089 } 090 091 return entry.remove( attr.getID() ); 092 } 093 094 095 /** 096 * Compare two values and return true if they are equal. 097 * 098 * @param value1 The first value 099 * @param value2 The second value 100 * @return true if both value are null or if they are equal. 101 */ 102 public static final boolean equals( Object value1, Object value2 ) 103 { 104 if ( value1 == value2 ) 105 { 106 return true; 107 } 108 109 if ( value1 == null ) 110 { 111 return ( value2 == null ); 112 } 113 114 if ( value1 instanceof byte[] ) 115 { 116 if ( value2 instanceof byte[] ) 117 { 118 return Arrays.equals( ( byte[] ) value1, ( byte[] ) value2 ); 119 } 120 else 121 { 122 return false; 123 } 124 } 125 else 126 { 127 return value1.equals( value2 ); 128 } 129 } 130 131 132 /** 133 * Clone the value. An attribute value is supposed to be either a String 134 * or a byte array. If it's a String, then we just return it ( as String 135 * is immutable, we don't need to copy it). If it's a bu=yte array, we 136 * create a new byte array and copy the bytes into it. 137 * 138 * @param value The value to clone 139 * @return The cloned value 140 */ 141 public static Object cloneValue( Object value ) 142 { 143 // First copy the value 144 Object newValue = null; 145 146 if ( value instanceof byte[] ) 147 { 148 newValue = ( ( byte[] ) value ).clone(); 149 } 150 else 151 { 152 newValue = value; 153 } 154 155 return newValue; 156 } 157 158 159 /** 160 * Switch from a BasicAttribute to a AttributeImpl. This is 161 * necessary to allow cloning to be correctly handled. 162 * 163 * @param attribute The attribute to transform 164 * @return A instance of AttributeImpl 165 */ 166 public static final Attribute toBasicAttribute( Attribute attribute ) 167 { 168 if ( attribute instanceof BasicAttribute ) 169 { 170 // Just return the attribute 171 return attribute; 172 } 173 else 174 { 175 // Create a new AttributeImpl from the original attribute 176 Attribute newAttribute = new BasicAttribute( attribute.getID() ); 177 178 try 179 { 180 NamingEnumeration<?> values = attribute.getAll(); 181 182 while ( values.hasMoreElements() ) 183 { 184 newAttribute.add( cloneValue( values.next() ) ); 185 } 186 187 return newAttribute; 188 } 189 catch ( NamingException ne ) 190 { 191 return newAttribute; 192 } 193 } 194 } 195 196 197 /** 198 * Utility method to extract an attribute from Attributes object using 199 * all combinationos of the name including aliases. 200 * 201 * @param attrs the Attributes to get the Attribute object from 202 * @param type the attribute type specification 203 * @return an Attribute with matching the attributeType spec or null 204 */ 205 public static final Attribute getAttribute( Attributes attrs, AttributeType type ) 206 { 207 // check if the attribute's OID is used 208 Attribute attr = attrs.get( type.getOid() ); 209 210 if ( attr != null ) 211 { 212 return attr; 213 } 214 215 // optimization bypass to avoid cost of the loop below 216 if ( type.getNames().size() == 1 ) 217 { 218 attr = attrs.get( type.getNames().get( 0 ) ); 219 220 if ( attr != null ) 221 { 222 return attr; 223 } 224 } 225 226 // iterate through aliases 227 for ( String alias : type.getNames() ) 228 { 229 attr = attrs.get( alias ); 230 231 if ( attr != null ) 232 { 233 return attr; 234 } 235 } 236 237 return null; 238 } 239 240 241 /** 242 * Check if an attribute contains a specific value, using the associated matchingRule for that 243 * 244 * @param attr The attribute we are searching in 245 * @param compared The object we are looking for 246 * @param type The attribute type 247 * @return <code>true</code> if the value exists in the attribute</code> 248 * @throws LdapException If something went wrong while accessing the data 249 */ 250 public static boolean containsValue( Attribute attr, Value<?> compared, AttributeType type ) throws LdapException 251 { 252 // quick bypass test 253 if ( attr.contains( compared ) ) 254 { 255 return true; 256 } 257 258 MatchingRule matchingRule = type.getEquality(); 259 260 Normalizer normalizer = null; 261 262 if ( matchingRule != null ) 263 { 264 normalizer = matchingRule.getNormalizer(); 265 } 266 else 267 { 268 normalizer = new NoOpNormalizer( type.getOid() ); 269 } 270 271 if ( type.getSyntax().isHumanReadable() ) 272 { 273 try 274 { 275 String comparedStr = normalizer.normalize( compared.getString() ); 276 277 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/) 278 { 279 String value = ( String ) values.nextElement(); 280 if ( comparedStr.equals( normalizer.normalize( value ) ) ) 281 { 282 return true; 283 } 284 } 285 } 286 catch( NamingException e ) 287 { 288 throw new LdapException( e.getMessage() ); 289 } 290 } 291 else 292 { 293 byte[] comparedBytes = null; 294 295 if ( !compared.isBinary() ) 296 { 297 if ( compared.getString().length() < 3 ) 298 { 299 return false; 300 } 301 302 // Transform the String to a byte array 303 int state = 1; 304 comparedBytes = new byte[compared.getString().length() / 3]; 305 int pos = 0; 306 307 for ( char c : compared.getString().toCharArray() ) 308 { 309 switch ( state ) 310 { 311 case 1: 312 if ( c != '\\' ) 313 { 314 return false; 315 } 316 317 state++; 318 break; 319 320 case 2: 321 int high = StringTools.getHexValue( c ); 322 323 if ( high == -1 ) 324 { 325 return false; 326 } 327 328 comparedBytes[pos] = ( byte ) ( high << 4 ); 329 330 state++; 331 break; 332 333 case 3: 334 int low = StringTools.getHexValue( c ); 335 336 if ( low == -1 ) 337 { 338 return false; 339 } 340 341 comparedBytes[pos] += ( byte ) low; 342 pos++; 343 344 state = 1; 345 break; 346 } 347 } 348 } 349 else 350 { 351 comparedBytes = compared.getBytes(); 352 } 353 354 try 355 { 356 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/) 357 { 358 Object value = values.nextElement(); 359 360 if ( value instanceof byte[] ) 361 { 362 if ( ArrayUtils.isEquals( comparedBytes, value ) ) 363 { 364 return true; 365 } 366 } 367 } 368 } 369 catch ( NamingException ne ) 370 { 371 throw new LdapException( ne.getMessage() ); 372 } 373 } 374 375 return false; 376 } 377 378 379 /** 380 * Check if an attribute contains a value. The test is case insensitive, 381 * and the value is supposed to be a String. If the value is a byte[], 382 * then the case sensitivity is useless. 383 * 384 * @param attr The attribute to check 385 * @param value The value to look for 386 * @return true if the value is present in the attribute 387 */ 388 public static boolean containsValueCaseIgnore( Attribute attr, Object value ) 389 { 390 // quick bypass test 391 if ( attr.contains( value ) ) 392 { 393 return true; 394 } 395 396 try 397 { 398 if ( value instanceof String ) 399 { 400 String strVal = ( String ) value; 401 402 NamingEnumeration<?> attrVals = attr.getAll(); 403 404 while ( attrVals.hasMoreElements() ) 405 { 406 Object attrVal = attrVals.nextElement(); 407 408 if ( attrVal instanceof String ) 409 { 410 if ( strVal.equalsIgnoreCase( ( String ) attrVal ) ) 411 { 412 return true; 413 } 414 } 415 } 416 } 417 else 418 { 419 byte[] valueBytes = ( byte[] ) value; 420 421 NamingEnumeration<?> attrVals = attr.getAll(); 422 423 while ( attrVals.hasMoreElements() ) 424 { 425 Object attrVal = attrVals.nextElement(); 426 427 if ( attrVal instanceof byte[] ) 428 { 429 if ( Arrays.equals( ( byte[] ) attrVal, valueBytes ) ) 430 { 431 return true; 432 } 433 434 } 435 } 436 } 437 } 438 catch ( NamingException ne ) 439 { 440 return false; 441 } 442 443 return false; 444 } 445 446 447 /* 448 public static boolean containsAnyValues( Attribute attr, Object[] compared, AttributeType type ) 449 throws NamingException 450 { 451 // quick bypass test 452 for ( Object object : compared ) 453 { 454 if ( attr.contains( object ) ) 455 { 456 return true; 457 } 458 } 459 460 Normalizer normalizer = type.getEquality().getNormalizer(); 461 462 if ( type.getSyntax().isHumanReadable() ) 463 { 464 for ( Object object : compared ) 465 { 466 String comparedStr = ( String ) normalizer.normalize( object ); 467 468 for ( int ii = attr.size(); ii >= 0; ii-- ) 469 { 470 String value = ( String ) attr.get( ii ); 471 472 if ( comparedStr.equals( normalizer.normalize( value ) ) ) 473 { 474 return true; 475 } 476 } 477 } 478 } 479 else 480 { 481 for ( Object object : compared ) 482 { 483 byte[] comparedBytes = ( byte[] ) object; 484 485 for ( int ii = attr.size(); ii >= 0; ii-- ) 486 { 487 if ( ArrayUtils.isEquals( comparedBytes, attr.get( ii ) ) ) 488 { 489 return true; 490 } 491 } 492 } 493 } 494 495 return false; 496 } 497 */ 498 499 500 /** 501 * Creates a new attribute which contains the values representing the 502 * difference of two attributes. If both attributes are null then we cannot 503 * determine the attribute ID and an {@link IllegalArgumentException} is 504 * raised. Note that the order of arguments makes a difference. 505 * 506 * @param attr0 507 * the first attribute 508 * @param attr1 509 * the second attribute 510 * @return a new attribute with the difference of values from both attribute 511 * arguments 512 * @throws NamingException 513 * if there are problems accessing attribute values 514 */ 515 public static Attribute getDifference( Attribute attr0, Attribute attr1 ) throws NamingException 516 { 517 String id; 518 519 if ( ( attr0 == null ) && ( attr1 == null ) ) 520 { 521 throw new IllegalArgumentException( I18n.err( I18n.ERR_04339 ) ); 522 } 523 else if ( attr0 == null ) 524 { 525 return new BasicAttribute( attr1.getID() ); 526 } 527 else if ( attr1 == null ) 528 { 529 return ( Attribute ) attr0.clone(); 530 } 531 else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) ) 532 { 533 throw new IllegalArgumentException( I18n.err( I18n.ERR_04340 ) ); 534 } 535 else 536 { 537 id = attr0.getID(); 538 } 539 540 Attribute attr = new BasicAttribute( id ); 541 542 for ( int ii = 0; ii < attr0.size(); ii++ ) 543 { 544 attr.add( attr0.get( ii ) ); 545 } 546 547 for ( int ii = 0; ii < attr1.size(); ii++ ) 548 { 549 attr.remove( attr1.get( ii ) ); 550 } 551 552 return attr; 553 } 554 555 556 /** 557 * Creates a new attribute which contains the values representing the union 558 * of two attributes. If one attribute is null then the resultant attribute 559 * returned is a copy of the non-null attribute. If both are null then we 560 * cannot determine the attribute ID and an {@link IllegalArgumentException} 561 * is raised. 562 * 563 * @param attr0 564 * the first attribute 565 * @param attr1 566 * the second attribute 567 * @return a new attribute with the union of values from both attribute 568 * arguments 569 * @throws NamingException 570 * if there are problems accessing attribute values 571 */ 572 public static Attribute getUnion( Attribute attr0, Attribute attr1 ) throws NamingException 573 { 574 String id; 575 576 if ( attr0 == null && attr1 == null ) 577 { 578 throw new IllegalArgumentException( I18n.err( I18n.ERR_04341 ) ); 579 } 580 else if ( attr0 == null ) 581 { 582 id = attr1.getID(); 583 } 584 else if ( attr1 == null ) 585 { 586 id = attr0.getID(); 587 } 588 else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) ) 589 { 590 throw new IllegalArgumentException( I18n.err( I18n.ERR_04342 ) ); 591 } 592 else 593 { 594 id = attr0.getID(); 595 } 596 597 Attribute attr = new BasicAttribute( id ); 598 599 if ( attr0 != null ) 600 { 601 for ( int ii = 0; ii < attr0.size(); ii++ ) 602 { 603 attr.add( attr0.get( ii ) ); 604 } 605 } 606 607 if ( attr1 != null ) 608 { 609 for ( int ii = 0; ii < attr1.size(); ii++ ) 610 { 611 attr.add( attr1.get( ii ) ); 612 } 613 } 614 615 return attr; 616 } 617 618 619 /** 620 * Check if the attributes is a BasicAttributes, and if so, switch 621 * the case sensitivity to false to avoid tricky problems in the server. 622 * (Ldap attributeTypes are *always* case insensitive) 623 * 624 * @param attributes The Attributes to check 625 */ 626 public static Attributes toCaseInsensitive( Attributes attributes ) 627 { 628 if ( attributes == null ) 629 { 630 return attributes; 631 } 632 633 if ( attributes instanceof BasicAttributes ) 634 { 635 if ( attributes.isCaseIgnored() ) 636 { 637 // Just do nothing if the Attributes is already case insensitive 638 return attributes; 639 } 640 else 641 { 642 // Ok, bad news : we have to create a new BasicAttributes 643 // which will be case insensitive 644 Attributes newAttrs = new BasicAttributes( true ); 645 646 NamingEnumeration<?> attrs = attributes.getAll(); 647 648 if ( attrs != null ) 649 { 650 // Iterate through the attributes now 651 while ( attrs.hasMoreElements() ) 652 { 653 newAttrs.put( ( Attribute ) attrs.nextElement() ); 654 } 655 } 656 657 return newAttrs; 658 } 659 } 660 else 661 { 662 // we can safely return the attributes if it's not a BasicAttributes 663 return attributes; 664 } 665 } 666 667 668 /** 669 * Return a string representing the attributes with tabs in front of the 670 * string 671 * 672 * @param tabs 673 * Spaces to be added before the string 674 * @param attribute 675 * The attribute to print 676 * @return A string 677 */ 678 public static String toString( String tabs, Attribute attribute ) 679 { 680 StringBuffer sb = new StringBuffer(); 681 682 sb.append( tabs ).append( "Attribute\n" ); 683 684 if ( attribute != null ) 685 { 686 sb.append( tabs ).append( " Type : '" ).append( attribute.getID() ).append( "'\n" ); 687 688 for ( int j = 0; j < attribute.size(); j++ ) 689 { 690 691 try 692 { 693 Object attr = attribute.get( j ); 694 695 if ( attr != null ) 696 { 697 if ( attr instanceof String ) 698 { 699 sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ).append( attr ) 700 .append( " \n" ); 701 } 702 else if ( attr instanceof byte[] ) 703 { 704 String string = StringTools.utf8ToString( ( byte[] ) attr ); 705 706 sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ); 707 sb.append( string ).append( '/' ); 708 sb.append( StringTools.dumpBytes( ( byte[] ) attr ) ); 709 sb.append( " \n" ); 710 } 711 else 712 { 713 sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ).append( attr ) 714 .append( " \n" ); 715 } 716 } 717 } 718 catch ( NamingException ne ) 719 { 720 sb.append( "Bad attribute : " ).append( ne.getMessage() ); 721 } 722 } 723 } 724 725 return sb.toString(); 726 } 727 728 729 /** 730 * Return a string representing the attribute 731 * 732 * @param attribute 733 * The attribute to print 734 * @return A string 735 */ 736 public static String toString( Attribute attribute ) 737 { 738 return toString( "", attribute ); 739 } 740 741 742 /** 743 * Return a string representing the attributes with tabs in front of the 744 * string 745 * 746 * @param tabs 747 * Spaces to be added before the string 748 * @param attributes 749 * The attributes to print 750 * @return A string 751 */ 752 public static String toString( String tabs, Attributes attributes ) 753 { 754 StringBuffer sb = new StringBuffer(); 755 sb.append( tabs ).append( "Attributes\n" ); 756 757 if ( attributes != null ) 758 { 759 NamingEnumeration<?> attributesIterator = attributes.getAll(); 760 761 while ( attributesIterator.hasMoreElements() ) 762 { 763 Attribute attribute = ( Attribute ) attributesIterator.nextElement(); 764 sb.append( tabs ).append( attribute.toString() ); 765 } 766 } 767 768 return sb.toString(); 769 } 770 771 772 /** 773 * Parse attribute's options : 774 * 775 * options = *( ';' option ) 776 * option = 1*keychar 777 * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-' 778 */ 779 private static void parseOptions( String str, Position pos ) throws ParseException 780 { 781 while ( StringTools.isCharASCII( str, pos.start, ';' ) ) 782 { 783 pos.start++; 784 785 // We have an option 786 if ( !StringTools.isAlphaDigitMinus( str, pos.start ) ) 787 { 788 // We must have at least one keychar 789 throw new ParseException( I18n.err( I18n.ERR_04343 ), pos.start ); 790 } 791 792 pos.start++; 793 794 while ( StringTools.isAlphaDigitMinus( str, pos.start ) ) 795 { 796 pos.start++; 797 } 798 } 799 } 800 801 802 /** 803 * Parse a number : 804 * 805 * number = '0' | '1'..'9' digits 806 * digits = '0'..'9'* 807 * 808 * @return true if a number has been found 809 */ 810 private static boolean parseNumber( String filter, Position pos ) 811 { 812 char c = StringTools.charAt( filter, pos.start ); 813 814 switch ( c ) 815 { 816 case '0': 817 // If we get a starting '0', we should get out 818 pos.start++; 819 return true; 820 821 case '1': 822 case '2': 823 case '3': 824 case '4': 825 case '5': 826 case '6': 827 case '7': 828 case '8': 829 case '9': 830 pos.start++; 831 break; 832 833 default: 834 // Not a number. 835 return false; 836 } 837 838 while ( StringTools.isDigit( filter, pos.start ) ) 839 { 840 pos.start++; 841 } 842 843 return true; 844 } 845 846 847 /** 848 * 849 * Parse an OID. 850 * 851 * numericoid = number 1*( '.' number ) 852 * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' ) 853 * 854 * @param str The OID to parse 855 * @param pos The current position in the string 856 * @return A valid OID 857 * @throws ParseException If we don't have a valid OID 858 */ 859 public static void parseOID( String str, Position pos ) throws ParseException 860 { 861 // We have an OID 862 parseNumber( str, pos ); 863 864 // We must have at least one '.' number 865 if ( !StringTools.isCharASCII( str, pos.start, '.' ) ) 866 { 867 throw new ParseException( I18n.err( I18n.ERR_04344 ), pos.start ); 868 } 869 870 pos.start++; 871 872 if ( !parseNumber( str, pos ) ) 873 { 874 throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start ); 875 } 876 877 while ( true ) 878 { 879 // Break if we get something which is not a '.' 880 if ( !StringTools.isCharASCII( str, pos.start, '.' ) ) 881 { 882 break; 883 } 884 885 pos.start++; 886 887 if ( !parseNumber( str, pos ) ) 888 { 889 throw new ParseException(I18n.err( I18n.ERR_04345 ), pos.start ); 890 } 891 } 892 } 893 894 895 /** 896 * Parse an attribute. The grammar is : 897 * attributedescription = attributetype options 898 * attributetype = oid 899 * oid = descr / numericoid 900 * descr = keystring 901 * numericoid = number 1*( '.' number ) 902 * options = *( ';' option ) 903 * option = 1*keychar 904 * keystring = leadkeychar *keychar 905 * leadkeychar = 'a'-z' | 'A'-'Z' 906 * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-' 907 * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' ) 908 * 909 * @param str The parsed attribute, 910 * @param pos The position of the attribute in the current string 911 * @return The parsed attribute if valid 912 */ 913 public static String parseAttribute( String str, Position pos, boolean withOption ) throws ParseException 914 { 915 // We must have an OID or an DESCR first 916 char c = StringTools.charAt( str, pos.start ); 917 918 if ( c == '\0' ) 919 { 920 throw new ParseException( I18n.err( I18n.ERR_04346 ), pos.start ); 921 } 922 923 int start = pos.start; 924 925 if ( StringTools.isAlpha( c ) ) 926 { 927 // A DESCR 928 pos.start++; 929 930 while ( StringTools.isAlphaDigitMinus( str, pos.start ) ) 931 { 932 pos.start++; 933 } 934 935 // Parse the options if needed 936 if ( withOption ) 937 { 938 parseOptions( str, pos ); 939 } 940 941 return str.substring( start, pos.start ); 942 } 943 else if ( StringTools.isDigit( c ) ) 944 { 945 // An OID 946 pos.start++; 947 948 // Parse the OID 949 parseOID( str, pos ); 950 951 // Parse the options 952 if ( withOption ) 953 { 954 parseOptions( str, pos ); 955 } 956 957 return str.substring( start, pos.start ); 958 } 959 else 960 { 961 throw new ParseException( I18n.err( I18n.ERR_04347 ), pos.start ); 962 } 963 } 964 965 966 /** 967 * Return a string representing the attributes 968 * 969 * @param attributes 970 * The attributes to print 971 * @return A string 972 */ 973 public static String toString( Attributes attributes ) 974 { 975 return toString( "", attributes ); 976 } 977 978 979 /** 980 * A method to apply a modification to an existing entry. 981 * 982 * @param entry The entry on which we want to apply a modification 983 * @param modification the Modification to be applied 984 * @throws LdapException if some operation fails. 985 */ 986 public static void applyModification( Entry entry, Modification modification ) throws LdapException 987 { 988 EntryAttribute modAttr = modification.getAttribute(); 989 String modificationId = modAttr.getId(); 990 991 switch ( modification.getOperation() ) 992 { 993 case ADD_ATTRIBUTE: 994 EntryAttribute modifiedAttr = entry.get( modificationId ); 995 996 if ( modifiedAttr == null ) 997 { 998 // The attribute should be added. 999 entry.put( modAttr ); 1000 } 1001 else 1002 { 1003 // The attribute exists : the values can be different, 1004 // so we will just add the new values to the existing ones. 1005 for ( Value<?> value : modAttr ) 1006 { 1007 // If the value already exist, nothing is done. 1008 // Note that the attribute *must* have been 1009 // normalized before. 1010 modifiedAttr.add( value ); 1011 } 1012 } 1013 1014 break; 1015 1016 case REMOVE_ATTRIBUTE: 1017 if ( modAttr.get() == null ) 1018 { 1019 // We have no value in the ModificationItem attribute : 1020 // we have to remove the whole attribute from the initial 1021 // entry 1022 entry.removeAttributes( modificationId ); 1023 } 1024 else 1025 { 1026 // We just have to remove the values from the original 1027 // entry, if they exist. 1028 modifiedAttr = entry.get( modificationId ); 1029 1030 if ( modifiedAttr == null ) 1031 { 1032 break; 1033 } 1034 1035 for ( Value<?> value : modAttr ) 1036 { 1037 // If the value does not exist, nothing is done. 1038 // Note that the attribute *must* have been 1039 // normalized before. 1040 modifiedAttr.remove( value ); 1041 } 1042 1043 if ( modifiedAttr.size() == 0 ) 1044 { 1045 // If this was the last value, remove the attribute 1046 entry.removeAttributes( modifiedAttr.getId() ); 1047 } 1048 } 1049 1050 break; 1051 1052 case REPLACE_ATTRIBUTE: 1053 if ( modAttr.get() == null ) 1054 { 1055 // If the modification does not have any value, we have 1056 // to delete the attribute from the entry. 1057 entry.removeAttributes( modificationId ); 1058 } 1059 else 1060 { 1061 // otherwise, just substitute the existing attribute. 1062 entry.put( modAttr ); 1063 } 1064 1065 break; 1066 } 1067 } 1068 1069 1070 /** 1071 * Check if an attribute contains a specific value and remove it using the associated 1072 * matchingRule for the attribute type supplied. 1073 * 1074 * @param attr the attribute we are searching in 1075 * @param compared the object we are looking for 1076 * @param type the attribute type 1077 * @return the value removed from the attribute, otherwise null 1078 * @throws NamingException if something went wrong while removing the value 1079 * 1080 public static Object removeValue( Attribute attr, Object compared, AttributeType type ) throws NamingException 1081 { 1082 // quick bypass test 1083 if ( attr.contains( compared ) ) 1084 { 1085 return attr.remove( compared ); 1086 } 1087 1088 MatchingRule matchingRule = type.getEquality(); 1089 Normalizer normalizer; 1090 1091 if ( matchingRule != null ) 1092 { 1093 normalizer = type.getEquality().getNormalizer(); 1094 } 1095 else 1096 { 1097 normalizer = new NoOpNormalizer(); 1098 } 1099 1100 if ( type.getSyntax().isHumanReadable() ) 1101 { 1102 String comparedStr = ( String ) normalizer.normalize( compared ); 1103 1104 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); ) 1105 { 1106 String value = ( String ) values.nextElement(); 1107 if ( comparedStr.equals( normalizer.normalize( value ) ) ) 1108 { 1109 return attr.remove( value ); 1110 } 1111 } 1112 } 1113 else 1114 { 1115 byte[] comparedBytes = null; 1116 1117 if ( compared instanceof String ) 1118 { 1119 if ( ( ( String ) compared ).length() < 3 ) 1120 { 1121 return null; 1122 } 1123 1124 // Tansform the String to a byte array 1125 int state = 1; 1126 comparedBytes = new byte[( ( String ) compared ).length() / 3]; 1127 int pos = 0; 1128 1129 for ( char c : ( ( String ) compared ).toCharArray() ) 1130 { 1131 switch ( state ) 1132 { 1133 case 1: 1134 if ( c != '\\' ) 1135 { 1136 return null; 1137 } 1138 1139 state++; 1140 break; 1141 1142 case 2: 1143 int high = StringTools.getHexValue( c ); 1144 1145 if ( high == -1 ) 1146 { 1147 return null; 1148 } 1149 1150 comparedBytes[pos] = ( byte ) ( high << 4 ); 1151 1152 state++; 1153 break; 1154 1155 case 3: 1156 int low = StringTools.getHexValue( c ); 1157 1158 if ( low == -1 ) 1159 { 1160 return null; 1161 } 1162 1163 comparedBytes[pos] += ( byte ) low; 1164 pos++; 1165 1166 state = 1; 1167 break; 1168 } 1169 } 1170 } 1171 else 1172 { 1173 comparedBytes = ( byte[] ) compared; 1174 } 1175 1176 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); ) 1177 { 1178 Object value = values.nextElement(); 1179 1180 if ( value instanceof byte[] ) 1181 { 1182 if ( ArrayUtils.isEquals( comparedBytes, value ) ) 1183 { 1184 return attr.remove( value ); 1185 } 1186 } 1187 } 1188 } 1189 1190 return null; 1191 } 1192 1193 1194 /** 1195 * Convert a BasicAttributes or a AttributesImpl to a ServerEntry 1196 * 1197 * @param attributes the BasicAttributes or AttributesImpl instance to convert 1198 * @param registries The registries, needed ro build a ServerEntry 1199 * @param dn The DN which is needed by the ServerEntry 1200 * @return An instance of a ServerEntry object 1201 * 1202 * @throws InvalidAttributeIdentifierException If we get an invalid attribute 1203 */ 1204 public static Entry toClientEntry( Attributes attributes, DN dn ) throws LdapException 1205 { 1206 if ( attributes instanceof BasicAttributes ) 1207 { 1208 try 1209 { 1210 Entry entry = new DefaultClientEntry( dn ); 1211 1212 for ( NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); ) 1213 { 1214 Attribute attr = attrs.nextElement(); 1215 1216 EntryAttribute entryAttribute = toClientAttribute( attr ); 1217 1218 if ( entryAttribute != null ) 1219 { 1220 entry.put( entryAttribute ); 1221 } 1222 } 1223 1224 return entry; 1225 } 1226 catch ( LdapException ne ) 1227 { 1228 throw new LdapInvalidAttributeTypeException( ne.getMessage() ); 1229 } 1230 } 1231 else 1232 { 1233 return null; 1234 } 1235 } 1236 1237 1238 /** 1239 * Converts an {@link Entry} to an {@link Attributes}. 1240 * 1241 * @param entry 1242 * the {@link Entry} to convert 1243 * @return 1244 * the equivalent {@link Attributes} 1245 */ 1246 public static Attributes toAttributes( Entry entry ) 1247 { 1248 if ( entry != null ) 1249 { 1250 Attributes attributes = new BasicAttributes( true ); 1251 1252 // Looping on attributes 1253 for ( Iterator<EntryAttribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); ) 1254 { 1255 EntryAttribute entryAttribute = ( EntryAttribute ) attributeIterator.next(); 1256 1257 attributes.put( toAttribute( entryAttribute ) ); 1258 } 1259 1260 return attributes; 1261 } 1262 1263 return null; 1264 } 1265 1266 1267 /** 1268 * Converts an {@link EntryAttribute} to an {@link Attribute}. 1269 * 1270 * @param entryAttribute 1271 * the {@link EntryAttribute} to convert 1272 * @return 1273 * the equivalent {@link Attribute} 1274 */ 1275 public static Attribute toAttribute( EntryAttribute entryAttribute ) 1276 { 1277 if ( entryAttribute != null ) 1278 { 1279 Attribute attribute = new BasicAttribute( entryAttribute.getId() ); 1280 1281 // Looping on values 1282 for ( Iterator<Value<?>> valueIterator = entryAttribute.iterator(); valueIterator.hasNext(); ) 1283 { 1284 Value<?> value = valueIterator.next(); 1285 attribute.add( value.get() ); 1286 } 1287 1288 return attribute; 1289 } 1290 1291 return null; 1292 } 1293 1294 1295 /** 1296 * Convert a BasicAttribute or a AttributeImpl to a EntryAttribute 1297 * 1298 * @param attribute the BasicAttributes or AttributesImpl instance to convert 1299 * @param attributeType 1300 * @return An instance of a ClientEntry object 1301 * 1302 * @throws InvalidAttributeIdentifierException If we had an incorrect attribute 1303 */ 1304 public static EntryAttribute toClientAttribute( Attribute attribute ) 1305 { 1306 if ( attribute == null ) 1307 { 1308 return null; 1309 } 1310 1311 try 1312 { 1313 EntryAttribute clientAttribute = new DefaultClientAttribute( attribute.getID() ); 1314 1315 for ( NamingEnumeration<?> values = attribute.getAll(); values.hasMoreElements(); ) 1316 { 1317 Object value = values.nextElement(); 1318 1319 if ( value instanceof String ) 1320 { 1321 clientAttribute.add( ( String ) value ); 1322 } 1323 else if ( value instanceof byte[] ) 1324 { 1325 clientAttribute.add( ( byte[] ) value ); 1326 } 1327 else 1328 { 1329 clientAttribute.add( ( String ) null ); 1330 } 1331 } 1332 1333 return clientAttribute; 1334 } 1335 catch ( NamingException ne ) 1336 { 1337 return null; 1338 } 1339 } 1340 }