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    package org.apache.directory.shared.ldap.util;
021    
022    
023    import org.apache.directory.shared.ldap.name.LdapDN;
024    
025    import java.util.ArrayList;
026    import java.util.List;
027    
028    import javax.naming.Name;
029    import javax.naming.NamingException;
030    
031    
032    /**
033     * Tools dealing with common Naming operations.
034     * 
035     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
036     * @version $Revision: 649846 $
037     */
038    public class NamespaceTools
039    {
040        private static final String[] EMPTY_STRING_ARRAY = new String[0];
041    
042        
043        /**
044         * Gets the attribute of a single attribute rdn or name component.
045         * 
046         * @param rdn the name component
047         * @return the attribute name TODO the name rdn is misused rename refactor
048         *         this method
049         */
050        public static String getRdnAttribute( String rdn )
051        {
052            int index = rdn.indexOf( '=' );
053            return rdn.substring( 0, index );
054        }
055    
056    
057        /**
058         * Gets the value of a single name component of a distinguished name.
059         * 
060         * @param rdn the name component to get the value from
061         * @return the value of the single name component TODO the name rdn is
062         *         misused rename refactor this method
063         */
064        public static String getRdnValue( String rdn )
065        {
066            int index = rdn.indexOf( '=' );
067            return rdn.substring( index + 1, rdn.length() );
068        }
069    
070    
071        /**
072         * Checks to see if two names are siblings.
073         * 
074         * @param name1 the first name
075         * @param name2 the second name
076         * @return true if the names are siblings, false otherwise.
077         */
078        public static boolean isSibling( Name name1, Name name2 ) throws NamingException
079        {
080            if ( name1.size() == name2.size() )
081            {
082                LdapDN parentDn = ( LdapDN ) name1.clone();
083                parentDn.remove( name1.size() - 1 );
084                return name2.startsWith( parentDn );
085            }
086    
087            return false;
088        }
089    
090    
091        /**
092         * Tests to see if a candidate entry is a descendant of a base.
093         * 
094         * @param ancestor the base ancestor
095         * @param descendant the candidate to test for descendancy
096         * @return true if the candidate is a descendant
097         */
098        public static boolean isDescendant( Name ancestor, Name descendant )
099        {
100            return descendant.startsWith( ancestor );
101        }
102    
103    
104        /**
105         * Gets the relative name between an ancestor and a potential descendant.
106         * Both name arguments must be normalized. The returned name is also
107         * normalized.
108         * 
109         * @param ancestor the normalized distinguished name of the ancestor context
110         * @param descendant the normalized distinguished name of the descendant context
111         * @return the relatve normalized name between the ancestor and the
112         *         descendant contexts
113         * @throws javax.naming.NamingException if the contexts are not related in the ancestual sense
114         */
115        public static Name getRelativeName( Name ancestor, Name descendant ) throws NamingException
116        {
117            LdapDN rdn = null;
118            
119            if ( descendant instanceof LdapDN )
120            {
121                rdn = ( LdapDN ) descendant.clone();
122            }
123            else
124            {
125                rdn = new LdapDN( descendant.toString() );
126            }
127    
128            if ( rdn.startsWith( ancestor ) )
129            {
130                for ( int ii = 0; ii < ancestor.size(); ii++ )
131                {
132                    rdn.remove( 0 );
133                }
134            }
135            else
136            {
137                NamingException e = new NamingException( descendant + " is not ancestually related to context:" + ancestor );
138    
139                throw e;
140            }
141    
142            return rdn;
143        }
144    
145    
146        /**
147         * Uses the algorithm in <a href="http://www.faqs.org/rfcs/rfc2247.html">RFC
148         * 2247</a> to infer an LDAP name from a Kerberos realm name or a DNS
149         * domain name.
150         * 
151         * @param realm the realm or domain name
152         * @return the LDAP name for the realm or domain
153         */
154        public static String inferLdapName( String realm )
155        {
156            if ( StringTools.isEmpty( realm ) )
157            {
158                return "";
159            }
160    
161            StringBuffer buf = new StringBuffer( realm.length() );
162            buf.append( "dc=" );
163    
164            int start = 0, end = 0;
165    
166            // Replace all the '.' by ",dc=". The comma is added because
167            // the string is not supposed to start with a dot, so another
168            // dc=XXXX already exists in any cases.
169            // The realm is also not supposed to finish with a '.'
170            while ( ( end = realm.indexOf( '.', start ) ) != -1 )
171            {
172                buf.append( realm.substring( start, end ) ).append( ",dc=" );
173                start = end + 1;
174    
175            }
176    
177            buf.append( realm.substring( start ) );
178            return buf.toString();
179        }
180    
181    
182        /**
183         * Gets the '+' appended components of a composite name component.
184         * 
185         * @param compositeNameComponent a single name component not a whole name
186         * @return the components of the complex name component in order
187         * @throws NamingException
188         *             if nameComponent is invalid (starts with a +)
189         */
190        public static String[] getCompositeComponents( String compositeNameComponent ) throws NamingException
191        {
192            int lastIndex = compositeNameComponent.length() - 1;
193            List<String> comps = new ArrayList<String>();
194    
195            for ( int ii = compositeNameComponent.length() - 1; ii >= 0; ii-- )
196            {
197                if ( compositeNameComponent.charAt( ii ) == '+' )
198                {
199                    if ( ii == 0 )
200                    {
201                        throw new NamingException( "invalid name - a name cannot start with a '+': "
202                            + compositeNameComponent );
203                    }
204                    
205                    if ( compositeNameComponent.charAt( ii - 1 ) != '\\' )
206                    {
207                        if ( lastIndex == compositeNameComponent.length() - 1 )
208                        {
209                            comps.add( 0, compositeNameComponent.substring( ii + 1, lastIndex + 1 ) );
210                        }
211                        else
212                        {
213                            comps.add( 0, compositeNameComponent.substring( ii + 1, lastIndex ) );
214                        }
215    
216                        lastIndex = ii;
217                    }
218                }
219                
220                if ( ii == 0 )
221                {
222                    if ( lastIndex == compositeNameComponent.length() - 1 )
223                    {
224                        comps.add( 0, compositeNameComponent );
225                    }
226                    else
227                    {
228                        comps.add( 0, compositeNameComponent.substring( ii, lastIndex ) );
229                    }
230    
231                    lastIndex = 0;
232                }
233            }
234    
235            if ( comps.size() == 0 )
236            {
237                comps.add( compositeNameComponent );
238            }
239    
240            return comps.toArray( EMPTY_STRING_ARRAY );
241        }
242    
243    
244        /**
245         * Checks to see if a name has name complex name components in it.
246         * 
247         * @param name The name to check 
248         * @return <code>true</code> if the name has composite components
249         * @throws NamingException If the name is invalid
250         */
251        public static boolean hasCompositeComponents( String name ) throws NamingException
252        {
253            for ( int ii = name.length() - 1; ii >= 0; ii-- )
254            {
255                if ( name.charAt( ii ) == '+' )
256                {
257                    if ( ii == 0 )
258                    {
259                        throw new NamingException( "invalid name - a name cannot start with a '+': " + name );
260                    }
261                    if ( name.charAt( ii - 1 ) != '\\' )
262                    {
263                        return true;
264                    }
265                }
266            }
267    
268            return false;
269        }
270    }