View Javadoc
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 org.mule.config.i18n.MessageFactory;
10  import org.mule.module.launcher.application.Application;
11  import org.mule.module.reboot.MuleContainerBootstrapUtils;
12  import org.mule.util.FileUtils;
13  import org.mule.util.FilenameUtils;
14  
15  import java.beans.Introspector;
16  import java.io.File;
17  import java.io.IOException;
18  import java.net.URISyntaxException;
19  import java.net.URL;
20  import java.util.concurrent.TimeUnit;
21  import java.util.concurrent.locks.ReentrantLock;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  public class DefaultMuleDeployer implements MuleDeployer
27  {
28  
29      protected transient final Log logger = LogFactory.getLog(getClass());
30      protected DeploymentService deploymentService;
31  
32      public DefaultMuleDeployer(DeploymentService deploymentService)
33      {
34          this.deploymentService = deploymentService;
35      }
36  
37      public void deploy(Application app)
38      {
39          final ReentrantLock lock = deploymentService.getLock();
40          try
41          {
42              if (!lock.tryLock(0, TimeUnit.SECONDS))
43              {
44                  return;
45              }
46              app.install();
47              app.init();
48              app.start();
49          }
50          catch (InterruptedException e)
51          {
52              Thread.currentThread().interrupt();
53              return;
54          }
55          catch (Throwable t)
56          {
57              app.dispose();
58  
59              if (t instanceof DeploymentException)
60              {
61                  // re-throw as is
62                  throw ((DeploymentException) t);
63              }
64  
65              final String msg = String.format("Failed to deploy application [%s]", app.getAppName());
66              throw new DeploymentException(MessageFactory.createStaticMessage(msg), t);
67          }
68          finally
69          {
70              if (lock.isHeldByCurrentThread())
71              {
72                  lock.unlock();
73              }
74          }
75      }
76  
77      public void undeploy(Application app)
78      {
79          final ReentrantLock lock = deploymentService.getLock();
80          try
81          {
82              if (!lock.tryLock(0, TimeUnit.SECONDS))
83              {
84                  return;
85              }
86          }
87          catch (InterruptedException e)
88          {
89              Thread.currentThread().interrupt();
90              return;
91          }
92  
93          try
94          {
95              tryToStopApp(app);
96              tryToDisposeApp(app);
97  
98              try
99              {
100                 final File appDir = new File(MuleContainerBootstrapUtils.getMuleAppsDir(), app.getAppName());
101                 FileUtils.deleteDirectory(appDir);
102 
103                 // remove a marker, harmless, but a tidy app dir is always better :)
104                 final File marker = new File(MuleContainerBootstrapUtils.getMuleAppsDir(), String.format("%s-anchor.txt", app.getAppName()));
105                 marker.delete();
106 
107                 Introspector.flushCaches();
108             }
109             catch (IOException e)
110             {
111                 throw new DeploymentException(MessageFactory.createStaticMessage("Cannot delete application folder"), e);
112             }
113         }
114         finally
115         {
116             if (lock.isHeldByCurrentThread())
117             {
118                 lock.unlock();
119             }
120         }
121     }
122 
123     private void tryToDisposeApp(Application app)
124     {
125         try
126         {
127             app.dispose();
128         }
129         catch (Throwable t)
130         {
131             logger.error(String.format("Unable to cleanly dispose application '%s'. Restart Mule if you get errors redeploying this application", app.getAppName()), t);
132         }
133     }
134 
135     private void tryToStopApp(Application app)
136     {
137         try
138         {
139             app.stop();
140         }
141         catch (Throwable t)
142         {
143             logger.error(String.format("Unable to cleanly stop application '%s'. Restart Mule if you get errors redeploying this application", app.getAppName()), t);
144         }
145     }
146 
147     public Application installFromAppDir(String packedMuleAppFileName) throws IOException
148     {
149         final ReentrantLock lock = deploymentService.getLock();
150         try
151         {
152             if (!lock.tryLock(0, TimeUnit.SECONDS))
153             {
154                 throw new IOException("Another deployment operation is in progress");
155             }
156 
157             final File appsDir = MuleContainerBootstrapUtils.getMuleAppsDir();
158             File appFile = new File(appsDir, packedMuleAppFileName);
159             // basic security measure: outside apps dir use installFrom(url) and go through any
160             // restrictions applied to it
161             if (!appFile.getParentFile().equals(appsDir))
162             {
163                 throw new SecurityException("installFromAppDir() can only deploy from $MULE_HOME/apps. Use installFrom(url) instead.");
164             }
165             return installFrom(appFile.toURL());
166         }
167         catch (InterruptedException e)
168         {
169             Thread.currentThread().interrupt();
170             throw new IOException("Install operation has been interrupted");
171         }
172         finally
173         {
174             if (lock.isHeldByCurrentThread())
175             {
176                 lock.unlock();
177             }
178         }
179     }
180 
181     public Application installFrom(URL url) throws IOException
182     {
183         // TODO plug in app-bloodhound/validator here?
184         if (!url.toString().endsWith(".zip"))
185         {
186             throw new IllegalArgumentException("Invalid Mule application archive: " + url);
187         }
188 
189         final String baseName = FilenameUtils.getBaseName(url.toString());
190         if (baseName.contains("%20"))
191         {
192             throw new DeploymentInitException(
193                     MessageFactory.createStaticMessage("Mule application name may not contain spaces: " + baseName));
194         }
195 
196         final ReentrantLock lock = deploymentService.getLock();
197 
198         String appName;
199         File appDir = null;
200         boolean errorEncountered = false;
201         try
202         {
203             if (!lock.tryLock(0, TimeUnit.SECONDS))
204             {
205                 throw new IOException("Another deployment operation is in progress");
206             }
207 
208             final File appsDir = MuleContainerBootstrapUtils.getMuleAppsDir();
209 
210             final String fullPath = url.toURI().toString();
211 
212             if (logger.isInfoEnabled())
213             {
214                 logger.info("Exploding a Mule application archive: " + fullPath);
215             }
216 
217             appName = FilenameUtils.getBaseName(fullPath);
218             appDir = new File(appsDir, appName);
219             // normalize the full path + protocol to make unzip happy
220             final File source = new File(url.toURI());
221             
222             FileUtils.unzip(source, appDir);
223             if ("file".equals(url.getProtocol()))
224             {
225                 FileUtils.deleteQuietly(source);
226             }
227         }
228         catch (URISyntaxException e)
229         {
230             errorEncountered = true;
231             final IOException ex = new IOException(e.getMessage());
232             ex.fillInStackTrace();
233             throw ex;
234         }
235         catch (InterruptedException e)
236         {
237             errorEncountered = true;
238             Thread.currentThread().interrupt();
239             throw new IOException("Install operation has been interrupted");
240         }
241         catch (IOException e)
242         {
243             errorEncountered = true;
244             // re-throw
245             throw e;
246         }
247         catch (Throwable t)
248         {
249             errorEncountered = true;
250             final String msg = "Failed to install app from URL: " + url;
251             throw new DeploymentInitException(MessageFactory.createStaticMessage(msg), t);
252         }
253         finally {
254             // delete an app dir, as it's broken
255             if (errorEncountered && appDir != null && appDir.exists())
256             {
257                 final boolean couldNotDelete = FileUtils.deleteTree(appDir);
258                 /*
259                 if (couldNotDelete)
260                 {
261                     final String msg = String.format("Couldn't delete app directory '%s' after it failed to install", appDir);
262                     logger.error(msg);
263                 }
264                 */
265             }
266             if (lock.isHeldByCurrentThread())
267             {
268                 lock.unlock();
269             }
270         }
271 
272         // appname is never null by now
273         return deploymentService.getAppFactory().createApp(appName);
274     }
275 }