1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 * <context-param>
57 * <param-name>contextClass</param-name>
58 * <param-value>org.apache.struts2.spring.ClassReloadingXMLWebApplicationContext</param-value>
59 * </context-param>
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 * <dependency>
64 * <groupId>org.apache.commons</groupId>
65 * <artifactId>commons-jci-fam</artifactId>
66 * <version>1.0</version>
67 * <optional>true</optional>
68 * </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
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
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
101 for (String watch : watchList) {
102 File file = new File(watch);
103
104
105 if (!file.isAbsolute())
106 file = new File(servletContext.getRealPath(watch));
107
108 if (watch.endsWith(".jar")) {
109 classLoader.addResourceStore(new JarResourceStore(file));
110
111 fam.addListener(file, this);
112 LOG.debug("Watching [#0] for changes", file.getAbsolutePath());
113 } else {
114
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
122 fam.addListener(dir, this);
123 LOG.debug("Watching [#0] for changes", dir.getAbsolutePath());
124 }
125 }
126 }
127
128 beanFactory = new ClassReloadingBeanFactory();
129 beanFactory.setInstantiationStrategy(new ClassReloadingInstantiationStrategy());
130 beanFactory.setBeanClassLoader(classLoader);
131
132
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
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 }