1
2
3
4
5
6
7
8
9
10
11 package org.mule.module.launcher;
12
13 import org.mule.MuleServer;
14 import org.mule.api.MuleContext;
15 import org.mule.api.MuleException;
16 import org.mule.api.config.ConfigurationBuilder;
17 import org.mule.api.config.MuleProperties;
18 import org.mule.api.context.notification.MuleContextNotificationListener;
19 import org.mule.config.builders.AutoConfigurationBuilder;
20 import org.mule.config.builders.SimpleConfigurationBuilder;
21 import org.mule.config.i18n.CoreMessages;
22 import org.mule.config.i18n.MessageFactory;
23 import org.mule.context.DefaultMuleContextFactory;
24 import org.mule.context.notification.MuleContextNotification;
25 import org.mule.context.notification.NotificationException;
26 import org.mule.module.launcher.descriptor.ApplicationDescriptor;
27 import org.mule.module.reboot.MuleContainerBootstrapUtils;
28 import org.mule.util.ClassUtils;
29 import org.mule.util.FileUtils;
30 import org.mule.util.StringUtils;
31
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.ScheduledExecutorService;
39 import java.util.concurrent.TimeUnit;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43
44 public class DefaultMuleApplication implements Application
45 {
46
47 protected static final int DEFAULT_RELOAD_CHECK_INTERVAL_MS = 3000;
48 protected static final String ANCHOR_FILE_BLURB = "Delete this file while Mule is running to undeploy this app in a clean way.";
49
50 protected transient final Log logger = LogFactory.getLog(getClass());
51
52 protected ScheduledExecutorService watchTimer;
53
54 private String appName;
55 private MuleContext muleContext;
56 private ClassLoader deploymentClassLoader;
57 protected ApplicationDescriptor descriptor;
58
59 protected String[] absoluteResourcePaths;
60
61 public DefaultMuleApplication(String appName)
62 {
63 this.appName = appName;
64 }
65
66 public void install()
67 {
68 if (logger.isInfoEnabled())
69 {
70 logger.info("Installing application: " + appName);
71 }
72
73 AppBloodhound bh = new DefaultAppBloodhound();
74 try
75 {
76 descriptor = bh.fetch(getAppName());
77 }
78 catch (IOException e)
79 {
80 throw new InstallException(MessageFactory.createStaticMessage("Failed to parse the application deployment descriptor"), e);
81 }
82
83
84 final String[] configResources = descriptor.getConfigResources();
85 absoluteResourcePaths = new String[configResources.length];
86 for (int i = 0; i < configResources.length; i++)
87 {
88 String resource = configResources[i];
89 final File file = toAbsoluteFile(resource);
90 if (!file.exists())
91 {
92 throw new InstallException(
93 MessageFactory.createStaticMessage(String.format("Config for app '%s' not found: %s", getAppName(), file))
94 );
95 }
96
97 absoluteResourcePaths[i] = file.getAbsolutePath();
98 }
99
100 createDeploymentClassLoader();
101 }
102
103 public String getAppName()
104 {
105 return appName;
106 }
107
108 public void setAppName(String appName)
109 {
110 this.appName = appName;
111 }
112
113 public void start()
114 {
115 if (logger.isInfoEnabled())
116 {
117 logger.info("Starting application: " + appName);
118 }
119
120 try
121 {
122 this.muleContext.start();
123
124 File marker = new File(MuleContainerBootstrapUtils.getMuleAppsDir(), String.format("%s-anchor.txt", getAppName()));
125 FileUtils.writeStringToFile(marker, ANCHOR_FILE_BLURB);
126 }
127 catch (MuleException e)
128 {
129
130 throw new DeploymentStartException(MessageFactory.createStaticMessage(appName), e);
131 }
132 catch (IOException e)
133 {
134
135 throw new DeploymentStartException(MessageFactory.createStaticMessage(appName), e);
136 }
137 }
138
139 public void init()
140 {
141 if (logger.isInfoEnabled())
142 {
143 logger.info("Initializing application: " + appName);
144 }
145
146 String configBuilderClassName = null;
147 try
148 {
149
150
151 final String builderFromDesc = descriptor.getConfigurationBuilder();
152 if ("spring".equalsIgnoreCase(builderFromDesc))
153 {
154 configBuilderClassName = ApplicationDescriptor.CLASSNAME_SPRING_CONFIG_BUILDER;
155 }
156 else if (builderFromDesc == null)
157 {
158 configBuilderClassName = AutoConfigurationBuilder.class.getName();
159 }
160 else
161 {
162 configBuilderClassName = builderFromDesc;
163 }
164
165 ConfigurationBuilder cfgBuilder = (ConfigurationBuilder) ClassUtils.instanciateClass(
166 configBuilderClassName, new Object[] {absoluteResourcePaths}, getDeploymentClassLoader());
167
168 if (!cfgBuilder.isConfigured())
169 {
170
171 List<ConfigurationBuilder> builders = new ArrayList<ConfigurationBuilder>(2);
172
173 final Map<String,String> appProperties = descriptor.getAppProperties();
174
175
176 appProperties.put(MuleProperties.APP_HOME_DIRECTORY_PROPERTY,
177 new File(MuleContainerBootstrapUtils.getMuleAppsDir(), getAppName()).getAbsolutePath());
178
179 builders.add(new SimpleConfigurationBuilder(appProperties));
180
181
182
183
184 if (ClassUtils.isClassOnPath(MuleServer.CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, getClass()))
185 {
186 Object configBuilder = ClassUtils.instanciateClass(
187 MuleServer.CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, ClassUtils.NO_ARGS, getClass());
188 builders.add((ConfigurationBuilder) configBuilder);
189 }
190
191 builders.add(cfgBuilder);
192
193 DefaultMuleContextFactory muleContextFactory = new DefaultMuleContextFactory();
194 this.muleContext = muleContextFactory.createMuleContext(builders, new ApplicationMuleContextBuilder(descriptor));
195
196 if (descriptor.isRedeploymentEnabled())
197 {
198 createRedeployMonitor();
199 }
200 }
201 }
202 catch (Exception e)
203 {
204 throw new DeploymentInitException(CoreMessages.failedToLoad(configBuilderClassName), e);
205 }
206 }
207
208 public MuleContext getMuleContext()
209 {
210 return muleContext;
211 }
212
213 public ClassLoader getDeploymentClassLoader()
214 {
215 return this.deploymentClassLoader;
216 }
217
218 public void dispose()
219 {
220 if (muleContext == null)
221 {
222 if (logger.isInfoEnabled())
223 {
224 logger.info("MuleContext not created, nothing to dispose of");
225 }
226 return;
227 }
228
229 if (muleContext.isStarted() && !muleContext.isDisposed())
230 {
231 stop();
232 }
233 if (logger.isInfoEnabled())
234 {
235 logger.info("Disposing application: " + appName);
236 }
237
238 muleContext.dispose();
239 muleContext = null;
240
241 Thread.currentThread().setContextClassLoader(null);
242 }
243
244 public void redeploy()
245 {
246 if (logger.isInfoEnabled())
247 {
248 logger.info("Redeploying application: " + appName);
249 }
250 dispose();
251 install();
252
253
254 final ClassLoader cl = getDeploymentClassLoader();
255 Thread.currentThread().setContextClassLoader(cl);
256
257 init();
258 start();
259
260
261 Thread.currentThread().setContextClassLoader(null);
262 }
263
264 public void stop()
265 {
266 if (this.muleContext == null)
267 {
268
269 return;
270 }
271 if (logger.isInfoEnabled())
272 {
273 logger.info("Stopping application: " + appName);
274 }
275 try
276 {
277 this.muleContext.stop();
278 }
279 catch (MuleException e)
280 {
281
282 throw new DeploymentStopException(MessageFactory.createStaticMessage(appName), e);
283 }
284 }
285
286 @Override
287 public String toString()
288 {
289 return String.format("%s[%s]@%s", getClass().getName(),
290 appName,
291 Integer.toHexString(System.identityHashCode(this)));
292 }
293
294 protected void createDeploymentClassLoader()
295 {
296 final String domain = descriptor.getDomain();
297 ClassLoader parent;
298
299 if (StringUtils.isBlank(domain) || DefaultMuleSharedDomainClassLoader.DEFAULT_DOMAIN_NAME.equals(domain))
300 {
301 parent = new DefaultMuleSharedDomainClassLoader(getClass().getClassLoader());
302 }
303 else
304 {
305
306 parent = new MuleSharedDomainClassLoader(domain, getClass().getClassLoader());
307 }
308
309 this.deploymentClassLoader = new MuleApplicationClassLoader(appName, parent);
310 }
311
312 protected void createRedeployMonitor() throws NotificationException
313 {
314 if (logger.isInfoEnabled())
315 {
316 logger.info("Monitoring for hot-deployment: " + new File(absoluteResourcePaths [0]));
317 }
318
319 final AbstractFileWatcher watcher = new ConfigFileWatcher(new File(absoluteResourcePaths [0]));
320
321
322 muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>()
323 {
324
325 public void onNotification(MuleContextNotification notification)
326 {
327 final int action = notification.getAction();
328 switch (action)
329 {
330 case MuleContextNotification.CONTEXT_STARTED:
331 scheduleConfigMonitor(watcher);
332 break;
333 case MuleContextNotification.CONTEXT_STOPPING:
334 watchTimer.shutdownNow();
335 muleContext.unregisterListener(this);
336 break;
337 }
338 }
339 });
340 }
341
342 protected void scheduleConfigMonitor(AbstractFileWatcher watcher)
343 {
344 final int reloadIntervalMs = DEFAULT_RELOAD_CHECK_INTERVAL_MS;
345 watchTimer = Executors.newSingleThreadScheduledExecutor(new ConfigChangeMonitorThreadFactory(appName));
346
347 watchTimer.scheduleWithFixedDelay(watcher, reloadIntervalMs, reloadIntervalMs, TimeUnit.MILLISECONDS);
348
349 if (logger.isInfoEnabled())
350 {
351 logger.info("Reload interval: " + reloadIntervalMs);
352 }
353 }
354
355
356
357
358
359
360 protected File toAbsoluteFile(String path)
361 {
362 final String muleHome = System.getProperty(MuleProperties.MULE_HOME_DIRECTORY_PROPERTY);
363 String configPath = String.format("%s/apps/%s/%s", muleHome, getAppName(), path);
364 return new File(configPath);
365 }
366
367 protected class ConfigFileWatcher extends AbstractFileWatcher
368 {
369 public ConfigFileWatcher(File watchedResource)
370 {
371 super(watchedResource);
372 }
373
374 @Override
375 protected synchronized void onChange(File file)
376 {
377 if (logger.isInfoEnabled())
378 {
379 logger.info("================== Reloading " + file);
380 }
381
382
383 final ClassLoader cl = getDeploymentClassLoader();
384 Thread.currentThread().setContextClassLoader(cl);
385 redeploy();
386 }
387 }
388 }