001    /*******************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.
003     * ---------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * LICENSE.txt file.
007     ******************************************************************************/
008    package org.picocontainer.script;
009    
010    import java.io.File;
011    import java.io.FileNotFoundException;
012    import java.io.FileReader;
013    import java.io.IOException;
014    import java.io.Reader;
015    import java.net.URL;
016    import org.picocontainer.ComponentAdapter;
017    import org.picocontainer.DefaultPicoContainer;
018    import org.picocontainer.MutablePicoContainer;
019    import org.picocontainer.classname.DefaultClassLoadingPicoContainer;
020    import org.picocontainer.classname.ClassName;
021    
022    /**
023     * Factory class for scripted container builders of various scripting languages.
024     * When using the constructors taking a File, the extensions must be one of the
025     * following:
026     * <ul>
027     * <li>.groovy - Groovy scripts</li>
028     * <li>.bsh - BeanShell scripts</li>
029     * <li>.js - Rhino scripts (Javascript)</li>
030     * <li>.py - Python scripts </li>
031     * <li>.xml - XML scripts</li>
032     * </ul>
033     * with the content of the file of the corresponding scripting language.
034     * 
035     * @author Paul Hammant
036     * @author Aslak Helles&oslah;y
037     * @author Obie Fernandez
038     * @author Michael Rimov
039     * @author Mauro Talevi
040     */
041    public class ScriptedContainerBuilderFactory {
042    
043        private ScriptedContainerBuilder containerBuilder;
044    
045        /**
046         * Creates a ScriptedContainerBuilderFactory
047         * 
048         * @param compositionFile File The script file.
049         * @param classLoader ClassLoader for class resolution once we resolve what
050         *            the name of the builder should be.
051         * @param scriptedBuilderResolver ScriptedBuilderNameResolver the resolver of
052         *            container builder class names from file names.
053         * @throws UnsupportedScriptTypeException if the extension of the file does
054         *             not match that of any known script.
055         * @throws FileNotFoundException if composition file is not found
056         */
057        public ScriptedContainerBuilderFactory(File compositionFile, ClassLoader classLoader,
058                ScriptedBuilderNameResolver scriptedBuilderResolver) throws UnsupportedScriptTypeException, FileNotFoundException {
059            this(new FileReader(fileExists(compositionFile)), scriptedBuilderResolver.getBuilderClassName(compositionFile),
060                    classLoader);
061        }
062    
063        /**
064         * Creates a ScriptedContainerBuilderFactory with default script builder
065         * resolver
066         * 
067         * @param compositionFile File The script file.
068         * @param classLoader ClassLoader for class resolution once we resolve what
069         *            the name of the builder should be.
070         * @see ScriptedContainerBuilderFactory#ScriptedContainerBuilderFactory(File,
071         *      ClassLoader, ScriptedBuilderNameResolver)
072         */
073        public ScriptedContainerBuilderFactory(File compositionFile, ClassLoader classLoader) throws IOException {
074            this(compositionFile, classLoader, new ScriptedBuilderNameResolver());
075        }
076    
077        /**
078         * Creates a ScriptedContainerBuilderFactory with default script builder
079         * resolver and context class loader
080         * 
081         * @param compositionFile File The script file.
082         * @see ScriptedContainerBuilderFactory#ScriptedContainerBuilderFactory(File,
083         *      ClassLoader, ScriptedBuilderNameResolver)
084         */
085        public ScriptedContainerBuilderFactory(File compositionFile) throws IOException {
086            this(compositionFile, Thread.currentThread().getContextClassLoader());
087        }
088    
089        /**
090         * Creates a ScriptedContainerBuilderFactory with default script builder
091         * resolver and context class loader
092         * 
093         * @param compositionURL The script URL.
094         * @throws UnsupportedScriptTypeException if the extension of the file does
095         *             not match that of any known script.
096         */
097        public ScriptedContainerBuilderFactory(URL compositionURL) {
098            this(compositionURL, Thread.currentThread().getContextClassLoader(), new ScriptedBuilderNameResolver());
099        }
100    
101        /**
102         * Creates a ScriptedContainerBuilderFactory
103         * 
104         * @param compositionURL The script URL.
105         * @param builderClassResolver ScriptedBuilderNameResolver the resolver for
106         *            figuring out file names to container builder class names.
107         * @param classLoader ClassLoader for class resolution once we resolve what
108         *            the name of the builder should be.. the specified builder
109         *            using the specified classloader.
110         * @throws UnsupportedScriptTypeException if the extension of the file does
111         *             not match that of any known script.
112         */
113        public ScriptedContainerBuilderFactory(URL compositionURL, ClassLoader classLoader,
114                ScriptedBuilderNameResolver builderClassResolver) throws UnsupportedScriptTypeException {
115            this(compositionURL, builderClassResolver.getBuilderClassName(compositionURL), classLoader);
116        }
117    
118        /**
119         * Creates a ScriptedContainerBuilderFactory
120         * 
121         * @param compositionURL The script URL.
122         * @param builderClassName the class name of the ContainerBuilder to
123         *            instantiate
124         * @param classLoader ClassLoader for class resolution once we resolve what
125         *            the name of the builder should be.. the specified builder
126         *            using the specified classloader.
127         */
128        public ScriptedContainerBuilderFactory(URL compositionURL, String builderClassName, ClassLoader classLoader) {
129            createContainerBuilder(compositionURL, builderClassName, classLoader);
130        }
131    
132        /**
133         * Creates a ScriptedContainerBuilderFactory with context class loader
134         * 
135         * @param composition the Reader encoding the script to create the builder
136         *            with
137         * @param builderClassName the class name of the ContainerBuilder to
138         *            instantiate
139         * @see ScriptedContainerBuilderFactory#ScriptedContainerBuilderFactory(Reader,
140         *      String, ClassLoader)
141         */
142        public ScriptedContainerBuilderFactory(Reader composition, String builderClassName) {
143            this(composition, builderClassName, Thread.currentThread().getContextClassLoader());
144        }
145    
146        /**
147         * Creates a ScriptedContainerBuilderFactory
148         * 
149         * @param composition the Reader encoding the script to create the builder
150         *            with
151         * @param builderClassName the class name of the ContainerBuilder to
152         *            instantiate
153         * @param classLoader the Classloader to use for instantiation
154         */
155        public ScriptedContainerBuilderFactory(Reader composition, String builderClassName, ClassLoader classLoader) {
156            createContainerBuilder(composition, builderClassName, classLoader);
157        }
158    
159        /**
160         * Performs the actual instantiation of the builder.
161         * 
162         * @param composition the composition source object - can be either a
163         *            Reader, a URL or a File
164         * @param builderClassName the class name of the ContainerBuilder to
165         *            instantiate
166         * @param classLoader the Classloader to use for instantiation
167         */
168        private void createContainerBuilder(Object composition, String builderClassName, ClassLoader classLoader) {
169            DefaultClassLoadingPicoContainer defaultScriptedContainer;
170            {
171                // transient.
172                DefaultPicoContainer factory = new DefaultPicoContainer();
173                if (composition == null) {
174                    throw new NullPointerException("composition can't be null");
175                }
176                factory.addComponent(composition);
177    
178                if (classLoader == null) {
179                    // Thread.currentThread().getContextClassLoader() MAY return
180                    // null, while Class.getClassLoader() should NEVER return null.
181                    // -MR
182                    classLoader = getClass().getClassLoader();
183                }
184                factory.addComponent(classLoader);
185    
186                // If we don't specify the classloader here, some of the things that
187                // make up a scripted container may bomb. And we're only talking a
188                // reload within a webapp! -MR
189                defaultScriptedContainer = new DefaultClassLoadingPicoContainer(classLoader, factory);
190            }
191            ClassName className = new ClassName(builderClassName);
192            MutablePicoContainer mutablePicoContainer = defaultScriptedContainer.addComponent(className, className);
193            ComponentAdapter<?> componentAdapter = mutablePicoContainer.getComponentAdapter(className);
194            containerBuilder = (ScriptedContainerBuilder) componentAdapter.getComponentInstance(defaultScriptedContainer, ComponentAdapter.NOTHING.class);
195        }
196    
197        private static File fileExists(final File file) throws FileNotFoundException {
198            if (file.exists()) {
199                return file;
200            } 
201            throw new FileNotFoundException("File " + file.getAbsolutePath() + " does not exist.");
202        }
203    
204        /**
205         * Returns the created container builder instance.
206         * 
207         * @return The ScriptedContainerBuilder instance
208         */
209        public ScriptedContainerBuilder getContainerBuilder() {
210            return containerBuilder;
211        }
212    
213    }