Coverage Report - org.mule.module.launcher.application.DefaultMuleApplication
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultMuleApplication
0%
0/156
0%
0/52
0
DefaultMuleApplication$1
0%
0/9
0%
0/5
0
DefaultMuleApplication$ConfigFileWatcher
0%
0/9
0%
0/2
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.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  0
 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  0
     protected transient final Log logger = LogFactory.getLog(getClass());
 63  0
     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  0
     {
 76  0
         this.appName = appName;
 77  0
     }
 78  
 
 79  
     public void install()
 80  
     {
 81  0
         if (logger.isInfoEnabled())
 82  
         {
 83  0
             logger.info(miniSplash(String.format("New app '%s'", appName)));
 84  
         }
 85  
 
 86  0
         AppBloodhound bh = new DefaultAppBloodhound();
 87  
         try
 88  
         {
 89  0
             descriptor = bh.fetch(getAppName());
 90  
         }
 91  0
         catch (IOException e)
 92  
         {
 93  0
             throw new InstallException(MessageFactory.createStaticMessage("Failed to parse the application deployment descriptor"), e);
 94  0
         }
 95  
 
 96  0
         createAnchorFile(getAppName());
 97  
 
 98  
         // convert to absolute paths
 99  0
         final String[] configResources = descriptor.getConfigResources();
 100  0
         absoluteResourcePaths = new String[configResources.length];
 101  0
         for (int i = 0; i < configResources.length; i++)
 102  
         {
 103  0
             String resource = configResources[i];
 104  0
             final File file = toAbsoluteFile(resource);
 105  0
             if (!file.exists())
 106  
             {
 107  0
                 throw new InstallException(
 108  
                         MessageFactory.createStaticMessage(String.format("Config for app '%s' not found: %s", getAppName(), file))
 109  
                 );
 110  
             }
 111  
 
 112  0
             absoluteResourcePaths[i] = file.getAbsolutePath();
 113  
         }
 114  
 
 115  0
         createDeploymentClassLoader();
 116  0
     }
 117  
 
 118  
     public String getAppName()
 119  
     {
 120  0
         return appName;
 121  
     }
 122  
 
 123  
     public ApplicationDescriptor getDescriptor()
 124  
     {
 125  0
         return descriptor;
 126  
     }
 127  
 
 128  
     public void setAppName(String appName)
 129  
     {
 130  0
         this.appName = appName;
 131  0
     }
 132  
 
 133  
     public void start()
 134  
     {
 135  0
         if (logger.isInfoEnabled())
 136  
         {
 137  0
             logger.info(miniSplash(String.format("Starting app '%s'", appName)));
 138  
         }
 139  
 
 140  
         try
 141  
         {
 142  0
             this.muleContext.start();
 143  
 
 144  
             // null CCL ensures we log at 'system' level
 145  
             // TODO create a more usable wrapper for any logger to be logged at sys level
 146  0
             final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
 147  
             try
 148  
             {
 149  0
                 Thread.currentThread().setContextClassLoader(null);
 150  0
                 deployLogger.info(miniSplash(String.format("Started app '%s'", appName)));
 151  
             }
 152  
             finally
 153  
             {
 154  0
                 Thread.currentThread().setContextClassLoader(oldCl);
 155  0
             }
 156  
         }
 157  0
         catch (MuleException e)
 158  
         {
 159  
             // log it here so it ends up in app log, sys log will only log a message without stacktrace
 160  0
             logger.error(null, ExceptionUtils.getRootCause(e));
 161  
             // TODO add app name to the exception field
 162  0
             throw new DeploymentStartException(CoreMessages.createStaticMessage(ExceptionUtils.getRootCauseMessage(e)), e);
 163  0
         }
 164  0
     }
 165  
 
 166  
     public void init()
 167  
     {
 168  0
         if (logger.isInfoEnabled())
 169  
         {
 170  0
             logger.info(miniSplash(String.format("Initializing app '%s'", appName)));
 171  
         }
 172  
 
 173  
         try
 174  
         {
 175  0
             ConfigurationBuilder cfgBuilder = createConfigurationBuiler();
 176  0
             if (!cfgBuilder.isConfigured())
 177  
             {
 178  0
                 List<ConfigurationBuilder> builders = new ArrayList<ConfigurationBuilder>(3);
 179  0
                 builders.add(createConfigurationBuilderFromApplicationProperties());
 180  
 
 181  
                 // We need to add this builder before spring so that we can use Mule annotations in Spring or any other builder
 182  0
                 addAnnotationsConfigBuilderIfPresent(builders);
 183  0
                 addIBeansConfigurationBuilderIfPackagesConfiguredForScanning(builders);
 184  
 
 185  0
                 builders.add(cfgBuilder);
 186  
 
 187  0
                 DefaultMuleContextFactory muleContextFactory = new DefaultMuleContextFactory();
 188  0
                 this.muleContext = muleContextFactory.createMuleContext(builders, new ApplicationMuleContextBuilder(descriptor));
 189  
 
 190  0
                 if (descriptor.isRedeploymentEnabled())
 191  
                 {
 192  0
                     createRedeployMonitor();
 193  
                 }
 194  
             }
 195  
         }
 196  0
         catch (Exception e)
 197  
         {
 198  
             // log it here so it ends up in app log, sys log will only log a message without stacktrace
 199  0
             logger.error(null, ExceptionUtils.getRootCause(e));
 200  0
             throw new DeploymentInitException(CoreMessages.createStaticMessage(ExceptionUtils.getRootCauseMessage(e)), e);
 201  0
         }
 202  0
     }
 203  
 
 204  
     protected ConfigurationBuilder createConfigurationBuiler() throws Exception
 205  
     {
 206  0
         String configBuilderClassName = determineConfigBuilderClassName();
 207  0
         return (ConfigurationBuilder) ClassUtils.instanciateClass(configBuilderClassName,
 208  
             new Object[] { absoluteResourcePaths }, getDeploymentClassLoader());
 209  
     }
 210  
 
 211  
     protected String determineConfigBuilderClassName()
 212  
     {
 213  
         // Provide a shortcut for Spring: "-builder spring"
 214  0
         final String builderFromDesc = descriptor.getConfigurationBuilder();
 215  0
         if ("spring".equalsIgnoreCase(builderFromDesc))
 216  
         {
 217  0
             return ApplicationDescriptor.CLASSNAME_SPRING_CONFIG_BUILDER;
 218  
         }
 219  0
         else if (builderFromDesc == null)
 220  
         {
 221  0
             return AutoConfigurationBuilder.class.getName();
 222  
         }
 223  
         else
 224  
         {
 225  0
             return builderFromDesc;
 226  
         }
 227  
     }
 228  
 
 229  
     protected ConfigurationBuilder createConfigurationBuilderFromApplicationProperties()
 230  
     {
 231  
         // Load application properties first since they may be needed by other configuration builders
 232  0
         final Map<String,String> appProperties = descriptor.getAppProperties();
 233  
 
 234  
         // Add the app.home variable to the context
 235  0
         File appPath = new File(MuleContainerBootstrapUtils.getMuleAppsDir(), getAppName());
 236  0
         appProperties.put(MuleProperties.APP_HOME_DIRECTORY_PROPERTY, appPath.getAbsolutePath());
 237  
 
 238  0
         appProperties.put(MuleProperties.APP_NAME_PROPERTY, getAppName());
 239  
 
 240  0
         return new SimpleConfigurationBuilder(appProperties);
 241  
     }
 242  
 
 243  
     protected void addAnnotationsConfigBuilderIfPresent(List<ConfigurationBuilder> builders) throws Exception
 244  
     {
 245  
         // If the annotations module is on the classpath, add the annotations config builder to
 246  
         // the list. This will enable annotations config for this instance.
 247  0
         if (ClassUtils.isClassOnPath(MuleServer.CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, getClass()))
 248  
         {
 249  0
             Object configBuilder = ClassUtils.instanciateClass(
 250  
                 MuleServer.CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, ClassUtils.NO_ARGS, getClass());
 251  0
             builders.add((ConfigurationBuilder) configBuilder);
 252  
         }
 253  0
     }
 254  
 
 255  
     protected void addIBeansConfigurationBuilderIfPackagesConfiguredForScanning(List<ConfigurationBuilder> builders)
 256  
         throws Exception
 257  
     {
 258  0
         String packagesToScan = descriptor.getPackagesToScan();
 259  0
         if (StringUtils.isNotEmpty(packagesToScan))
 260  
         {
 261  0
             String[] paths = packagesToScan.split(",");
 262  0
             Object configBuilder = ClassUtils.instanciateClass(
 263  
                 MuleServer.CLASSNAME_IBEANS_CONFIG_BUILDER, new Object[] { paths }, getClass());
 264  0
             builders.add((ConfigurationBuilder) configBuilder);
 265  
         }
 266  0
     }
 267  
 
 268  
     public MuleContext getMuleContext()
 269  
     {
 270  0
         return muleContext;
 271  
     }
 272  
 
 273  
     public ClassLoader getDeploymentClassLoader()
 274  
     {
 275  0
         return this.deploymentClassLoader;
 276  
     }
 277  
 
 278  
     public void dispose()
 279  
     {
 280  
         // moved wrapper logic into the actual implementation, as redeploy() invokes it directly, bypassing
 281  
         // classloader cleanup
 282  
         try
 283  
         {
 284  0
             ClassLoader appCl = getDeploymentClassLoader();
 285  
             // if not initialized yet, it can be null
 286  0
             if (appCl != null)
 287  
             {
 288  0
                 Thread.currentThread().setContextClassLoader(appCl);
 289  
             }
 290  
 
 291  0
             doDispose();
 292  
 
 293  0
             if (appCl != null)
 294  
             {
 295  
                 // close classloader to release jar connections in lieu of Java 7's ClassLoader.close()
 296  0
                 if (appCl instanceof GoodCitizenClassLoader)
 297  
                 {
 298  0
                     GoodCitizenClassLoader classLoader = (GoodCitizenClassLoader) appCl;
 299  0
                     classLoader.close();
 300  
                 }
 301  
             }
 302  
         }
 303  
         finally
 304  
         {
 305  
             // kill any refs to the old classloader to avoid leaks
 306  0
             Thread.currentThread().setContextClassLoader(null);
 307  0
         }
 308  0
     }
 309  
 
 310  
     public void redeploy()
 311  
     {
 312  0
         if (logger.isInfoEnabled())
 313  
         {
 314  0
             logger.info(miniSplash(String.format("Redeploying app '%s'", appName)));
 315  
         }
 316  0
         dispose();
 317  0
         install();
 318  
 
 319  
         // update thread with the fresh new classloader just created during the install phase
 320  0
         final ClassLoader cl = getDeploymentClassLoader();
 321  0
         Thread.currentThread().setContextClassLoader(cl);
 322  
 
 323  0
         init();
 324  0
         start();
 325  
 
 326  
         // release the ref
 327  0
         Thread.currentThread().setContextClassLoader(null);
 328  0
     }
 329  
 
 330  
     public void stop()
 331  
     {
 332  0
         if (this.muleContext == null)
 333  
         {
 334  
             // app never started, maybe due to a previous error
 335  0
             return;
 336  
         }
 337  0
         if (logger.isInfoEnabled())
 338  
         {
 339  0
             logger.info(miniSplash(String.format("Stopping app '%s'", appName)));
 340  
         }
 341  
         try
 342  
         {
 343  0
             this.muleContext.stop();
 344  
         }
 345  0
         catch (MuleException e)
 346  
         {
 347  
             // TODO add app name to the exception field
 348  0
             throw new DeploymentStopException(MessageFactory.createStaticMessage(appName), e);
 349  0
         }
 350  0
     }
 351  
 
 352  
     @Override
 353  
     public String toString()
 354  
     {
 355  0
         return String.format("%s[%s]@%s", getClass().getName(),
 356  
                              appName,
 357  
                              Integer.toHexString(System.identityHashCode(this)));
 358  
     }
 359  
 
 360  
     protected void doDispose()
 361  
     {
 362  0
         if (muleContext == null)
 363  
         {
 364  0
             if (logger.isInfoEnabled())
 365  
             {
 366  0
                 logger.info(String.format("App '%s' never started, nothing to dispose of", appName));
 367  
             }
 368  0
             return;
 369  
         }
 370  
 
 371  0
         if (muleContext.isStarted() && !muleContext.isDisposed())
 372  
         {
 373  
             try
 374  
             {
 375  0
                 stop();
 376  
             }
 377  0
             catch (DeploymentStopException e)
 378  
             {
 379  
                 // catch the stop errors and just log, we're disposing of an app anyway
 380  0
                 logger.error(e);
 381  0
             }
 382  
         }
 383  0
         if (logger.isInfoEnabled())
 384  
         {
 385  0
             logger.info(miniSplash(String.format("Disposing app '%s'", appName)));
 386  
         }
 387  
 
 388  0
         muleContext.dispose();
 389  0
         muleContext = null;
 390  0
     }
 391  
 
 392  
     protected void createDeploymentClassLoader()
 393  
     {
 394  0
         final String domain = descriptor.getDomain();
 395  
         ClassLoader parent;
 396  
 
 397  0
         if (StringUtils.isBlank(domain) || DefaultMuleSharedDomainClassLoader.DEFAULT_DOMAIN_NAME.equals(domain))
 398  
         {
 399  0
             parent = new DefaultMuleSharedDomainClassLoader(getClass().getClassLoader());
 400  
         }
 401  
         else
 402  
         {
 403  
             // TODO handle non-existing domains with an exception
 404  0
             parent = new MuleSharedDomainClassLoader(domain, getClass().getClassLoader());
 405  
         }
 406  
 
 407  0
         this.deploymentClassLoader = new MuleApplicationClassLoader(appName, parent);
 408  0
     }
 409  
 
 410  
     protected void createRedeployMonitor() throws NotificationException
 411  
     {
 412  0
         if (logger.isInfoEnabled())
 413  
         {
 414  0
             logger.info("Monitoring for hot-deployment: " + new File(absoluteResourcePaths [0]));
 415  
         }
 416  
 
 417  0
         final AbstractFileWatcher watcher = new ConfigFileWatcher(new File(absoluteResourcePaths [0]));
 418  
 
 419  
         // register a config monitor only after context has started, as it may take some time
 420  0
         muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>()
 421  0
         {
 422  
 
 423  
             public void onNotification(MuleContextNotification notification)
 424  
             {
 425  0
                 final int action = notification.getAction();
 426  0
                 switch (action)
 427  
                 {
 428  
                     case MuleContextNotification.CONTEXT_STARTED:
 429  0
                         scheduleConfigMonitor(watcher);
 430  0
                         break;
 431  
                     case MuleContextNotification.CONTEXT_STOPPING:
 432  0
                         if (watchTimer != null)
 433  
                         {
 434  
                             // edge case when app startup was interrupted and we haven't started monitoring it yet
 435  0
                             watchTimer.shutdownNow();
 436  
                         }
 437  0
                         muleContext.unregisterListener(this);
 438  
                         break;
 439  
                 }
 440  0
             }
 441  
         });
 442  0
     }
 443  
 
 444  
     protected void scheduleConfigMonitor(AbstractFileWatcher watcher)
 445  
     {
 446  0
         final int reloadIntervalMs = DEFAULT_RELOAD_CHECK_INTERVAL_MS;
 447  0
         watchTimer = Executors.newSingleThreadScheduledExecutor(new ConfigChangeMonitorThreadFactory(appName));
 448  
 
 449  0
         watchTimer.scheduleWithFixedDelay(watcher, reloadIntervalMs, reloadIntervalMs, TimeUnit.MILLISECONDS);
 450  
 
 451  0
         if (logger.isInfoEnabled())
 452  
         {
 453  0
             logger.info("Reload interval: " + reloadIntervalMs);
 454  
         }
 455  0
     }
 456  
 
 457  
     /**
 458  
      * Resolve a resource relative to an application root.
 459  
      * @param path the relative path to resolve
 460  
      * @return absolute path, may not actually exist (check with File.exists())
 461  
      */
 462  
     protected File toAbsoluteFile(String path)
 463  
     {
 464  0
         final String muleHome = System.getProperty(MuleProperties.MULE_HOME_DIRECTORY_PROPERTY);
 465  0
         String configPath = String.format("%s/apps/%s/%s", muleHome, getAppName(), path);
 466  0
         return new File(configPath);
 467  
     }
 468  
 
 469  
     private void createAnchorFile(String appName)
 470  
     {
 471  
         try
 472  
         {
 473  0
             File marker = new File(MuleContainerBootstrapUtils.getMuleAppsDir(), String.format("%s-anchor.txt", appName));
 474  0
             FileUtils.writeStringToFile(marker, ANCHOR_FILE_BLURB);
 475  
         }
 476  0
         catch (IOException e)
 477  
         {
 478  
             // log it here so it ends up in app log, sys log will only log a message without stacktrace
 479  0
             logger.error(null, ExceptionUtils.getRootCause(e));
 480  
             // TODO add app name to the exception field
 481  0
             throw new DeploymentInitException(CoreMessages.createStaticMessage(ExceptionUtils.getRootCauseMessage(e)), e);
 482  0
         }
 483  0
     }
 484  
 
 485  
     protected class ConfigFileWatcher extends AbstractFileWatcher
 486  
     {
 487  
         public ConfigFileWatcher(File watchedResource)
 488  0
         {
 489  0
             super(watchedResource);
 490  0
         }
 491  
 
 492  
         @Override
 493  
         protected synchronized void onChange(File file)
 494  
         {
 495  0
             if (logger.isInfoEnabled())
 496  
             {
 497  0
                 logger.info("================== Reloading " + file);
 498  
             }
 499  
 
 500  
             // grab the proper classloader for our context
 501  0
             final ClassLoader cl = getDeploymentClassLoader();
 502  0
             Thread.currentThread().setContextClassLoader(cl);
 503  0
             redeploy();
 504  0
         }
 505  
     }
 506  
 }