View Javadoc

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