001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.xbean.server.deployer; 018 019 import org.apache.commons.logging.Log; 020 import org.apache.commons.logging.LogFactory; 021 import org.apache.xbean.kernel.Kernel; 022 import org.apache.xbean.kernel.ServiceAlreadyExistsException; 023 import org.apache.xbean.kernel.ServiceFactory; 024 import org.apache.xbean.kernel.ServiceRegistrationException; 025 import org.apache.xbean.kernel.StringServiceName; 026 import org.apache.xbean.classloader.NamedClassLoader; 027 import org.apache.xbean.server.spring.configuration.SpringConfigurationServiceFactory; 028 import org.apache.xbean.spring.context.ResourceXmlApplicationContext; 029 import org.apache.xbean.spring.context.SpringApplicationContext; 030 import org.springframework.beans.BeansException; 031 import org.springframework.beans.factory.InitializingBean; 032 import org.springframework.context.ApplicationContext; 033 import org.springframework.context.ApplicationContextAware; 034 import org.springframework.context.support.AbstractXmlApplicationContext; 035 import org.springframework.core.io.FileSystemResource; 036 037 import java.io.File; 038 import java.io.FileInputStream; 039 import java.io.IOException; 040 import java.net.MalformedURLException; 041 import java.net.URL; 042 import java.util.ArrayList; 043 import java.util.Collections; 044 import java.util.Iterator; 045 import java.util.LinkedHashMap; 046 import java.util.List; 047 import java.util.Map; 048 import java.util.Properties; 049 import java.util.StringTokenizer; 050 051 /** 052 * A service which auto-deploys services within a recursive file system. 053 * 054 * @org.apache.xbean.XBean namespace="http://xbean.apache.org/schemas/server" 055 * element="file-deployer" description="Deploys services in a file system" 056 * @version $Revision: 551137 $ 057 */ 058 public class FileDeployer implements Runnable, InitializingBean, ApplicationContextAware { 059 060 private static final Log log = LogFactory.getLog(FileDeployer.class); 061 062 private File baseDir; 063 private Kernel kernel; 064 private ClassLoader classLoader; 065 private boolean verbose; 066 private String[] jarDirectoryNames = { "lib", "classes" }; 067 private List beanFactoryPostProcessors = Collections.EMPTY_LIST; 068 private List xmlPreprocessors = Collections.EMPTY_LIST; 069 private ApplicationContext applicationContext; 070 private boolean showIgnoredFiles; 071 072 public void afterPropertiesSet() throws Exception { 073 if (classLoader == null) { 074 classLoader = Thread.currentThread().getContextClassLoader(); 075 } 076 if (classLoader == null) { 077 classLoader = getClass().getClassLoader(); 078 } 079 if (baseDir == null) { 080 log.warn("No directory specified so using current directory"); 081 baseDir = new File("."); 082 } 083 baseDir = baseDir.getAbsoluteFile(); 084 log.info("Starting to load components from: " + baseDir); 085 086 // lets load the deployment 087 processDirectory("", classLoader, applicationContext, baseDir); 088 089 log.info("Loading completed"); 090 } 091 092 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 093 this.applicationContext = applicationContext; 094 } 095 096 public void run() { 097 try { 098 String name = ""; 099 if (applicationContext != null) { 100 name = applicationContext.getDisplayName(); 101 } 102 processDirectory(name, classLoader, applicationContext, baseDir); 103 } 104 catch (Exception e) { 105 log.error("Failed to deploy services: " + e, e); 106 } 107 } 108 109 // Properties 110 // ------------------------------------------------------------------------- 111 public ClassLoader getClassLoader() { 112 return classLoader; 113 } 114 115 public void setClassLoader(ClassLoader classLoader) { 116 this.classLoader = classLoader; 117 } 118 119 /** 120 * Sets the kernel in which configurations are loaded. 121 * 122 * @param kernel 123 * the kernel in which configurations are loaded 124 */ 125 public void setKernel(Kernel kernel) { 126 this.kernel = kernel; 127 } 128 129 /** 130 * Gets the base directory from which configuration locations are resolved. 131 * 132 * @return the base directory from which configuration locations are 133 * resolved 134 */ 135 public File getBaseDir() { 136 return baseDir; 137 } 138 139 /** 140 * Sets the base directory from which configuration locations are resolved. 141 * 142 * @param baseDir 143 * the base directory from which configuration locations are 144 * resolved 145 */ 146 public void setBaseDir(File baseDir) { 147 this.baseDir = baseDir; 148 } 149 150 /** 151 * Gets the SpringXmlPreprocessors applied to the configuration. 152 * 153 * @return the SpringXmlPreprocessors applied to the configuration 154 */ 155 public List getXmlPreprocessors() { 156 return xmlPreprocessors; 157 } 158 159 /** 160 * Sets the SpringXmlPreprocessors applied to the configuration. 161 * 162 * @param xmlPreprocessors 163 * the SpringXmlPreprocessors applied to the configuration 164 */ 165 public void setXmlPreprocessors(List xmlPreprocessors) { 166 this.xmlPreprocessors = xmlPreprocessors; 167 } 168 169 /** 170 * Gets the BeanFactoryPostProcessors to apply to the configuration. 171 * 172 * @return the BeanFactoryPostProcessors to apply to the configuration 173 */ 174 public List getBeanFactoryPostProcessors() { 175 return beanFactoryPostProcessors; 176 } 177 178 /** 179 * Sets the BeanFactoryPostProcessors to apply to the configuration. 180 * 181 * @param beanFactoryPostProcessors 182 * the BeanFactoryPostProcessors to apply to the configuration 183 */ 184 public void setBeanFactoryPostProcessors(List beanFactoryPostProcessors) { 185 this.beanFactoryPostProcessors = beanFactoryPostProcessors; 186 } 187 188 public boolean isVerbose() { 189 return verbose; 190 } 191 192 /** 193 * Allows verbose logging to show what classpaths are being created 194 */ 195 public void setVerbose(boolean verbose) { 196 this.verbose = verbose; 197 } 198 199 public boolean isShowIgnoredFiles() { 200 return showIgnoredFiles; 201 } 202 203 /** 204 * Sets whether or not ignored files should be logged as they are 205 * encountered. This can sometimes be useful to catch typeos. 206 */ 207 public void setShowIgnoredFiles(boolean showIgnoredFiles) { 208 this.showIgnoredFiles = showIgnoredFiles; 209 } 210 211 public String[] getJarDirectoryNames() { 212 return jarDirectoryNames; 213 } 214 215 /** 216 * Sets the names of the directories to be treated as folders of jars or 217 * class loader files. Defaults to "lib", "classes". If you wish to disable 218 * the use of lib and classes as being special folders containing jars or 219 * config files then just set this property to null or an empty array. 220 */ 221 public void setJarDirectoryNames(String[] jarDirectoryNames) { 222 this.jarDirectoryNames = jarDirectoryNames; 223 } 224 225 // Implementation methods 226 // ------------------------------------------------------------------------- 227 protected void processDirectory(String parentName, ClassLoader classLoader, ApplicationContext parentContext, File directory) 228 throws ServiceAlreadyExistsException, ServiceRegistrationException, BeansException, IOException { 229 log.debug("Processing directory: " + directory); 230 File[] files = directory.listFiles(); 231 if (files == null) { 232 return; 233 } 234 235 // lets create a new classloader... 236 Properties properties = new Properties(); 237 Map fileMap = new LinkedHashMap(); 238 Map directoryMap = new LinkedHashMap(); 239 for (int i = 0; i < files.length; i++) { 240 File file = files[i]; 241 if (isClassLoaderDirectory(file)) { 242 classLoader = createChildClassLoader(parentName, file, classLoader); 243 log.debug("Created class loader: " + classLoader); 244 } 245 else if (isXBeansPropertyFile(file)) { 246 properties.load(new FileInputStream(file)); 247 } 248 else { 249 if (file.isDirectory()) { 250 directoryMap.put(file.getName(), file); 251 } 252 else { 253 fileMap.put(file.getName(), file); 254 } 255 } 256 } 257 258 String[] names = getFileNameOrder(properties); 259 260 // Lets process the files first 261 262 // process ordered files first in order 263 for (int i = 0; i < names.length; i++) { 264 String orderName = names[i]; 265 File file = (File) fileMap.remove(orderName); 266 if (file != null) { 267 String name = getChildName(parentName, file); 268 createServiceForFile(name, file, classLoader, parentContext); 269 } 270 } 271 272 // now lets process whats left 273 for (Iterator iter = fileMap.values().iterator(); iter.hasNext();) { 274 File file = (File) iter.next(); 275 String name = getChildName(parentName, file); 276 createServiceForFile(name, file, classLoader, parentContext); 277 } 278 279 // now lets process the child directories 280 281 // process ordered files first in order 282 for (int i = 0; i < names.length; i++) { 283 String orderName = names[i]; 284 File file = (File) directoryMap.remove(orderName); 285 if (file != null) { 286 String name = getChildName(parentName, file); 287 processDirectory(name, classLoader, parentContext, file); 288 } 289 } 290 291 // now lets process whats left 292 for (Iterator iter = directoryMap.values().iterator(); iter.hasNext();) { 293 File file = (File) iter.next(); 294 String name = getChildName(parentName, file); 295 processDirectory(name, classLoader, parentContext, file); 296 } 297 } 298 299 protected ClassLoader createChildClassLoader(String name, File dir, ClassLoader parentClassLoader) throws MalformedURLException { 300 List urls = new ArrayList(); 301 if (verbose) { 302 try { 303 log.info("Adding to classpath: " + dir.getCanonicalPath()); 304 } 305 catch (Exception e) { 306 } 307 } 308 File[] files = dir.listFiles(); 309 if (files != null) { 310 for (int j = 0; j < files.length; j++) { 311 if (files[j].getName().endsWith(".zip") || files[j].getName().endsWith(".jar")) { 312 if (verbose) { 313 try { 314 log.info("Adding to classpath: " + name + " jar: " + files[j].getCanonicalPath()); 315 } 316 catch (Exception e) { 317 } 318 } 319 urls.add(files[j].toURL()); 320 } 321 } 322 } 323 URL u[] = new URL[urls.size()]; 324 urls.toArray(u); 325 return new NamedClassLoader(name + ".ClassLoader", u, parentClassLoader); 326 } 327 328 protected void createServiceForFile(String name, File file, ClassLoader classLoader, ApplicationContext parentContext) 329 throws ServiceAlreadyExistsException, ServiceRegistrationException, BeansException, IOException { 330 if (isSpringConfigFile(file)) { 331 // make the current directory available to spring files 332 System.setProperty("xbean.current.file", file.getAbsolutePath()); 333 System.setProperty("xbean.current.dir", file.getParent()); 334 335 // we have to set the context class loader while loading the spring 336 // file 337 // so we can auto-discover xbean configurations 338 // and perform introspection 339 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); 340 Thread.currentThread().setContextClassLoader(classLoader); 341 log.debug("Loading file: " + file + " using classLoader: " + classLoader); 342 try { 343 AbstractXmlApplicationContext applicationContext = new ResourceXmlApplicationContext(new FileSystemResource(file), xmlPreprocessors, parentContext, beanFactoryPostProcessors, false); 344 applicationContext.setDisplayName(name); 345 applicationContext.setClassLoader(classLoader); 346 347 ServiceFactory serviceFactory = new SpringConfigurationServiceFactory(applicationContext); 348 349 log.info("Registering spring services service: " + name + " from: " + file.getAbsolutePath() + " into the Kernel"); 350 351 kernel.registerService(new StringServiceName(name), serviceFactory); 352 } 353 finally { 354 Thread.currentThread().setContextClassLoader(oldClassLoader); 355 } 356 } 357 else { 358 if (showIgnoredFiles) { 359 log.info("Ignoring file: " + file.getName() + " in directory: " + file.getParent()); 360 } 361 } 362 } 363 364 protected boolean isClassLoaderDirectory(File file) { 365 if (jarDirectoryNames != null) { 366 for (int i = 0; i < jarDirectoryNames.length; i++) { 367 String name = jarDirectoryNames[i]; 368 if (file.getName().equalsIgnoreCase(name)) { 369 return true; 370 } 371 } 372 } 373 return false; 374 } 375 376 protected boolean isSpringConfigFile(File file) { 377 String fileName = file.getName(); 378 return fileName.endsWith("spring.xml") || fileName.endsWith("xbean.xml"); 379 } 380 381 private boolean isXBeansPropertyFile(File file) { 382 String fileName = file.getName(); 383 return fileName.equalsIgnoreCase("xbean.properties"); 384 } 385 386 /** 387 * Extracts the file names from the properties file for the order in which 388 * things should be deployed 389 */ 390 protected String[] getFileNameOrder(Properties properties) { 391 String order = properties.getProperty("order", ""); 392 List list = new ArrayList(); 393 StringTokenizer iter = new StringTokenizer(order, ","); 394 while (iter.hasMoreTokens()) { 395 list.add(iter.nextToken()); 396 } 397 String[] answer = new String[list.size()]; 398 list.toArray(answer); 399 return answer; 400 } 401 402 protected String getChildName(String parentName, File file) { 403 StringBuffer buffer = new StringBuffer(parentName); 404 if (parentName.length() > 0) { 405 buffer.append("/"); 406 } 407 buffer.append(file.getName()); 408 return buffer.toString(); 409 } 410 411 }