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