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