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.server.schema.registries;
021    
022    
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.concurrent.ConcurrentHashMap;
029    
030    import javax.naming.NamingException;
031    import javax.naming.directory.NoSuchAttributeException;
032    
033    import org.slf4j.Logger;
034    import org.slf4j.LoggerFactory;
035    import org.apache.directory.shared.asn1.primitives.OID;
036    import org.apache.directory.shared.ldap.util.StringTools;
037    
038    
039    /**
040     * Default OID registry implementation used to resolve a schema object OID 
041     * to a name and vice-versa. 
042     * <br/>
043     * We are storing the schema elements in two data structures :
044     * <li>an oid to names map</li>
045     * <li>a name to oid map</li>
046     * <br/>
047     * The first data structure contains a list of names associated with the given
048     * oid. The oid itself is not necessarily stored in this list, unless the schema
049     * object does not have any name.<br/>
050     * The second data structure contains all the names with the associated OID. We 
051     * also store the oid -> oid relation, to allow us to look for a registered 
052     * schema object using its oid.<br/>
053     * <br/>
054     * For instance, if we have registered the C AttributeType, which OID is
055     * 2.5.4.6 and has another name, 'country', the data structure will contain :<br>
056     * 
057     * <ul>
058     * <li>
059     * byOid
060     *   <ul>
061     *     <li>2.5.4.6 -> {'c', 'country'}</li>
062     *   </ul>
063     * </li>
064     * <li>
065     * byName
066     *   <ul>
067     *     <li>'c' -> 2.5.4.6</li>
068     *     <li>'country' -> 2.5.4.6</li>
069     *     <li>'2.5.4.6' -> 2.5.4.6</li>
070     *   </ul>
071     * </li>
072     * </ul>
073     * 
074     * 
075     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
076     * @version $Rev: 777089 $
077     */
078    public class DefaultOidRegistry implements OidRegistry
079    {
080        /** static class logger */
081        private static final Logger LOG = LoggerFactory.getLogger( DefaultOidRegistry.class );
082    
083        /** Speedup for DEBUG mode */
084        private static final boolean IS_DEBUG = LOG.isDebugEnabled();
085        
086        /** Maps OID to a name or a list of names if more than one name exists */
087        private Map<String, List<String>> byOid = new ConcurrentHashMap<String, List<String>>();
088        
089        /** Maps several names to an OID */
090        private Map<String,String> byName = new ConcurrentHashMap<String,String>();
091    
092    
093        /**
094         * {@inheritDoc}
095         */
096        public String getOid( String name ) throws NamingException
097        {
098            if ( StringTools.isEmpty( name ) )
099            {
100                throw new NamingException( "name should not be empty" );
101            }
102            
103            /* If name is an OID then we return it back since inherently the
104             * OID is another name for the object referred to by OID and the
105             * caller does not know that the argument is an OID String.
106             */
107            if ( StringTools.isDigit( name.charAt( 0 ) ) )
108            {
109                return name;
110            }
111    
112            // If name is mapped to a OID already return OID
113            if ( byName.containsKey( name ) )
114            {
115                String oid = byName.get( name );
116                
117                if ( IS_DEBUG )
118                {
119                    LOG.debug( "looked up OID '" + oid + "' with id '" + name + "'" );
120                }
121                
122                return oid;
123            }
124    
125            /*
126             * As a last resort we check if name is not normalized and if the
127             * normalized version used as a key returns an OID.  If the normalized
128             * name works add the normalized name as a key with its OID to the
129             * byName lookup.  BTW these normalized versions of the key are not
130             * returned on a getNameSet.
131             */
132            String lowerCase = name.trim().toLowerCase();
133            
134            String oid = byName.get( lowerCase );
135            
136            if ( oid != null )
137            {
138                if ( IS_DEBUG )
139                {
140                    LOG.debug( "looked up OID '{}' with id '{}'", oid, name );
141                }
142    
143                return oid;
144            }
145    
146            NamingException fault = new NoSuchAttributeException( "OID for name '" + name + "' was not "
147                + "found within the OID registry" );
148            LOG.warn( fault.getMessage() );
149            throw fault;
150        }
151    
152    
153        /**
154         * {@inheritDoc}
155         */
156        public boolean hasOid( String name )
157        {
158            if ( StringTools.isEmpty( name ) )
159            {
160                return false;
161            }
162            
163            String normalized = name.trim().toLowerCase();
164            
165            return byName.containsKey( normalized );
166        }
167    
168    
169        /**
170         * {@inheritDoc}
171         */
172        public String getPrimaryName( String oid ) throws NamingException
173        {
174            List<String> value = byOid.get( oid );
175    
176            if ( null == value )
177            {
178                String msg = "OID '" + oid + "' was not found within the OID registry";
179                LOG.error( msg );
180                throw new NamingException( msg );
181            }
182    
183            String name = value.get( 0 );
184            
185            if ( IS_DEBUG )
186            {
187                LOG.debug( "looked up primary name '{}' with OID '{}'", name, oid );
188            }
189            
190            return name;
191        }
192    
193    
194        /**
195         * {@inheritDoc}
196         */
197        public List<String> getNameSet( String oid ) throws NamingException
198        {
199            List<String> value = byOid.get( oid );
200    
201            if ( null == value )
202            {
203                String msg = "OID '" + oid + "' was not found within the OID registry";
204                LOG.error( msg );
205                throw new NamingException( msg );
206            }
207    
208            if ( IS_DEBUG )
209            {
210                LOG.debug( "looked up names '{}' for OID '{}'", value, oid );
211            }
212            
213            return value;
214        }
215    
216    
217        /**
218         * {@inheritDoc}
219         */
220        public Iterator<String> list()
221        {
222            return Collections.unmodifiableSet( byOid.keySet() ).iterator();
223        }
224    
225    
226        /**
227         * {@inheritDoc}
228         */
229        public Map<String, String> getOidByName()
230        {
231            return byName;
232        }
233    
234    
235        /**
236         * {@inheritDoc}
237         */
238        public Map<String, List<String>> getNameByOid()
239        {
240            return byOid;
241        }
242    
243    
244        /**
245         * {@inheritDoc}
246         */
247        public void register( String name, String oid ) throws NamingException
248        {
249            if ( !OID.isOID( oid ) )
250            {
251                String message = "Swap the parameter order: the oid " + 
252                "does not start with a digit, or is not an OID!";
253                
254                LOG.debug( message );
255                throw new NamingException( message );
256            }
257            
258            if ( StringTools.isEmpty( name ) )
259            {
260                String message = "The name is empty for OID " + oid;
261                LOG.error( message );
262                throw new NamingException( message );
263            }
264    
265            /*
266             * Add the entry for the given name as is and its lowercased version if
267             * the lower cased name is different from the given name name.  
268             */
269            String lowerCase = name.trim().toLowerCase();
270    
271            // Put both the name and the oid as names
272            byName.put( lowerCase, oid );
273            byName.put( oid, oid );
274    
275            /*
276             * Update OID Map
277             * 
278             * 1). Check if we already have a value[s] stored
279             *      1a). Value is a single value and is a String
280             *          Replace value with list containing old and new values
281             *      1b). More than one value stored in a list
282             *          Add new value to the list
283             * 2). If we do not have a value then we just add it as a String
284             */
285            List<String> value = byOid.get( oid );
286            
287            if ( value == null )
288            {
289                value = new ArrayList<String>( 1 );
290                value.add( lowerCase );
291            }
292            else
293            {
294                if ( value.contains( lowerCase ) )
295                {
296                    return;
297                }
298    
299                value.add( lowerCase );
300            }
301    
302            byOid.put( oid, value );
303            
304            if ( IS_DEBUG )
305            {
306                LOG.debug( "registed name '" + name + "' with OID: " + oid );
307            }
308        }
309    
310    
311        /**
312         * {@inheritDoc}
313         */
314        public void unregister( String numericOid ) throws NamingException
315        {
316            // First, remove the <OID, names> from the byOID map
317            List<String> names = byOid.remove( numericOid );
318            
319            // Then remove all the <name, OID> from the byName map
320            if ( names != null )
321            {
322                for ( String name:names )
323                {
324                    byName.remove( name );
325                }
326            }
327    
328            // Last, remove the <OID, OID> from the byName map
329            byName.remove( numericOid );
330            
331            if ( IS_DEBUG )
332            {
333                LOG.debug( "Unregisted name '{}' with OID: {}", StringTools.listToString( names ), numericOid );
334            }
335        }
336    }