    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&oslash;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&oslash;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    }