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 }