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 021 package org.apache.directory.shared.ldap.name; 022 023 024 import java.io.Externalizable; 025 import java.io.IOException; 026 import java.io.ObjectInput; 027 import java.io.ObjectOutput; 028 import java.io.Serializable; 029 import java.util.ArrayList; 030 import java.util.Enumeration; 031 import java.util.Iterator; 032 import java.util.List; 033 import java.util.Map; 034 import java.util.NoSuchElementException; 035 036 import javax.naming.InvalidNameException; 037 import javax.naming.Name; 038 import javax.naming.ldap.LdapName; 039 040 import org.apache.directory.shared.i18n.I18n; 041 import org.apache.directory.shared.ldap.exception.LdapException; 042 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException; 043 import org.apache.directory.shared.ldap.message.ResultCodeEnum; 044 import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer; 045 import org.apache.directory.shared.ldap.util.StringTools; 046 import org.slf4j.Logger; 047 import org.slf4j.LoggerFactory; 048 049 050 /** 051 * The DN class contains a DN (Distinguished Name). 052 * 053 * Its specification can be found in RFC 2253, 054 * "UTF-8 String Representation of Distinguished Names". 055 * 056 * We will store two representation of a DN : 057 * - a user Provider representation, which is the parsed String given by a user 058 * - an internal representation. 059 * 060 * A DN is formed of RDNs, in a specific order : 061 * RDN[n], RDN[n-1], ... RDN[1], RDN[0] 062 * 063 * It represents a tree, in which the root is the last RDN (RDN[0]) and the leaf 064 * is the first RDN (RDN[n]). 065 * 066 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 067 * @version $Rev: 930282 $, $Date: 2010-04-02 16:29:58 +0200 (Fri, 02 Apr 2010) $ 068 */ 069 public class DN implements Cloneable, Serializable, Comparable<DN>, Iterable<RDN> 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 /** A flag used to tell if the DN has been normalized */ 090 private boolean normalized; 091 092 // ~ Static fields/initializers 093 // ----------------------------------------------------------------- 094 /** 095 * The RDNs that are elements of the DN 096 * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC! 097 * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0) 098 */ 099 protected List<RDN> rdns = new ArrayList<RDN>( 5 ); 100 101 /** The user provided name */ 102 private String upName; 103 104 /** The normalized name */ 105 private String normName; 106 107 /** The bytes representation of the normName */ 108 private byte[] bytes; 109 110 /** A null DN */ 111 public static final DN EMPTY_DN = new DN(); 112 113 114 // ~ Methods 115 // ------------------------------------------------------------------------------------ 116 117 /** 118 * Construct an empty DN object 119 */ 120 public DN() 121 { 122 upName = ""; 123 normName = null; 124 normalized = true; 125 } 126 127 128 /** 129 * Copies a DN to an DN. 130 * 131 * @param name composed of String name components. 132 * @throws LdapInvalidDnException If the Name is invalid. 133 */ 134 public DN( DN dn ) throws LdapInvalidDnException 135 { 136 if ( ( dn != null ) && ( dn.size() != 0 ) ) 137 { 138 for ( int ii = 0; ii < dn.size(); ii++ ) 139 { 140 String nameComponent = dn.get( ii ); 141 add( nameComponent ); 142 } 143 } 144 145 normalized = false; 146 } 147 148 149 /** 150 * Parse a String and checks that it is a valid DN <br> 151 * <p> 152 * <distinguishedName> ::= <name> | e <br> 153 * <name> ::= <name-component> <name-components> <br> 154 * <name-components> ::= <spaces> <separator> 155 * <spaces> <name-component> <name-components> | e <br> 156 * </p> 157 * 158 * @param upName The String that contains the DN. 159 * @throws LdapInvalidNameException if the String does not contain a valid DN. 160 */ 161 public DN( String upName ) throws LdapInvalidDnException 162 { 163 if ( upName != null ) 164 { 165 DnParser.parseInternal( upName, rdns ); 166 } 167 168 // Stores the representations of a DN : internal (as a string and as a 169 // byte[]) and external. 170 normalizeInternal(); 171 normalized = false; 172 173 this.upName = upName; 174 } 175 176 177 /** 178 * Creates a new instance of DN, using varargs to declare the RDNs. Each 179 * String is either a full RDN, or a couple of AttributeType DI and a value. 180 * If the String contains a '=' symbol, the the constructor will assume that 181 * the String arg contains afull RDN, otherwise, it will consider that the 182 * following arg is the value. 183 * An example of usage would be : 184 * <pre> 185 * String exampleName = "example"; 186 * String baseDn = "dc=apache,dc=org"; 187 * 188 * DN dn = new DN( 189 * "cn=Test", 190 * "ou", exampleName, 191 * baseDn); 192 * </pre> 193 * 194 * @param upNames 195 * @throws LdapInvalidDnException 196 */ 197 public DN( String... upRdns ) throws LdapInvalidDnException 198 { 199 StringBuilder sb = new StringBuilder(); 200 boolean valueExpected = false; 201 boolean isFirst = true; 202 203 for ( String upRdn : upRdns ) 204 { 205 if ( isFirst ) 206 { 207 isFirst = false; 208 } 209 else if ( !valueExpected ) 210 { 211 sb.append( ',' ); 212 } 213 214 if ( !valueExpected ) 215 { 216 sb.append( upRdn ); 217 218 if ( upRdn.indexOf( '=' ) == -1 ) 219 { 220 valueExpected = true; 221 } 222 } 223 else 224 { 225 sb.append( "=" ).append( upRdn ); 226 227 valueExpected = false; 228 } 229 } 230 231 if ( valueExpected ) 232 { 233 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04202 ) ); 234 } 235 236 // Stores the representations of a DN : internal (as a string and as a 237 // byte[]) and external. 238 upName = sb.toString(); 239 DnParser.parseInternal( upName, rdns ); 240 normalizeInternal(); 241 normalized = false; 242 } 243 244 /** 245 * Create a DN when deserializing it. 246 * 247 * Note : this constructor is used only by the deserialization method. 248 * @param upName The user provided name 249 * @param normName the normalized name 250 * @param bytes the name as a byte[] 251 */ 252 DN( String upName, String normName, byte[] bytes ) 253 { 254 normalized = true; 255 this.upName = upName; 256 this.normName = normName; 257 this.bytes = bytes; 258 } 259 260 261 /** 262 * Static factory which creates a normalized DN from a String and a Map of OIDs. 263 * 264 * @param name The DN as a String 265 * @param oidsMap The OID mapping 266 * @return A valid DN 267 * @throws LdapInvalidNameException If the DN is invalid. 268 * @throws LdapInvalidDnException If something went wrong. 269 */ 270 public static DN normalize( String name, Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException 271 { 272 if ( ( name == null ) || ( name.length() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) ) 273 { 274 return DN.EMPTY_DN; 275 } 276 277 DN newDn = new DN( name ); 278 279 Enumeration<RDN> rdns = newDn.getAllRdn(); 280 281 // Loop on all RDNs 282 while ( rdns.hasMoreElements() ) 283 { 284 RDN rdn = rdns.nextElement(); 285 String upName = rdn.getName(); 286 rdnOidToName( rdn, oidsMap ); 287 rdn.normalize(); 288 rdn.setUpName( upName ); 289 } 290 291 newDn.normalizeInternal(); 292 newDn.normalized = true; 293 294 return newDn; 295 } 296 297 298 /** 299 * Normalize the DN by triming useless spaces and lowercasing names. 300 */ 301 void normalizeInternal() 302 { 303 normName = toNormName(); 304 } 305 306 307 /** 308 * Build the normalized DN as a String, 309 * 310 * @return A String representing the normalized DN 311 */ 312 private String toNormName() 313 { 314 if ( rdns.size() == 0 ) 315 { 316 bytes = null; 317 return ""; 318 } 319 else 320 { 321 StringBuffer sb = new StringBuffer(); 322 boolean isFirst = true; 323 324 for ( RDN rdn : rdns ) 325 { 326 if ( isFirst ) 327 { 328 isFirst = false; 329 } 330 else 331 { 332 sb.append( ',' ); 333 } 334 335 sb.append( rdn.getNormName() ); 336 } 337 338 String newNormName = sb.toString(); 339 340 if ( ( normName == null ) || !normName.equals( newNormName ) ) 341 { 342 bytes = StringTools.getBytesUtf8( newNormName ); 343 normName = newNormName; 344 } 345 346 return normName; 347 } 348 } 349 350 351 /** 352 * Return the normalized DN as a String. It returns the same value as the 353 * getNormName method 354 * 355 * @return A String representing the normalized DN 356 */ 357 public String toString() 358 { 359 return getName(); 360 } 361 362 363 /** 364 * Return the User Provided DN as a String, 365 * 366 * @return A String representing the User Provided DN 367 */ 368 private String toUpName() 369 { 370 if ( rdns.size() == 0 ) 371 { 372 upName = ""; 373 } 374 else 375 { 376 StringBuffer sb = new StringBuffer(); 377 boolean isFirst = true; 378 379 for ( RDN rdn : rdns ) 380 { 381 if ( isFirst ) 382 { 383 isFirst = false; 384 } 385 else 386 { 387 sb.append( ',' ); 388 } 389 390 sb.append( rdn.getName() ); 391 } 392 393 upName = sb.toString(); 394 } 395 396 return upName; 397 } 398 399 400 /** 401 * Return the User Provided prefix representation of the DN starting at the 402 * posn position. 403 * 404 * If posn = 0, return an empty string. 405 * 406 * for DN : sn=smith, dc=apache, dc=org 407 * getUpname(0) -> "" 408 * getUpName(1) -> "dc=org" 409 * getUpname(3) -> "sn=smith, dc=apache, dc=org" 410 * getUpName(4) -> ArrayOutOfBoundException 411 * 412 * Warning ! The returned String is not exactly the 413 * user provided DN, as spaces before and after each RDNs have been trimmed. 414 * 415 * @param posn 416 * The starting position 417 * @return The truncated DN 418 */ 419 private String getUpNamePrefix( int posn ) 420 { 421 if ( posn == 0 ) 422 { 423 return ""; 424 } 425 426 if ( posn > rdns.size() ) 427 { 428 String message = I18n.err( I18n.ERR_04203, posn, rdns.size() ); 429 LOG.error( message ); 430 throw new ArrayIndexOutOfBoundsException( message ); 431 } 432 433 int start = rdns.size() - posn; 434 StringBuffer sb = new StringBuffer(); 435 boolean isFirst = true; 436 437 for ( int i = start; i < rdns.size(); i++ ) 438 { 439 if ( isFirst ) 440 { 441 isFirst = false; 442 } 443 else 444 { 445 sb.append( ',' ); 446 } 447 448 sb.append( rdns.get( i ).getName() ); 449 } 450 451 return sb.toString(); 452 } 453 454 455 /** 456 * Return the User Provided suffix representation of the DN starting at the 457 * posn position. 458 * If posn = 0, return an empty string. 459 * 460 * for DN : sn=smith, dc=apache, dc=org 461 * getUpname(0) -> "sn=smith, dc=apache, dc=org" 462 * getUpName(1) -> "sn=smith, dc=apache" 463 * getUpname(3) -> "sn=smith" 464 * getUpName(4) -> "" 465 * 466 * Warning ! The returned String is not exactly the user 467 * provided DN, as spaces before and after each RDNs have been trimmed. 468 * 469 * @param posn The starting position 470 * @return The truncated DN 471 */ 472 private String getUpNameSuffix( int posn ) 473 { 474 if ( posn > rdns.size() ) 475 { 476 return ""; 477 } 478 479 int end = rdns.size() - posn; 480 StringBuffer sb = new StringBuffer(); 481 boolean isFirst = true; 482 483 for ( int i = 0; i < end; i++ ) 484 { 485 if ( isFirst ) 486 { 487 isFirst = false; 488 } 489 else 490 { 491 sb.append( ',' ); 492 } 493 494 sb.append( rdns.get( i ).getName() ); 495 } 496 497 return sb.toString(); 498 } 499 500 501 /** 502 * Gets the hash code of this name. 503 * 504 * @see java.lang.Object#hashCode() 505 * @return the instance hash code 506 */ 507 public int hashCode() 508 { 509 int result = 37; 510 511 for ( RDN rdn : rdns ) 512 { 513 result = result * 17 + rdn.hashCode(); 514 } 515 516 return result; 517 } 518 519 520 /** 521 * Get the initial DN 522 * 523 * @return The DN as a String 524 */ 525 public String getName() 526 { 527 return ( upName == null ? "" : upName ); 528 } 529 530 531 /** 532 * Sets the up name. 533 * 534 * @param upName the new up name 535 */ 536 void setUpName( String upName ) 537 { 538 this.upName = upName; 539 } 540 541 542 /** 543 * Get the normalized DN 544 * 545 * @return The DN as a String 546 */ 547 public String getNormName() 548 { 549 if ( normName == null ) 550 { 551 normName = toNormName(); 552 } 553 554 return normName; 555 } 556 557 558 /** 559 * {@inheritDoc} 560 */ 561 public int size() 562 { 563 return rdns.size(); 564 } 565 566 567 /** 568 * Get the number of bytes necessary to store this DN 569 570 * @param dn The DN. 571 * @return A integer, which is the size of the UTF-8 byte array 572 */ 573 public static int getNbBytes( DN dn ) 574 { 575 return dn.bytes == null ? 0 : dn.bytes.length; 576 } 577 578 579 /** 580 * Get an UTF-8 representation of the normalized form of the DN 581 * 582 * @param dn The DN. 583 * @return A byte[] representation of the DN 584 */ 585 public static byte[] getBytes( DN dn ) 586 { 587 return dn == null ? null : dn.bytes; 588 } 589 590 591 /** 592 * Tells if the current DN is a parent of another DN.<br> 593 * For instance, <b>dc=com</b> is a parent 594 * of <b>dc=example, dc=com</b> 595 * 596 * @param dn The child 597 * @return true if the current DN is a parent of the given DN 598 */ 599 public boolean isParentOf( String dn ) 600 { 601 try 602 { 603 return isParentOf( new DN( dn ) ); 604 } 605 catch( LdapInvalidDnException lide ) 606 { 607 return false; 608 } 609 } 610 611 612 /** 613 * Tells if the current DN is a parent of another DN.<br> 614 * For instance, <b>dc=com</b> is a parent 615 * of <b>dc=example, dc=com</b> 616 * 617 * @param dn The child 618 * @return true if the current DN is a parent of the given DN 619 */ 620 public boolean isParentOf( DN dn ) 621 { 622 if ( dn == null ) 623 { 624 return false; 625 } 626 627 return dn.isChildOf( this ); 628 } 629 630 631 /** 632 * Tells if a DN is a child of another DN.<br> 633 * For instance, <b>dc=example, dc=com</b> is a child 634 * of <b>dc=com</b> 635 * 636 * @param dn The parent 637 * @return true if the current DN is a child of the given DN 638 */ 639 public boolean isChildOf( String dn ) 640 { 641 try 642 { 643 return isChildOf( new DN( dn ) ); 644 } 645 catch( LdapInvalidDnException lide ) 646 { 647 return false; 648 } 649 } 650 651 652 /** 653 * Tells if a DN is a child of another DN.<br> 654 * For instance, <b>dc=example, dc=com</b> is a child 655 * of <b>dc=com</b> 656 * 657 * @param dn The parent 658 * @return true if the current DN is a child of the given DN 659 */ 660 public boolean isChildOf( DN dn ) 661 { 662 if ( dn == null ) 663 { 664 return true; 665 } 666 667 if ( dn.size() == 0 ) 668 { 669 return true; 670 } 671 672 if ( dn.size() > size() ) 673 { 674 // The name is longer than the current DN. 675 return false; 676 } 677 678 // Ok, iterate through all the RDN of the name, 679 // starting a the end of the current list. 680 681 for ( int i = dn.size() - 1; i >= 0; i-- ) 682 { 683 RDN nameRdn = dn.rdns.get( dn.rdns.size() - i - 1 ); 684 RDN ldapRdn = rdns.get( rdns.size() - i - 1 ); 685 686 if ( nameRdn.compareTo( ldapRdn ) != 0 ) 687 { 688 return false; 689 } 690 } 691 692 return true; 693 } 694 695 696 /** 697 * Determines whether this name has a specific suffix. A name 698 * <tt>name</tt> has a DN as a suffix if its right part contains the given DN 699 * 700 * Be aware that for a specific 701 * DN like : <b>cn=xxx, ou=yyy</b> the hasSuffix method will return false with 702 * <b>ou=yyy</b>, and true with <b>cn=xxx</b> 703 * 704 * @param dn the name to check 705 * @return true if <tt>dn</tt> is a suffix of this name, false otherwise 706 */ 707 public boolean hasSuffix( DN dn ) 708 { 709 710 if ( dn == null ) 711 { 712 return true; 713 } 714 715 if ( dn.size() == 0 ) 716 { 717 return true; 718 } 719 720 if ( dn.size() > size() ) 721 { 722 // The name is longer than the current DN. 723 return false; 724 } 725 726 // Ok, iterate through all the RDN of the name, 727 // starting a the end of the current list. 728 729 for ( int i = 0; i < dn.size(); i++ ) 730 { 731 RDN nameRdn = dn.rdns.get( i ); 732 RDN ldapRdn = rdns.get( i ); 733 734 if ( nameRdn.compareTo( ldapRdn ) != 0 ) 735 { 736 return false; 737 } 738 } 739 740 return true; 741 } 742 743 744 /** 745 * return true if this DN contains no RDNs 746 */ 747 public boolean isEmpty() 748 { 749 return ( rdns.size() == 0 ); 750 } 751 752 753 /** 754 * Get the given RDN as a String. The position is used in the 755 * reverse order. Assuming that we have a DN like 756 * <pre>dc=example,dc=apache,dc=org</pre> 757 * then : 758 * <li><code>get(0)</code> will return dc=org</li> 759 * <li><code>get(1)</code> will return dc=apache</li> 760 * <li><code>get(2)</code> will return dc=example</li> 761 * 762 * @param posn The position of the wanted RDN in the DN. 763 */ 764 public String get( int posn ) 765 { 766 if ( rdns.size() == 0 ) 767 { 768 return ""; 769 } 770 else 771 { 772 RDN rdn = rdns.get( rdns.size() - posn - 1 ); 773 774 return rdn.getNormName(); 775 } 776 } 777 778 779 /** 780 * Retrieves a component of this name. 781 * 782 * @param posn 783 * the 0-based index of the component to retrieve. Must be in the 784 * range [0,size()). 785 * @return the component at index posn 786 * @throws ArrayIndexOutOfBoundsException 787 * if posn is outside the specified range 788 */ 789 public RDN getRdn( int posn ) 790 { 791 if ( rdns.size() == 0 ) 792 { 793 return null; 794 } 795 else 796 { 797 RDN rdn = rdns.get( rdns.size() - posn - 1 ); 798 799 return rdn; 800 } 801 } 802 803 804 /** 805 * Retrieves the last (leaf) component of this name. 806 * 807 * @return the last component of this DN 808 */ 809 public RDN getRdn() 810 { 811 if ( rdns.size() == 0 ) 812 { 813 return null; 814 } 815 else 816 { 817 return rdns.get( 0 ); 818 } 819 } 820 821 822 /** 823 * Retrieves all the components of this name. 824 * 825 * @return All the components 826 */ 827 public List<RDN> getRdns() 828 { 829 List<RDN> newRdns = new ArrayList<RDN>(); 830 831 // We will clone the list, to avoid user modifications 832 for ( RDN rdn : rdns ) 833 { 834 newRdns.add( ( RDN ) rdn.clone() ); 835 } 836 837 return newRdns; 838 } 839 840 841 /** 842 * Retrieves the components of this name as an enumeration of strings. The 843 * effect on the enumeration of updates to this name is undefined. If the 844 * name has zero components, an empty (non-null) enumeration is returned. 845 * This starts at the root (rightmost) rdn. 846 * 847 * @return an enumeration of the components of this name, as Rdn 848 */ 849 public Enumeration<RDN> getAllRdn() 850 { 851 /* 852 * Note that by accessing the name component using the get() method on 853 * the name rather than get() on the list we are reading components from 854 * right to left with increasing index values. LdapName.get() does the 855 * index translation on m_list for us. 856 */ 857 return new Enumeration<RDN>() 858 { 859 private int pos; 860 861 862 public boolean hasMoreElements() 863 { 864 return pos < rdns.size(); 865 } 866 867 868 public RDN nextElement() 869 { 870 if ( pos >= rdns.size() ) 871 { 872 LOG.error( I18n.err( I18n.ERR_04205 ) ); 873 throw new NoSuchElementException(); 874 } 875 876 RDN rdn = rdns.get( rdns.size() - pos - 1 ); 877 pos++; 878 return rdn; 879 } 880 }; 881 } 882 883 884 /** 885 * {@inheritDoc} 886 */ 887 public DN getPrefix( int posn ) 888 { 889 if ( rdns.size() == 0 ) 890 { 891 return EMPTY_DN; 892 } 893 894 if ( ( posn < 0 ) || ( posn > rdns.size() ) ) 895 { 896 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() ); 897 LOG.error( message ); 898 throw new ArrayIndexOutOfBoundsException( message ); 899 } 900 901 DN newDN = new DN(); 902 903 for ( int i = rdns.size() - posn; i < rdns.size(); i++ ) 904 { 905 // Don't forget to clone the rdns ! 906 newDN.rdns.add( ( RDN ) rdns.get( i ).clone() ); 907 } 908 909 newDN.normName = newDN.toNormName(); 910 newDN.upName = getUpNamePrefix( posn ); 911 912 return newDN; 913 } 914 915 916 /** 917 * {@inheritDoc} 918 */ 919 public DN getSuffix( int posn ) 920 { 921 if ( rdns.size() == 0 ) 922 { 923 return EMPTY_DN; 924 } 925 926 if ( ( posn < 0 ) || ( posn > rdns.size() ) ) 927 { 928 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() ); 929 LOG.error( message ); 930 throw new ArrayIndexOutOfBoundsException( message ); 931 } 932 933 DN newDN = new DN(); 934 935 for ( int i = 0; i < size() - posn; i++ ) 936 { 937 // Don't forget to clone the rdns ! 938 newDN.rdns.add( ( RDN ) rdns.get( i ).clone() ); 939 } 940 941 newDN.normName = newDN.toNormName(); 942 newDN.upName = getUpNameSuffix( posn ); 943 944 return newDN; 945 } 946 947 948 /** 949 * Adds the components of a name -- in order -- at a specified position 950 * within this name. Components of this name at or after the index of the 951 * first new component are shifted up (away from 0) to accommodate the new 952 * components. Compoenents are supposed to be normalized. 953 * 954 * @param posn the index in this name at which to add the new components. 955 * Must be in the range [0,size()]. Note this is from the opposite end as rnds.get(posn) 956 * @param name the components to add 957 * @return the updated name (not a new one) 958 * @throws ArrayIndexOutOfBoundsException 959 * if posn is outside the specified range 960 * @throws LdapInvalidDnException 961 * if <tt>n</tt> is not a valid name, or if the addition of 962 * the components would violate the syntax rules of this name 963 */ 964 public DN addAllNormalized( int posn, DN name ) throws LdapInvalidDnException 965 { 966 if ( name instanceof DN ) 967 { 968 DN dn = (DN)name; 969 970 if ( ( dn == null ) || ( dn.size() == 0 ) ) 971 { 972 return this; 973 } 974 975 // Concatenate the rdns 976 rdns.addAll( size() - posn, dn.rdns ); 977 978 if ( StringTools.isEmpty( normName ) ) 979 { 980 normName = dn.normName; 981 bytes = dn.bytes; 982 upName = dn.upName; 983 } 984 else 985 { 986 normName = dn.normName + "," + normName; 987 bytes = StringTools.getBytesUtf8( normName ); 988 upName = dn.upName + "," + upName; 989 } 990 } 991 else 992 { 993 if ( ( name == null ) || ( name.size() == 0 ) ) 994 { 995 return this; 996 } 997 998 for ( int i = name.size() - 1; i >= 0; i-- ) 999 { 1000 RDN rdn = new RDN( name.get( i ) ); 1001 rdns.add( size() - posn, rdn ); 1002 } 1003 1004 normalizeInternal(); 1005 toUpName(); 1006 } 1007 1008 return this; 1009 } 1010 1011 /** 1012 * {@inheritDoc} 1013 */ 1014 public DN addAll( DN suffix ) throws LdapInvalidDnException 1015 { 1016 addAll( rdns.size(), suffix ); 1017 normalizeInternal(); 1018 toUpName(); 1019 1020 return this; 1021 } 1022 1023 1024 /** 1025 * {@inheritDoc} 1026 */ 1027 public DN addAll( int posn, Name name ) throws InvalidNameException, LdapInvalidDnException 1028 { 1029 if ( ( name == null ) || ( name.size() == 0 ) ) 1030 { 1031 return this; 1032 } 1033 1034 for ( int i = name.size() - 1; i >= 0; i-- ) 1035 { 1036 RDN rdn = new RDN( name.get( i ) ); 1037 rdns.add( size() - posn, rdn ); 1038 } 1039 1040 normalizeInternal(); 1041 toUpName(); 1042 1043 return this; 1044 } 1045 1046 1047 /** 1048 * {@inheritDoc} 1049 */ 1050 public DN addAll( int posn, DN dn ) throws LdapInvalidDnException 1051 { 1052 if ( ( dn == null ) || ( dn.size() == 0 ) ) 1053 { 1054 return this; 1055 } 1056 1057 // Concatenate the rdns 1058 rdns.addAll( size() - posn, dn.rdns ); 1059 1060 // Regenerate the normalized name and the original string 1061 if ( this.isNormalized() && dn.isNormalized() ) 1062 { 1063 if ( this.size() != 0 ) 1064 { 1065 normName = dn.getNormName() + "," + normName; 1066 bytes = StringTools.getBytesUtf8( normName ); 1067 upName = dn.getName() + "," + upName; 1068 } 1069 } 1070 else 1071 { 1072 normalizeInternal(); 1073 toUpName(); 1074 } 1075 1076 return this; 1077 } 1078 1079 1080 /** 1081 * {@inheritDoc} 1082 */ 1083 public DN add( String comp ) throws LdapInvalidDnException 1084 { 1085 if ( comp.length() == 0 ) 1086 { 1087 return this; 1088 } 1089 1090 //FIXME this try-catch block is for the time being, during removal of 1091 // java.naming.Name we have to remove this 1092 try 1093 { 1094 // We have to parse the nameComponent which is given as an argument 1095 RDN newRdn = new RDN( comp ); 1096 1097 rdns.add( 0, newRdn ); 1098 } 1099 catch( LdapInvalidDnException le ) 1100 { 1101 throw new LdapInvalidDnException( le.getMessage() ); 1102 } 1103 1104 normalizeInternal(); 1105 toUpName(); 1106 1107 return this; 1108 } 1109 1110 1111 /** 1112 * Adds a single RDN to the (leaf) end of this name. 1113 * 1114 * @param newRdn the RDN to add 1115 * @return the updated name (not a new one) 1116 */ 1117 public DN add( RDN newRdn ) 1118 { 1119 rdns.add( 0, newRdn ); 1120 1121 normalizeInternal(); 1122 toUpName(); 1123 1124 return this; 1125 } 1126 1127 1128 /** 1129 * Adds a single RDN to a specific position. 1130 * 1131 * @param newRdn the RDN to add 1132 * @param pos The position where we want to add the Rdn 1133 * @return the updated name (not a new one) 1134 */ 1135 public DN add( int pos, RDN newRdn ) 1136 { 1137 rdns.add( newRdn ); 1138 1139 normalizeInternal(); 1140 toUpName(); 1141 1142 return this; 1143 } 1144 1145 1146 /** 1147 * Adds a single normalized RDN to the (leaf) end of this name. 1148 * 1149 * @param newRdn the RDN to add 1150 * @return the updated name (not a new one) 1151 */ 1152 public DN addNormalized( RDN newRdn ) 1153 { 1154 rdns.add( 0, newRdn ); 1155 1156 // Avoid a call to the toNormName() method which 1157 // will iterate through all the rdns, when we only 1158 // have to build a new normName by using the current 1159 // RDN normalized name. The very same for upName. 1160 if (rdns.size() == 1 ) 1161 { 1162 normName = newRdn.getNormName(); 1163 upName = newRdn.getName(); 1164 } 1165 else 1166 { 1167 normName = newRdn + "," + normName; 1168 upName = newRdn.getName() + "," + upName; 1169 } 1170 1171 bytes = StringTools.getBytesUtf8( normName ); 1172 1173 return this; 1174 } 1175 1176 1177 /** 1178 * {@inheritDoc} 1179 */ 1180 public DN add( int posn, String comp ) throws LdapInvalidDnException 1181 { 1182 if ( ( posn < 0 ) || ( posn > size() ) ) 1183 { 1184 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() ); 1185 LOG.error( message ); 1186 throw new ArrayIndexOutOfBoundsException( message ); 1187 } 1188 1189 //FIXME this try-catch block is for the time being, during removal of 1190 // java.naming.Name we have to remove this 1191 try 1192 { 1193 // We have to parse the nameComponent which is given as an argument 1194 RDN newRdn = new RDN( comp ); 1195 1196 int realPos = size() - posn; 1197 rdns.add( realPos, newRdn ); 1198 } 1199 catch( LdapInvalidDnException le ) 1200 { 1201 throw new LdapInvalidDnException( le.getMessage() ); 1202 } 1203 1204 normalizeInternal(); 1205 toUpName(); 1206 1207 return this; 1208 } 1209 1210 1211 /** 1212 * {@inheritDoc} 1213 */ 1214 public RDN remove( int posn ) throws LdapInvalidDnException 1215 { 1216 if ( rdns.size() == 0 ) 1217 { 1218 return RDN.EMPTY_RDN; 1219 } 1220 1221 if ( ( posn < 0 ) || ( posn >= rdns.size() ) ) 1222 { 1223 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() ); 1224 LOG.error( message ); 1225 throw new ArrayIndexOutOfBoundsException( message ); 1226 } 1227 1228 int realPos = size() - posn - 1; 1229 RDN rdn = rdns.remove( realPos ); 1230 1231 normalizeInternal(); 1232 toUpName(); 1233 1234 return rdn; 1235 } 1236 1237 1238 /** 1239 * {@inheritDoc} 1240 */ 1241 public Object clone() 1242 { 1243 try 1244 { 1245 DN dn = ( DN ) super.clone(); 1246 dn.rdns = new ArrayList<RDN>(); 1247 1248 for ( RDN rdn : rdns ) 1249 { 1250 dn.rdns.add( ( RDN ) rdn.clone() ); 1251 } 1252 1253 return dn; 1254 } 1255 catch ( CloneNotSupportedException cnse ) 1256 { 1257 LOG.error( I18n.err( I18n.ERR_04207 ) ); 1258 throw new Error( I18n.err( I18n.ERR_04208 ) ); 1259 } 1260 } 1261 1262 1263 /** 1264 * @see java.lang.Object#equals(java.lang.Object) 1265 * @return <code>true</code> if the two instances are equals 1266 */ 1267 public boolean equals( Object obj ) 1268 { 1269 if ( obj instanceof String ) 1270 { 1271 return normName.equals( obj ); 1272 } 1273 else if ( obj instanceof DN ) 1274 { 1275 DN name = ( DN ) obj; 1276 1277 if ( name.size() != this.size() ) 1278 { 1279 return false; 1280 } 1281 1282 for ( int i = 0; i < this.size(); i++ ) 1283 { 1284 if ( name.rdns.get( i ).compareTo( rdns.get( i ) ) != 0 ) 1285 { 1286 return false; 1287 } 1288 } 1289 1290 // All components matched so we return true 1291 return true; 1292 } 1293 else 1294 { 1295 return false; 1296 } 1297 } 1298 1299 1300 /** 1301 * {@inheritDoc} 1302 */ 1303 public int compareTo( DN dn ) 1304 { 1305 if ( dn.size() != size() ) 1306 { 1307 return size() - dn.size(); 1308 } 1309 1310 for ( int i = rdns.size(); i > 0; i-- ) 1311 { 1312 RDN rdn1 = rdns.get( i - 1 ); 1313 RDN rdn2 = dn.rdns.get( i - 1 ); 1314 int res = rdn1.compareTo( rdn2 ); 1315 1316 if ( res != 0 ) 1317 { 1318 return res; 1319 } 1320 } 1321 1322 return EQUAL; 1323 } 1324 1325 1326 private static AVA atavOidToName( AVA atav, Map<String, OidNormalizer> oidsMap ) 1327 throws LdapInvalidDnException 1328 { 1329 String type = StringTools.trim( atav.getNormType() ); 1330 1331 if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) ) 1332 { 1333 type = type.substring( 4 ); 1334 } 1335 1336 if ( StringTools.isNotEmpty( type ) ) 1337 { 1338 if ( oidsMap == null ) 1339 { 1340 return atav; 1341 } 1342 else 1343 { 1344 OidNormalizer oidNormalizer = oidsMap.get( type.toLowerCase() ); 1345 1346 if ( oidNormalizer != null ) 1347 { 1348 try 1349 { 1350 return new AVA( 1351 atav.getUpType(), 1352 oidNormalizer.getAttributeTypeOid(), 1353 atav.getUpValue(), 1354 oidNormalizer.getNormalizer().normalize( atav.getNormValue() ), 1355 atav.getUpName() ); 1356 } 1357 catch ( LdapException le ) 1358 { 1359 throw new LdapInvalidDnException( le.getMessage() ); 1360 } 1361 } 1362 else 1363 { 1364 // We don't have a normalizer for this OID : just do nothing. 1365 return atav; 1366 } 1367 } 1368 } 1369 else 1370 { 1371 // The type is empty : this is not possible... 1372 LOG.error( I18n.err( I18n.ERR_04209 ) ); 1373 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04209 ) ); 1374 } 1375 } 1376 1377 1378 /** 1379 * Transform a RDN by changing the value to its OID counterpart and 1380 * normalizing the value accordingly to its type. 1381 * 1382 * @param rdn The RDN to modify. 1383 * @param oidsMap The map of all existing oids and normalizer. 1384 * @throws LdapInvalidDnException If the RDN is invalid. 1385 */ 1386 /** No qualifier */ static void rdnOidToName( RDN rdn, Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException 1387 { 1388 if ( rdn.getNbAtavs() > 1 ) 1389 { 1390 // We have more than one ATAV for this RDN. We will loop on all 1391 // ATAVs 1392 RDN rdnCopy = ( RDN ) rdn.clone(); 1393 rdn.clear(); 1394 1395 for ( AVA val:rdnCopy ) 1396 { 1397 AVA newAtav = atavOidToName( val, oidsMap ); 1398 rdn.addAttributeTypeAndValue( newAtav ); 1399 } 1400 } 1401 else 1402 { 1403 AVA val = rdn.getAtav(); 1404 rdn.clear(); 1405 AVA newAtav = atavOidToName( val, oidsMap ); 1406 rdn.addAttributeTypeAndValue( newAtav ); 1407 } 1408 } 1409 1410 1411 /** 1412 * Change the internal DN, using the OID instead of the first name or other 1413 * aliases. As we still have the UP name of each RDN, we will be able to 1414 * provide both representation of the DN. example : dn: 2.5.4.3=People, 1415 * dc=example, domainComponent=com will be transformed to : 2.5.4.3=People, 1416 * 0.9.2342.19200300.100.1.25=example, 0.9.2342.19200300.100.1.25=com 1417 * because 2.5.4.3 is the OID for cn and dc is the first 1418 * alias of the couple of aliases (dc, domaincomponent), which OID is 1419 * 0.9.2342.19200300.100.1.25. 1420 * This is really important do have such a representation, as 'cn' and 1421 * 'commonname' share the same OID. 1422 * 1423 * @param dn The DN to transform. 1424 * @param oidsMap The mapping between names and oids. 1425 * @return A normalized form of the DN. 1426 * @throws LdapInvalidDnException If something went wrong. 1427 */ 1428 public static DN normalize( DN dn, Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException 1429 { 1430 if ( ( dn == null ) || ( dn.size() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) ) 1431 { 1432 return dn; 1433 } 1434 1435 Enumeration<RDN> rdns = dn.getAllRdn(); 1436 1437 // Loop on all RDNs 1438 while ( rdns.hasMoreElements() ) 1439 { 1440 RDN rdn = rdns.nextElement(); 1441 String upName = rdn.getName(); 1442 rdnOidToName( rdn, oidsMap ); 1443 rdn.normalize(); 1444 rdn.setUpName( upName ); 1445 } 1446 1447 dn.normalizeInternal(); 1448 1449 dn.normalized = true; 1450 return dn; 1451 } 1452 1453 1454 /** 1455 * Change the internal DN, using the OID instead of the first name or other 1456 * aliases. As we still have the UP name of each RDN, we will be able to 1457 * provide both representation of the DN. example : dn: 2.5.4.3=People, 1458 * dc=example, domainComponent=com will be transformed to : 2.5.4.3=People, 1459 * 0.9.2342.19200300.100.1.25=example, 0.9.2342.19200300.100.1.25=com 1460 * because 2.5.4.3 is the OID for cn and dc is the first 1461 * alias of the couple of aliases (dc, domaincomponent), which OID is 1462 * 0.9.2342.19200300.100.1.25. 1463 * This is really important do have such a representation, as 'cn' and 1464 * 'commonname' share the same OID. 1465 * 1466 * @param oidsMap The mapping between names and oids. 1467 * @throws LdapInvalidDnException If something went wrong. 1468 * @return The normalized DN 1469 */ 1470 public DN normalize( Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException 1471 { 1472 if ( ( oidsMap == null ) || ( oidsMap.size() == 0 ) ) 1473 { 1474 return this; 1475 } 1476 1477 if ( size() == 0 ) 1478 { 1479 normalized = true; 1480 return this; 1481 } 1482 1483 Enumeration<RDN> localRdns = getAllRdn(); 1484 1485 // Loop on all RDNs 1486 while ( localRdns.hasMoreElements() ) 1487 { 1488 RDN rdn = localRdns.nextElement(); 1489 String localUpName = rdn.getName(); 1490 rdnOidToName( rdn, oidsMap ); 1491 rdn.normalize(); 1492 rdn.setUpName( localUpName ); 1493 } 1494 1495 normalizeInternal(); 1496 normalized = true; 1497 return this; 1498 } 1499 1500 1501 /** 1502 * Check if a DistinguishedName is syntactically valid. 1503 * 1504 * @param dn The DN to validate 1505 * @return <code>true></code> if the DN is valid, <code>false</code> 1506 * otherwise 1507 */ 1508 public static boolean isValid( String dn ) 1509 { 1510 return DnParser.validateInternal( dn ); 1511 } 1512 1513 1514 /** 1515 * Tells if the DN has already been normalized or not 1516 * 1517 * @return <code>true</code> if the DN is already normalized. 1518 */ 1519 public boolean isNormalized() 1520 { 1521 return normalized; 1522 } 1523 1524 1525 /** 1526 * @see Externalizable#readExternal(ObjectInput)<p> 1527 * 1528 * We have to store a DN data efficiently. Here is the structure : 1529 * 1530 * <li>upName</li> The User provided DN<p> 1531 * <li>normName</li> May be null if the normName is equaivalent to 1532 * the upName<p> 1533 * <li>rdns</li> The rdn's List.<p> 1534 * 1535 * for each rdn : 1536 * <li>call the RDN write method</li> 1537 * 1538 *@param out The stream in which the DN will be serialized 1539 *@throws IOException If the serialization fail 1540 */ 1541 public void writeExternal( ObjectOutput out ) throws IOException 1542 { 1543 if ( upName == null ) 1544 { 1545 String message = I18n.err( I18n.ERR_04210 ); 1546 LOG.error( message ); 1547 throw new IOException( message ); 1548 } 1549 1550 // Write the UPName 1551 out.writeUTF( upName ); 1552 1553 // Write the NormName if different 1554 if ( isNormalized() ) 1555 { 1556 if ( upName.equals( normName ) ) 1557 { 1558 out.writeUTF( "" ); 1559 } 1560 else 1561 { 1562 out.writeUTF( normName ); 1563 } 1564 } 1565 else 1566 { 1567 String message = I18n.err( I18n.ERR_04211 ); 1568 LOG.error( message ); 1569 throw new IOException( message ); 1570 } 1571 1572 // Should we store the byte[] ??? 1573 1574 // Write the RDNs. Is it's null, the number will be -1. 1575 out.writeInt( rdns.size() ); 1576 1577 // Loop on the RDNs 1578 for ( RDN rdn:rdns ) 1579 { 1580 out.writeObject( rdn ); 1581 } 1582 } 1583 1584 1585 /** 1586 * @see Externalizable#readExternal(ObjectInput) 1587 * 1588 * We read back the data to create a new DN. The structure 1589 * read is exposed in the {@link DN#writeExternal(ObjectOutput)} 1590 * method<p> 1591 * 1592 * @param in The stream from which the DN is read 1593 * @throws IOException If the stream can't be read 1594 * @throws ClassNotFoundException If the RDN can't be created 1595 */ 1596 public void readExternal( ObjectInput in ) throws IOException , ClassNotFoundException 1597 { 1598 // Read the UPName 1599 upName = in.readUTF(); 1600 1601 // Read the NormName 1602 normName = in.readUTF(); 1603 1604 if ( normName.length() == 0 ) 1605 { 1606 // As the normName is equal to the upName, 1607 // we didn't saved the nbnormName on disk. 1608 // restore it by copying the upName. 1609 normName = upName; 1610 } 1611 1612 // A serialized DN is always normalized. 1613 normalized = true; 1614 1615 // Should we read the byte[] ??? 1616 bytes = StringTools.getBytesUtf8( upName ); 1617 1618 // Read the RDNs. Is it's null, the number will be -1. 1619 int nbRdns = in.readInt(); 1620 rdns = new ArrayList<RDN>( nbRdns ); 1621 1622 for ( int i = 0; i < nbRdns; i++ ) 1623 { 1624 RDN rdn = (RDN)in.readObject(); 1625 rdns.add( rdn ); 1626 } 1627 } 1628 1629 1630 /** 1631 * Convert a {@link javax.naming.Name} to a DN 1632 * 1633 * @param name The Name to convert 1634 * @return A DN 1635 */ 1636 public static DN fromName( Name name ) 1637 { 1638 try 1639 { 1640 DN dn = new DN( name.toString() ); 1641 1642 return dn; 1643 } 1644 catch ( LdapInvalidDnException lide ) 1645 { 1646 // TODO : check if we must throw an exception. 1647 // Logically, the Name must be valid. 1648 return null; 1649 } 1650 } 1651 1652 1653 /** 1654 * Convert a DN to a {@link javax.naming.Name} 1655 * 1656 * @param name The DN to convert 1657 * @return A Name 1658 */ 1659 public static Name toName( DN dn ) 1660 { 1661 try 1662 { 1663 Name name = new LdapName( dn.toString() ); 1664 1665 return name; 1666 } 1667 catch ( InvalidNameException ine ) 1668 { 1669 // TODO : check if we must throw an exception. 1670 // Logically, the DN must be valid. 1671 return null; 1672 } 1673 } 1674 1675 1676 /** 1677 * {@inheritDoc} 1678 */ 1679 public Iterator<RDN> iterator() 1680 { 1681 return rdns.iterator(); 1682 } 1683 }