Coverage Report - org.mule.module.launcher.DeploymentService
 
Classes in this File Line Coverage Branch Coverage Complexity
DeploymentService
0%
0/158
0%
0/46
0
DeploymentService$AppDirWatcher
0%
0/89
0%
0/46
0
DeploymentService$AppDirWatcher$1
0%
0/6
0%
0/6
0
DeploymentService$StartupListener
N/A
N/A
0
 
 1  
 /*
 2  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 3  
  * The software in this package is published under the terms of the CPAL v1.0
 4  
  * license, a copy of which has been included with this distribution in the
 5  
  * LICENSE.txt file.
 6  
  */
 7  
 package org.mule.module.launcher;
 8  
 
 9  
 import static org.mule.util.SplashScreen.miniSplash;
 10  
 import org.mule.config.StartupContext;
 11  
 import org.mule.config.i18n.MessageFactory;
 12  
 import org.mule.module.launcher.application.Application;
 13  
 import org.mule.module.launcher.application.ApplicationFactory;
 14  
 import org.mule.module.launcher.util.DebuggableReentrantLock;
 15  
 import org.mule.module.launcher.util.ElementAddedEvent;
 16  
 import org.mule.module.launcher.util.ElementRemovedEvent;
 17  
 import org.mule.module.launcher.util.ObservableList;
 18  
 import org.mule.module.reboot.MuleContainerBootstrapUtils;
 19  
 import org.mule.util.ArrayUtils;
 20  
 import org.mule.util.CollectionUtils;
 21  
 import org.mule.util.FileUtils;
 22  
 import org.mule.util.StringUtils;
 23  
 
 24  
 import java.beans.PropertyChangeEvent;
 25  
 import java.beans.PropertyChangeListener;
 26  
 import java.io.File;
 27  
 import java.io.IOException;
 28  
 import java.net.MalformedURLException;
 29  
 import java.net.URL;
 30  
 import java.util.ArrayList;
 31  
 import java.util.Arrays;
 32  
 import java.util.Collection;
 33  
 import java.util.Collections;
 34  
 import java.util.HashMap;
 35  
 import java.util.LinkedList;
 36  
 import java.util.List;
 37  
 import java.util.Map;
 38  
 import java.util.concurrent.CopyOnWriteArrayList;
 39  
 import java.util.concurrent.Executors;
 40  
 import java.util.concurrent.ScheduledExecutorService;
 41  
 import java.util.concurrent.TimeUnit;
 42  
 import java.util.concurrent.locks.ReentrantLock;
 43  
 
 44  
 import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
 45  
 import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
 46  
 import org.apache.commons.io.filefilter.AndFileFilter;
 47  
 import org.apache.commons.io.filefilter.DirectoryFileFilter;
 48  
 import org.apache.commons.io.filefilter.FileFileFilter;
 49  
 import org.apache.commons.io.filefilter.IOFileFilter;
 50  
 import org.apache.commons.io.filefilter.SuffixFileFilter;
 51  
 import org.apache.commons.logging.Log;
 52  
 import org.apache.commons.logging.LogFactory;
 53  
 
 54  0
 public class DeploymentService
 55  
 {
 56  
     public static final String APP_ANCHOR_SUFFIX = "-anchor.txt";
 57  
     public static final String ZIP_FILE_SUFFIX = ".zip";
 58  0
     public static final IOFileFilter ZIP_APPS_FILTER = new AndFileFilter(new SuffixFileFilter(ZIP_FILE_SUFFIX), FileFileFilter.FILE);
 59  
 
 60  
     protected static final int DEFAULT_CHANGES_CHECK_INTERVAL_MS = 5000;
 61  
 
 62  
     protected ScheduledExecutorService appDirMonitorTimer;
 63  
 
 64  0
     protected transient final Log logger = LogFactory.getLog(getClass());
 65  
     protected MuleDeployer deployer;
 66  
     protected ApplicationFactory appFactory;
 67  
     // fair lock
 68  0
     private ReentrantLock lock = new DebuggableReentrantLock(true);
 69  
 
 70  0
     private ObservableList<Application> applications = new ObservableList<Application>();
 71  0
     private Map<URL, Long> zombieMap = new HashMap<URL, Long>();
 72  
 
 73  0
     private List<StartupListener> startupListeners = new ArrayList<StartupListener>();
 74  
 
 75  0
     private List<DeploymentListener> deploymentListeners = new CopyOnWriteArrayList<DeploymentListener>();
 76  
 
 77  
     public DeploymentService()
 78  0
     {
 79  0
         deployer = new DefaultMuleDeployer(this);
 80  0
         appFactory = new ApplicationFactory(this);
 81  0
     }
 82  
 
 83  
     public void start()
 84  
     {
 85  
         // install phase
 86  0
         final Map<String, Object> options = StartupContext.get().getStartupOptions();
 87  0
         String appString = (String) options.get("app");
 88  
 
 89  0
         final File appsDir = MuleContainerBootstrapUtils.getMuleAppsDir();
 90  
 
 91  
         // delete any leftover anchor files from previous unclean shutdowns
 92  0
         String[] appAnchors = appsDir.list(new SuffixFileFilter(APP_ANCHOR_SUFFIX));
 93  0
         for (String anchor : appAnchors)
 94  
         {
 95  
             // ignore result
 96  0
             new File(appsDir, anchor).delete();
 97  
         }
 98  
 
 99  0
         String[] apps = ArrayUtils.EMPTY_STRING_ARRAY;
 100  
 
 101  
         // mule -app app1:app2:app3 will restrict deployment only to those specified apps
 102  0
         final boolean explicitAppSet = appString != null;
 103  
 
 104  0
         DeploymentStatusTracker deploymentStatusTracker = new DeploymentStatusTracker();
 105  0
         addDeploymentListener(deploymentStatusTracker);
 106  
 
 107  0
         StartupSummaryDeploymentListener summaryDeploymentListener = new StartupSummaryDeploymentListener(deploymentStatusTracker);
 108  0
         addStartupListener(summaryDeploymentListener);
 109  
 
 110  0
         if (!explicitAppSet)
 111  
         {
 112  0
             String[] dirApps = appsDir.list(DirectoryFileFilter.DIRECTORY);
 113  0
             apps = (String[]) ArrayUtils.addAll(apps, dirApps);
 114  
 
 115  0
             String[] zipApps = appsDir.list(ZIP_APPS_FILTER);
 116  0
             for (int i = 0; i < zipApps.length; i++)
 117  
             {
 118  0
                 zipApps[i] = StringUtils.removeEndIgnoreCase(zipApps[i], ZIP_FILE_SUFFIX);
 119  
             }
 120  
 
 121  
             // TODO this is a place to put a FQN of the custom sorter (use AND filter)
 122  
             // Add string shortcuts for bundled ones
 123  0
             apps = (String[]) ArrayUtils.addAll(dirApps, zipApps);
 124  0
             Arrays.sort(apps);
 125  0
         }
 126  
         else
 127  
         {
 128  0
             apps = appString.split(":");
 129  
         }
 130  
 
 131  0
         apps = removeDuplicateAppNames(apps);
 132  
 
 133  0
         for (String app : apps)
 134  
         {
 135  
             final Application a;
 136  0
             String appMarker = app;
 137  0
             File applicationFile = null;
 138  
             try
 139  
             {
 140  
                 // if there's a zip, explode and install it
 141  0
                 applicationFile = new File(appsDir, app + ".zip");
 142  0
                 if (applicationFile.exists() && applicationFile.isFile())
 143  
                 {
 144  0
                     appMarker = app + ZIP_FILE_SUFFIX;
 145  0
                     a = deployer.installFromAppDir(applicationFile.getName());
 146  
                 }
 147  
                 else
 148  
                 {
 149  
                     // otherwise just create an app object from a deployed app
 150  0
                     applicationFile = new File(appsDir, appMarker);
 151  0
                     a = appFactory.createApp(app);
 152  
                 }
 153  0
                 applications.add(a);
 154  
             }
 155  0
             catch (Throwable t)
 156  
             {
 157  0
                 fireOnDeploymentFailure(appMarker, t);
 158  
 
 159  
                 try
 160  
                 {
 161  0
                     URL url = applicationFile.toURI().toURL();
 162  0
                     addZombie(url);
 163  
                 }
 164  0
                 catch (MalformedURLException e)
 165  
                 {
 166  0
                     if (logger.isDebugEnabled())
 167  
                     {
 168  0
                         logger.debug("Error adding zombie app", e);
 169  
                     }
 170  0
                 }
 171  0
                 logger.error(String.format("Failed to create application [%s]", appMarker), t);
 172  0
             }
 173  
         }
 174  
 
 175  
 
 176  0
         for (Application application : applications)
 177  
         {
 178  
             try
 179  
             {
 180  0
                 fireOnDeploymentStart(application.getAppName());
 181  0
                 deployer.deploy(application);
 182  0
                 fireOnDeploymentSuccess(application.getAppName());
 183  
             }
 184  0
             catch (Throwable t)
 185  
             {
 186  0
                 fireOnDeploymentFailure(application.getAppName(), t);
 187  
 
 188  
                 // error text has been created by the deployer already
 189  0
                 final String msg = miniSplash(String.format("Failed to deploy app '%s', see below", application.getAppName()));
 190  0
                 logger.error(msg);
 191  0
                 logger.error(t);
 192  0
             }
 193  
         }
 194  
 
 195  0
         for (StartupListener listener : startupListeners)
 196  
         {
 197  
             try
 198  
             {
 199  0
                 listener.onAfterStartup();
 200  
             }
 201  0
             catch (Throwable t)
 202  
             {
 203  0
                 logger.error(t);
 204  0
             }
 205  
         }
 206  
 
 207  
         // only start the monitor thread if we launched in default mode without explicitly
 208  
         // stated applications to launch
 209  0
         if (!explicitAppSet)
 210  
         {
 211  0
             scheduleChangeMonitor(appsDir);
 212  
         }
 213  
         else
 214  
         {
 215  0
             if (logger.isInfoEnabled())
 216  
             {
 217  0
                 logger.info(miniSplash("Mule is up and running in a fixed app set mode"));
 218  
             }
 219  
         }
 220  0
     }
 221  
 
 222  
     private String[] removeDuplicateAppNames(String[] apps)
 223  
     {
 224  0
         List<String> appNames = new LinkedList<String>();
 225  
 
 226  0
         for (String appName : apps)
 227  
         {
 228  0
             if (!appNames.contains(appName))
 229  
             {
 230  0
                 appNames.add(appName);
 231  
             }
 232  
         }
 233  
 
 234  0
         return appNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
 235  
     }
 236  
 
 237  
     protected void scheduleChangeMonitor(File appsDir)
 238  
     {
 239  0
         final int reloadIntervalMs = DEFAULT_CHANGES_CHECK_INTERVAL_MS;
 240  0
         appDirMonitorTimer = Executors.newSingleThreadScheduledExecutor(new AppDeployerMonitorThreadFactory());
 241  
 
 242  0
         appDirMonitorTimer.scheduleWithFixedDelay(new AppDirWatcher(appsDir),
 243  
                                                   0,
 244  
                                                   reloadIntervalMs,
 245  
                                                   TimeUnit.MILLISECONDS);
 246  
 
 247  0
         if (logger.isInfoEnabled())
 248  
         {
 249  0
             logger.info(miniSplash(String.format("Mule is up and kicking (every %dms)", reloadIntervalMs)));
 250  
         }
 251  0
     }
 252  
 
 253  
     public void stop()
 254  
     {
 255  0
         if (appDirMonitorTimer != null)
 256  
         {
 257  0
             appDirMonitorTimer.shutdownNow();
 258  
         }
 259  
 
 260  
         // tear down apps in reverse order
 261  0
         Collections.reverse(applications);
 262  0
         for (Application application : applications)
 263  
         {
 264  
             try
 265  
             {
 266  0
                 application.stop();
 267  0
                 application.dispose();
 268  
             }
 269  0
             catch (Throwable t)
 270  
             {
 271  0
                 logger.error(t);
 272  0
             }
 273  
         }
 274  
 
 275  0
     }
 276  
 
 277  
     /**
 278  
      * Find an active application.
 279  
      * @return null if not found
 280  
      */
 281  
     public Application findApplication(String appName)
 282  
     {
 283  0
         return (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
 284  
     }
 285  
 
 286  
     /**
 287  
      * @return immutable applications list
 288  
      */
 289  
     public List<Application> getApplications()
 290  
     {
 291  0
         return Collections.unmodifiableList(applications);
 292  
     }
 293  
 
 294  
     /**
 295  
      * @return URL/lastModified of apps which previously failed to deploy
 296  
      */
 297  
     public Map<URL, Long> getZombieMap()
 298  
     {
 299  0
         return zombieMap;
 300  
     }
 301  
 
 302  
     protected MuleDeployer getDeployer()
 303  
     {
 304  0
         return deployer;
 305  
     }
 306  
 
 307  
     public void setDeployer(MuleDeployer deployer)
 308  
     {
 309  0
         this.deployer = deployer;
 310  0
     }
 311  
 
 312  
     public void setAppFactory(ApplicationFactory appFactory)
 313  
     {
 314  0
         this.appFactory = appFactory;
 315  0
     }
 316  
 
 317  
     public ApplicationFactory getAppFactory()
 318  
     {
 319  0
         return appFactory;
 320  
     }
 321  
 
 322  
     public ReentrantLock getLock() {
 323  0
         return lock;
 324  
     }
 325  
 
 326  
     public void onApplicationInstalled(Application a)
 327  
     {
 328  0
         applications.add(a);
 329  0
     }
 330  
 
 331  
     protected void undeploy(Application app)
 332  
     {
 333  0
         if (logger.isInfoEnabled())
 334  
         {
 335  0
             logger.info("================== Request to Undeploy Application: " + app.getAppName());
 336  
         }
 337  
 
 338  0
         applications.remove(app);
 339  0
         deployer.undeploy(app);
 340  0
     }
 341  
 
 342  
     public void undeploy(String appName)
 343  
     {
 344  0
         Application app = (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
 345  0
         undeploy(app);
 346  0
     }
 347  
 
 348  
     public void deploy(URL appArchiveUrl) throws IOException
 349  
     {
 350  
         final Application application;
 351  
         try
 352  
         {
 353  0
             application = deployer.installFrom(appArchiveUrl);
 354  0
             applications.add(application);
 355  0
             deployer.deploy(application);
 356  
         }
 357  0
         catch (Throwable t)
 358  
         {
 359  0
             addZombie(appArchiveUrl);
 360  0
             if (t instanceof DeploymentException)
 361  
             {
 362  
                 // re-throw
 363  0
                 throw ((DeploymentException) t);
 364  
             }
 365  
 
 366  0
             final String msg = "Failed to deploy from URL: " + appArchiveUrl;
 367  0
             throw new DeploymentException(MessageFactory.createStaticMessage(msg), t);
 368  0
         }
 369  0
     }
 370  
 
 371  
     protected void addZombie(URL appArchiveUrl)
 372  
     {
 373  
         // no sync required as deploy operations are single-threaded
 374  0
         if (appArchiveUrl == null)
 375  
         {
 376  0
             return;
 377  
         }
 378  
 
 379  0
         long lastModified = FileUtils.getFileTimeStamp(appArchiveUrl);
 380  
 
 381  0
         zombieMap.put(appArchiveUrl, lastModified);
 382  0
     }
 383  
 
 384  
     public void addStartupListener(StartupListener listener)
 385  
     {
 386  0
         this.startupListeners.add(listener);
 387  0
     }
 388  
 
 389  
     public void removeStartupListener(StartupListener listener)
 390  
     {
 391  0
         this.startupListeners.remove(listener);
 392  0
     }
 393  
 
 394  
     public void addDeploymentListener(DeploymentListener listener)
 395  
     {
 396  0
         this.deploymentListeners.add(listener);
 397  0
     }
 398  
 
 399  
     public void removeDeploymentListener(DeploymentListener listener)
 400  
     {
 401  0
         this.deploymentListeners.remove(listener);
 402  0
     }
 403  
 
 404  
     /**
 405  
      * Notifies all deployment listeners that the deploy for a given application
 406  
      * has just started.
 407  
      *
 408  
      * @param appName the name of the application being deployed.
 409  
      */
 410  
     protected void fireOnDeploymentStart(String appName)
 411  
     {
 412  0
         for (DeploymentListener listener : deploymentListeners)
 413  
         {
 414  
             try
 415  
             {
 416  0
                 listener.onDeploymentStart(appName);
 417  
             }
 418  0
             catch (Throwable t)
 419  
             {
 420  0
                 logger.error("Listener failed to process onDeploymentStart notification", t);
 421  0
             }
 422  
         }
 423  0
     }
 424  
 
 425  
     /**
 426  
      * Notifies all deployment listeners that the deploy for a given application
 427  
      * has successfully finished.
 428  
      *
 429  
      * @param appName the name of the deployed application.
 430  
      */
 431  
     protected void fireOnDeploymentSuccess(String appName)
 432  
     {
 433  0
         for (DeploymentListener listener : deploymentListeners)
 434  
         {
 435  
             try
 436  
             {
 437  0
                 listener.onDeploymentSuccess(appName);
 438  
             }
 439  0
             catch (Throwable t)
 440  
             {
 441  0
                 logger.error("Listener failed to process onDeploymentSuccess notification", t);
 442  0
             }
 443  
         }
 444  0
     }
 445  
 
 446  
     /**
 447  
      * Notifies all deployment listeners that the deploy for a given application
 448  
      * has finished with a failure.
 449  
      *
 450  
      * @param appName the name of the deployed application.
 451  
      * @param cause the cause of the deployment failure.
 452  
      */
 453  
     protected void fireOnDeploymentFailure(String appName, Throwable cause)
 454  
     {
 455  0
         for (DeploymentListener listener : deploymentListeners)
 456  
         {
 457  
             try
 458  
             {
 459  0
                 listener.onDeploymentFailure(appName, cause);
 460  
             }
 461  0
             catch (Throwable t)
 462  
             {
 463  0
                 logger.error("Listener failed to process onDeploymentFailure notification", t);
 464  0
             }
 465  
         }
 466  0
     }
 467  
 
 468  
     public interface StartupListener
 469  
     {
 470  
 
 471  
         /**
 472  
          * Invoked after all apps have passed the deployment phase. Any exceptions thrown by implementations
 473  
          * will be ignored.
 474  
          */
 475  
         void onAfterStartup();
 476  
     }
 477  
 
 478  
     /**
 479  
      * Not thread safe. Correctness is guaranteed by a single-threaded executor.
 480  
      */
 481  
     protected class AppDirWatcher implements Runnable
 482  
     {
 483  
         protected File appsDir;
 484  
 
 485  
         protected volatile boolean dirty;
 486  
 
 487  
         public AppDirWatcher(final File appsDir)
 488  0
         {
 489  0
             this.appsDir = appsDir;
 490  0
             applications.addPropertyChangeListener(new PropertyChangeListener()
 491  0
             {
 492  
                 public void propertyChange(PropertyChangeEvent e)
 493  
                 {
 494  0
                     if (e instanceof ElementAddedEvent || e instanceof ElementRemovedEvent)
 495  
                     {
 496  0
                         if (logger.isDebugEnabled())
 497  
                         {
 498  0
                             logger.debug("Deployed applications set has been modified, flushing state.");
 499  
                         }
 500  0
                         dirty = true;
 501  
                     }
 502  0
                 }
 503  
             });
 504  0
         }
 505  
 
 506  
         // Cycle is:
 507  
         //   undeploy removed apps
 508  
         //   deploy archives
 509  
         //   deploy exploded
 510  
         public void run()
 511  
         {
 512  
             try
 513  
             {
 514  0
                 if (logger.isDebugEnabled())
 515  
                 {
 516  0
                     logger.debug("Checking for changes...");
 517  
                 }
 518  
                 // use non-barging lock to preserve fairness, according to javadocs
 519  
                 // if there's a lock present - wait for next poll to do anything
 520  0
                 if (!lock.tryLock(0, TimeUnit.SECONDS))
 521  
                 {
 522  0
                     if (logger.isDebugEnabled())
 523  
                     {
 524  0
                         logger.debug("Another deployment operation in progress, will skip this cycle. Owner thread: " +
 525  
                                      ((DebuggableReentrantLock) lock).getOwner());
 526  
                     }
 527  
                     return;
 528  
                 }
 529  
 
 530  
                 // list new apps
 531  0
                 final String[] zips = appsDir.list(ZIP_APPS_FILTER);
 532  0
                 String[] apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
 533  
 
 534  
                 // we care only about removed anchors
 535  0
                 String[] currentAnchors = appsDir.list(new SuffixFileFilter(APP_ANCHOR_SUFFIX));
 536  0
                 if (logger.isDebugEnabled())
 537  
                 {
 538  0
                     StringBuilder sb = new StringBuilder();
 539  0
                     sb.append(String.format("Current anchors:%n"));
 540  0
                     for (String currentAnchor : currentAnchors)
 541  
                     {
 542  0
                         sb.append(String.format("  %s%n", currentAnchor));
 543  
                     }
 544  0
                     logger.debug(sb.toString());
 545  
                 }
 546  
 
 547  0
                 String[] appAnchors = findExpectedAnchorFiles();
 548  
 
 549  
                 @SuppressWarnings("unchecked")
 550  0
                 final Collection<String> deletedAnchors = CollectionUtils.subtract(Arrays.asList(appAnchors), Arrays.asList(currentAnchors));
 551  0
                 if (logger.isDebugEnabled())
 552  
                 {
 553  0
                     StringBuilder sb = new StringBuilder();
 554  0
                     sb.append(String.format("Deleted anchors:%n"));
 555  0
                     for (String deletedAnchor : deletedAnchors)
 556  
                     {
 557  0
                         sb.append(String.format("  %s%n", deletedAnchor));
 558  
                     }
 559  0
                     logger.debug(sb.toString());
 560  
                 }
 561  
 
 562  0
                 for (String deletedAnchor : deletedAnchors)
 563  
                 {
 564  0
                     String appName = StringUtils.removeEnd(deletedAnchor, APP_ANCHOR_SUFFIX);
 565  
                     try
 566  
                     {
 567  0
                         if (findApplication(appName) != null)
 568  
                         {
 569  0
                             undeploy(appName);
 570  
                         }
 571  0
                         else if (logger.isDebugEnabled())
 572  
                         {
 573  0
                             logger.debug(String.format("Application [%s] has already been undeployed via API", appName));
 574  
                         }
 575  
                     }
 576  0
                     catch (Throwable t)
 577  
                     {
 578  0
                         logger.error("Failed to undeploy application: " + appName, t);
 579  0
                     }
 580  0
                 }
 581  
 
 582  
                 // new packed Mule apps
 583  0
                 for (String zip : zips)
 584  
                 {
 585  0
                     URL url = null;
 586  
                     try
 587  
                     {
 588  
                         // check if this app is running first, undeploy it then
 589  0
                         final String appName = StringUtils.removeEnd(zip, ".zip");
 590  0
                         Application app = (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
 591  0
                         if (app != null)
 592  
                         {
 593  0
                             undeploy(appName);
 594  
                         }
 595  0
                         url = new File(appsDir, zip).toURI().toURL();
 596  
 
 597  0
                         if (isZombieApplicationFile(url))
 598  
                         {
 599  
                             // Skips the file because it was already deployed with failure
 600  0
                             continue;
 601  
                         }
 602  
 
 603  0
                         deploy(url);
 604  
                     }
 605  0
                     catch (Throwable t)
 606  
                     {
 607  0
                         logger.error("Failed to deploy application archive: " + zip, t);
 608  0
                         addZombie(url);
 609  0
                     }
 610  
                 }
 611  
 
 612  
                 // re-scan exploded apps and update our state, as deploying Mule app archives might have added some
 613  0
                 if (zips.length > 0 || dirty)
 614  
                 {
 615  0
                     apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
 616  
                 }
 617  
 
 618  0
                 Collection deployedAppNames = CollectionUtils.collect(applications, new BeanToPropertyValueTransformer("appName"));
 619  
 
 620  
                 // new exploded Mule apps
 621  
                 @SuppressWarnings("unchecked")
 622  0
                 final Collection<String> addedApps = CollectionUtils.subtract(Arrays.asList(apps), deployedAppNames);
 623  0
                 for (String addedApp : addedApps)
 624  
                 {
 625  
                     try
 626  
                     {
 627  0
                         onNewExplodedApplication(addedApp);
 628  
                     }
 629  0
                     catch (Throwable t)
 630  
                     {
 631  0
                         logger.error("Failed to deploy exploded application: " + addedApp, t);
 632  
                         try
 633  
                         {
 634  0
                             addZombie(new File(appsDir, addedApp).toURI().toURL());
 635  
                         }
 636  0
                         catch (MalformedURLException e)
 637  
                         {
 638  0
                             if (logger.isDebugEnabled())
 639  
                             {
 640  0
                                 logger.debug(e);
 641  
                             }
 642  0
                         }
 643  0
                     }
 644  
                 }
 645  
 
 646  
             }
 647  0
             catch (InterruptedException e)
 648  
             {
 649  
                 // preserve the flag for the thread
 650  0
                 Thread.currentThread().interrupt();
 651  
             }
 652  
             finally
 653  
             {
 654  0
                 if (lock.isHeldByCurrentThread())
 655  
                 {
 656  0
                     lock.unlock();
 657  
                 }
 658  0
                 dirty = false;
 659  0
             }
 660  0
         }
 661  
 
 662  
         /**
 663  
          * Returns the list of anchor file names for the deployed apps
 664  
          *
 665  
          * @return a non null list of file names
 666  
          */
 667  
         private String[] findExpectedAnchorFiles()
 668  
         {
 669  0
             String[] appAnchors = new String[applications.size()];
 670  0
             int i =0;
 671  0
             for (Application application : applications)
 672  
             {
 673  0
                 appAnchors[i++] = application.getAppName() + APP_ANCHOR_SUFFIX;
 674  
             }
 675  0
             return appAnchors;
 676  
         }
 677  
 
 678  
         /**
 679  
          * Determines if a given URL points to the same file that an existent
 680  
          * zombie application.
 681  
          *
 682  
          * @param url the URL representing the resource to be checked
 683  
          * @return true if the URL already a zombie application and both file
 684  
          *         timestamps are the same.
 685  
          */
 686  
         protected boolean isZombieApplicationFile(URL url)
 687  
         {
 688  0
             boolean result = false;
 689  
 
 690  0
             if (FileUtils.isFile(url) && zombieMap.containsKey(url))
 691  
             {
 692  0
                 long originalTimeStamp = zombieMap.get(url);
 693  0
                 long newTimeStamp = FileUtils.getFileTimeStamp(url);
 694  
 
 695  0
                 if (originalTimeStamp == newTimeStamp)
 696  
                 {
 697  0
                     result = true;
 698  
                 }
 699  
             }
 700  
 
 701  0
             return result;
 702  
         }
 703  
 
 704  
         /**
 705  
          * @param appName application name as it appears in $MULE_HOME/apps
 706  
          */
 707  
         protected void onNewExplodedApplication(String appName) throws Exception
 708  
         {
 709  0
             if (logger.isInfoEnabled())
 710  
             {
 711  0
                 logger.info("================== New Exploded Application: " + appName);
 712  
             }
 713  
 
 714  0
             Application a = appFactory.createApp(appName);
 715  
             // add to the list of known apps first to avoid deployment loop on failure
 716  0
             onApplicationInstalled(a);
 717  0
             deployer.deploy(a);
 718  0
         }
 719  
 
 720  
     }
 721  
 }