View Javadoc

1   /*
2    * $Id: DeploymentService.java 19191 2010-08-25 21:05:23Z tcarlson $
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.config.StartupContext;
14  import org.mule.module.reboot.MuleContainerBootstrapUtils;
15  import org.mule.util.CollectionUtils;
16  import org.mule.util.FileUtils;
17  import org.mule.util.FilenameUtils;
18  import org.mule.util.StringUtils;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.concurrent.Executors;
29  import java.util.concurrent.ScheduledExecutorService;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
33  import org.apache.commons.io.filefilter.DirectoryFileFilter;
34  import org.apache.commons.io.filefilter.SuffixFileFilter;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  public class DeploymentService
39  {
40      public static final String APP_ANCHOR_SUFFIX = "-anchor.txt";
41      protected static final int DEFAULT_CHANGES_CHECK_INTERVAL_MS = 5000;
42  
43      protected ScheduledExecutorService appDirMonitorTimer;
44  
45      protected transient final Log logger = LogFactory.getLog(getClass());
46      protected MuleDeployer deployer = new DefaultMuleDeployer();
47      private List<Application> applications = new ArrayList<Application>();
48  
49      public void start()
50      {
51          // install phase
52          final Map<String, Object> options = StartupContext.get().getStartupOptions();
53          String appString = (String) options.get("app");
54  
55          final File appsDir = MuleContainerBootstrapUtils.getMuleAppsDir();
56  
57          // delete any leftover anchor files from previous unclean shutdowns
58          String[] appAnchors = appsDir.list(new SuffixFileFilter(APP_ANCHOR_SUFFIX));
59          for (String anchor : appAnchors)
60          {
61              // ignore result
62              new File(appsDir, anchor).delete();
63          }
64  
65          String[] apps;
66  
67          // mule -app app1:app2:app3 will restrict deployment only to those specified apps
68          final boolean explicitAppSet = appString != null;
69  
70          if (!explicitAppSet)
71          {
72              // explode any app zips first
73              final String[] zips = appsDir.list(new SuffixFileFilter(".zip"));
74              for (String zip : zips)
75              {
76                  try
77                  {
78                      // we don't care about the returned app object on startup
79                      deployer.installFromAppDir(zip);
80                  }
81                  catch (IOException e)
82                  {
83                      // TODO logging
84                      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
85                  }
86              }
87  
88              // TODO this is a place to put a FQN of the custom sorter (use AND filter)
89              // Add string shortcuts for bundled ones
90              apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
91          }
92          else
93          {
94              apps = appString.split(":");
95          }
96  
97          for (String app : apps)
98          {
99              final ApplicationWrapper a = new ApplicationWrapper(new DefaultMuleApplication(app));
100             applications.add(a);
101         }
102 
103 
104         for (Application application : applications)
105         {
106             try
107             {
108                 deployer.deploy(application);
109             }
110             catch (Throwable t)
111             {
112                 // TODO logging
113                 t.printStackTrace();
114             }
115         }
116 
117         // only start the monitor thread if we launched in default mode without explicitly
118         // stated applications to launch
119         if (!explicitAppSet)
120         {
121             scheduleChangeMonitor(appsDir);
122         }
123     }
124 
125     protected void scheduleChangeMonitor(File appsDir)
126     {
127         final int reloadIntervalMs = DEFAULT_CHANGES_CHECK_INTERVAL_MS;
128         appDirMonitorTimer = Executors.newSingleThreadScheduledExecutor(new AppDeployerMonitorThreadFactory());
129 
130         appDirMonitorTimer.scheduleWithFixedDelay(new AppDirWatcher(appsDir),
131                                                   0,
132                                                   reloadIntervalMs,
133                                                   TimeUnit.MILLISECONDS);
134 
135         if (logger.isInfoEnabled())
136         {
137             logger.info("Application directory check interval: " + reloadIntervalMs);
138         }
139     }
140 
141     public void stop()
142     {
143         if (appDirMonitorTimer != null)
144         {
145             appDirMonitorTimer.shutdownNow();
146         }
147 
148         // tear down apps in reverse order
149         Collections.reverse(applications);
150         for (Application application : applications)
151         {
152             try
153             {
154                 application.stop();
155                 application.dispose();
156             }
157             catch (Throwable t)
158             {
159                 // TODO logging
160                 t.printStackTrace();
161             }
162         }
163 
164     }
165 
166     /**
167      * @return immutable applications list
168      */
169     public List<Application> getApplications()
170     {
171         return Collections.unmodifiableList(applications);
172     }
173 
174     public MuleDeployer getDeployer()
175     {
176         return deployer;
177     }
178 
179     public void setDeployer(MuleDeployer deployer)
180     {
181         this.deployer = deployer;
182     }
183 
184     /**
185      * Not thread safe. Correctness is guaranteed by a single-threaded executor.
186      */
187     protected class AppDirWatcher implements Runnable
188     {
189         protected File appsDir;
190 
191         protected String[] deployedApps;
192 
193         // written on app start, will be used to cleanly undeploy the app without file locking issues
194         protected String[] appAnchors = new String[0];
195 
196         public AppDirWatcher(File appsDir)
197         {
198             this.appsDir = appsDir;
199             // save the list of known apps on startup
200             this.deployedApps = new String[applications.size()];
201             for (int i = 0; i < applications.size(); i++)
202             {
203                 deployedApps[i] = applications.get(i).getAppName();
204 
205             }
206         }
207 
208         // Cycle is:
209         //   undeploy removed apps
210         //   deploy archives
211         //   deploy exploded
212         public void run()
213         {
214             // list new apps
215             final String[] zips = appsDir.list(new SuffixFileFilter(".zip"));
216             String[] apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
217             // we care only about removed anchors
218             String[] currentAnchors = appsDir.list(new SuffixFileFilter(APP_ANCHOR_SUFFIX));
219             @SuppressWarnings("unchecked")
220             final Collection<String> deletedAnchors = CollectionUtils.subtract(Arrays.asList(appAnchors), Arrays.asList(currentAnchors));
221             for (String deletedAnchor : deletedAnchors)
222             {
223                 // apps.find ( it.appName = (removedAnchor - suffix))
224                 String appName = StringUtils.removeEnd(deletedAnchor, APP_ANCHOR_SUFFIX);
225                 try
226                 {
227                     onApplicationUndeployRequested(appName);
228                 }
229                 catch (Throwable t)
230                 {
231                     logger.error("Failed to undeploy application: " + appName, t);
232                 }
233             }
234             appAnchors = currentAnchors;
235 
236 
237             // new packed Mule apps
238             for (String zip : zips)
239             {
240                 try
241                 {
242                     // check if this app is running first, undeploy it then
243                     final String appName = StringUtils.removeEnd(zip, ".zip");
244                     Application app = (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
245                     if (app != null)
246                     {
247                         onApplicationUndeployRequested(appName);
248                     }
249                     onNewApplicationArchive(new File(appsDir, zip));
250                 }
251                 catch (Throwable t)
252                 {
253                     logger.error("Failed to deploy application archive: " + zip, t);
254                 }
255             }
256 
257             // re-scan exploded apps and update our state, as deploying Mule app archives might have added some
258             if (zips.length > 0)
259             {
260                 apps = appsDir.list(DirectoryFileFilter.DIRECTORY);
261                 deployedApps = apps;
262             }
263 
264             // new exploded Mule apps
265             @SuppressWarnings("unchecked")
266             final Collection<String> addedApps = CollectionUtils.subtract(Arrays.asList(apps), Arrays.asList(deployedApps));
267             for (String addedApp : addedApps)
268             {
269                 try
270                 {
271                     onNewExplodedApplication(addedApp);
272                 }
273                 catch (Throwable t)
274                 {
275                     logger.error("Failed to deploy exploded application: " + addedApp, t);
276                 }
277             }
278 
279             deployedApps = apps;
280         }
281 
282         protected void onApplicationUndeployRequested(String appName) throws Exception
283         {
284             if (logger.isInfoEnabled())
285             {
286                 logger.info("================== Request to Undeploy Application: " + appName);
287             }
288 
289             Application app = (Application) CollectionUtils.find(applications, new BeanPropertyValueEqualsPredicate("appName", appName));
290             applications.remove(app);
291             deployer.undeploy(app);
292         }
293 
294         /**
295          * @param appName application name as it appears in $MULE_HOME/apps
296          */
297         protected void onNewExplodedApplication(String appName) throws Exception
298         {
299             if (logger.isInfoEnabled())
300             {
301                 logger.info("================== New Exploded Application: " + appName);
302             }
303 
304             Application a = new ApplicationWrapper(new DefaultMuleApplication(appName));
305             // add to the list of known apps first to avoid deployment loop on failure
306             applications.add(a);
307             deployer.deploy(a);
308         }
309 
310         protected void onNewApplicationArchive(File file) throws Exception
311         {
312             if (logger.isInfoEnabled())
313             {
314                 logger.info("================== New Application Archive: " + file);
315             }
316 
317             // check if there are any broken leftovers and clean it up before exploded an updated zip
318             final String appName = FilenameUtils.getBaseName(file.getName());
319             FileUtils.deleteTree(new File(appsDir, appName));
320 
321             Application app = deployer.installFrom(file.toURL());
322             // add to the list of known apps first to avoid deployment loop on failure
323             applications.add(app);
324             deployer.deploy(app);
325         }
326     }
327 
328 }