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