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.Collections;
024    import java.util.HashSet;
025    import java.util.Iterator;
026    import java.util.Map;
027    import java.util.Set;
028    import java.util.concurrent.ConcurrentHashMap;
029    
030    import javax.naming.NamingException;
031    import javax.naming.directory.NoSuchAttributeException;
032    
033    import org.apache.directory.shared.asn1.primitives.OID;
034    import org.apache.directory.shared.ldap.schema.AttributeType;
035    import org.apache.directory.shared.ldap.schema.MatchingRule;
036    import org.apache.directory.shared.ldap.schema.normalizers.NoOpNormalizer;
037    import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer;
038    
039    import org.slf4j.Logger;
040    import org.slf4j.LoggerFactory;
041    
042    
043    /**
044     * A plain old java object implementation of an AttributeTypeRegistry.
045     *
046     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047     * @version $Rev: 778529 $
048     */
049    public class DefaultAttributeTypeRegistry implements AttributeTypeRegistry
050    {
051        /** static class logger */
052        private static final Logger LOG = LoggerFactory.getLogger( DefaultAttributeTypeRegistry.class );
053    
054        /** Speedup for DEBUG mode */
055        private static final boolean IS_DEBUG = LOG.isDebugEnabled();
056        
057        /** maps an OID to an AttributeType */
058        private final Map<String,AttributeType> byOidAT;
059        
060        /** maps OIDs to a Set of descendants for that OID */
061        private final Map<String,Set<AttributeType>> oidToDescendantSet;
062        
063        /** the Registry used to resolve names to OIDs */
064        private final OidRegistry oidRegistry;
065        
066        /** cached Oid/normalizer mapping */
067        private transient Map<String, OidNormalizer> oidNormalizerMap;
068        
069    
070        // ------------------------------------------------------------------------
071        // C O N S T R U C T O R S
072        // ------------------------------------------------------------------------
073        /**
074         * Creates an empty DefaultAttributeTypeRegistry.
075         *
076         * @param oidRegistry used by this registry for OID to name resolution of
077         * dependencies and to automatically register and unregister it's aliases and OIDs
078         */
079        public DefaultAttributeTypeRegistry( OidRegistry oidRegistry )
080        {
081            byOidAT = new ConcurrentHashMap<String,AttributeType>();
082            oidToDescendantSet= new ConcurrentHashMap<String,Set<AttributeType>>();
083            oidNormalizerMap = new ConcurrentHashMap<String, OidNormalizer>();
084            this.oidRegistry = oidRegistry;
085        }
086    
087    
088        // ------------------------------------------------------------------------
089        // Service Methods
090        // ------------------------------------------------------------------------
091        /**
092         * {@inheritDoc}
093         */
094        public void register( AttributeType attributeType ) throws NamingException
095        {
096            if ( byOidAT.containsKey( attributeType.getOid() ) )
097            {
098                String msg = "attributeType " + attributeType.getName() + " w/ OID " + attributeType.getOid()
099                + " has already been registered!";
100                LOG.error( msg );
101                throw new NamingException( msg );
102            }
103    
104            // First, register the AttributeType names and oid in the global
105            // OidRegistry
106            String[] names = attributeType.getNamesRef();
107            String oid = attributeType.getOid();
108            
109            for ( String name : names )
110            {
111                oidRegistry.register( name, oid );
112            }
113            
114            // Also register the oid/oid relation
115            oidRegistry.register( oid, oid );
116    
117            // Inject the attributeType into the oid/normalizer map
118            addMappingFor( attributeType );
119    
120            // Register this AttributeType into the Descendant map
121            registerDescendants( attributeType );
122            
123            // Internally associate the OID to the registered AttributeType
124            byOidAT.put( attributeType.getOid(), attributeType );
125            
126            if ( IS_DEBUG )
127            {
128                LOG.debug( "registred attributeType: {}", attributeType );
129            }
130        }
131    
132    
133        /**
134         * Store the AttributeType into a map associating an AttributeType to its
135         * descendants.
136         * 
137         * @param attributeType The attributeType to register
138         * @throws NamingException If something went wrong
139         */
140        public void registerDescendants( AttributeType attributeType ) throws NamingException
141        {
142            // add this attribute to descendant list of other attributes in superior chain
143            onRegisterAddToAncestorDescendants( attributeType, attributeType.getSuperior() );
144        }
145        
146        
147        /**
148         * Recursively adds a new attributeType to the descendant's list of all ancestors
149         * until top is reached.  Top will not have the new type added.
150         * 
151         * @param newType the new attributeType being added
152         * @param ancestor some ancestor from superior up to and including top
153         * @throws NamingException if there are resolution failures
154         */
155        protected void onRegisterAddToAncestorDescendants( AttributeType newType, AttributeType ancestor ) 
156            throws NamingException
157        {
158            if ( ancestor == null )
159            {
160                return;
161            }
162            
163            // Get the ancestor's descendant, if any
164            Set<AttributeType> descendants = oidToDescendantSet.get( ancestor.getOid() );
165    
166            // Initialize the descendant Set to store the descendants for the attributeType
167            if ( descendants == null )
168            {
169                descendants = new HashSet<AttributeType>( 1 );
170                oidToDescendantSet.put( ancestor.getOid(), descendants );
171            }
172            
173            // Add the current type as a descendant
174            descendants.add( newType );
175            
176            // And recurse until we reach the top of the hierarchy
177            onRegisterAddToAncestorDescendants( newType, ancestor.getSuperior() );
178        }
179        
180    
181        /**
182         * {@inheritDoc}
183         */
184        public AttributeType lookup( String id ) throws NamingException
185        {
186            String oid = oidRegistry.getOid( id );
187            AttributeType attributeType = byOidAT.get( oid );
188    
189            if ( attributeType == null )
190            {
191                String msg = "attributeType w/ OID " + oid + " not registered!";
192                LOG.error( msg );
193                throw new NoSuchAttributeException( msg );
194            }
195    
196            if ( IS_DEBUG )
197            {
198                LOG.debug( "lookup with id '{}' for attributeType: {}", oid, attributeType );
199            }
200            
201            return attributeType;
202        }
203    
204    
205        /**
206         * {@inheritDoc}
207         */
208        public boolean hasAttributeType( String id )
209        {
210            try
211            {
212                String oid = oidRegistry.getOid( id );
213                
214                if ( oid != null )
215                {
216                    return byOidAT.containsKey( oid );
217                }
218                
219                return false;
220            }
221            catch ( NamingException e )
222            {
223                return false;
224            }
225        }
226    
227    
228        /**
229         * {@inheritDoc}
230         */
231        public String getSchemaName( String id ) throws NamingException
232        {
233            AttributeType at = byOidAT.get( oidRegistry.getOid( id ) );
234            
235            if ( at != null )
236            {
237                return at.getSchema();
238            }
239    
240            String msg = "OID " + id + " not found in oid to " + "AttributeType map!";
241            LOG.debug( msg );
242            throw new NamingException( msg );
243        }
244    
245    
246        /**
247         * Remove the AttributeType normalizer from the OidNormalizer map 
248         */
249        private void removeMappingFor( AttributeType type ) throws NamingException
250        {
251            if ( type == null )
252            {
253                return;
254            }
255            
256            oidNormalizerMap.remove( type.getOid() );
257    
258            // We also have to remove all the short names for this attribute
259            String[] aliases = type.getNamesRef();
260            
261            for ( String aliase : aliases )
262            {
263                oidNormalizerMap.remove( aliase );
264                oidNormalizerMap.remove( aliase.toLowerCase() );
265            }
266        }
267    
268    
269        /**
270         * Add a new Oid/Normalizer couple in the OidNormalizer map
271         */
272        private void addMappingFor( AttributeType type ) throws NamingException
273        {
274            MatchingRule matchingRule = type.getEquality();
275            OidNormalizer oidNormalizer;
276            String oid = type.getOid();
277    
278            if ( matchingRule == null )
279            {
280                LOG.debug( "Attribute {} does not have normalizer : using NoopNormalizer", type.getName() );
281                oidNormalizer = new OidNormalizer( oid, new NoOpNormalizer() );
282            }
283            else
284            {
285                oidNormalizer = new OidNormalizer( oid, matchingRule.getNormalizer() );
286            }
287    
288            oidNormalizerMap.put( oid, oidNormalizer );
289            
290            // Also inject the attributeType's short nampes in the map
291            String[] aliases = type.getNamesRef();
292            
293            for ( String aliase : aliases )
294            {
295                oidNormalizerMap.put( aliase, oidNormalizer );
296                oidNormalizerMap.put( aliase.toLowerCase(), oidNormalizer );
297            }
298        }
299    
300        
301        /**
302         * {@inheritDoc}
303         */
304        public Map<String,OidNormalizer> getNormalizerMapping() throws NamingException
305        {
306            return Collections.unmodifiableMap( oidNormalizerMap );
307        }
308    
309    
310        /**
311         * {@inheritDoc}
312         */
313        public Iterator<AttributeType> descendants( String ancestorId ) throws NamingException
314        {
315            String oid = oidRegistry.getOid( ancestorId );
316            Set<AttributeType> descendants = oidToDescendantSet.get( oid );
317            
318            if ( descendants == null )
319            {
320                return Collections.EMPTY_SET.iterator();
321            }
322            
323            return descendants.iterator();
324        }
325    
326    
327        /**
328         * {@inheritDoc}
329         */
330        public boolean hasDescendants( String ancestorId ) throws NamingException
331        {
332            String oid = oidRegistry.getOid( ancestorId );
333            Set<AttributeType> descendants = oidToDescendantSet.get( oid );
334            return (descendants != null) && !descendants.isEmpty();
335        }
336    
337    
338        /**
339         * {@inheritDoc}
340         */
341        public Iterator<AttributeType> iterator()
342        {
343            return byOidAT.values().iterator();
344        }
345        
346        
347        /**
348         * {@inheritDoc}
349         */
350        public void unregister( String numericOid ) throws NamingException
351        {
352            if ( ! OID.isOID( numericOid ) )
353            {
354                String msg = "Looks like the arg " + numericOid + " is not a numeric OID";
355                LOG.error(msg );
356                throw new NamingException( msg );
357            }
358    
359            removeMappingFor( byOidAT.get( numericOid ));
360            byOidAT.remove( numericOid );
361            oidToDescendantSet.remove( numericOid );
362        }
363        
364        
365        public String toString()
366        {
367            StringBuilder sb = new StringBuilder();
368            
369            for ( String value:byOidAT.keySet() )
370            {
371                sb.append( value ).append( ":" ).append( byOidAT.get( value ) ).append( '\n' );
372            }
373            
374            return sb.toString();
375        }
376    }