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    }