1
2
3
4
5
6
7
8
9
10
11 package org.mule.module.launcher;
12
13 import org.mule.config.StartupContext;
14 import org.mule.config.i18n.MessageFactory;
15 import org.mule.module.launcher.application.Application;
16 import org.mule.module.launcher.application.ApplicationFactory;
17 import org.mule.module.launcher.util.DebuggableReentrantLock;
18 import org.mule.module.launcher.util.ElementAddedEvent;
19 import org.mule.module.launcher.util.ElementRemovedEvent;
20 import org.mule.module.launcher.util.ObservableList;
21 import org.mule.module.reboot.MuleContainerBootstrapUtils;
22 import org.mule.util.CollectionUtils;
23 import org.mule.util.StringUtils;
24
25 import java.beans.PropertyChangeEvent;
26 import java.beans.PropertyChangeListener;
27 import java.io.File;
28 import java.io.IOException;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.HashMap;
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 import java.util.concurrent.locks.ReentrantLock;
41
42 import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
43 import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
44 import org.apache.commons.io.filefilter.DirectoryFileFilter;
45 import org.apache.commons.io.filefilter.SuffixFileFilter;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48
49 public class DeploymentService
50 {
51 public static final String APP_ANCHOR_SUFFIX = "-anchor.txt";
52 protected static final int DEFAULT_CHANGES_CHECK_INTERVAL_MS = 5000;
53
54 protected ScheduledExecutorService appDirMonitorTimer;
55
56 protected transient final Log logger = LogFactory.getLog(getClass());
57 protected MuleDeployer deployer;
58 protected ApplicationFactory appFactory;
59
60 private ReentrantLock lock = new DebuggableReentrantLock(true);
61
62 private ObservableList<Application> applications = new ObservableList<Application>();
63 private Map<URL, Long> zombieMap = new HashMap<URL, Long>();
64 private ObservableList<URL> zombieLand = new ObservableList<URL>();
65
66 public DeploymentService()
67 {
68 deployer = new DefaultMuleDeployer(this);
69 appFactory = new ApplicationFactory(this);
70 }
71
72 public void start()
73 {
74
75 final Map<String, Object> options = StartupContext.get().getStartupOptions();
76 String appString = (String) options.get("app");
77
78 final File appsDir = MuleContainerBootstrapUtils.getMuleAppsDir();
79
80
81 String[] appAnchors = appsDir.list(new SuffixFileFilter(APP_ANCHOR_SUFFIX));
82 for (String anchor : appAnchors)
83 {
84
85 new File(appsDir, anchor).delete();
86 }
87
88 String[] apps;
89
90
91 final boolean explicitAppSet = appString != null;
92
93 if (!explicitAppSet)
94 {
95
96 final String[] zips = appsDir.list(new SuffixFileFilter(".zip"));
97 Arrays.sort(zips);
98 for (String zip : zips)
99 {
100 try
101 {
102
103 deployer.installFromAppDir(zip);
104 }
105 catch (Throwable t)
106 {
107 logger.error(String.format("Failed to install app from archive '%s'", zip), t);
108 File appFile = new File(appsDir, zip);
109 try
110 {
111 addZombie(appFile.toURL());
112 }
113 catch (MalformedURLException muex)
114 {
115 if (logger.isDebugEnabled())
116 {
117 logger.debug(muex);
118 }
119 }
120 }
121 }
122
123
124
125 apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
126 Arrays.sort(apps);
127 }
128 else
129 {
130 apps = appString.split(":");
131 }
132
133 for (String app : apps)
134 {
135 final Application a;
136 try
137 {
138 a = appFactory.createApp(app);
139 applications.add(a);
140 }
141 catch (IOException e)
142 {
143
144 e.printStackTrace();
145 }
146 }
147
148
149 for (Application application : applications)
150 {
151 try
152 {
153 deployer.deploy(application);
154 }
155 catch (Throwable t)
156 {
157
158 t.printStackTrace();
159 }
160 }
161
162
163
164 if (!explicitAppSet)
165 {
166 scheduleChangeMonitor(appsDir);
167 }
168 }
169
170 protected void scheduleChangeMonitor(File appsDir)
171 {
172 final int reloadIntervalMs = DEFAULT_CHANGES_CHECK_INTERVAL_MS;
173 appDirMonitorTimer = Executors.newSingleThreadScheduledExecutor(new AppDeployerMonitorThreadFactory());
174
175 appDirMonitorTimer.scheduleWithFixedDelay(new AppDirWatcher(appsDir),
176 0,
177 reloadIntervalMs,
178 TimeUnit.MILLISECONDS);
179
180 if (logger.isInfoEnabled())
181 {
182 logger.info("Application directory check interval: " + reloadIntervalMs);
183 }
184 }
185
186 public void stop()
187 {
188 if (appDirMonitorTimer != null)
189 {
190 appDirMonitorTimer.shutdownNow();
191 }
192
193
194 Collections.reverse(applications);
195 for (Application application : applications)
196 {
197 try
198 {
199 application.stop();
200 application.dispose();
201 }
202 catch (Throwable t)
203 {
204
205 t.printStackTrace();
206 }
207 }
208
209 }
210
211
212
213
214
215 public Application findApplication(String appName)
216 {
217 return (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
218 }
219
220
221
222
223 public List<Application> getApplications()
224 {
225 return Collections.unmodifiableList(applications);
226 }
227
228
229
230
231 public Map<URL, Long> getZombieMap()
232 {
233 return zombieMap;
234 }
235
236
237
238 protected MuleDeployer getDeployer()
239 {
240 return deployer;
241 }
242
243 public void setDeployer(MuleDeployer deployer)
244 {
245 this.deployer = deployer;
246 }
247
248 public ApplicationFactory getAppFactory()
249 {
250 return appFactory;
251 }
252
253 public ReentrantLock getLock() {
254 return lock;
255 }
256
257 public void onApplicationInstalled(Application a)
258 {
259 applications.add(a);
260 }
261
262 protected void undeploy(Application app)
263 {
264 if (logger.isInfoEnabled())
265 {
266 logger.info("================== Request to Undeploy Application: " + app.getAppName());
267 }
268
269 deployer.undeploy(app);
270 }
271
272 public void undeploy(String appName)
273 {
274 Application app = (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
275 applications.remove(app);
276 deployer.undeploy(app);
277 }
278
279 public void deploy(URL appArchiveUrl) throws IOException
280 {
281 final Application application;
282 try
283 {
284 application = deployer.installFrom(appArchiveUrl);
285 applications.add(application);
286 deployer.deploy(application);
287 }
288 catch (Throwable t)
289 {
290 addZombie(appArchiveUrl);
291 if (t instanceof DeploymentException)
292 {
293
294 throw ((DeploymentException) t);
295 }
296
297 final String msg = "Failed to deploy from URL: " + appArchiveUrl;
298 throw new DeploymentException(MessageFactory.createStaticMessage(msg), t);
299 }
300 }
301
302 protected void addZombie(URL appArchiveUrl)
303 {
304
305 if (appArchiveUrl == null)
306 {
307 return;
308 }
309
310 long lastModified = -1;
311
312 if ("file".equals(appArchiveUrl.getProtocol()))
313 {
314 lastModified = new File(appArchiveUrl.getFile()).lastModified();
315 }
316
317 zombieMap.put(appArchiveUrl, lastModified);
318 }
319
320
321
322
323 protected class AppDirWatcher implements Runnable
324 {
325 protected File appsDir;
326
327
328 protected String[] appAnchors = new String[0];
329 protected volatile boolean dirty;
330
331 public AppDirWatcher(final File appsDir)
332 {
333 this.appsDir = appsDir;
334 applications.addPropertyChangeListener(new PropertyChangeListener()
335 {
336 public void propertyChange(PropertyChangeEvent e)
337 {
338 if (e instanceof ElementAddedEvent || e instanceof ElementRemovedEvent)
339 {
340 if (logger.isDebugEnabled())
341 {
342 logger.debug("Deployed applications set has been modified, flushing state.");
343 }
344 dirty = true;
345 }
346 }
347 });
348 }
349
350
351
352
353
354 public void run()
355 {
356 try
357 {
358 if (logger.isDebugEnabled())
359 {
360 logger.debug("Checking for changes...");
361 }
362
363
364 if (!lock.tryLock(0, TimeUnit.SECONDS))
365 {
366 if (logger.isDebugEnabled())
367 {
368 logger.debug("Another deployment operation in progress, will skip this cycle. Owner thread: " +
369 ((DebuggableReentrantLock) lock).getOwner());
370 }
371 return;
372 }
373
374
375
376 final String[] zips = appsDir.list(new SuffixFileFilter(".zip"));
377 String[] apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
378
379
380
381 String[] currentAnchors = appsDir.list(new SuffixFileFilter(APP_ANCHOR_SUFFIX));
382 if (logger.isDebugEnabled())
383 {
384 StringBuilder sb = new StringBuilder();
385 sb.append(String.format("Current anchors:%n"));
386 for (String currentAnchor : currentAnchors)
387 {
388 sb.append(String.format(" %s%n", currentAnchor));
389 }
390 logger.debug(sb.toString());
391 }
392 @SuppressWarnings("unchecked")
393 final Collection<String> deletedAnchors = CollectionUtils.subtract(Arrays.asList(appAnchors), Arrays.asList(currentAnchors));
394 if (logger.isDebugEnabled())
395 {
396 StringBuilder sb = new StringBuilder();
397 sb.append(String.format("Deleted anchors:%n"));
398 for (String deletedAnchor : deletedAnchors)
399 {
400 sb.append(String.format(" %s%n", deletedAnchor));
401 }
402 logger.debug(sb.toString());
403 }
404
405 for (String deletedAnchor : deletedAnchors)
406 {
407 String appName = StringUtils.removeEnd(deletedAnchor, APP_ANCHOR_SUFFIX);
408 try
409 {
410 if (findApplication(appName) != null)
411 {
412 undeploy(appName);
413 }
414 else if (logger.isDebugEnabled())
415 {
416 logger.debug(String.format("Application [%s] has already been undeployed via API", appName));
417 }
418 }
419 catch (Throwable t)
420 {
421 logger.error("Failed to undeploy application: " + appName, t);
422 }
423 }
424 appAnchors = currentAnchors;
425
426
427
428 for (String zip : zips)
429 {
430 URL url = null;
431 try
432 {
433
434 final String appName = StringUtils.removeEnd(zip, ".zip");
435 Application app = (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
436 if (app != null)
437 {
438 undeploy(appName);
439 }
440 url = new File(appsDir, zip).toURI().toURL();
441 deploy(url);
442 }
443 catch (Throwable t)
444 {
445 logger.error("Failed to deploy application archive: " + zip, t);
446 addZombie(url);
447 }
448 }
449
450
451 if (zips.length > 0 || dirty)
452 {
453 apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
454 }
455
456 Collection deployedAppNames = CollectionUtils.collect(applications, new BeanToPropertyValueTransformer("appName"));
457
458
459 @SuppressWarnings("unchecked")
460 final Collection<String> addedApps = CollectionUtils.subtract(Arrays.asList(apps), deployedAppNames);
461 for (String addedApp : addedApps)
462 {
463 try
464 {
465 onNewExplodedApplication(addedApp);
466 }
467 catch (Throwable t)
468 {
469 logger.error("Failed to deploy exploded application: " + addedApp, t);
470 try
471 {
472 addZombie(new File(appsDir, addedApp).toURI().toURL());
473 }
474 catch (MalformedURLException e)
475 {
476 if (logger.isDebugEnabled())
477 {
478 logger.debug(e);
479 }
480 }
481 }
482 }
483
484 }
485 catch (InterruptedException e)
486 {
487
488 Thread.currentThread().interrupt();
489 }
490 finally
491 {
492 if (lock.isHeldByCurrentThread())
493 {
494 lock.unlock();
495 }
496 dirty = false;
497 }
498 }
499
500
501
502
503 protected void onNewExplodedApplication(String appName) throws Exception
504 {
505 if (logger.isInfoEnabled())
506 {
507 logger.info("================== New Exploded Application: " + appName);
508 }
509
510 Application a = appFactory.createApp(appName);
511
512 onApplicationInstalled(a);
513 deployer.deploy(a);
514 }
515
516 }
517
518 }