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