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 }