View Javadoc

1   /*
2    * $Id: ClassReloadingXMLWebApplicationContext.java 811701 2009-09-05 19:07:51Z musachy $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts2.spring;
22  
23  import com.opensymphony.xwork2.util.classloader.FileResourceStore;
24  import com.opensymphony.xwork2.util.classloader.JarResourceStore;
25  import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader;
26  import com.opensymphony.xwork2.util.logging.Logger;
27  import com.opensymphony.xwork2.util.logging.LoggerFactory;
28  import org.apache.commons.jci.monitor.FilesystemAlterationListener;
29  import org.apache.commons.jci.monitor.FilesystemAlterationMonitor;
30  import org.apache.commons.jci.monitor.FilesystemAlterationObserver;
31  import org.apache.commons.lang.xwork.StringUtils;
32  import org.apache.struts2.dispatcher.Dispatcher;
33  import org.springframework.web.context.support.XmlWebApplicationContext;
34  import org.springframework.beans.factory.support.DefaultListableBeanFactory;
35  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
36  import org.springframework.beans.BeansException;
37  
38  import javax.servlet.ServletContext;
39  import java.io.File;
40  import java.util.List;
41  import java.util.ArrayList;
42  import java.util.Set;
43  import java.util.HashSet;
44  import java.util.regex.Pattern;
45  
46  /***
47   * This class can be used instead of XmlWebApplicationContext, and it will watch jar files and directories for changes
48   * and reload then changed classes.
49   * <br />
50   * To use this class:
51   * <ul>
52   * <li>Set "struts.devMode" to "true" </li>
53   * <li>Set "struts.class.reloading.watchList" to a comma separated list of directories, or jar files (absolute paths)</p>
54   * <li>Add this to web.xml:
55   * <pre>
56   *  &lt;context-param&gt;
57   *       &lt;param-name&gt;contextClass&lt;/param-name&gt;
58   *       &lt;param-value&gt;org.apache.struts2.spring.ClassReloadingXMLWebApplicationContext&lt;/param-value&gt;
59   *   &lt;/context-param&gt;
60   *  </li>
61   * <li>Add Apache Commons JCI FAM to the classpath. If you are using maven, add this to pom.xml:
62   *  <pre>
63   *  &lt;dependency&gt;
64   *       &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
65   *       &lt;artifactId&gt;commons-jci-fam&lt;/artifactId&gt;
66   *       &lt;version&gt;1.0&lt;/version&gt;
67   *       &lt;optional&gt;true&lt;/optional&gt;
68   *  &lt;/dependency>
69   *  </pre>
70   * </li>
71   * </ul>
72   */
73  public class ClassReloadingXMLWebApplicationContext extends XmlWebApplicationContext implements FilesystemAlterationListener {
74      private static final Logger LOG = LoggerFactory.getLogger(ClassReloadingXMLWebApplicationContext.class);
75  
76      protected ReloadingClassLoader classLoader;
77      protected FilesystemAlterationMonitor fam;
78  
79      protected ClassReloadingBeanFactory beanFactory;
80      //reload the runtime configuration when a change is detected
81      private boolean reloadConfig;
82  
83      public void setupReloading(String[] watchList, String acceptClasses, ServletContext servletContext, boolean reloadConfig) {
84          this.reloadConfig = reloadConfig;
85  
86          classLoader = new ReloadingClassLoader(ClassReloadingXMLWebApplicationContext.class.getClassLoader());
87  
88          //make a list of accepted classes
89          if (StringUtils.isNotBlank(acceptClasses)) {
90              String[] splitted = acceptClasses.split(",");
91              Set<Pattern> patterns = new HashSet<Pattern>(splitted.length);
92              for (String pattern : splitted)
93                  patterns.add(Pattern.compile(pattern));
94  
95              classLoader.setAccepClasses(patterns);
96          }
97  
98          fam = new FilesystemAlterationMonitor();
99  
100         //setup stores
101         for (String watch : watchList) {
102             File file = new File(watch);
103 
104             //make it absolute, if it is a relative path
105             if (!file.isAbsolute())
106                 file = new File(servletContext.getRealPath(watch));
107 
108             if (watch.endsWith(".jar")) {
109                 classLoader.addResourceStore(new JarResourceStore(file));
110                 //register with the fam
111                 fam.addListener(file, this);
112                 LOG.debug("Watching [#0] for changes", file.getAbsolutePath());
113             } else {
114                 //get all subdirs
115                 List<File> dirs = new ArrayList<File>();
116                 getAllPaths(file, dirs);
117 
118                 classLoader.addResourceStore(new FileResourceStore(file));
119 
120                 for (File dir : dirs) {
121                     //register with the fam
122                     fam.addListener(dir, this);
123                     LOG.debug("Watching [#0] for changes", dir.getAbsolutePath());
124                 }
125             }
126         }
127         //setup the bean factory
128         beanFactory = new ClassReloadingBeanFactory();
129         beanFactory.setInstantiationStrategy(new ClassReloadingInstantiationStrategy());
130         beanFactory.setBeanClassLoader(classLoader);
131 
132         //start watch thread
133         fam.start();
134     }
135 
136     /***
137      * If root is a dir, find al the subdir paths
138      */
139     private void getAllPaths(File root, List<File> dirs) {
140         dirs.add(root);
141 
142         if (root.isDirectory()) {
143             File[] files = root.listFiles();
144             if (files != null) {
145                 for (File file : files) {
146                     if (file.isDirectory()) {
147                         getAllPaths(file, dirs);
148                     }
149                 }
150             }
151         }
152     }
153 
154     public void close() {
155         super.close();
156 
157         if (fam != null) {
158             fam.removeListener(this);
159             fam.stop();
160         }
161     }
162 
163     public void refresh() throws BeansException, IllegalStateException {
164         if (classLoader != null) {
165             classLoader.reload();
166         }
167 
168         super.refresh();
169     }
170 
171     protected DefaultListableBeanFactory createBeanFactory() {
172         return beanFactory != null ? beanFactory : super.createBeanFactory();
173     }
174 
175     protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
176         super.prepareBeanFactory(beanFactory);
177 
178         //overwrite the class loader in the bean factory
179         if (classLoader != null)
180             beanFactory.setBeanClassLoader(classLoader);
181     }
182 
183     public void onDirectoryChange(File file) {
184         reload(file);
185     }
186 
187     public void onDirectoryCreate(File file) {
188         reload(file);
189     }
190 
191     public void onDirectoryDelete(File file) {
192     }
193 
194     public void onFileChange(File file) {
195         reload(file);
196     }
197 
198     public void onFileCreate(File file) {
199         reload(file);
200     }
201 
202     private void reload(File file) {
203         if (classLoader != null) {
204             final boolean debugEnabled = LOG.isDebugEnabled();
205             if (debugEnabled)
206                 LOG.debug("Change detected in file [#0], reloading class loader", file.getAbsolutePath());
207             classLoader.reload();
208             if (reloadConfig && Dispatcher.getInstance() != null) {
209                 if (debugEnabled)
210                     LOG.debug("Change detected in file [#0], reloading configuration", file.getAbsolutePath());
211                 Dispatcher.getInstance().getConfigurationManager().reload();
212             }
213         }
214     }
215 
216     public void onFileDelete(File file) {
217     }
218 
219     public void onStart(FilesystemAlterationObserver filesystemAlterationObserver) {
220     }
221 
222     public void onStop(FilesystemAlterationObserver filesystemAlterationObserver) {
223     }
224 
225     public ReloadingClassLoader getReloadingClassLoader() {
226         return classLoader;
227     }
228 }