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