001    /*****************************************************************************
002     * Copyright (C) NanoContainer Organization. All rights reserved.            *
003     * ------------------------------------------------------------------------- *
004     * The software in this package is published under the terms of the BSD      *
005     * style license a copy of which has been included with this distribution in *
006     * the LICENSE.txt file.                                                     *
007     *                                                                           *
008     * Original code by                                                          *
009     *****************************************************************************/
010    
011    /**
012     * @author Aslak Hellesøy
013     * @version $Revision: 2947 $
014     */
015    package org.nanocontainer.deployer;
016    
017    import java.io.InputStreamReader;
018    import java.io.Reader;
019    
020    import org.apache.commons.vfs.FileObject;
021    import org.apache.commons.vfs.FileSelectInfo;
022    import org.apache.commons.vfs.FileSelector;
023    import org.apache.commons.vfs.FileSystemException;
024    import org.apache.commons.vfs.FileSystemManager;
025    import org.apache.commons.vfs.impl.VFSClassLoader;
026    import org.apache.commons.vfs.VFS;
027    import org.nanocontainer.integrationkit.ContainerBuilder;
028    import org.nanocontainer.script.ScriptedContainerBuilderFactory;
029    import org.picocontainer.defaults.ObjectReference;
030    import org.picocontainer.defaults.SimpleReference;
031    import org.nanocontainer.script.UnsupportedScriptTypeException;
032    import org.nanocontainer.script.ScriptBuilderResolver;
033    
034    /**
035     * This class is capable of deploying an application from any kind of file system
036     * supported by <a href="http://jakarta.apache.org/commons/sandbox/vfs/">Jakarta VFS</a>.
037     * (Like local files, zip files etc.) - following the ScriptedContainerBuilderFactory scripting model.
038     *
039     * The root folder to deploy must have the following file structure:
040     * <pre>
041     * +-someapp/
042     *   +-META-INF/
043     *   | +-nanocontainer.[py|js|xml|bsh]
044     *   +-com/
045     *     +-blablah/
046     *       +-Hip.class
047     *       +-Hop.class
048     * </pre>
049     *
050     * For those familiar with J2EE containers (or other containers for that matter), the
051     * META-INF/picocontainer script is the ScriptedContainerBuilderFactory <em>composition script</em>. It plays the same
052     * role as more classical "deployment descriptors", except that deploying via a full blown
053     * scripting language is a lot more powerful!
054     *
055     * A new class loader (which will be a child of parentClassLoader) will be created. This classloader will make
056     * the classes under the root folder available to the deployment script.
057     *
058     * IMPORTANT NOTE:
059     * The scripting engine (rhino, jython, groovy etc.) should be loaded by the same classLoader as
060     * the appliacation classes, i.e. the VFSClassLoader pointing to the app directory.
061     *
062     * <pre>
063     *    +-------------------+
064     *    | xxx               |  <-- parent app loader (must not contain classes from app builder classloader)
065     *    +-------------------+
066     *              |
067     *    +-------------------+
068     *    | someapp           | <-- app classloader (must not contain classes from app builder classloader)
069     *    +-------------------+
070     *              |
071     *    +-------------------+
072     *    | picocontainer     |
073     *    | nanocontainer     |  <-- app builder classloader
074     *    | rhino             |
075     *    | jython            |
076     *    | groovy            |
077     *    +-------------------+
078     * </pre>
079     *
080     * This means that these scripting engines should *not* be accessible by any of the app classloader, since this
081     * may prevent the scripting engine from seeing the classes loaded by the VFSClassLoader. In other words,
082     * the scripting engine classed may be loaded several times by different class loaders - once for each
083     * deployed application.
084     *
085     * @author Aslak Hellesøy
086     */
087    public class NanoContainerDeployer implements Deployer {
088    
089        /**
090         * VFS file system manager.
091         */
092        private final FileSystemManager fileSystemManager;
093    
094        /**
095         * File system basename.  Defaults to 'nanocontainer'.  May be set differently
096         * for other applications.
097         */
098        private final String fileBasename;
099    
100    
101        /**
102         * File Name to builder class name resolver.
103         */
104        private ScriptBuilderResolver resolver;
105    
106    
107        /**
108         * Default constructor that makes sensible defaults.
109         * @throws FileSystemException
110         */
111        public NanoContainerDeployer() throws FileSystemException {
112            this(VFS.getManager(), new ScriptBuilderResolver());
113        }
114    
115        /**
116         * Constructs a nanocontainer deployer with the specified file system manager.
117         * @param fileSystemManager A VFS FileSystemManager.
118         */
119        public NanoContainerDeployer(final FileSystemManager fileSystemManager) {
120            this(fileSystemManager,"nanocontainer");
121        }
122    
123    
124        /**
125         * Constructs this object with both a VFS file system manager, and
126         * @param fileSystemManager FileSystemManager
127         * @param builderResolver ScriptBuilderResolver
128         */
129        public NanoContainerDeployer(final FileSystemManager fileSystemManager, ScriptBuilderResolver builderResolver) {
130            this(fileSystemManager);
131            resolver = builderResolver;
132        }
133    
134        /**
135         * Constructs a nanocontainer deployer with the specified file system manager
136         * and specifies a 'base name' for the configuration file that will be loaded.
137         * @param fileSystemManager A VFS FileSystemManager.
138         * @todo Deprecate this and replace 'base file name' with the concept
139         * of a ArchiveLayout that defines where jars are stored, where the composition
140         * script is stored, etc.
141         */
142        public NanoContainerDeployer(final FileSystemManager fileSystemManager, String baseFileName) {
143            this.fileSystemManager = fileSystemManager;
144            fileBasename = baseFileName;
145            resolver = new ScriptBuilderResolver();
146        }
147    
148    
149        /**
150         * Deploys an application.
151         *
152         * @param applicationFolder the root applicationFolder of the application.
153         * @param parentClassLoader the classloader that loads the application classes.
154         * @param parentContainerRef reference to the parent container (can be used to lookup components form a parent container).
155         * @return an ObjectReference holding a PicoContainer with the deployed components
156         * @throws org.apache.commons.vfs.FileSystemException if the file structure was bad.
157         * @throws org.nanocontainer.integrationkit.PicoCompositionException if the deployment failed for some reason.
158         */
159        public ObjectReference deploy(FileObject applicationFolder, ClassLoader parentClassLoader, ObjectReference parentContainerRef) throws FileSystemException, ClassNotFoundException {
160            return deploy(applicationFolder, parentClassLoader, parentContainerRef, null);
161        }
162    
163        public ObjectReference deploy(FileObject applicationFolder, ClassLoader parentClassLoader, ObjectReference parentContainerRef, Object assemblyScope) throws FileSystemException, ClassNotFoundException {
164            ClassLoader applicationClassLoader = new VFSClassLoader(applicationFolder, fileSystemManager, parentClassLoader);
165    
166            FileObject deploymentScript = getDeploymentScript(applicationFolder);
167    
168            ObjectReference result = new SimpleReference();
169    
170            String extension = "." + deploymentScript.getName().getExtension();
171            Reader scriptReader = new InputStreamReader(deploymentScript.getContent().getInputStream());
172            String builderClassName = null;
173            try {
174                builderClassName = resolver.getBuilderClassName(extension);
175            } catch (UnsupportedScriptTypeException ex) {
176                throw new FileSystemException("Could not find a suitable builder for: " + deploymentScript.getName()
177                    + ".  Known extensions are: [groovy|bsh|js|py|xml]", ex);
178            }
179    
180    
181            ScriptedContainerBuilderFactory scriptedContainerBuilderFactory = new ScriptedContainerBuilderFactory(scriptReader, builderClassName, applicationClassLoader);
182            ContainerBuilder builder = scriptedContainerBuilderFactory.getContainerBuilder();
183            builder.buildContainer(result, parentContainerRef, assemblyScope, true);
184    
185            return result;
186    
187        }
188    
189    
190    
191    
192        /**
193         * Given the base application folder, return a file object that represents the
194         * nanocontainer configuration script.
195         * @param applicationFolder FileObject
196         * @return FileObject
197         * @throws FileSystemException
198         */
199        protected FileObject getDeploymentScript(FileObject applicationFolder) throws FileSystemException {
200            final FileObject metaInf = applicationFolder.getChild("META-INF");
201            if(metaInf == null) {
202                throw new FileSystemException("Missing META-INF folder in " + applicationFolder.getName().getPath());
203            }
204            final FileObject[] nanocontainerScripts = metaInf.findFiles(new FileSelector(){
205    
206                public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
207                    return fileSelectInfo.getFile().getName().getBaseName().startsWith(getFileBasename());
208                }
209    
210                public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
211                  //
212                  //nanocontainer.* can easily be deep inside a directory tree and
213                  //we end up not picking up our desired script.
214                  //
215                  if (fileSelectInfo.getDepth() > 1) {
216                    return false;
217                  } else {
218                    return true;
219                  }
220                }
221            });
222    
223            if(nanocontainerScripts == null || nanocontainerScripts.length < 1) {
224                throw new FileSystemException("No deployment script ("+ getFileBasename() +".[groovy|bsh|js|py|xml]) in " + applicationFolder.getName().getPath() + "/META-INF");
225            }
226    
227            if (nanocontainerScripts.length == 1) {
228              return nanocontainerScripts[0];
229            } else {
230              throw new FileSystemException("Found more than one candidate config script in : " + applicationFolder.getName().getPath() + "/META-INF."
231                  + "Please only have one " + getFileBasename() + ".[groovy|bsh|js|py|xml] this directory.");
232            }
233    
234        }
235    
236    
237        /**
238         * Retrieve the file base name.
239         * @return String
240         */
241        public String getFileBasename() {
242            return fileBasename;
243        }
244    }