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.bootstrap;
021    
022    
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.Comparator;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Properties;
031    import java.util.Stack;
032    
033    import javax.naming.NamingException;
034    
035    import org.apache.directory.server.constants.MetaSchemaConstants;
036    import org.apache.directory.server.schema.bootstrap.SystemSchema;
037    import org.apache.directory.server.schema.bootstrap.BootstrapSchema;
038    import org.apache.directory.server.schema.bootstrap.ProducerTypeEnum;
039    import org.apache.directory.server.schema.bootstrap.AbstractBootstrapProducer.BootstrapAttributeType;
040    import org.apache.directory.server.schema.bootstrap.AbstractBootstrapProducer.BootstrapMatchingRule;
041    import org.apache.directory.server.schema.bootstrap.AbstractBootstrapProducer.BootstrapObjectClass;
042    import org.apache.directory.server.schema.bootstrap.AbstractBootstrapProducer.BootstrapSyntax;
043    import org.apache.directory.server.schema.registries.AbstractSchemaLoader;
044    import org.apache.directory.server.schema.registries.ComparatorRegistry;
045    import org.apache.directory.server.schema.registries.DefaultRegistries;
046    import org.apache.directory.server.schema.registries.MatchingRuleRegistry;
047    import org.apache.directory.server.schema.registries.NormalizerRegistry;
048    import org.apache.directory.server.schema.registries.ObjectClassRegistry;
049    import org.apache.directory.server.schema.registries.Registries;
050    import org.apache.directory.server.schema.registries.SyntaxCheckerRegistry;
051    import org.apache.directory.server.schema.registries.SyntaxRegistry;
052    import org.apache.directory.shared.ldap.schema.AttributeType;
053    import org.apache.directory.shared.ldap.schema.MatchingRule;
054    import org.apache.directory.shared.ldap.schema.Normalizer;
055    import org.apache.directory.shared.ldap.schema.ObjectClass;
056    import org.apache.directory.shared.ldap.schema.Syntax;
057    import org.apache.directory.shared.ldap.schema.SyntaxChecker;
058    import org.apache.directory.shared.ldap.schema.parsers.ComparatorDescription;
059    import org.apache.directory.shared.ldap.schema.parsers.NormalizerDescription;
060    import org.apache.directory.shared.ldap.schema.parsers.SyntaxCheckerDescription;
061    
062    import org.slf4j.Logger;
063    import org.slf4j.LoggerFactory;
064    
065    
066    /**
067     * Class which handles bootstrap schema class file loading.
068     *
069     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
070     * @version $Rev: 780471 $
071     */
072    public class BootstrapSchemaLoader extends AbstractSchemaLoader
073    {
074        private static final Logger log = LoggerFactory.getLogger( BootstrapSchemaLoader.class );
075    
076        private ClassLoader cl = getClass().getClassLoader();
077    
078        /** stores schemas of producers for callback access */
079        private ThreadLocal<BootstrapSchema> schemas;
080        
081        /** stores registries associated with producers for callback access */
082        private ThreadLocal<Registries> registries;
083        
084        /** the callback that just calls register() */
085        private final ProducerCallback cb = new ProducerCallback()
086        {
087            public void schemaObjectProduced( BootstrapProducer producer, String registryKey, Object schemaObject )
088                throws NamingException
089            {
090                register( producer.getType(), registryKey, schemaObject );
091            }
092        };
093    
094    
095        /**
096         * Creates a BootstrapSchema loader.
097         */
098        public BootstrapSchemaLoader()
099        {
100            schemas = new ThreadLocal<BootstrapSchema>();
101            registries = new ThreadLocal<Registries>();
102        }
103    
104    
105        public BootstrapSchemaLoader( ClassLoader cl )
106        {
107            this();
108            this.cl = cl;
109        }
110    
111        public final void loadWithDependencies( Schema schema, Registries registries ) throws Exception
112        {
113            if ( ! ( schema instanceof BootstrapSchema ) )
114            {
115                throw new NamingException( "Expecting schema to be of sub-type BootstrapSchema" );
116            }
117            
118            Map<String, Schema> notLoaded = new HashMap<String, Schema>();
119            notLoaded.put( schema.getSchemaName(), schema );
120            Properties props = new Properties();
121            props.put( "package", ( ( BootstrapSchema ) schema ).getPackageName() );
122            loadDepsFirst( schema, new Stack<String>(), notLoaded, schema, registries, props );
123        }
124    
125        
126        /**
127         * Loads a set of schemas by loading and running all producers for each
128         * dependent schema first.
129         *
130         * @param bootstrapSchemas Collection of {@link BootstrapSchema}s to load
131         * @param registries the registries to fill with producer created objects
132         * @throws NamingException if there are any failures during this process
133         */
134        public final void loadWithDependencies( Collection<Schema> bootstrapSchemas, Registries registries ) throws Exception
135        {
136            BootstrapSchema[] schemas = new BootstrapSchema[bootstrapSchemas.size()];
137            schemas = bootstrapSchemas.toArray( schemas );
138            HashMap<String,Schema> loaded = new HashMap<String,Schema>();
139            HashMap<String,Schema> notLoaded = new HashMap<String,Schema>();
140    
141            for ( BootstrapSchema schema:schemas )
142            {
143                notLoaded.put( schema.getSchemaName(), schema );
144            }
145    
146            BootstrapSchema schema;
147    
148            // Create system schema and kick it off by loading system which
149            // will never depend on anything.
150            schema = new SystemSchema();
151            load( schema, registries, false );
152            notLoaded.remove( schema.getSchemaName() ); // Remove if user specified it.
153            loaded.put( schema.getSchemaName(), schema );
154    
155            Iterator<Schema> list = notLoaded.values().iterator();
156            
157            while ( list.hasNext() )
158            {
159                schema = ( BootstrapSchema ) list.next();
160                Properties props = new Properties();
161                props.put( "package", schema.getPackageName() );
162                loadDepsFirst( schema, new Stack<String>(), notLoaded, schema, registries, props );
163                list = notLoaded.values().iterator();
164            }
165        }
166    
167    
168        /**
169         * Loads a schema by loading and running all producers for the schema.
170         *
171         * @param schema the schema to load
172         * @param registries the registries to fill with producer created objects
173         * @throws NamingException if there are any failures during this process
174         */
175        public final void load( Schema schema, Registries registries, boolean isDepLoad ) throws NamingException
176        {
177            if ( registries.getLoadedSchemas().containsKey( schema.getSchemaName() ) )
178            {
179                return;
180            }
181            
182            if ( ! ( schema instanceof BootstrapSchema ) )
183            {
184                throw new NamingException( "Expecting schema to be of sub-type BootstrapSchema" );
185            }
186            
187            this.registries.set( registries );
188            this.schemas.set( ( BootstrapSchema ) schema );
189    
190            try
191            {
192                for ( ProducerTypeEnum producerType:ProducerTypeEnum.getList() )
193                {
194                    BootstrapProducer producer = getProducer( ( BootstrapSchema ) schema, producerType.getName() );
195                    producer.produce( registries, cb );
196                }
197            }
198            finally
199            {
200                // Don't forget to release the ThreadLocal variables when done !
201                this.registries.set( null );
202                this.schemas.set( null );
203            }
204    
205            notifyListenerOrRegistries( schema, registries );
206        }
207    
208    
209        // ------------------------------------------------------------------------
210        // Utility Methods
211        // ------------------------------------------------------------------------
212    
213        /**
214         * Registers objects
215         *
216         * @param type the type of the producer which determines the type of object produced
217         * @param id the primary key identifying the created object in a registry
218         * @param schemaObject the object being registered
219         * @throws NamingException if there are problems when registering the object
220         * in any of the registries
221         */
222        private void register( ProducerTypeEnum type, String id, Object schemaObject ) throws NamingException
223        {
224            BootstrapSchema schema = this.schemas.get();
225            DefaultRegistries registries = ( DefaultRegistries ) this.registries.get();
226            List<String> values = new ArrayList<String>(1);
227            values.add( schema.getSchemaName() );
228    
229            switch ( type )
230            {
231                case NORMALIZER_PRODUCER :
232                    Normalizer normalizer = ( Normalizer ) schemaObject;
233                    NormalizerRegistry normalizerRegistry;
234                    normalizerRegistry = registries.getNormalizerRegistry();
235                    
236                    NormalizerDescription normalizerDescription = new NormalizerDescription();
237                    normalizerDescription.setNumericOid( id );
238                    normalizerDescription.setFqcn( normalizer.getClass().getName() );
239                    normalizerDescription.addExtension( MetaSchemaConstants.X_SCHEMA, values );
240                    
241                    normalizerRegistry.register( normalizerDescription, normalizer );
242                    break;
243                    
244                case COMPARATOR_PRODUCER :
245                    Comparator comparator = ( Comparator ) schemaObject;
246                    ComparatorRegistry comparatorRegistry;
247                    comparatorRegistry = registries.getComparatorRegistry();
248                    
249                    ComparatorDescription comparatorDescription = new ComparatorDescription();
250                    comparatorDescription.addExtension( MetaSchemaConstants.X_SCHEMA, values );
251                    comparatorDescription.setFqcn( comparator.getClass().getName() );
252                    comparatorDescription.setNumericOid( id );
253                    
254                    comparatorRegistry.register( comparatorDescription, comparator );
255                    break;
256                    
257                case SYNTAX_CHECKER_PRODUCER :
258                    SyntaxChecker syntaxChecker = ( SyntaxChecker ) schemaObject;
259                    SyntaxCheckerRegistry syntaxCheckerRegistry;
260                    syntaxCheckerRegistry = registries.getSyntaxCheckerRegistry();
261                    
262                    SyntaxCheckerDescription syntaxCheckerDescription = new SyntaxCheckerDescription();
263                    syntaxCheckerDescription.addExtension( MetaSchemaConstants.X_SCHEMA, values );
264                    syntaxCheckerDescription.setFqcn( syntaxChecker.getClass().getName() );
265                    syntaxCheckerDescription.setNumericOid( id );
266                    
267                    syntaxCheckerRegistry.register( syntaxCheckerDescription, syntaxChecker );
268                    break;
269                    
270                case SYNTAX_PRODUCER :
271                    Syntax syntax = ( Syntax ) schemaObject;
272                    
273                    if ( schemaObject instanceof BootstrapSyntax )
274                    {
275                        ( ( BootstrapSyntax ) syntax ).setSchema( schema.getSchemaName() );
276                    }
277    
278                    SyntaxRegistry syntaxRegistry = registries.getSyntaxRegistry();
279                    syntaxRegistry.register( syntax );
280                    break;
281                    
282                case MATCHING_RULE_PRODUCER :
283                    MatchingRule matchingRule = ( MatchingRule ) schemaObject;
284                    
285                    if ( schemaObject instanceof BootstrapMatchingRule )
286                    {
287                        ( ( BootstrapMatchingRule ) matchingRule ).setSchema( schema.getSchemaName() );
288                    }
289    
290                    MatchingRuleRegistry matchingRuleRegistry;
291                    matchingRuleRegistry = registries.getMatchingRuleRegistry();
292                    matchingRuleRegistry.register( matchingRule );
293                    break;
294                    
295                case ATTRIBUTE_TYPE_PRODUCER :
296                    AttributeType attributeType = ( AttributeType ) schemaObject;
297                    
298                    if ( attributeType instanceof BootstrapAttributeType )
299                    {
300                        ( ( BootstrapAttributeType ) attributeType ).setSchema( schema.getSchemaName() );
301                    }
302                    
303                    registries.getAttributeTypeRegistry().register( attributeType );
304                    break;
305                    
306                case OBJECT_CLASS_PRODUCER :
307                    ObjectClass objectClass = ( ObjectClass ) schemaObject;
308                    
309                    if ( objectClass instanceof BootstrapObjectClass )
310                    {
311                        ( ( BootstrapObjectClass ) objectClass ).setSchema( schema.getSchemaName() );
312                    }
313                    
314                    ObjectClassRegistry objectClassRegistry;
315                    objectClassRegistry = registries.getObjectClassRegistry();
316                    objectClassRegistry.register( objectClass );
317                    break;
318                    
319                default:
320                    throw new IllegalStateException( "ProducerTypeEnum value is invalid: " + type );
321            }
322        }
323    
324    
325        /**
326         * Attempts first to try to load the target class for the Producer,
327         * then tries for the default if the target load fails.
328         *
329         * @param schema the bootstrap schema
330         * @param producerBase the producer's base name
331         * @throws NamingException if there are failures loading classes
332         */
333        private BootstrapProducer getProducer( BootstrapSchema schema, String producerBase ) throws NamingException
334        {
335            Class<?> clazz = null;
336            boolean failedTargetLoad = false;
337            String defaultClassName;
338            String targetClassName = schema.getBaseClassName() + producerBase;
339    
340            try
341            {
342                clazz = Class.forName( targetClassName, true, cl );
343            }
344            catch ( ClassNotFoundException e )
345            {
346                failedTargetLoad = true;
347                log.debug( "Failed to load '" + targetClassName + "'.  Trying the alternative.", e );
348            }
349    
350            if ( failedTargetLoad )
351            {
352                defaultClassName = schema.getDefaultBaseClassName() + producerBase;
353    
354                try
355                {
356                    clazz = Class.forName( defaultClassName, true, cl );
357                }
358                catch ( ClassNotFoundException e )
359                {
360                    NamingException ne = new NamingException( "Failed to load " + producerBase + " for "
361                        + schema.getSchemaName() + " schema using following classes: " + targetClassName + ", "
362                        + defaultClassName );
363                    ne.setRootCause( e );
364                    throw ne;
365                }
366            }
367    
368            try
369            {
370                return ( BootstrapProducer ) clazz.newInstance();
371            }
372            catch ( IllegalAccessException e )
373            {
374                NamingException ne = new NamingException( "Failed to create " + clazz );
375                ne.setRootCause( e );
376                throw ne;
377            }
378            catch ( InstantiationException e )
379            {
380                NamingException ne = new NamingException( "Failed to create " + clazz );
381                ne.setRootCause( e );
382                throw ne;
383            }
384        }
385    
386    
387        public Schema getSchema( String schemaName ) throws NamingException
388        {
389            return getSchema( schemaName, null );
390        }
391        
392        
393        public Schema getSchema( String schemaName, Properties schemaProperties ) throws NamingException
394        {
395            String baseName = schemaName;
396            schemaName = schemaName.toLowerCase();
397            StringBuffer buf = new StringBuffer();
398    
399            
400            if ( schemaProperties == null || schemaProperties.getProperty( "package" ) == null )
401            {
402                // first see if we can load a schema object using the default bootstrap package
403                Properties props = new Properties();
404                props.put( "package", "org.apache.directory.server.schema.bootstrap" );
405                
406                try
407                {
408                    Schema schema = getSchema( baseName, props );
409                    return schema;
410                }
411                catch( NamingException e )
412                {
413                    throw new NamingException( "Can't find the bootstrap schema class in the default " +
414                            "\n bootstrap schema package.  I need a package name property with key \"package\"." );
415                }
416            }
417            
418            buf.append( schemaProperties.getProperty( "package" ) );
419            buf.append( '.' );
420            buf.append( Character.toUpperCase( schemaName.charAt( 0 ) ) );
421            buf.append( schemaName.substring( 1 ) );
422            schemaName = buf.toString();
423            
424            Schema schema = null;
425            try
426            {
427                schema = ( Schema ) Class.forName( schemaName, true, cl ).newInstance();
428            }
429            catch ( InstantiationException e )
430            {
431                NamingException ne = new NamingException( "Failed to instantiate schema object: " + schemaName );
432                ne.setRootCause( e );
433                throw ne;
434            }
435            catch ( IllegalAccessException e )
436            {
437                NamingException ne = 
438                    new NamingException( "Failed to access default constructor of schema object: " + schemaName );
439                ne.setRootCause( e );
440                throw ne;
441            }
442            catch ( ClassNotFoundException e )
443            {
444                NamingException ne = new NamingException( "Schema class not found: " + schemaName );
445                ne.setRootCause( e );
446                throw ne;
447            }
448            
449            return schema;
450        }
451    }