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