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 */ 020package org.apache.directory.shared.ldap.model.name; 021 022 023import java.io.Externalizable; 024import java.io.IOException; 025import java.io.ObjectInput; 026import java.io.ObjectOutput; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Iterator; 030import java.util.List; 031 032import org.apache.commons.collections.MultiMap; 033import org.apache.commons.collections.map.MultiValueMap; 034import org.apache.directory.shared.i18n.I18n; 035import org.apache.directory.shared.ldap.model.entry.StringValue; 036import org.apache.directory.shared.ldap.model.entry.Value; 037import org.apache.directory.shared.ldap.model.exception.LdapException; 038import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException; 039import org.apache.directory.shared.ldap.model.schema.AttributeType; 040import org.apache.directory.shared.ldap.model.schema.SchemaManager; 041import org.apache.directory.shared.ldap.model.schema.normalizers.OidNormalizer; 042import org.apache.directory.shared.util.Chars; 043import org.apache.directory.shared.util.Hex; 044import org.apache.directory.shared.util.StringConstants; 045import org.apache.directory.shared.util.Strings; 046import org.apache.directory.shared.util.Unicode; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050 051/** 052 * This class store the name-component part or the following BNF grammar (as of 053 * RFC2253, par. 3, and RFC1779, fig. 1) : <br> - <name-component> ::= 054 * <attributeType> <spaces> '=' <spaces> 055 * <attributeValue> <attributeTypeAndValues> <br> - 056 * <attributeTypeAndValues> ::= <spaces> '+' <spaces> 057 * <attributeType> <spaces> '=' <spaces> 058 * <attributeValue> <attributeTypeAndValues> | e <br> - 059 * <attributeType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] 060 * <digits> <oids> | [0-9] <digits> <oids> <br> - 061 * <keychars> ::= [a-zA-Z] <keychars> | [0-9] <keychars> | '-' 062 * <keychars> | e <br> - <oidPrefix> ::= 'OID.' | 'oid.' | e <br> - 063 * <oids> ::= '.' [0-9] <digits> <oids> | e <br> - 064 * <attributeValue> ::= <pairs-or-strings> | '#' <hexstring> 065 * |'"' <quotechar-or-pairs> '"' <br> - <pairs-or-strings> ::= '\' 066 * <pairchar> <pairs-or-strings> | <stringchar> 067 * <pairs-or-strings> | e <br> - <quotechar-or-pairs> ::= 068 * <quotechar> <quotechar-or-pairs> | '\' <pairchar> 069 * <quotechar-or-pairs> | e <br> - <pairchar> ::= ',' | '=' | '+' | 070 * '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F] <br> - 071 * <hexstring> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> <br> - 072 * <hexpairs> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> | e <br> - 073 * <digits> ::= [0-9] <digits> | e <br> - <stringchar> ::= 074 * [0x00-0xFF] - [,=+<>#;\"\n\r] <br> - <quotechar> ::= [0x00-0xFF] - 075 * [\"] <br> - <separator> ::= ',' | ';' <br> - <spaces> ::= ' ' 076 * <spaces> | e <br> 077 * <br> 078 * A Rdn is a part of a Dn. It can be composed of many types, as in the Rdn 079 * following Rdn :<br> 080 * ou=value + cn=other value<br> 081 * <br> 082 * or <br> 083 * ou=value + ou=another value<br> 084 * <br> 085 * In this case, we have to store an 'ou' and a 'cn' in the Rdn.<br> 086 * <br> 087 * The types are case insensitive. <br> 088 * Spaces before and after types and values are not stored.<br> 089 * Spaces before and after '+' are not stored.<br> 090 * <br> 091 * Thus, we can consider that the following RDNs are equals :<br> 092 * <br> 093 * 'ou=test 1'<br> ' ou=test 1'<br> 094 * 'ou =test 1'<br> 095 * 'ou= test 1'<br> 096 * 'ou=test 1 '<br> ' ou = test 1 '<br> 097 * <br> 098 * So are the following :<br> 099 * <br> 100 * 'ou=test 1+cn=test 2'<br> 101 * 'ou = test 1 + cn = test 2'<br> ' ou =test 1+ cn =test 2 ' <br> 102 * 'cn = test 2 +ou = test 1'<br> 103 * <br> 104 * but the following are not equal :<br> 105 * 'ou=test 1' <br> 106 * 'ou=test 1'<br> 107 * because we have more than one spaces inside the value.<br> 108 * <br> 109 * The Rdn is composed of one or more Ava. Those Avas 110 * are ordered in the alphabetical natural order : a < b < c ... < z As the type 111 * are not case sensitive, we can say that a = A 112 * <br> 113 * This class is immutable. 114 * 115 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 116 */ 117public class Rdn implements Cloneable, Externalizable, Iterable<Ava> 118{ 119 /** The LoggerFactory used by this class */ 120 protected static final Logger LOG = LoggerFactory.getLogger( Rdn.class ); 121 122 /** An empty Rdn */ 123 public static final Rdn EMPTY_RDN = new Rdn(); 124 125 /** 126 * Declares the Serial Version Uid. 127 * 128 * @see <a 129 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always 130 * Declare Serial Version Uid</a> 131 */ 132 private static final long serialVersionUID = 1L; 133 134 /** The User Provided Rdn */ 135 private String upName = null; 136 137 /** The normalized Rdn */ 138 private String normName = null; 139 140 /** 141 * Stores all couple type = value. We may have more than one type, if the 142 * '+' character appears in the Ava. This is a TreeSet, 143 * because we want the Avas to be sorted. An Ava may contain more than one 144 * value. In this case, the values are String stored in a List. 145 */ 146 private List<Ava> avas = null; 147 148 /** 149 * We also keep a set of types, in order to use manipulations. A type is 150 * connected with the Ava it represents. 151 * 152 * Note : there is no Generic available classes in commons-collection... 153 */ 154 private MultiMap avaTypes = new MultiValueMap(); 155 156 /** 157 * We keep the type for a single valued Rdn, to avoid the creation of an HashMap 158 */ 159 private String avaType = null; 160 161 /** 162 * A simple Ava is used to store the Rdn for the simple 163 * case where we only have a single type=value. This will be 99.99% the 164 * case. This avoids the creation of a HashMap. 165 */ 166 protected Ava ava = null; 167 168 /** 169 * The number of Avas. We store this number here to avoid complex 170 * manipulation of Ava and Avas 171 */ 172 private int nbAvas = 0; 173 174 /** CompareTo() results */ 175 public static final int UNDEFINED = Integer.MAX_VALUE; 176 177 /** Constant used in comparisons */ 178 public static final int SUPERIOR = 1; 179 180 /** Constant used in comparisons */ 181 public static final int INFERIOR = -1; 182 183 /** Constant used in comparisons */ 184 public static final int EQUAL = 0; 185 186 /** A flag used to tell if the Rdn has been normalized */ 187 private boolean normalized = false; 188 189 /** the schema manager */ 190 private SchemaManager schemaManager; 191 192 /** The computed hashcode */ 193 private volatile int h; 194 195 196 /** 197 * A empty constructor. 198 */ 199 public Rdn() 200 { 201 this( ( SchemaManager ) null ); 202 } 203 204 205 /** 206 * 207 * Creates a new schema aware instance of Rdn. 208 * 209 * @param schemaManager the schema manager 210 */ 211 public Rdn( SchemaManager schemaManager ) 212 { 213 // Don't waste space... This is not so often we have multiple 214 // name-components in a Rdn... So we won't initialize the Map and the 215 // treeSet. 216 this.schemaManager = schemaManager; 217 upName = ""; 218 normName = ""; 219 normalized = false; 220 h = 0; 221 } 222 223 224 /** 225 * A constructor that parse a String representing a schema aware Rdn. 226 * 227 * @param schemaManager the schema manager 228 * @param rdn the String containing the Rdn to parse 229 * @throws LdapInvalidDnException if the Rdn is invalid 230 */ 231 public Rdn( SchemaManager schemaManager, String rdn ) throws LdapInvalidDnException 232 { 233 if ( Strings.isNotEmpty(rdn) ) 234 { 235 // Parse the string. The Rdn will be updated. 236 parse( rdn, this ); 237 238 // create the internal normalized form 239 // and store the user provided form 240 if ( schemaManager != null ) 241 { 242 this.schemaManager = schemaManager; 243 apply( schemaManager ); 244 normalized = true; 245 } 246 else 247 { 248 normalize(); 249 normalized = false; 250 } 251 252 upName = rdn; 253 } 254 else 255 { 256 upName = ""; 257 normName = ""; 258 normalized = false; 259 } 260 261 hashCode(); 262 } 263 264 265 /** 266 * A constructor that parse a String representing a Rdn. 267 * 268 * @param rdn the String containing the Rdn to parse 269 * @throws LdapInvalidDnException if the Rdn is invalid 270 */ 271 public Rdn( String rdn ) throws LdapInvalidDnException 272 { 273 this( ( SchemaManager ) null, rdn ); 274 } 275 276 277 /** 278 * A constructor that constructs a schema aware Rdn from a type and a value. 279 * <p> 280 * The string attribute values are not interpreted as RFC 414 formatted Rdn 281 * strings. That is, the values are used literally (not parsed) and assumed 282 * to be un-escaped. 283 * 284 * @param schemaManager the schema manager 285 * @param upType the user provided type of the Rdn 286 * @param upValue the user provided value of the Rdn 287 * @throws LdapInvalidDnException if the Rdn is invalid 288 */ 289 public Rdn( SchemaManager schemaManager, String upType, String upValue ) throws LdapInvalidDnException 290 { 291 addAVA( schemaManager, upType, upType, new StringValue( upValue ), new StringValue( upValue ) ); 292 293 upName = upType + '=' + upValue; 294 295 if( schemaManager != null ) 296 { 297 this.schemaManager = schemaManager; 298 apply( schemaManager ); 299 normalized = true; 300 } 301 else 302 { 303 // create the internal normalized form 304 normalize(); 305 306 // As strange as it seems, the Rdn is *not* normalized against the schema at this point 307 normalized = false; 308 } 309 310 hashCode(); 311 } 312 313 314 /** 315 * A constructor that constructs a Rdn from a type and a value. 316 * 317 * @param upType the user provided type of the Rdn 318 * @param upValue the user provided value of the Rdn 319 * @throws LdapInvalidDnException if the Rdn is invalid 320 * @see #Rdn( SchemaManager, String, String ) 321 */ 322 public Rdn( String upType, String upValue ) throws LdapInvalidDnException 323 { 324 this( null, upType, upValue ); 325 } 326 327 328 /** 329 * Constructs an Rdn from the given rdn. The content of the rdn is simply 330 * copied into the newly created Rdn. 331 * 332 * @param rdn The non-null Rdn to be copied. 333 */ 334 public Rdn( Rdn rdn ) 335 { 336 nbAvas = rdn.size(); 337 this.normName = rdn.normName; 338 this.upName = rdn.getName(); 339 normalized = rdn.normalized; 340 schemaManager = rdn.schemaManager; 341 342 switch ( rdn.size() ) 343 { 344 case 0: 345 hashCode(); 346 347 return; 348 349 case 1: 350 this.ava = (Ava) rdn.ava.clone(); 351 hashCode(); 352 353 return; 354 355 default: 356 // We must duplicate the treeSet and the hashMap 357 avas = new ArrayList<Ava>(); 358 avaTypes = new MultiValueMap(); 359 360 for ( Ava currentAva : rdn.avas ) 361 { 362 avas.add( (Ava) currentAva.clone() ); 363 avaTypes.put( currentAva.getNormType(), currentAva ); 364 } 365 366 hashCode(); 367 368 return; 369 } 370 } 371 372 373 /** 374 * Transform the external representation of the current Rdn to an internal 375 * normalized form where : 376 * - types are trimmed and lower cased 377 * - values are trimmed and lower cased 378 */ 379 // WARNING : The protection level is left unspecified on purpose. 380 // We need this method to be visible from the DnParser class, but not 381 // from outside this package. 382 /* Unspecified protection */void normalize() 383 { 384 switch ( nbAvas ) 385 { 386 case 0: 387 // An empty Rdn 388 normName = ""; 389 break; 390 391 case 1: 392 // We have a single Ava 393 // We will trim and lowercase type and value. 394 if ( ava.getNormValue().isHumanReadable() ) 395 { 396 normName = ava.getNormName(); 397 } 398 else 399 { 400 normName = ava.getNormType() + "=#" + Strings.dumpHexPairs( ava.getNormValue().getBytes() ); 401 } 402 403 break; 404 405 default: 406 // We have more than one Ava 407 StringBuffer sb = new StringBuffer(); 408 409 boolean isFirst = true; 410 411 for ( Ava ata : avas ) 412 { 413 if ( isFirst ) 414 { 415 isFirst = false; 416 } 417 else 418 { 419 sb.append( '+' ); 420 } 421 422 sb.append( ata.getNormName() ); 423 } 424 425 normName = sb.toString(); 426 break; 427 } 428 429 hashCode(); 430 } 431 432 433 /** 434 * Transform a Rdn by changing the value to its OID counterpart and 435 * normalizing the value accordingly to its type. 436 * 437 * @param schemaManager the SchemaManager 438 * @return this Rdn, normalized 439 * @throws LdapInvalidDnException if the Rdn is invalid 440 */ 441 public Rdn apply( SchemaManager schemaManager ) throws LdapInvalidDnException 442 { 443 if ( normalized ) 444 { 445 return this; 446 } 447 448 String savedUpName = getName(); 449 Dn.rdnOidToName( this, schemaManager ); 450 normalize(); 451 this.upName = savedUpName; 452 normalized = true; 453 this.schemaManager = schemaManager; 454 hashCode(); 455 456 return this; 457 } 458 459 460 /** 461 * Add an Ava to the current Rdn 462 * 463 * @param upType The user provided type of the added Rdn. 464 * @param type The normalized provided type of the added Rdn. 465 * @param upValue The user provided value of the added Rdn 466 * @param value The normalized provided value of the added Rdn 467 * @throws LdapInvalidDnException 468 * If the Rdn is invalid 469 */ 470 private void addAVA( SchemaManager schemaManager, String upType, String type, Value<?> upValue, 471 Value<?> value ) throws LdapInvalidDnException 472 { 473 // First, let's normalize the type 474 Value<?> normalizedValue = value; 475 String normalizedType = Strings.lowerCaseAscii(type); 476 this.schemaManager = schemaManager; 477 478 if ( schemaManager != null ) 479 { 480 OidNormalizer oidNormalizer = schemaManager.getNormalizerMapping().get( normalizedType ); 481 normalizedType = oidNormalizer.getAttributeTypeOid(); 482 483 try 484 { 485 normalizedValue = oidNormalizer.getNormalizer().normalize( value ); 486 } 487 catch( LdapException e ) 488 { 489 throw new LdapInvalidDnException( e.getMessage(), e ); 490 } 491 } 492 493 switch ( nbAvas ) 494 { 495 case 0: 496 // This is the first Ava. Just stores it. 497 ava = new Ava( schemaManager, upType, normalizedType, upValue, normalizedValue ); 498 nbAvas = 1; 499 avaType = normalizedType; 500 hashCode(); 501 502 return; 503 504 case 1: 505 // We already have an Ava. We have to put it in the HashMap 506 // before adding a new one. 507 // First, create the HashMap, 508 avas = new ArrayList<Ava>(); 509 510 // and store the existing Ava into it. 511 avas.add( ava ); 512 avaTypes = new MultiValueMap(); 513 avaTypes.put( avaType, ava ); 514 515 ava = null; 516 517 // Now, fall down to the commmon case 518 // NO BREAK !!! 519 520 default: 521 // add a new Ava 522 Ava newAva = new Ava( schemaManager, upType, normalizedType, upValue, normalizedValue ); 523 avas.add( newAva ); 524 avaTypes.put( normalizedType, newAva ); 525 nbAvas++; 526 hashCode(); 527 528 return; 529 530 } 531 } 532 533 534 /** 535 * Add an Ava to the current schema aware Rdn 536 * 537 * @param value The added Ava 538 */ 539 // WARNING : The protection level is left unspecified intentionally. 540 // We need this method to be visible from the DnParser class, but not 541 // from outside this package. 542 /* Unspecified protection */void addAVA( SchemaManager schemaManager, Ava value ) 543 { 544 this.schemaManager = schemaManager; 545 String normalizedType = value.getNormType(); 546 547 switch ( nbAvas ) 548 { 549 case 0: 550 // This is the first Ava. Just stores it. 551 ava = value; 552 nbAvas = 1; 553 avaType = normalizedType; 554 hashCode(); 555 556 return; 557 558 case 1: 559 // We already have an Ava. We have to put it in the HashMap 560 // before adding a new one. 561 // First, create the HashMap, 562 avas = new ArrayList<Ava>(); 563 564 // and store the existing Ava into it. 565 avas.add( ava ); 566 avaTypes = new MultiValueMap(); 567 avaTypes.put( avaType, ava ); 568 569 this.ava = null; 570 571 // Now, fall down to the commmon case 572 // NO BREAK !!! 573 574 default: 575 // add a new Ava 576 avas.add( value ); 577 avaTypes.put( normalizedType, value ); 578 nbAvas++; 579 hashCode(); 580 581 break; 582 } 583 } 584 585 586 /** 587 * Clear the Rdn, removing all the Avas. 588 */ 589 // WARNING : The protection level is left unspecified intentionally. 590 // We need this method to be visible from the DnParser class, but not 591 // from outside this package. 592 /* No protection */ void clear() 593 { 594 ava = null; 595 avas = null; 596 avaType = null; 597 avaTypes.clear(); 598 nbAvas = 0; 599 normName = ""; 600 upName = ""; 601 normalized = false; 602 h = 0; 603 } 604 605 606 /** 607 * Get the Value of the Ava which type is given as an 608 * argument. 609 * 610 * @param type the type of the NameArgument 611 * @return the Value to be returned, or null if none found. 612 * @throws LdapInvalidDnException if the Rdn is invalid 613 */ 614 public Object getValue( String type ) throws LdapInvalidDnException 615 { 616 // First, let's normalize the type 617 String normalizedType = Strings.lowerCaseAscii( Strings.trim( type ) ); 618 619 if ( schemaManager != null ) 620 { 621 AttributeType attributeType = schemaManager.getAttributeType( normalizedType ); 622 623 if ( attributeType != null ) 624 { 625 normalizedType = attributeType.getOid(); 626 } 627 } 628 629 switch ( nbAvas ) 630 { 631 case 0: 632 return ""; 633 634 case 1: 635 if ( Strings.equals( ava.getNormType(), normalizedType ) ) 636 { 637 return ava.getNormValue().getValue(); 638 } 639 640 return ""; 641 642 default: 643 if ( avaTypes.containsKey( normalizedType ) ) 644 { 645 @SuppressWarnings("unchecked") 646 Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType ); 647 StringBuffer sb = new StringBuffer(); 648 boolean isFirst = true; 649 650 for ( Ava elem : atavList ) 651 { 652 if ( isFirst ) 653 { 654 isFirst = false; 655 } 656 else 657 { 658 sb.append( ',' ); 659 } 660 661 sb.append( elem.getNormValue() ); 662 } 663 664 return sb.toString(); 665 } 666 667 return ""; 668 } 669 } 670 671 672 /** 673 * Get the Ava which type is given as an argument. If we 674 * have more than one value associated with the type, we will return only 675 * the first one. 676 * 677 * @param type 678 * The type of the NameArgument to be returned 679 * @return The Ava, of null if none is found. 680 */ 681 public Ava getAva( String type ) 682 { 683 // First, let's normalize the type 684 String normalizedType = Strings.lowerCaseAscii(Strings.trim(type)); 685 686 switch ( nbAvas ) 687 { 688 case 0: 689 return null; 690 691 case 1: 692 if ( ava.getNormType().equals( normalizedType ) ) 693 { 694 return ava; 695 } 696 697 return null; 698 699 default: 700 if ( avaTypes.containsKey( normalizedType ) ) 701 { 702 @SuppressWarnings("unchecked") 703 Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType ); 704 return atavList.iterator().next(); 705 } 706 707 return null; 708 } 709 } 710 711 712 /** 713 * Retrieves the components of this Rdn as an iterator of Avas. 714 * The effect on the iterator of updates to this Rdn is undefined. If the 715 * Rdn has zero components, an empty (non-null) iterator is returned. 716 * 717 * @return an iterator of the components of this Rdn, each an Ava 718 */ 719 public Iterator<Ava> iterator() 720 { 721 if ( nbAvas == 1 || nbAvas == 0 ) 722 { 723 return new Iterator<Ava>() 724 { 725 private boolean hasMoreElement = nbAvas == 1; 726 727 728 public boolean hasNext() 729 { 730 return hasMoreElement; 731 } 732 733 734 public Ava next() 735 { 736 Ava obj = ava; 737 hasMoreElement = false; 738 return obj; 739 } 740 741 742 public void remove() 743 { 744 // nothing to do 745 } 746 }; 747 } 748 else 749 { 750 return avas.iterator(); 751 } 752 } 753 754 755 /** 756 * Clone the Rdn 757 * 758 * @return A clone of the current Rdn 759 */ 760 public Rdn clone() 761 { 762 try 763 { 764 Rdn rdn = (Rdn) super.clone(); 765 rdn.normalized = normalized; 766 767 // The Ava is immutable. We won't clone it 768 769 switch ( rdn.size() ) 770 { 771 case 0: 772 break; 773 774 case 1: 775 rdn.ava = (Ava) this.ava.clone(); 776 rdn.avaTypes = avaTypes; 777 break; 778 779 default: 780 // We must duplicate the treeSet and the hashMap 781 rdn.avaTypes = new MultiValueMap(); 782 rdn.avas = new ArrayList<Ava>(); 783 784 for ( Ava currentAva : this.avas ) 785 { 786 rdn.avas.add( (Ava) currentAva.clone() ); 787 rdn.avaTypes.put( currentAva.getNormType(), currentAva ); 788 } 789 790 break; 791 } 792 793 return rdn; 794 } 795 catch ( CloneNotSupportedException cnse ) 796 { 797 throw new Error( "Assertion failure" ); 798 } 799 } 800 801 802 /** 803 * @return the user provided name 804 */ 805 public String getName() 806 { 807 return upName; 808 } 809 810 811 /** 812 * @return The normalized name 813 */ 814 public String getNormName() 815 { 816 return normName == null ? "" : normName; 817 } 818 819 820 /** 821 * Set the User Provided Name. 822 * 823 * Package private because Rdn is immutable, only used by the Dn parser. 824 * 825 * @param upName the User Provided dame 826 */ 827 void setUpName( String upName ) 828 { 829 this.upName = upName; 830 } 831 832 833 /** 834 * Return the unique Ava, or the first one of we have more 835 * than one 836 * 837 * @return The first Ava of this Rdn 838 */ 839 public Ava getAva() 840 { 841 switch ( nbAvas ) 842 { 843 case 0: 844 return null; 845 846 case 1: 847 return ava; 848 849 default: 850 return avas.get( 0 ).clone(); 851 } 852 } 853 854 855 /** 856 * Return the user provided type, or the first one of we have more than one (the lowest) 857 * 858 * @return The first user provided type of this Rdn 859 */ 860 public String getUpType() 861 { 862 switch ( nbAvas ) 863 { 864 case 0: 865 return null; 866 867 case 1: 868 return ava.getUpType(); 869 870 default: 871 return avas.get( 0 ).getUpType(); 872 } 873 } 874 875 876 /** 877 * Return the normalized type, or the first one of we have more than one (the lowest) 878 * 879 * @return The first normalized type of this Rdn 880 */ 881 public String getNormType() 882 { 883 switch ( nbAvas ) 884 { 885 case 0: 886 return null; 887 888 case 1: 889 return ava.getNormType(); 890 891 default: 892 return avas.get( 0 ).getNormType(); 893 } 894 } 895 896 897 /** 898 * Return the User Provided value 899 * 900 * @return The first User provided value of this Rdn 901 */ 902 public Value<?> getUpValue() 903 { 904 switch ( nbAvas ) 905 { 906 case 0: 907 return null; 908 909 case 1: 910 return ava.getUpValue(); 911 912 default: 913 return avas.get( 0 ).getUpValue(); 914 } 915 } 916 917 918 /** 919 * Return the normalized value, or the first one of we have more than one (the lowest) 920 * 921 * @return The first normalized value of this Rdn 922 */ 923 public Value<?> getNormValue() 924 { 925 switch ( nbAvas ) 926 { 927 case 0: 928 return null; 929 930 case 1: 931 return ava.getNormValue(); 932 933 default: 934 return avas.get( 0 ).getNormValue(); 935 } 936 } 937 938 939 /** 940 * Compares the specified Object with this Rdn for equality. Returns true if 941 * the given object is also a Rdn and the two Rdns represent the same 942 * attribute type and value mappings. The order of components in 943 * multi-valued Rdns is not significant. 944 * 945 * @param rdn 946 * Rdn to be compared for equality with this Rdn 947 * @return true if the specified object is equal to this Rdn 948 */ 949 public boolean equals( Object that ) 950 { 951 if ( this == that ) 952 { 953 return true; 954 } 955 956 if ( !( that instanceof Rdn) ) 957 { 958 return false; 959 } 960 961 Rdn rdn = (Rdn)that; 962 963 if ( rdn.nbAvas != nbAvas ) 964 { 965 // We don't have the same number of Avas. The Rdn which 966 // has the higher number of Ava is the one which is 967 // superior 968 return false; 969 } 970 971 switch ( nbAvas ) 972 { 973 case 0: 974 return true; 975 976 case 1: 977 return ava.equals( rdn.ava ); 978 979 default: 980 // We have more than one value. We will 981 // go through all of them. 982 983 // the types are already normalized and sorted in the Avas Map 984 // so we could compare the first element with all of the second 985 // Ava elemnts, etc. 986 Iterator<Ava> localIterator = avas.iterator(); 987 988 while ( localIterator.hasNext() ) 989 { 990 Iterator<Ava> paramIterator = rdn.avas.iterator(); 991 992 Ava localAva = localIterator.next(); 993 boolean equals = false; 994 995 while ( paramIterator.hasNext() ) 996 { 997 Ava paramAva = paramIterator.next(); 998 999 if ( localAva.equals( paramAva ) ) 1000 { 1001 equals = true; 1002 break; 1003 } 1004 } 1005 1006 if ( !equals ) 1007 { 1008 return false; 1009 } 1010 } 1011 1012 return true; 1013 } 1014 } 1015 1016 1017 /** 1018 * Get the number of Avas of this Rdn 1019 * 1020 * @return The number of Avas in this Rdn 1021 */ 1022 public int size() 1023 { 1024 return nbAvas; 1025 } 1026 1027 1028 /** 1029 * Unescape the given string according to RFC 2253 If in <string> form, a 1030 * LDAP string representation asserted value can be obtained by replacing 1031 * (left-to-right, non-recursively) each <pair> appearing in the <string> as 1032 * follows: replace <ESC><ESC> with <ESC>; replace <ESC><special> with 1033 * <special>; replace <ESC><hexpair> with the octet indicated by the 1034 * <hexpair> If in <hexstring> form, a BER representation can be obtained 1035 * from converting each <hexpair> of the <hexstring> to the octet indicated 1036 * by the <hexpair> 1037 * 1038 * @param value The value to be unescaped 1039 * @return Returns a string value as a String, and a binary value as a byte 1040 * array. 1041 * @throws IllegalArgumentException When an Illegal value is provided. 1042 */ 1043 public static Object unescapeValue( String value ) throws IllegalArgumentException 1044 { 1045 if ( Strings.isEmpty(value) ) 1046 { 1047 return ""; 1048 } 1049 1050 char[] chars = value.toCharArray(); 1051 1052 if ( chars[0] == '#' ) 1053 { 1054 if ( chars.length == 1 ) 1055 { 1056 // The value is only containing a # 1057 return StringConstants.EMPTY_BYTES; 1058 } 1059 1060 if ( ( chars.length % 2 ) != 1 ) 1061 { 1062 throw new IllegalArgumentException( I18n.err( I18n.ERR_04213 ) ); 1063 } 1064 1065 // HexString form 1066 byte[] hexValue = new byte[( chars.length - 1 ) / 2]; 1067 int pos = 0; 1068 1069 for ( int i = 1; i < chars.length; i += 2 ) 1070 { 1071 if ( Chars.isHex(chars, i) && Chars.isHex(chars, i + 1) ) 1072 { 1073 hexValue[pos++] = Hex.getHexValue(chars[i], chars[i + 1]); 1074 } 1075 else 1076 { 1077 throw new IllegalArgumentException( I18n.err( I18n.ERR_04214 ) ); 1078 } 1079 } 1080 1081 return hexValue; 1082 } 1083 else 1084 { 1085 boolean escaped = false; 1086 boolean isHex = false; 1087 byte pair = -1; 1088 int pos = 0; 1089 1090 byte[] bytes = new byte[chars.length * 6]; 1091 1092 for ( int i = 0; i < chars.length; i++ ) 1093 { 1094 if ( escaped ) 1095 { 1096 escaped = false; 1097 1098 switch ( chars[i] ) 1099 { 1100 case '\\': 1101 case '"': 1102 case '+': 1103 case ',': 1104 case ';': 1105 case '<': 1106 case '>': 1107 case '#': 1108 case '=': 1109 case ' ': 1110 bytes[pos++] = ( byte ) chars[i]; 1111 break; 1112 1113 default: 1114 if ( Chars.isHex(chars, i) ) 1115 { 1116 isHex = true; 1117 pair = ( ( byte ) ( Hex.getHexValue(chars[i]) << 4 ) ); 1118 } 1119 1120 break; 1121 } 1122 } 1123 else 1124 { 1125 if ( isHex ) 1126 { 1127 if ( Chars.isHex(chars, i) ) 1128 { 1129 pair += Hex.getHexValue(chars[i]); 1130 bytes[pos++] = pair; 1131 } 1132 } 1133 else 1134 { 1135 switch ( chars[i] ) 1136 { 1137 case '\\': 1138 escaped = true; 1139 break; 1140 1141 // We must not have a special char 1142 // Specials are : '"', '+', ',', ';', '<', '>', ' ', 1143 // '#' and '=' 1144 case '"': 1145 case '+': 1146 case ',': 1147 case ';': 1148 case '<': 1149 case '>': 1150 case '#': 1151 if ( i != 0 ) 1152 { 1153 // '#' are allowed if not in first position 1154 bytes[pos++] = '#'; 1155 break; 1156 } 1157 case '=': 1158 throw new IllegalArgumentException( I18n.err( I18n.ERR_04215 ) ); 1159 1160 case ' ': 1161 if ( ( i == 0 ) || ( i == chars.length - 1 ) ) 1162 { 1163 throw new IllegalArgumentException( I18n.err( I18n.ERR_04215 ) ); 1164 } 1165 else 1166 { 1167 bytes[pos++] = ' '; 1168 break; 1169 } 1170 1171 default: 1172 if ( ( chars[i] >= 0 ) && ( chars[i] < 128 ) ) 1173 { 1174 bytes[pos++] = ( byte ) chars[i]; 1175 } 1176 else 1177 { 1178 byte[] result = Unicode.charToBytes(chars[i]); 1179 System.arraycopy( result, 0, bytes, pos, result.length ); 1180 pos += result.length; 1181 } 1182 1183 break; 1184 } 1185 } 1186 } 1187 } 1188 1189 return Strings.utf8ToString(bytes, pos); 1190 } 1191 } 1192 1193 1194 /** 1195 * Transform a value in a String, accordingly to RFC 2253 1196 * 1197 * @param value The attribute value to be escaped 1198 * @return The escaped string value. 1199 */ 1200 public static String escapeValue( String value ) 1201 { 1202 if ( Strings.isEmpty(value) ) 1203 { 1204 return ""; 1205 } 1206 1207 char[] chars = value.toCharArray(); 1208 char[] newChars = new char[chars.length * 3]; 1209 int pos = 0; 1210 1211 for ( int i = 0; i < chars.length; i++ ) 1212 { 1213 switch ( chars[i] ) 1214 { 1215 case ' ': 1216 if ( ( i > 0 ) && ( i < chars.length - 1 ) ) 1217 { 1218 newChars[pos++] = chars[i]; 1219 } 1220 else 1221 { 1222 newChars[pos++] = '\\'; 1223 newChars[pos++] = chars[i]; 1224 } 1225 1226 break; 1227 1228 case '#': 1229 if ( i != 0 ) 1230 { 1231 newChars[pos++] = chars[i]; 1232 } 1233 else 1234 { 1235 newChars[pos++] = '\\'; 1236 newChars[pos++] = chars[i]; 1237 } 1238 1239 break; 1240 1241 case '"': 1242 case '+': 1243 case ',': 1244 case ';': 1245 case '=': 1246 case '<': 1247 case '>': 1248 case '\\': 1249 newChars[pos++] = '\\'; 1250 newChars[pos++] = chars[i]; 1251 break; 1252 1253 case 0x7F: 1254 newChars[pos++] = '\\'; 1255 newChars[pos++] = '7'; 1256 newChars[pos++] = 'F'; 1257 break; 1258 1259 case 0x00: 1260 case 0x01: 1261 case 0x02: 1262 case 0x03: 1263 case 0x04: 1264 case 0x05: 1265 case 0x06: 1266 case 0x07: 1267 case 0x08: 1268 case 0x09: 1269 case 0x0A: 1270 case 0x0B: 1271 case 0x0C: 1272 case 0x0D: 1273 case 0x0E: 1274 case 0x0F: 1275 newChars[pos++] = '\\'; 1276 newChars[pos++] = '0'; 1277 newChars[pos++] = Strings.dumpHex( ( byte ) ( chars[i] & 0x0F ) ); 1278 break; 1279 1280 case 0x10: 1281 case 0x11: 1282 case 0x12: 1283 case 0x13: 1284 case 0x14: 1285 case 0x15: 1286 case 0x16: 1287 case 0x17: 1288 case 0x18: 1289 case 0x19: 1290 case 0x1A: 1291 case 0x1B: 1292 case 0x1C: 1293 case 0x1D: 1294 case 0x1E: 1295 case 0x1F: 1296 newChars[pos++] = '\\'; 1297 newChars[pos++] = '1'; 1298 newChars[pos++] = Strings.dumpHex( ( byte ) ( chars[i] & 0x0F ) ); 1299 break; 1300 1301 default: 1302 newChars[pos++] = chars[i]; 1303 break; 1304 1305 } 1306 } 1307 1308 return new String( newChars, 0, pos ); 1309 } 1310 1311 1312 /** 1313 * Transform a value in a String, accordingly to RFC 2253 1314 * 1315 * @param attrValue 1316 * The attribute value to be escaped 1317 * @return The escaped string value. 1318 */ 1319 public static String escapeValue( byte[] attrValue ) 1320 { 1321 if ( Strings.isEmpty(attrValue) ) 1322 { 1323 return ""; 1324 } 1325 1326 String value = Strings.utf8ToString(attrValue); 1327 1328 return escapeValue( value ); 1329 } 1330 1331 1332 /** 1333 * Tells if the Rdn is schema aware. 1334 * 1335 * @return <code>true</code> if the Rdn is schema aware 1336 */ 1337 public boolean isSchemaAware() 1338 { 1339 return schemaManager != null; 1340 } 1341 1342 1343 /** 1344 * Validate a NameComponent : <br> 1345 * <p> 1346 * <name-component> ::= <attributeType> <spaces> '=' 1347 * <spaces> <attributeValue> <nameComponents> 1348 * </p> 1349 * 1350 * @param dn The string to parse 1351 * @return <code>true</code> if the Rdn is valid 1352 */ 1353 public static boolean isValid( String dn ) 1354 { 1355 Rdn rdn = new Rdn(); 1356 try 1357 { 1358 parse( dn, rdn ); 1359 return true; 1360 } 1361 catch ( LdapInvalidDnException e ) 1362 { 1363 return false; 1364 } 1365 } 1366 1367 1368 /** 1369 * Parse a NameComponent : <br> 1370 * <p> 1371 * <name-component> ::= <attributeType> <spaces> '=' 1372 * <spaces> <attributeValue> <nameComponents> 1373 * </p> 1374 * 1375 * @param dn The String to parse 1376 * @param rdn The Rdn to fill. Beware that if the Rdn is not empty, the new 1377 * AttributeTypeAndValue will be added. 1378 * @throws LdapInvalidDnException If the NameComponent is invalid 1379 */ 1380 private static void parse( String dn, Rdn rdn ) throws LdapInvalidDnException 1381 { 1382 try 1383 { 1384 FastDnParser.parseRdn( dn, rdn ); 1385 } 1386 catch ( TooComplexException e ) 1387 { 1388 rdn.clear(); 1389 new ComplexDnParser().parseRdn( dn, rdn ); 1390 } 1391 } 1392 1393 1394 /** 1395 * Gets the hashcode of this rdn. 1396 * 1397 * @see java.lang.Object#hashCode() 1398 * @return the instance's hash code 1399 */ 1400 public int hashCode() 1401 { 1402 if ( h == 0 ) 1403 { 1404 h = 37; 1405 1406 switch ( nbAvas ) 1407 { 1408 case 0: 1409 // An empty Rdn 1410 break; 1411 1412 case 1: 1413 // We have a single Ava 1414 h = h * 17 + ava.hashCode(); 1415 break; 1416 1417 default: 1418 // We have more than one Ava 1419 1420 for ( Ava ata : avas ) 1421 { 1422 h = h * 17 + ata.hashCode(); 1423 } 1424 1425 break; 1426 } 1427 } 1428 1429 return h; 1430 } 1431 1432 1433 /** 1434 * A Rdn is composed of on to many Avas (AttributeType And Value). 1435 * We should write all those Avas sequencially, following the 1436 * structure : 1437 * <ul> 1438 * <li> 1439 * <b>parentId</b> The parent entry's Id 1440 * </li> 1441 * <li> 1442 * <b>nbAvas</b> The number of Avas to write. Can't be 0. 1443 * </li> 1444 * <li> 1445 * <b>upName</b> The User provided Rdn 1446 * </li> 1447 * <li> 1448 * <b>normName</b> The normalized Rdn. It can be empty if the normalized 1449 * name equals the upName. 1450 * </li> 1451 * <li> 1452 * <b>Avas</b> 1453 * </li> 1454 * </ul> 1455 * <br/> 1456 * For each Ava : 1457 * <ul> 1458 * <li> 1459 * <b>start</b> The position of this Ava in the upName string 1460 * </li> 1461 * <li> 1462 * <b>length</b> The Ava user provided length 1463 * </li> 1464 * <li> 1465 * <b>Call the Ava write method</b> The Ava itself 1466 * </li> 1467 * </ul> 1468 * 1469 * @see Externalizable#readExternal(ObjectInput) 1470 * @param out The stream into which the serialized Rdn will be put 1471 * @throws IOException If the stream can't be written 1472 */ 1473 public void writeExternal( ObjectOutput out ) throws IOException 1474 { 1475 out.writeInt( nbAvas ); 1476 out.writeUTF( upName ); 1477 1478 if ( upName.equals( normName ) ) 1479 { 1480 out.writeUTF( "" ); 1481 } 1482 else 1483 { 1484 out.writeUTF( normName ); 1485 } 1486 1487 switch ( nbAvas ) 1488 { 1489 case 0: 1490 break; 1491 1492 case 1: 1493 ava.writeExternal( out ); 1494 break; 1495 1496 default: 1497 for ( Ava localAva : avas ) 1498 { 1499 localAva.writeExternal( out ); 1500 } 1501 1502 break; 1503 } 1504 1505 out.writeInt( h ); 1506 1507 out.flush(); 1508 } 1509 1510 1511 /** 1512 * We read back the data to create a new RDB. The structure 1513 * read is exposed in the {@link Rdn#writeExternal(ObjectOutput)} 1514 * method 1515 * 1516 * @see Externalizable#readExternal(ObjectInput) 1517 * @param in The input stream from which the Rdn will be read 1518 * @throws IOException If we can't read from the input stream 1519 * @throws ClassNotFoundException If we can't create a new Rdn 1520 */ 1521 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 1522 { 1523 // Read the Ava number 1524 nbAvas = in.readInt(); 1525 1526 // Read the UPName 1527 upName = in.readUTF(); 1528 1529 // Read the normName 1530 normName = in.readUTF(); 1531 1532 if ( Strings.isEmpty(normName) ) 1533 { 1534 normName = upName; 1535 } 1536 1537 switch ( nbAvas ) 1538 { 1539 case 0: 1540 ava = null; 1541 break; 1542 1543 case 1: 1544 ava = new Ava( schemaManager ); 1545 ava.readExternal( in ); 1546 avaType = ava.getNormType(); 1547 1548 break; 1549 1550 default: 1551 avas = new ArrayList<Ava>(); 1552 1553 avaTypes = new MultiValueMap(); 1554 1555 for ( int i = 0; i < nbAvas; i++ ) 1556 { 1557 Ava ava = new Ava( schemaManager ); 1558 ava.readExternal( in ); 1559 avas.add( ava ); 1560 avaTypes.put( ava.getNormType(), ava ); 1561 } 1562 1563 ava = null; 1564 avaType = null; 1565 1566 break; 1567 } 1568 1569 h = in.readInt(); 1570 } 1571 1572 1573 /** 1574 * @return a String representation of the Rdn. The caller will get back the user 1575 * provided Rdn 1576 */ 1577 public String toString() 1578 { 1579 return upName == null ? "" : upName; 1580 } 1581}