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