View Javadoc

1   /*
2    * $Id: DefaultMuleApplication.java 19667 2010-09-16 16:32:41Z aperepel $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
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.AppBloodhound;
28  import org.mule.module.launcher.ApplicationMuleContextBuilder;
29  import org.mule.module.launcher.ConfigChangeMonitorThreadFactory;
30  import org.mule.module.launcher.DefaultAppBloodhound;
31  import org.mule.module.launcher.DefaultMuleSharedDomainClassLoader;
32  import org.mule.module.launcher.DeploymentInitException;
33  import org.mule.module.launcher.DeploymentStartException;
34  import org.mule.module.launcher.DeploymentStopException;
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.reboot.MuleContainerBootstrapUtils;
40  import org.mule.util.ClassUtils;
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  
64      protected ScheduledExecutorService watchTimer;
65  
66      private String appName;
67      private MuleContext muleContext;
68      private ClassLoader deploymentClassLoader;
69      private ApplicationDescriptor descriptor;
70  
71      protected String[] absoluteResourcePaths;
72  
73      protected DefaultMuleApplication(String appName)
74      {
75          this.appName = appName;
76      }
77  
78      public void install()
79      {
80          if (logger.isInfoEnabled())
81          {
82              logger.info("Installing application: " + appName);
83          }
84  
85          AppBloodhound bh = new DefaultAppBloodhound();
86          try
87          {
88              descriptor = bh.fetch(getAppName());
89          }
90          catch (IOException e)
91          {
92              throw new InstallException(MessageFactory.createStaticMessage("Failed to parse the application deployment descriptor"), e);
93          }
94  
95          // convert to absolute paths
96          final String[] configResources = descriptor.getConfigResources();
97          absoluteResourcePaths = new String[configResources.length];
98          for (int i = 0; i < configResources.length; i++)
99          {
100             String resource = configResources[i];
101             final File file = toAbsoluteFile(resource);
102             if (!file.exists())
103             {
104                 throw new InstallException(
105                         MessageFactory.createStaticMessage(String.format("Config for app '%s' not found: %s", getAppName(), file))
106                 );
107             }
108 
109             absoluteResourcePaths[i] = file.getAbsolutePath();
110         }
111 
112         createDeploymentClassLoader();
113     }
114 
115     public String getAppName()
116     {
117         return appName;
118     }
119 
120     public ApplicationDescriptor getDescriptor()
121     {
122         return descriptor;
123     }
124 
125     public void setAppName(String appName)
126     {
127         this.appName = appName;
128     }
129 
130     public void start()
131     {
132         if (logger.isInfoEnabled())
133         {
134             logger.info("Starting application: " + appName);
135         }
136 
137         try
138         {
139             this.muleContext.start();
140             // save app's state in the marker file
141             File marker = new File(MuleContainerBootstrapUtils.getMuleAppsDir(), String.format("%s-anchor.txt", getAppName()));
142             FileUtils.writeStringToFile(marker, ANCHOR_FILE_BLURB);
143         }
144         catch (MuleException e)
145         {
146             // TODO add app name to the exception field
147             throw new DeploymentStartException(MessageFactory.createStaticMessage(appName), e);
148         }
149         catch (IOException e)
150         {
151             // TODO add app name to the exception field
152             throw new DeploymentStartException(MessageFactory.createStaticMessage(appName), e);
153         }
154     }
155 
156     public void init()
157     {
158         if (logger.isInfoEnabled())
159         {
160             logger.info("Initializing application: " + appName);
161         }
162 
163         String configBuilderClassName = null;
164         try
165         {
166             // Configuration builder
167             // Provide a shortcut for Spring: "-builder spring"
168             final String builderFromDesc = descriptor.getConfigurationBuilder();
169             if ("spring".equalsIgnoreCase(builderFromDesc))
170             {
171                 configBuilderClassName = ApplicationDescriptor.CLASSNAME_SPRING_CONFIG_BUILDER;
172             }
173             else if (builderFromDesc == null)
174             {
175                 configBuilderClassName = AutoConfigurationBuilder.class.getName();
176             }
177             else
178             {
179                 configBuilderClassName = builderFromDesc;
180             }
181 
182             ConfigurationBuilder cfgBuilder = (ConfigurationBuilder) ClassUtils.instanciateClass(
183                 configBuilderClassName, new Object[] {absoluteResourcePaths}, getDeploymentClassLoader());
184 
185             if (!cfgBuilder.isConfigured())
186             {
187                 //Load application properties first since they may be needed by other configuration builders
188                 List<ConfigurationBuilder> builders = new ArrayList<ConfigurationBuilder>(2);
189 
190                 final Map<String,String> appProperties = descriptor.getAppProperties();
191 
192                 //Add the app.home variable to the context
193                 appProperties.put(MuleProperties.APP_HOME_DIRECTORY_PROPERTY,
194                         new File(MuleContainerBootstrapUtils.getMuleAppsDir(), getAppName()).getAbsolutePath());
195 
196                 builders.add(new SimpleConfigurationBuilder(appProperties));
197 
198                 // If the annotations module is on the classpath, add the annotations config builder to the list
199                 // This will enable annotations config for this instance
200                 //We need to add this builder before spring so that we can use Mule annotations in Spring or any other builder
201                 if (ClassUtils.isClassOnPath(MuleServer.CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, getClass()))
202                 {
203                     Object configBuilder = ClassUtils.instanciateClass(
204                         MuleServer.CLASSNAME_ANNOTATIONS_CONFIG_BUILDER, ClassUtils.NO_ARGS, getClass());
205                     builders.add((ConfigurationBuilder) configBuilder);
206                 }
207 
208                 builders.add(cfgBuilder);
209 
210                 DefaultMuleContextFactory muleContextFactory = new DefaultMuleContextFactory();
211                 this.muleContext = muleContextFactory.createMuleContext(builders, new ApplicationMuleContextBuilder(descriptor));
212 
213                 if (descriptor.isRedeploymentEnabled())
214                 {
215                     createRedeployMonitor();
216                 }
217             }
218         }
219         catch (Exception e)
220         {
221             throw new DeploymentInitException(CoreMessages.failedToLoad(configBuilderClassName), e);
222         }
223     }
224 
225     public MuleContext getMuleContext()
226     {
227         return muleContext;
228     }
229 
230     public ClassLoader getDeploymentClassLoader()
231     {
232         return this.deploymentClassLoader;
233     }
234 
235     public void dispose()
236     {
237         if (muleContext == null)
238         {
239             if (logger.isInfoEnabled())
240             {
241                 logger.info("MuleContext not created, nothing to dispose of");
242             }
243             return;
244         }
245 
246         if (muleContext.isStarted() && !muleContext.isDisposed())
247         {
248             stop();
249         }
250         if (logger.isInfoEnabled())
251         {
252             logger.info("Disposing application: " + appName);
253         }
254 
255         muleContext.dispose();
256         muleContext = null;
257         // kill any refs to the old classloader to avoid leaks
258         Thread.currentThread().setContextClassLoader(null);
259     }
260 
261     public void redeploy()
262     {
263         if (logger.isInfoEnabled())
264         {
265             logger.info("Redeploying application: " + appName);
266         }
267         dispose();
268         install();
269 
270         // update thread with the fresh new classloader just created during the install phase
271         final ClassLoader cl = getDeploymentClassLoader();
272         Thread.currentThread().setContextClassLoader(cl);
273 
274         init();
275         start();
276 
277         // release the ref
278         Thread.currentThread().setContextClassLoader(null);
279     }
280 
281     public void stop()
282     {
283         if (this.muleContext == null)
284         {
285             // app never started, maybe due to a previous error
286             return;
287         }
288         if (logger.isInfoEnabled())
289         {
290             logger.info("Stopping application: " + appName);
291         }
292         try
293         {
294             this.muleContext.stop();
295         }
296         catch (MuleException e)
297         {
298             // TODO add app name to the exception field
299             throw new DeploymentStopException(MessageFactory.createStaticMessage(appName), e);
300         }
301     }
302 
303     @Override
304     public String toString()
305     {
306         return String.format("%s[%s]@%s", getClass().getName(),
307                              appName,
308                              Integer.toHexString(System.identityHashCode(this)));
309     }
310 
311     protected void createDeploymentClassLoader()
312     {
313         final String domain = descriptor.getDomain();
314         ClassLoader parent;
315 
316         if (StringUtils.isBlank(domain) || DefaultMuleSharedDomainClassLoader.DEFAULT_DOMAIN_NAME.equals(domain))
317         {
318             parent = new DefaultMuleSharedDomainClassLoader(getClass().getClassLoader());
319         }
320         else
321         {
322             // TODO handle non-existing domains with an exception
323             parent = new MuleSharedDomainClassLoader(domain, getClass().getClassLoader());
324         }
325 
326         this.deploymentClassLoader = new MuleApplicationClassLoader(appName, parent);
327     }
328 
329     protected void createRedeployMonitor() throws NotificationException
330     {
331         if (logger.isInfoEnabled())
332         {
333             logger.info("Monitoring for hot-deployment: " + new File(absoluteResourcePaths [0]));
334         }
335 
336         final AbstractFileWatcher watcher = new ConfigFileWatcher(new File(absoluteResourcePaths [0]));
337 
338         // register a config monitor only after context has started, as it may take some time
339         muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>()
340         {
341 
342             public void onNotification(MuleContextNotification notification)
343             {
344                 final int action = notification.getAction();
345                 switch (action)
346                 {
347                     case MuleContextNotification.CONTEXT_STARTED:
348                         scheduleConfigMonitor(watcher);
349                         break;
350                     case MuleContextNotification.CONTEXT_STOPPING:
351                         watchTimer.shutdownNow();
352                         muleContext.unregisterListener(this);
353                         break;
354                 }
355             }
356         });
357     }
358 
359     protected void scheduleConfigMonitor(AbstractFileWatcher watcher)
360     {
361         final int reloadIntervalMs = DEFAULT_RELOAD_CHECK_INTERVAL_MS;
362         watchTimer = Executors.newSingleThreadScheduledExecutor(new ConfigChangeMonitorThreadFactory(appName));
363 
364         watchTimer.scheduleWithFixedDelay(watcher, reloadIntervalMs, reloadIntervalMs, TimeUnit.MILLISECONDS);
365 
366         if (logger.isInfoEnabled())
367         {
368             logger.info("Reload interval: " + reloadIntervalMs);
369         }
370     }
371 
372     /**
373      * Resolve a resource relative to an application root.
374      * @param path the relative path to resolve
375      * @return absolute path, may not actually exist (check with File.exists())
376      */
377     protected File toAbsoluteFile(String path)
378     {
379         final String muleHome = System.getProperty(MuleProperties.MULE_HOME_DIRECTORY_PROPERTY);
380         String configPath = String.format("%s/apps/%s/%s", muleHome, getAppName(), path);
381         return new File(configPath);
382     }
383 
384     protected class ConfigFileWatcher extends AbstractFileWatcher
385     {
386         public ConfigFileWatcher(File watchedResource)
387         {
388             super(watchedResource);
389         }
390 
391         @Override
392         protected synchronized void onChange(File file)
393         {
394             if (logger.isInfoEnabled())
395             {
396                 logger.info("================== Reloading " + file);
397             }
398 
399             // grab the proper classloader for our context
400             final ClassLoader cl = getDeploymentClassLoader();
401             Thread.currentThread().setContextClassLoader(cl);
402             redeploy();
403         }
404     }
405 }