View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  
21  package org.apache.directory.api.ldap.model.name;
22  
23  
24  import java.io.Externalizable;
25  import java.io.IOException;
26  import java.io.ObjectInput;
27  import java.io.ObjectOutput;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.TreeSet;
36  
37  import org.apache.commons.collections.list.UnmodifiableList;
38  import org.apache.directory.api.i18n.I18n;
39  import org.apache.directory.api.ldap.model.entry.BinaryValue;
40  import org.apache.directory.api.ldap.model.entry.StringValue;
41  import org.apache.directory.api.ldap.model.entry.Value;
42  import org.apache.directory.api.ldap.model.exception.LdapException;
43  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
44  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
45  import org.apache.directory.api.ldap.model.schema.AttributeType;
46  import org.apache.directory.api.ldap.model.schema.SchemaManager;
47  import org.apache.directory.api.ldap.model.schema.normalizers.OidNormalizer;
48  import org.apache.directory.api.util.Strings;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  
53  /**
54   * The Dn class contains a Dn (Distinguished Name). This class is immutable.
55   * <br>
56   * Its specification can be found in RFC 2253,
57   * "UTF-8 String Representation of Distinguished Names".
58   * <br>
59   * We will store two representation of a Dn :
60   * <ul>
61   * <li>a user Provider representation, which is the parsed String given by a user</li>
62   * <li>an internal representation.</li>
63   * </ul>
64   *
65   * A Dn is formed of RDNs, in a specific order :<br>
66   *  Rdn[n], Rdn[n-1], ... Rdn[1], Rdn[0]<br>
67   *
68   * It represents a position in a hierarchy, in which the root is the last Rdn (Rdn[0]) and the leaf
69   * is the first Rdn (Rdn[n]).
70   *
71   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
72   */
73  public class Dn implements Iterable<Rdn>, Externalizable
74  {
75      /** The LoggerFactory used by this class */
76      protected static final Logger LOG = LoggerFactory.getLogger( Dn.class );
77  
78      /**
79       * Declares the Serial Version Uid.
80       *
81       * @see <a
82       *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
83       *      Declare Serial Version Uid</a>
84       */
85      private static final long serialVersionUID = 1L;
86  
87      /** Value returned by the compareTo method if values are not equals */
88      public static final int NOT_EQUAL = -1;
89  
90      /** Value returned by the compareTo method if values are equals */
91      public static final int EQUAL = 0;
92  
93      /**
94       *  The RDNs that are elements of the Dn<br>
95       * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!<br>
96       * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0)
97       * <br>
98       * For instance,if the Dn is "dc=c, dc=b, dc=a", then the RDNs are stored as :
99       * <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 }