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