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 }