/*****************************************************************************
* Copyright (C) NanoContainer Organization. All rights reserved. *
* ------------------------------------------------------------------------- *
* The software in this package is published under the terms of the BSD *
* style license a copy of which has been included with this distribution in *
* the LICENSE.txt file. *
* *
* Original code by *
*****************************************************************************/
/**
* @author Aslak Hellesøy
* @version $Revision: 2343 $
*/
package org.nanocontainer.deployer;
import java.io.InputStreamReader;
import java.io.Reader;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSelectInfo;
import org.apache.commons.vfs.FileSelector;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.impl.VFSClassLoader;
import org.nanocontainer.integrationkit.ContainerBuilder;
import org.nanocontainer.script.ScriptedContainerBuilderFactory;
import org.picocontainer.defaults.ObjectReference;
import org.picocontainer.defaults.SimpleReference;
/**
* This class is capable of deploying an application from any kind of file system
* supported by <a href="http://jakarta.apache.org/commons/sandbox/vfs/">Jakarta VFS</a>.
* (Like local files, zip files etc.) - following the ScriptedContainerBuilderFactory scripting model.
*
* The root folder to deploy must have the following file structure:
* <pre>
* +-someapp/
* +-META-INF/
* | +-nanocontainer.[py|js|xml|bsh]
* +-com/
* +-blablah/
* +-Hip.class
* +-Hop.class
* </pre>
*
* For those familiar with J2EE containers (or other containers for that matter), the
* META-INF/picocontainer script is the ScriptedContainerBuilderFactory <em>composition script</em>. It plays the same
* role as more classical "deployment descriptors", except that deploying via a full blown
* scripting language is a lot more powerful!
*
* A new class loader (which will be a child of parentClassLoader) will be created. This classloader will make
* the classes under the root folder available to the deployment script.
*
* IMPORTANT NOTE:
* The scripting engine (rhino, jython, groovy etc.) should be loaded by the same classLoader as
* the appliacation classes, i.e. the VFSClassLoader pointing to the app directory.
*
* <pre>
* +-------------------+
* | xxx | <-- parent app loader (must not contain classes from app builder classloader)
* +-------------------+
* |
* +-------------------+
* | someapp | <-- app classloader (must not contain classes from app builder classloader)
* +-------------------+
* |
* +-------------------+
* | picocontainer |
* | nanocontainer | <-- app builder classloader
* | rhino |
* | jython |
* | groovy |
* +-------------------+
* </pre>
*
* This means that these scripting engines should *not* be accessible by any of the app classloader, since this
* may prevent the scripting engine from seeing the classes loaded by the VFSClassLoader. In other words,
* the scripting engine classed may be loaded several times by different class loaders - once for each
* deployed application.
*
* @author Aslak Hellesøy
*/
public class NanoContainerDeployer implements Deployer {
/**
* VFS file system manager.
*/
private final FileSystemManager fileSystemManager;
/**
* File system basename. Defaults to 'nanocontainer'. May be set differently
* for other applications.
*/
private final String fileBasename;
/**
* Constructs a nanocontainer deployer with the specified file system manager.
* @param fileSystemManager A VFS FileSystemManager.
*/
public NanoContainerDeployer(final FileSystemManager fileSystemManager) {
this(fileSystemManager,"nanocontainer");
}
/**
* Constructs a nanocontainer deployer with the specified file system manager
* and specifies a 'base name' for the configuration file that will be loaded.
* @param fileSystemManager A VFS FileSystemManager.
*/
public NanoContainerDeployer(final FileSystemManager fileSystemManager, String baseFileName) {
this.fileSystemManager = fileSystemManager;
fileBasename = baseFileName;
}
/**
* Deploys an application.
*
* @param applicationFolder the root applicationFolder of the application.
* @param parentClassLoader the classloader that loads the application classes.
* @param parentContainerRef reference to the parent container (can be used to lookup components form a parent container).
* @return an ObjectReference holding a PicoContainer with the deployed components
* @throws org.apache.commons.vfs.FileSystemException if the file structure was bad.
* @throws org.nanocontainer.integrationkit.PicoCompositionException if the deployment failed for some reason.
*/
public ObjectReference deploy(FileObject applicationFolder, ClassLoader parentClassLoader, ObjectReference parentContainerRef) throws FileSystemException, ClassNotFoundException {
ClassLoader applicationClassLoader = new VFSClassLoader(applicationFolder, fileSystemManager, parentClassLoader);
FileObject deploymentScript = getDeploymentScript(applicationFolder);
ObjectReference result = new SimpleReference();
String extension = "." + deploymentScript.getName().getExtension();
Reader scriptReader = new InputStreamReader(deploymentScript.getContent().getInputStream());
String builderClassName = ScriptedContainerBuilderFactory.getBuilderClassName(extension);
if (builderClassName == null) {
throw new FileSystemException("Could not find a suitable builder for: " + deploymentScript.getName()
+ ". Known extensions are: [groovy|bsh|js|py|xml]");
}
ScriptedContainerBuilderFactory scriptedContainerBuilderFactory = new ScriptedContainerBuilderFactory(scriptReader, builderClassName, applicationClassLoader);
ContainerBuilder builder = scriptedContainerBuilderFactory.getContainerBuilder();
builder.buildContainer(result, parentContainerRef, null, true);
return result;
}
/**
* Given the base application folder, return a file object that represents the
* nanocontainer configuration script.
* @param applicationFolder FileObject
* @return FileObject
* @throws FileSystemException
*/
private FileObject getDeploymentScript(FileObject applicationFolder) throws FileSystemException {
final FileObject metaInf = applicationFolder.getChild("META-INF");
if(metaInf == null) {
throw new FileSystemException("Missing META-INF folder in " + applicationFolder.getName().getPath());
}
final FileObject[] nanocontainerScripts = metaInf.findFiles(new FileSelector(){
public boolean includeFile(FileSelectInfo fileSelectInfo) throws Exception {
return fileSelectInfo.getFile().getName().getBaseName().startsWith(getFileBasename());
}
public boolean traverseDescendents(FileSelectInfo fileSelectInfo) throws Exception {
//
//nanocontainer.* can easily be deep inside a directory tree and
//we end up not picking up our desired script.
//
if (fileSelectInfo.getDepth() > 1) {
return false;
} else {
return true;
}
}
});
if(nanocontainerScripts == null || nanocontainerScripts.length < 1) {
throw new FileSystemException("No deployment script ("+ getFileBasename() +".[groovy|bsh|js|py|xml]) in " + applicationFolder.getName().getPath() + "/META-INF");
}
if (nanocontainerScripts.length == 1) {
return nanocontainerScripts[0];
} else {
throw new FileSystemException("Found more than one candidate config script in : " + applicationFolder.getName().getPath() + "/META-INF."
+ "Please only have one " + getFileBasename() + ".[groovy|bsh|js|py|xml] this directory.");
}
}
/**
* Retrieve the file base name.
* @return String
*/
public String getFileBasename() {
return fileBasename;
}
}