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 021package org.apache.directory.shared.ldap.model.name; 022 023 024import java.io.Externalizable; 025import java.io.IOException; 026import java.io.ObjectInput; 027import java.io.ObjectOutput; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Map; 033 034import org.apache.commons.collections.list.UnmodifiableList; 035import org.apache.directory.shared.i18n.I18n; 036import org.apache.directory.shared.ldap.model.exception.LdapException; 037import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException; 038import org.apache.directory.shared.ldap.model.message.ResultCodeEnum; 039import org.apache.directory.shared.ldap.model.schema.SchemaManager; 040import org.apache.directory.shared.ldap.model.schema.normalizers.OidNormalizer; 041import org.apache.directory.shared.util.Strings; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045 046/** 047 * The Dn class contains a Dn (Distinguished Name). This class is immutable. 048 * <br/> 049 * Its specification can be found in RFC 2253, 050 * "UTF-8 String Representation of Distinguished Names". 051 * <br/> 052 * We will store two representation of a Dn : 053 * <ul> 054 * <li>a user Provider representation, which is the parsed String given by a user</li> 055 * <li>an internal representation.</li> 056 * </ul> 057 * 058 * A Dn is formed of RDNs, in a specific order :<br/> 059 * Rdn[n], Rdn[n-1], ... Rdn[1], Rdn[0]<br/> 060 * 061 * It represents a position in a hierarchy, in which the root is the last Rdn (Rdn[0]) and the leaf 062 * is the first Rdn (Rdn[n]). 063 * 064 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 065 */ 066public class Dn implements Iterable<Rdn>, Externalizable 067{ 068 /** The LoggerFactory used by this class */ 069 protected static final Logger LOG = LoggerFactory.getLogger( Dn.class ); 070 071 /** 072 * Declares the Serial Version Uid. 073 * 074 * @see <a 075 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always 076 * Declare Serial Version Uid</a> 077 */ 078 private static final long serialVersionUID = 1L; 079 080 /** Value returned by the compareTo method if values are not equals */ 081 public static final int NOT_EQUAL = -1; 082 083 /** Value returned by the compareTo method if values are equals */ 084 public static final int EQUAL = 0; 085 086 /** 087 * The RDNs that are elements of the Dn<br/> 088 * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!<br/> 089 * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0) 090 * <br> 091 * For instance,if the Dn is "dc=c, dc=b, dc=a", then the RDNs are stored as : 092 * <ul> 093 * <li>[0] : dc=c</li> 094 * <li>[1] : dc=b</li> 095 * <li>[2] : dc=a</li> 096 * </ul> 097 */ 098 protected List<Rdn> rdns = new ArrayList<Rdn>( 5 ); 099 100 /** The user provided name */ 101 private String upName; 102 103 /** The normalized name */ 104 private String normName; 105 106 /** The bytes representation of the normName */ 107 private byte[] bytes; 108 109 /** A null Dn */ 110 public static final Dn EMPTY_DN = new Dn(); 111 112 /** The rootDSE */ 113 public static final Dn ROOT_DSE = new Dn(); 114 115 /** the schema manager */ 116 private SchemaManager schemaManager; 117 118 /** 119 * An iterator over RDNs 120 */ 121 private final class RdnIterator implements Iterator<Rdn> 122 { 123 // The current index 124 int index; 125 126 private RdnIterator() 127 { 128 index = rdns != null ? rdns.size() - 1 : -1; 129 } 130 131 132 /** 133 * {@inheritDoc} 134 */ 135 public boolean hasNext() 136 { 137 return index >= 0; 138 } 139 140 141 /** 142 * {@inheritDoc} 143 */ 144 public Rdn next() 145 { 146 return index >= 0 ? rdns.get( index-- ) : null; 147 } 148 149 150 /** 151 * {@inheritDoc} 152 */ 153 public void remove() 154 { 155 // Not implemented 156 } 157 } 158 159 160 /** 161 * Construct an empty Dn object 162 */ 163 public Dn() 164 { 165 this( ( SchemaManager ) null ); 166 } 167 168 169 /** 170 * Construct an empty Schema aware Dn object 171 * 172 * @param schemaManager The SchemaManager to use 173 */ 174 public Dn( SchemaManager schemaManager ) 175 { 176 this.schemaManager = schemaManager; 177 upName = ""; 178 normName = ""; 179 } 180 181 182 /** 183 * Creates a new instance of Dn, using varargs to declare the RDNs. Each 184 * String is either a full Rdn, or a couple of AttributeType DI and a value. 185 * If the String contains a '=' symbol, the the constructor will assume that 186 * the String arg contains afull Rdn, otherwise, it will consider that the 187 * following arg is the value.<br/> 188 * The created Dn is Schema aware. 189 * <br/><br/> 190 * An example of usage would be : 191 * <pre> 192 * String exampleName = "example"; 193 * String baseDn = "dc=apache,dc=org"; 194 * 195 * Dn dn = new Dn( DefaultSchemaManager.INSTANCE, 196 * "cn=Test", 197 * "ou", exampleName, 198 * baseDn); 199 * </pre> 200 * 201 * @param schemaManager the schema manager 202 * @param upRdns The list of String composing the Dn 203 * @throws LdapInvalidDnException If the resulting Dn is invalid 204 */ 205 public Dn(String... upRdns) throws LdapInvalidDnException 206 { 207 this( null, upRdns ); 208 } 209 210 211 /** 212 * Creates a new instance of schema aware Dn, using varargs to declare the RDNs. Each 213 * String is either a full Rdn, or a couple of AttributeType DI and a value. 214 * If the String contains a '=' symbol, the the constructor will assume that 215 * the String arg contains afull Rdn, otherwise, it will consider that the 216 * following arg is the value.<br/> 217 * The created Dn is Schema aware. 218 * <br/><br/> 219 * An example of usage would be : 220 * <pre> 221 * String exampleName = "example"; 222 * String baseDn = "dc=apache,dc=org"; 223 * 224 * Dn dn = new Dn( DefaultSchemaManager.INSTANCE, 225 * "cn=Test", 226 * "ou", exampleName, 227 * baseDn); 228 * </pre> 229 * 230 * @param schemaManager the schema manager 231 * @param upRdns The list of String composing the Dn 232 * @throws LdapInvalidDnException If the resulting Dn is invalid 233 */ 234 public Dn( SchemaManager schemaManager, String... upRdns ) throws LdapInvalidDnException 235 { 236 StringBuilder sb = new StringBuilder(); 237 boolean valueExpected = false; 238 boolean isFirst = true; 239 240 for ( String upRdn : upRdns ) 241 { 242 if ( Strings.isEmpty( upRdn ) ) 243 { 244 continue; 245 } 246 247 if ( isFirst ) 248 { 249 isFirst = false; 250 } 251 else if ( !valueExpected ) 252 { 253 sb.append( ',' ); 254 } 255 256 if ( !valueExpected ) 257 { 258 sb.append( upRdn ); 259 260 if ( upRdn.indexOf( '=' ) == -1 ) 261 { 262 valueExpected = true; 263 } 264 } 265 else 266 { 267 sb.append( "=" ).append( upRdn ); 268 269 valueExpected = false; 270 } 271 } 272 273 if ( !isFirst && valueExpected ) 274 { 275 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04202 ) ); 276 } 277 278 // Stores the representations of a Dn : internal (as a string and as a 279 // byte[]) and external. 280 upName = sb.toString(); 281 parseInternal( upName, rdns ); 282 283 apply( schemaManager ); 284 } 285 286 287 /** 288 * Create a schema aware Dn while deserializing it. 289 * <br/> 290 * Note : this constructor is used only by the deserialization method. 291 * 292 * @param schemaManager the schema manager 293 * @param upName The user provided name 294 * @param normName the normalized name 295 * @param rdns the list of RDNs for this Dn 296 */ 297 /* No protection */ Dn( SchemaManager schemaManager, String upName, String normName, Rdn... rdns ) 298 { 299 this.schemaManager = schemaManager; 300 this.upName = upName; 301 this.normName = normName; 302 bytes = Strings.getBytesUtf8( upName ); 303 this.rdns = Arrays.asList( rdns ); 304 } 305 306 307 /** 308 * Creates a Dn from a list of Rdns. 309 * 310 * @param rdns the list of Rdns to be used for the Dn 311 * @throws LdapInvalidDnException If the resulting Dn is invalid 312 */ 313 public Dn( Rdn... rdns ) throws LdapInvalidDnException 314 { 315 if ( rdns == null ) 316 { 317 return; 318 } 319 320 for ( Rdn rdn : rdns) 321 { 322 this.rdns.add( rdn.clone() ); 323 } 324 325 apply( null ); 326 toUpName(); 327 } 328 329 330 /** 331 * Creates a Dn concatenating a Rdn and a Dn. 332 * 333 * @param rdn the Rdn to add to the Dn 334 * @param dn the Dn 335 * @throws LdapInvalidDnException If the resulting Dn is invalid 336 */ 337 public Dn( Rdn rdn, Dn dn ) throws LdapInvalidDnException 338 { 339 if ( ( dn == null ) || ( rdn == null ) ) 340 { 341 throw new IllegalArgumentException( "Either the dn or the rdn is null" ); 342 } 343 344 for ( Rdn rdnParent : dn ) 345 { 346 rdns.add( 0, rdnParent ); 347 } 348 349 rdns.add( 0, rdn ); 350 351 apply( dn.schemaManager ); 352 toUpName(); 353 } 354 355 356 /** 357 * Creates a Schema aware Dn from a list of Rdns. 358 * 359 * @param schemaManager The SchemaManager to use 360 * @param rdns the list of Rdns to be used for the Dn 361 * @throws LdapInvalidDnException If the resulting Dn is invalid 362 */ 363 public Dn( SchemaManager schemaManager, Rdn... rdns ) throws LdapInvalidDnException 364 { 365 if ( rdns == null ) 366 { 367 return; 368 } 369 370 for ( Rdn rdn : rdns) 371 { 372 this.rdns.add( rdn.clone() ); 373 } 374 375 apply( schemaManager ); 376 } 377 378 379 /** 380 * Get the associated SchemaManager if any. 381 * 382 * @return The SchemaManager 383 */ 384 public SchemaManager getSchemaManager() 385 { 386 return schemaManager; 387 } 388 389 390 /** 391 * Return the User Provided Dn as a String, 392 * 393 * @return A String representing the User Provided Dn 394 */ 395 private String toUpName() 396 { 397 if ( rdns.size() == 0 ) 398 { 399 upName = ""; 400 } 401 else 402 { 403 StringBuffer sb = new StringBuffer(); 404 boolean isFirst = true; 405 406 for ( Rdn rdn : rdns ) 407 { 408 if ( isFirst ) 409 { 410 isFirst = false; 411 } 412 else 413 { 414 sb.append( ',' ); 415 } 416 417 sb.append( rdn.getName() ); 418 } 419 420 upName = sb.toString(); 421 } 422 423 return upName; 424 } 425 426 427 /** 428 * Gets the hash code of this Dn. 429 * 430 * @see java.lang.Object#hashCode() 431 * @return the instance hash code 432 */ 433 @Override 434 public int hashCode() 435 { 436 int result = 37; 437 438 for ( Rdn rdn : rdns ) 439 { 440 result = result * 17 + rdn.hashCode(); 441 } 442 443 return result; 444 } 445 446 447 /** 448 * Get the user provided Dn 449 * 450 * @return The user provided Dn as a String 451 */ 452 public String getName() 453 { 454 return ( upName == null ? "" : upName ); 455 } 456 457 458 /** 459 * Sets the up name. 460 * 461 * Package private because Dn is immutable, only used by the Dn parser. 462 * 463 * @param upName the new up name 464 */ 465 /* No qualifier */ void setUpName( String upName ) 466 { 467 this.upName = upName; 468 } 469 470 471 /** 472 * Get the normalized Dn. If the Dn is schema aware, the AttributeType 473 * will be represented using its OID :<br/> 474 * <pre> 475 * Dn dn = new Dn( schemaManager, "ou = Example , ou = com" ); 476 * assert( "2.5.4.11=example,2.5.4.11=com".equals( dn.getNormName ) ); 477 * </pre> 478 * Otherwise, it will return a Dn with the AttributeType in lower case 479 * and the value trimmed : <br/> 480 * <pre> 481 * Dn dn = new Dn( " CN = A Test " ); 482 * assertEquals( "cn=A Test", dn.getNormName() ); 483 * </pre> 484 * 485 * @return The normalized Dn as a String 486 */ 487 public String getNormName() 488 { 489 return normName; 490 } 491 492 493 /** 494 * Get the number of RDNs present in the DN 495 * @return The umber of RDNs in the DN 496 */ 497 public int size() 498 { 499 return rdns.size(); 500 } 501 502 503 /** 504 * Get the number of bytes necessary to store this Dn 505 506 * @param dn The Dn. 507 * @return A integer, which is the size of the UTF-8 byte array 508 */ 509 public static int getNbBytes( Dn dn ) 510 { 511 return dn.bytes == null ? 0 : dn.bytes.length; 512 } 513 514 515 /** 516 * Get an UTF-8 representation of the normalized form of the Dn 517 * 518 * @param dn The Dn. 519 * @return A byte[] representation of the Dn 520 */ 521 public static byte[] getBytes( Dn dn ) 522 { 523 return dn == null ? null : dn.bytes; 524 } 525 526 527 /** 528 * Tells if the current Dn is a parent of another Dn.<br> 529 * For instance, <b>dc=com</b> is a ancestor 530 * of <b>dc=example, dc=com</b> 531 * 532 * @param dn The child 533 * @return true if the current Dn is a parent of the given Dn 534 */ 535 public boolean isAncestorOf( String dn ) 536 { 537 try 538 { 539 return isAncestorOf( new Dn( dn ) ); 540 } 541 catch ( LdapInvalidDnException lide ) 542 { 543 return false; 544 } 545 } 546 547 548 /** 549 * Tells if the current Dn is a parent of another Dn.<br> 550 * For instance, <b>dc=com</b> is a ancestor 551 * of <b>dc=example, dc=com</b> 552 * 553 * @param dn The child 554 * @return true if the current Dn is a parent of the given Dn 555 */ 556 public boolean isAncestorOf( Dn dn ) 557 { 558 if ( dn == null ) 559 { 560 return false; 561 } 562 563 return dn.isDescendantOf( this ); 564 } 565 566 567 /** 568 * Tells if a Dn is a child of another Dn.<br> 569 * For instance, <b>dc=example, dc=com</b> is a descendant 570 * of <b>dc=com</b> 571 * 572 * @param dn The parent 573 * @return true if the current Dn is a child of the given Dn 574 */ 575 public boolean isDescendantOf( String dn ) 576 { 577 try 578 { 579 return isDescendantOf( new Dn( schemaManager, dn ) ); 580 } 581 catch ( LdapInvalidDnException lide ) 582 { 583 return false; 584 } 585 } 586 587 588 /** 589 * Tells if a Dn is a child of another Dn.<br> 590 * For instance, <b>dc=example, dc=apache, dc=com</b> is a descendant 591 * of <b>dc=com</b> 592 * 593 * @param dn The parent 594 * @return true if the current Dn is a child of the given Dn 595 */ 596 public boolean isDescendantOf( Dn dn ) 597 { 598 if ( ( dn == null ) || dn.isRootDSE() ) 599 { 600 return true; 601 } 602 603 if ( dn.size() > size() ) 604 { 605 // The name is longer than the current Dn. 606 return false; 607 } 608 609 // Ok, iterate through all the Rdn of the name, 610 // starting a the end of the current list. 611 612 for ( int i = dn.size() - 1; i >= 0; i-- ) 613 { 614 Rdn nameRdn = dn.rdns.get( dn.rdns.size() - i - 1 ); 615 Rdn ldapRdn = rdns.get( rdns.size() - i - 1 ); 616 617 if ( !nameRdn.equals( ldapRdn ) ) 618 { 619 return false; 620 } 621 } 622 623 return true; 624 } 625 626 627 /** 628 * Tells if the Dn contains no Rdn 629 * 630 * @return <code>true</code> if the Dn is empty 631 */ 632 public boolean isEmpty() 633 { 634 return ( rdns.size() == 0 ); 635 } 636 637 638 /** 639 * Tells if the Dn is the RootDSE Dn (ie, an empty Dn) 640 * 641 * @return <code>true</code> if the Dn is the RootDSE's Dn 642 */ 643 public boolean isRootDSE() 644 { 645 return ( rdns.size() == 0 ); 646 } 647 648 649 /** 650 * Retrieves a component of this name. 651 * 652 * @param posn the 0-based index of the component to retrieve. Must be in the 653 * range [0,size()). 654 * @return the component at index posn 655 * @throws ArrayIndexOutOfBoundsException 656 * if posn is outside the specified range 657 */ 658 public Rdn getRdn( int posn ) 659 { 660 if ( rdns.size() == 0 ) 661 { 662 return null; 663 } 664 665 if ( ( posn < 0 ) || ( posn >= rdns.size() ) ) 666 { 667 throw new IllegalArgumentException( "Invalid position : " + posn ); 668 } 669 670 Rdn rdn = rdns.get( posn ); 671 672 return rdn.clone(); 673 } 674 675 676 /** 677 * Retrieves the last (leaf) component of this name. 678 * 679 * @return the last component of this Dn 680 */ 681 public Rdn getRdn() 682 { 683 if ( isNullOrEmpty( this ) ) 684 { 685 return Rdn.EMPTY_RDN; 686 } 687 688 return rdns.get( 0 ).clone(); 689 } 690 691 692 /** 693 * Retrieves all the components of this name. 694 * 695 * @return All the components 696 */ 697 @SuppressWarnings("unchecked") 698 public List<Rdn> getRdns() 699 { 700 return UnmodifiableList.decorate( rdns ); 701 } 702 703 704 /** 705 * Get the descendant of a given DN, using the ancestr DN. Assuming that 706 * a DN has two parts :<br/> 707 * DN = [descendant DN][ancestor DN]<br/> 708 * To get back the descendant from the full DN, you just pass the ancestor DN 709 * as a parameter. Here is a working example : 710 * <pre> 711 * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); 712 * 713 * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" ); 714 * 715 * // At this point, the descendant contains cn=test, dc=server, dc=directory" 716 * </pre> 717 */ 718 public Dn getDescendantOf( String ancestor ) throws LdapInvalidDnException 719 { 720 return getDescendantOf( new Dn( schemaManager, ancestor ) ); 721 } 722 723 724 725 /** 726 * Get the descendant of a given DN, using the ancestr DN. Assuming that 727 * a DN has two parts :<br/> 728 * DN = [descendant DN][ancestor DN]<br/> 729 * To get back the descendant from the full DN, you just pass the ancestor DN 730 * as a parameter. Here is a working example : 731 * <pre> 732 * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); 733 * 734 * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" ); 735 * 736 * // At this point, the descendant contains cn=test, dc=server, dc=directory" 737 * </pre> 738 */ 739 public Dn getDescendantOf( Dn ancestor ) throws LdapInvalidDnException 740 { 741 if ( ( ancestor == null ) || ( ancestor.size() == 0 ) ) 742 { 743 return this; 744 } 745 746 if ( rdns.size() == 0 ) 747 { 748 return EMPTY_DN; 749 } 750 751 int length = ancestor.size(); 752 753 if ( length > rdns.size() ) 754 { 755 String message = I18n.err( I18n.ERR_04206, length, rdns.size() ); 756 LOG.error( message ); 757 throw new ArrayIndexOutOfBoundsException( message ); 758 } 759 760 Dn newDn = new Dn( schemaManager ); 761 List<Rdn> rdnsAncestor = ancestor.getRdns(); 762 763 for ( int i = 0; i < ancestor.size(); i++ ) 764 { 765 Rdn rdn = rdns.get( size() -1 - i ); 766 Rdn rdnDescendant = rdnsAncestor.get( ancestor.size() - 1 - i ); 767 768 if ( !rdn.equals( rdnDescendant ) ) 769 { 770 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX ); 771 } 772 } 773 774 for ( int i = 0; i < rdns.size() - length; i++ ) 775 { 776 // Don't forget to clone the rdns ! 777 newDn.rdns.add( rdns.get( i ).clone() ); 778 } 779 780 newDn.toUpName(); 781 newDn.apply( schemaManager ); 782 783 return newDn; 784 } 785 786 /** 787 * Get the ancestor of a given DN, using the descendant DN. Assuming that 788 * a DN has two parts :<br/> 789 * DN = [descendant DN][ancestor DN]<br/> 790 * To get back the ancestor from the full DN, you just pass the descendant DN 791 * as a parameter. Here is a working example : 792 * <pre> 793 * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); 794 * 795 * Dn ancestor = dn.getAncestorOf( "cn=test, dc=server, dc=directory" ); 796 * 797 * // At this point, the ancestor contains "dc=apache, dc=org" 798 * </pre> 799 */ 800 public Dn getAncestorOf( String descendant ) throws LdapInvalidDnException 801 { 802 return getAncestorOf( new Dn( schemaManager, descendant ) ); 803 } 804 805 806 /** 807 * Get the ancestor of a given DN, using the descendant DN. Assuming that 808 * a DN has two parts :<br/> 809 * DN = [descendant DN][ancestor DN]<br/> 810 * To get back the ancestor from the full DN, you just pass the descendant DN 811 * as a parameter. Here is a working example : 812 * <pre> 813 * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); 814 * 815 * Dn ancestor = dn.getAncestorOf( new Dn( "cn=test, dc=server, dc=directory" ) ); 816 * 817 * // At this point, the ancestor contains "dc=apache, dc=org" 818 * </pre> 819 */ 820 public Dn getAncestorOf( Dn descendant ) throws LdapInvalidDnException 821 { 822 if ( ( descendant == null ) || ( descendant.size() == 0 ) ) 823 { 824 return this; 825 } 826 827 if ( rdns.size() == 0 ) 828 { 829 return EMPTY_DN; 830 } 831 832 int length = descendant.size(); 833 834 if ( length > rdns.size() ) 835 { 836 String message = I18n.err( I18n.ERR_04206, length, rdns.size() ); 837 LOG.error( message ); 838 throw new ArrayIndexOutOfBoundsException( message ); 839 } 840 841 Dn newDn = new Dn( schemaManager ); 842 List<Rdn> rdnsDescendant = descendant.getRdns(); 843 844 for ( int i = 0; i < descendant.size(); i++ ) 845 { 846 Rdn rdn = rdns.get( i ); 847 Rdn rdnDescendant = rdnsDescendant.get( i ); 848 849 if ( !rdn.equals( rdnDescendant ) ) 850 { 851 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX ); 852 } 853 } 854 855 for ( int i = length; i < rdns.size(); i++ ) 856 { 857 // Don't forget to clone the rdns ! 858 newDn.rdns.add( rdns.get( i ).clone() ); 859 } 860 861 newDn.toUpName(); 862 newDn.apply( schemaManager ); 863 864 return newDn; 865 } 866 867 868 /** 869 * {@inheritDoc} 870 */ 871 public Dn add( Dn suffix ) throws LdapInvalidDnException 872 { 873 if ( ( suffix == null ) || ( suffix.size() == 0 ) ) 874 { 875 return this; 876 } 877 878 Dn clonedDn = copy(); 879 880 // Concatenate the rdns 881 clonedDn.rdns.addAll( 0, suffix.rdns ); 882 883 // Regenerate the normalized name and the original string 884 if ( clonedDn.isSchemaAware() && suffix.isSchemaAware() ) 885 { 886 if ( clonedDn.size() != 0 ) 887 { 888 clonedDn.normName = suffix.getNormName() + "," + normName; 889 clonedDn.bytes = Strings.getBytesUtf8(normName); 890 clonedDn.upName = suffix.getName() + "," + upName; 891 } 892 } 893 else 894 { 895 clonedDn.apply( schemaManager ); 896 clonedDn.toUpName(); 897 } 898 899 return clonedDn; 900 } 901 902 903 /** 904 * {@inheritDoc} 905 */ 906 public Dn add( String comp ) throws LdapInvalidDnException 907 { 908 if ( comp.length() == 0 ) 909 { 910 return this; 911 } 912 913 Dn clonedDn = copy(); 914 915 // We have to parse the nameComponent which is given as an argument 916 Rdn newRdn = new Rdn( schemaManager, comp ); 917 918 clonedDn.rdns.add( 0, newRdn ); 919 920 clonedDn.apply( schemaManager ); 921 clonedDn.toUpName(); 922 923 return clonedDn; 924 } 925 926 927 /** 928 * Adds a single Rdn to the (leaf) end of this name. 929 * 930 * @param newRdn the Rdn to add 931 * @return the updated cloned Dn 932 */ 933 public Dn add( Rdn newRdn ) throws LdapInvalidDnException 934 { 935 if ( ( newRdn == null ) || ( newRdn.size() == 0 ) ) 936 { 937 return this; 938 } 939 940 Dn clonedDn = copy(); 941 942 clonedDn.rdns.add( 0, newRdn.clone() ); 943 clonedDn.apply( schemaManager ); 944 clonedDn.toUpName(); 945 946 return clonedDn; 947 } 948 949 950 /** 951 * Gets the parent Dn of this Dn. Null if this Dn doesn't have a parent, i.e. because it 952 * is the empty Dn.<br/> 953 * The Parent is the right part of the Dn, when the Rdn has been removed. 954 * 955 * @return the parent Dn of this Dn 956 */ 957 public Dn getParent() 958 { 959 if ( isNullOrEmpty( this ) ) 960 { 961 return this; 962 } 963 964 int posn = rdns.size() - 1; 965 966 Dn newDn = new Dn( schemaManager ); 967 968 for ( int i = rdns.size() - posn; i < rdns.size(); i++ ) 969 { 970 // Don't forget to clone the rdns ! 971 newDn.rdns.add( rdns.get( i ).clone() ); 972 } 973 974 try 975 { 976 newDn.apply( schemaManager ); 977 } 978 catch ( LdapInvalidDnException e ) 979 { 980 LOG.error( e.getMessage(), e ); 981 } 982 983 newDn.toUpName(); 984 985 return newDn; 986 } 987 988 989 /** 990 * Create a copy of the current Dn 991 */ 992 private Dn copy() 993 { 994 Dn dn = new Dn( schemaManager ); 995 dn.rdns = new ArrayList<Rdn>(); 996 997 for ( Rdn rdn : rdns ) 998 { 999 dn.rdns.add( rdn.clone() ); 1000 } 1001 1002 return dn; 1003 } 1004 1005 1006 /** 1007 * @see java.lang.Object#equals(java.lang.Object) 1008 * @return <code>true</code> if the two instances are equals 1009 */ 1010 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", 1011 justification = "String is a special case") 1012 @Override 1013 public boolean equals( Object obj ) 1014 { 1015 if ( obj instanceof String ) 1016 { 1017 return normName.equals( obj ); 1018 } 1019 else if ( obj instanceof Dn) 1020 { 1021 Dn name = (Dn) obj; 1022 1023 if ( name.size() != this.size() ) 1024 { 1025 return false; 1026 } 1027 1028 for ( int i = 0; i < this.size(); i++ ) 1029 { 1030 if ( !name.rdns.get( i ).equals( rdns.get( i ) ) ) 1031 { 1032 return false; 1033 } 1034 } 1035 1036 // All components matched so we return true 1037 return true; 1038 } 1039 else 1040 { 1041 return false; 1042 } 1043 } 1044 1045 1046 /** 1047 * Normalize the Ava 1048 */ 1049 private static Ava atavOidToName( Ava atav, SchemaManager schemaManager ) 1050 throws LdapInvalidDnException 1051 { 1052 Map<String, OidNormalizer> oidsMap = schemaManager.getNormalizerMapping(); 1053 String type = Strings.trim( atav.getNormType() ); 1054 1055 if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) ) 1056 { 1057 type = type.substring( 4 ); 1058 } 1059 1060 if ( Strings.isNotEmpty( type ) ) 1061 { 1062 if ( oidsMap == null ) 1063 { 1064 return atav; 1065 } 1066 1067 type = Strings.toLowerCase( type ); 1068 1069 // Check that we have an existing AttributeType for this type 1070 if ( !oidsMap.containsKey( type ) ) 1071 { 1072 // No AttributeType : this is an error 1073 String msg = I18n.err( I18n.ERR_04268_OID_NOT_FOUND, atav.getUpType() ); 1074 LOG.error( msg ); 1075 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, msg ); 1076 } 1077 1078 OidNormalizer oidNormalizer = oidsMap.get( type ); 1079 1080 if ( oidNormalizer != null ) 1081 { 1082 try 1083 { 1084 Ava newAva = new Ava( 1085 atav.getUpType(), 1086 oidNormalizer.getAttributeTypeOid(), 1087 atav.getUpValue(), 1088 oidNormalizer.getNormalizer().normalize( atav.getNormValue() ), 1089 atav.getUpName() ); 1090 newAva.apply( schemaManager ); 1091 1092 return newAva; 1093 } 1094 catch ( LdapException le ) 1095 { 1096 throw new LdapInvalidDnException( le.getMessage(), le ); 1097 } 1098 } 1099 else 1100 { 1101 // We don't have a normalizer for this OID : just do nothing. 1102 return atav; 1103 } 1104 } 1105 else 1106 { 1107 // The type is empty : this is not possible... 1108 String msg = I18n.err( I18n.ERR_04209_EMPTY_TYPE_NOT_ALLOWED ); 1109 LOG.error( msg ); 1110 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, msg ); 1111 } 1112 } 1113 1114 1115 /** 1116 * Transform a Rdn by changing the value to its OID counterpart and 1117 * normalizing the value accordingly to its type. 1118 * 1119 * @param rdn The Rdn to modify. 1120 * @param SchemaManager The schema manager 1121 * @throws LdapInvalidDnException If the Rdn is invalid. 1122 */ 1123 /** No qualifier */ 1124 static void rdnOidToName( Rdn rdn, SchemaManager schemaManager ) throws LdapInvalidDnException 1125 { 1126 // We have more than one ATAV for this Rdn. We will loop on all 1127 // ATAVs 1128 Rdn rdnCopy = rdn.clone(); 1129 rdn.clear(); 1130 1131 for ( Ava val : rdnCopy ) 1132 { 1133 Ava newAtav = atavOidToName( val, schemaManager ); 1134 rdn.addAVA( schemaManager, newAtav ); 1135 } 1136 } 1137 1138 1139 /** 1140 * Normalizes the Dn using the given the schema manager 1141 * 1142 * @param schemaManager The schemaManagerto use to normalize the Dn 1143 * @return The normalized Dn 1144 * @throws LdapInvalidDnException If the Dn is invalid. 1145 */ 1146 public Dn apply( SchemaManager schemaManager ) throws LdapInvalidDnException 1147 { 1148 this.schemaManager = schemaManager; 1149 1150 if ( this.schemaManager != null ) 1151 { 1152 synchronized ( this ) 1153 { 1154 if ( size() == 0 ) 1155 { 1156 bytes = null; 1157 normName = ""; 1158 1159 return this; 1160 } 1161 1162 StringBuilder sb = new StringBuilder(); 1163 boolean isFirst = true; 1164 1165 for ( Rdn rdn : rdns ) 1166 { 1167 rdn.apply( schemaManager ); 1168 1169 if ( isFirst ) 1170 { 1171 isFirst = false; 1172 } 1173 else 1174 { 1175 sb.append( ',' ); 1176 } 1177 1178 sb.append( rdn.getNormName() ); 1179 } 1180 1181 String newNormName = sb.toString(); 1182 1183 if ( ( normName == null ) || !normName.equals( newNormName ) ) 1184 { 1185 bytes = Strings.getBytesUtf8(newNormName); 1186 normName = newNormName; 1187 } 1188 1189 return this; 1190 } 1191 } 1192 else 1193 { 1194 if ( rdns.size() == 0 ) 1195 { 1196 bytes = null; 1197 normName = ""; 1198 } 1199 else 1200 { 1201 StringBuffer sb = new StringBuffer(); 1202 boolean isFirst = true; 1203 1204 for ( Rdn rdn : rdns ) 1205 { 1206 if ( isFirst ) 1207 { 1208 isFirst = false; 1209 } 1210 else 1211 { 1212 sb.append( ',' ); 1213 } 1214 1215 sb.append( rdn.getNormName() ); 1216 } 1217 1218 String newNormName = sb.toString(); 1219 1220 if ( ( normName == null ) || !normName.equals( newNormName ) ) 1221 { 1222 bytes = Strings.getBytesUtf8(newNormName); 1223 normName = newNormName; 1224 } 1225 } 1226 } 1227 1228 return this; 1229 } 1230 1231 1232 /** 1233 * Tells if the Dn is schema aware 1234 * 1235 * @return <code>true</code> if the Dn is schema aware. 1236 */ 1237 public boolean isSchemaAware() 1238 { 1239 return schemaManager != null; 1240 } 1241 1242 1243 /** 1244 * Iterate over the inner Rdn. The Rdn are returned from 1245 * the rightmost to the leftmost. For instance, the following code :<br/> 1246 * <pre> 1247 * Dn dn = new Dn( "sn=test, dc=apache, dc=org ); 1248 * 1249 * for ( Rdn rdn : dn ) 1250 * { 1251 * System.out.println( rdn.toString() ); 1252 * } 1253 * </pre> 1254 * will produce this output : <br/> 1255 * <pre> 1256 * dc=org 1257 * dc=apache 1258 * sn=test 1259 * </pre> 1260 * 1261 */ 1262 public Iterator<Rdn> iterator() 1263 { 1264 return new RdnIterator(); 1265 } 1266 1267 1268 /** 1269 * Check if a DistinguishedName is null or empty. 1270 * 1271 * @param dn The Dn to check 1272 * @return <code>true></code> if the Dn is null or empty, <code>false</code> 1273 * otherwise 1274 */ 1275 public static boolean isNullOrEmpty( Dn dn ) 1276 { 1277 return ( dn == null ) || dn.isEmpty(); 1278 } 1279 1280 1281 /** 1282 * Check if a DistinguishedName is syntactically valid. 1283 * 1284 * @param dn The Dn to validate 1285 * @return <code>true></code> if the Dn is valid, <code>false</code> 1286 * otherwise 1287 */ 1288 public static boolean isValid( String name ) 1289 { 1290 Dn dn = new Dn(); 1291 1292 try 1293 { 1294 parseInternal( name, dn.rdns ); 1295 return true; 1296 } 1297 catch ( LdapInvalidDnException e ) 1298 { 1299 return false; 1300 } 1301 } 1302 1303 1304 /** 1305 * Parse a Dn. 1306 * 1307 * @param name The Dn to be parsed 1308 * @param rdns The list that will contain the RDNs 1309 * @throws LdapInvalidDnException If the Dn is invalid 1310 */ 1311 private static void parseInternal( String name, List<Rdn> rdns ) throws LdapInvalidDnException 1312 { 1313 try 1314 { 1315 FastDnParser.parseDn( name, rdns ); 1316 } 1317 catch ( TooComplexException e ) 1318 { 1319 rdns.clear(); 1320 new ComplexDnParser().parseDn( name, rdns ); 1321 } 1322 } 1323 1324 1325 /** 1326 * {@inheritDoc} 1327 */ 1328 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 1329 { 1330 // Read the UPName 1331 upName = in.readUTF(); 1332 1333 // Read the NormName 1334 normName = in.readUTF(); 1335 1336 if ( normName.length() == 0 ) 1337 { 1338 // As the normName is equal to the upName, 1339 // we didn't saved the nbnormName on disk. 1340 // restore it by copying the upName. 1341 normName = upName; 1342 } 1343 1344 // Read the RDNs. Is it's null, the number will be -1. 1345 int nbRdns = in.readInt(); 1346 1347 rdns = new ArrayList<Rdn>( nbRdns ); 1348 1349 for ( int i = 0; i < nbRdns; i++ ) 1350 { 1351 Rdn rdn = new Rdn( schemaManager ); 1352 rdn.readExternal( in ); 1353 rdns.add( rdn ); 1354 } 1355 } 1356 1357 1358 /** 1359 * {@inheritDoc} 1360 */ 1361 public void writeExternal( ObjectOutput out ) throws IOException 1362 { 1363 if ( upName == null ) 1364 { 1365 String message = "Cannot serialize a NULL Dn"; 1366 LOG.error( message ); 1367 throw new IOException( message ); 1368 } 1369 1370 // Write the UPName 1371 out.writeUTF( upName ); 1372 1373 // Write the NormName if different 1374 if ( upName.equals( normName ) ) 1375 { 1376 out.writeUTF( "" ); 1377 } 1378 else 1379 { 1380 out.writeUTF( normName ); 1381 } 1382 1383 // Write the RDNs. 1384 // First the number of RDNs 1385 out.writeInt( size() ); 1386 1387 // Loop on the RDNs 1388 for ( Rdn rdn : rdns ) 1389 { 1390 rdn.writeExternal( out ); 1391 } 1392 1393 out.flush(); 1394 } 1395 1396 1397 /** 1398 * Return the user provided Dn as a String. It returns the same value as the 1399 * getName method 1400 * 1401 * @return A String representing the user provided Dn 1402 */ 1403 @Override 1404 public String toString() 1405 { 1406 return getName(); 1407 } 1408}