View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.deployment;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.net.URI;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Enumeration;
27  import java.util.Hashtable;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Properties;
31  import java.util.Set;
32  import java.util.jar.Attributes;
33  import java.util.jar.JarFile;
34  import java.util.jar.Manifest;
35  
36  import javax.management.ObjectName;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.geronimo.common.DeploymentException;
41  import org.apache.geronimo.deployment.util.DeploymentUtil;
42  import org.apache.geronimo.gbean.AbstractName;
43  import org.apache.geronimo.gbean.AbstractNameQuery;
44  import org.apache.geronimo.gbean.GBeanInfo;
45  import org.apache.geronimo.gbean.GBeanInfoBuilder;
46  import org.apache.geronimo.kernel.GBeanNotFoundException;
47  import org.apache.geronimo.kernel.Kernel;
48  import org.apache.geronimo.kernel.config.Configuration;
49  import org.apache.geronimo.kernel.config.ConfigurationData;
50  import org.apache.geronimo.kernel.config.ConfigurationManager;
51  import org.apache.geronimo.kernel.config.ConfigurationStore;
52  import org.apache.geronimo.kernel.config.ConfigurationUtil;
53  import org.apache.geronimo.kernel.config.InvalidConfigException;
54  import org.apache.geronimo.kernel.config.DeploymentWatcher;
55  import org.apache.geronimo.kernel.repository.Artifact;
56  import org.apache.geronimo.kernel.repository.ArtifactResolver;
57  import org.apache.geronimo.system.configuration.ExecutableConfigurationUtil;
58  import org.apache.geronimo.system.main.CommandLineManifest;
59  
60  /**
61   * GBean that knows how to deploy modules (by consulting available module builders)
62   *
63   * @version $Rev: 427123 $ $Date: 2006-07-31 07:16:46 -0700 (Mon, 31 Jul 2006) $
64   */
65  public class Deployer {
66      private static final Log log = LogFactory.getLog(Deployer.class);
67      private final int REAPER_INTERVAL = 60 * 1000;
68      private final Properties pendingDeletionIndex = new Properties();
69      private DeployerReaper reaper;
70      private final Collection builders;
71      private final Collection stores;
72      private final Collection watchers;
73      private final ArtifactResolver artifactResolver;
74      private final Kernel kernel;
75  
76      public Deployer(Collection builders, Collection stores, Collection watchers, Kernel kernel) {
77          this(builders, stores, watchers, getArtifactResolver(kernel), kernel);
78      }
79  
80      private static ArtifactResolver getArtifactResolver(Kernel kernel) {
81          ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
82          return configurationManager.getArtifactResolver();
83      }
84  
85      public Deployer(Collection builders, Collection stores, Collection watchers, ArtifactResolver artifactResolver, Kernel kernel) {
86          this.builders = builders;
87          this.stores = stores;
88          this.watchers = watchers;
89          this.artifactResolver = artifactResolver;
90          this.kernel = kernel;
91  
92          // Create and start the reaper...
93          this.reaper = new DeployerReaper(REAPER_INTERVAL);
94          Thread t = new Thread(reaper, "Geronimo Config Store Reaper");
95          t.setDaemon(true);
96          t.start();
97      }
98  
99      public List deploy(boolean inPlace, File moduleFile, File planFile) throws DeploymentException {
100         return deploy(inPlace, moduleFile, planFile, null);
101     }
102 
103     public List deploy(boolean inPlace, File moduleFile, File planFile, String targetConfigStore) throws DeploymentException {
104         File originalModuleFile = moduleFile;
105         File tmpDir = null;
106         if (moduleFile != null && !moduleFile.isDirectory()) {
107             // todo jar url handling with Sun's VM on Windows leaves a lock on the module file preventing rebuilds
108             // to address this we use a gross hack and copy the file to a temporary directory
109             // the lock on the file will prevent that being deleted properly until the URLJarFile has
110             // been GC'ed.
111             try {
112                 tmpDir = File.createTempFile("geronimo-deployer", ".tmpdir");
113                 tmpDir.delete();
114                 tmpDir.mkdir();
115                 File tmpFile = new File(tmpDir, moduleFile.getName());
116                 DeploymentUtil.copyFile(moduleFile, tmpFile);
117                 moduleFile = tmpFile;
118             } catch (IOException e) {
119                 throw new DeploymentException(e);
120             }
121         }
122 
123         try {
124             return deploy(inPlace, planFile, moduleFile, null, true, null, null, null, null, null, null, null, targetConfigStore);
125         } catch (DeploymentException e) {
126             log.debug("Deployment failed: plan=" + planFile + ", module=" + originalModuleFile, e);
127             throw e.cleanse();
128         } finally {
129             if (tmpDir != null) {
130                 if (!DeploymentUtil.recursiveDelete(tmpDir)) {
131                     pendingDeletionIndex.setProperty(tmpDir.getName(), "delete");
132                 }
133             }
134         }
135     }
136 
137     /**
138      * Gets a URL that a remote deploy client can use to upload files to the
139      * server. Looks up a remote deploy web application by searching for a
140      * particular GBean and figuring out a reference to the web application
141      * based on that.  Then constructs a URL pointing to that web application
142      * based on available connectors for the web container and the context
143      * root for the web application.
144      *
145      * @return The URL that clients should use for deployment file uploads.
146      */
147     public String getRemoteDeployUploadURL() {
148         // Get the token GBean from the remote deployment configuration
149         Set set = kernel.listGBeans(new AbstractNameQuery("org.apache.geronimo.deployment.remote.RemoteDeployToken"));
150         if (set.size() == 0) {
151             return null;
152         }
153         AbstractName token = (AbstractName) set.iterator().next();
154         // Identify the parent configuration for that GBean
155         set = kernel.getDependencyManager().getParents(token);
156         ObjectName config = null;
157         for (Iterator it = set.iterator(); it.hasNext();) {
158             AbstractName name = (AbstractName) it.next();
159             if (Configuration.isConfigurationObjectName(name.getObjectName())) {
160                 config = name.getObjectName();
161                 break;
162             }
163         }
164         if (config == null) {
165             log.warn("Unable to find remote deployment configuration; is the remote deploy web application running?");
166             return null;
167         }
168         // Generate the URL based on the remote deployment configuration
169         Hashtable hash = new Hashtable();
170         hash.put("J2EEApplication", token.getObjectName().getKeyProperty("J2EEApplication"));
171         hash.put("j2eeType", "WebModule");
172         try {
173             hash.put("name", Configuration.getConfigurationID(config).toString());
174             Set names = kernel.listGBeans(new AbstractNameQuery(null, hash));
175             if (names.size() != 1) {
176                 log.error("Unable to look up remote deploy upload URL");
177                 return null;
178             }
179             AbstractName module = (AbstractName) names.iterator().next();
180             return kernel.getAttribute(module, "URLFor") + "/upload";
181         } catch (Exception e) {
182             log.error("Unable to look up remote deploy upload URL", e);
183             return null;
184         }
185     }
186 
187     public List deploy(boolean inPlace,
188             File planFile,
189             File moduleFile,
190             File targetFile,
191             boolean install,
192             String mainClass,
193             String mainGBean, String mainMethod, String manifestConfigurations, String classPath,
194             String endorsedDirs,
195             String extensionDirs,
196             String targetConfigurationStore) throws DeploymentException {
197         if (planFile == null && moduleFile == null) {
198             throw new DeploymentException("No plan or module specified");
199         }
200 
201         if (planFile != null) {
202             if (!planFile.exists()) {
203                 throw new DeploymentException("Plan file does not exist: " + planFile.getAbsolutePath());
204             }
205             if (!planFile.isFile()) {
206                 throw new DeploymentException("Plan file is not a regular file: " + planFile.getAbsolutePath());
207             }
208         }
209 
210         JarFile module = null;
211         if (moduleFile != null) {
212             if (inPlace && !moduleFile.isDirectory()) {
213                 throw new DeploymentException("In place deployment is not allowed for packed module");
214             }
215             if (!moduleFile.exists()) {
216                 throw new DeploymentException("Module file does not exist: " + moduleFile.getAbsolutePath());
217             }
218             try {
219                 module = DeploymentUtil.createJarFile(moduleFile);
220             } catch (IOException e) {
221                 throw new DeploymentException("Cound not open module file: " + moduleFile.getAbsolutePath(), e);
222             }
223         }
224 
225 //        File configurationDir = null;
226         ModuleIDBuilder idBuilder = new ModuleIDBuilder();
227         try {
228             Object plan = null;
229             ConfigurationBuilder builder = null;
230             for (Iterator i = builders.iterator(); i.hasNext();) {
231                 ConfigurationBuilder candidate = (ConfigurationBuilder) i.next();
232                 plan = candidate.getDeploymentPlan(planFile, module, idBuilder);
233                 if (plan != null) {
234                     builder = candidate;
235                     break;
236                 }
237             }
238             if (builder == null) {
239                 throw new DeploymentException("Cannot deploy the requested application module because no deployer is able to handle it. " +
240                         " This can happen if you have omitted the J2EE deployment descriptor, disabled a deployer module, or if, for example, you are trying to deploy an" +
241                         " EJB module on a minimal Geronimo server that does not have EJB support installed.  (" +
242                         (planFile == null ? "" : "planFile=" + planFile.getAbsolutePath()) +
243                         (moduleFile == null ? "" : (planFile == null ? "" : ", ") + "moduleFile=" + moduleFile.getAbsolutePath()) + ")");
244             }
245 
246             Artifact configID = builder.getConfigurationID(plan, module, idBuilder);
247             // If the Config ID isn't fully resolved, populate it with defaults
248             if (!configID.isResolved()) {
249                 configID = idBuilder.resolve(configID, "car");
250             }
251             // Make sure this configuration doesn't already exist
252             try {
253                 kernel.getGBeanState(Configuration.getConfigurationAbstractName(configID));
254                 throw new DeploymentException("Module " + configID + " already exists in the server.  Try to undeploy it first or use the redeploy command.");
255             } catch (GBeanNotFoundException e) {
256                 // this is good
257             }
258 
259             // create the manifest
260             Manifest manifest;
261             if (mainClass != null) {
262                 manifest = new Manifest();
263                 Attributes mainAttributes = manifest.getMainAttributes();
264                 mainAttributes.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
265                 if (mainClass != null) {
266                     mainAttributes.putValue(Attributes.Name.MAIN_CLASS.toString(), mainClass);
267                 }
268                 if (mainGBean != null) {
269                     mainAttributes.putValue(CommandLineManifest.MAIN_GBEAN.toString(), mainGBean);
270                 }
271                 if (mainMethod != null) {
272                     mainAttributes.putValue(CommandLineManifest.MAIN_METHOD.toString(), mainMethod);
273                 }
274                 if (manifestConfigurations != null) {
275                     mainAttributes.putValue(CommandLineManifest.CONFIGURATIONS.toString(), manifestConfigurations);
276                 }
277                 if (classPath != null) {
278                     mainAttributes.putValue(Attributes.Name.CLASS_PATH.toString(), classPath);
279                 }
280                 if (endorsedDirs != null) {
281                     mainAttributes.putValue(CommandLineManifest.ENDORSED_DIRS.toString(), endorsedDirs);
282                 }
283                 if (extensionDirs != null) {
284                     mainAttributes.putValue(CommandLineManifest.EXTENSION_DIRS.toString(), extensionDirs);
285                 }
286             } else {
287                 manifest = null;
288             }
289 
290             if (stores.isEmpty()) {
291                 throw new DeploymentException("No ConfigurationStores!");
292             }
293             ConfigurationStore store;
294             if (targetConfigurationStore != null) {
295                 AbstractName targetStoreName = new AbstractName(new URI(targetConfigurationStore));
296                 store = (ConfigurationStore) kernel.getGBean(targetStoreName);
297             } else {
298                 store = (ConfigurationStore) stores.iterator().next();
299             }
300 
301             // It's our responsibility to close this context, once we're done with it...
302             DeploymentContext context = builder.buildConfiguration(inPlace, configID, plan, module, stores, artifactResolver, store);
303             List configurations = new ArrayList();
304             boolean configsCleanupRequired = false;
305             configurations.add(context.getConfigurationData());
306             configurations.addAll(context.getAdditionalDeployment());
307 
308             if (configurations.isEmpty()) {
309                 throw new DeploymentException("Deployer did not create any configurations");
310             }
311 
312             // Set TCCL to the classloader for the configuration being deployed
313             // so that any static blocks invoked during the loading of classes 
314             // during serialization of the configuration have the correct TCCL 
315             // ( a TCCL that is consistent with what is set when the same
316             // classes are loaded when the configuration is started.
317             Thread thread = Thread.currentThread();
318             ClassLoader oldCl = thread.getContextClassLoader();
319             thread.setContextClassLoader( context.getConfiguration().getConfigurationClassLoader());
320             try {
321                 if (targetFile != null) {
322                     if (configurations.size() > 1) {
323                         throw new DeploymentException("Deployer created more than one configuration");
324                     }
325                     ConfigurationData configurationData = (ConfigurationData) configurations.get(0);
326                     ExecutableConfigurationUtil.createExecutableConfiguration(configurationData, manifest, targetFile);
327                 }
328                 if (install) {
329                     List deployedURIs = new ArrayList();
330                     for (Iterator iterator = configurations.iterator(); iterator.hasNext();) {
331                         ConfigurationData configurationData = (ConfigurationData) iterator.next();
332                         store.install(configurationData);
333                         deployedURIs.add(configurationData.getId().toString());
334                     }
335                     notifyWatchers(deployedURIs);
336                     return deployedURIs;
337                 } else {
338                     configsCleanupRequired = true;
339                     return Collections.EMPTY_LIST;
340                 }
341             } catch (DeploymentException e) {
342                 configsCleanupRequired = true;
343                 throw e;
344             } catch (IOException e) {
345                 configsCleanupRequired = true;
346                 throw e;
347             } catch (InvalidConfigException e) {
348                 configsCleanupRequired = true;
349                 // unlikely as we just built this
350                 throw new DeploymentException(e);
351             } catch (Throwable e) {
352                 // Could get here if serialization of the configuration failed (GERONIMO-1996)
353                 configsCleanupRequired = true;
354                 throw e;
355             } finally {
356                 thread.setContextClassLoader(oldCl);
357                 if (context != null) {
358                     context.close();
359                 }
360                 if (configsCleanupRequired) {
361                     // We do this after context is closed so the module jar isn't open
362                     cleanupConfigurations(configurations);
363                 }
364             }
365         } catch (Throwable e) {
366             //TODO not clear all errors will result in total cleanup
367 //            File configurationDir = configurationData.getConfigurationDir();
368 //            if (!DeploymentUtil.recursiveDelete(configurationDir)) {
369 //                pendingDeletionIndex.setProperty(configurationDir.getName(), new String("delete"));
370 //                log.debug("Queued deployment directory to be reaped " + configurationDir);
371 //            }
372 //            if (targetFile != null) {
373 //                targetFile.delete();
374 //            }
375 
376             if (e instanceof Error) {
377                 log.error("Deployment failed due to ", e);
378                 throw (Error) e;
379             } else if (e instanceof DeploymentException) {
380                 throw (DeploymentException) e;
381             } else if (e instanceof Exception) {
382                 log.error("Deployment failed due to ", e);
383                 throw new DeploymentException(e);
384             }
385             throw new Error(e);
386         } finally {
387             DeploymentUtil.close(module);
388         }
389     }
390 
391     private void notifyWatchers(List list) {
392         Artifact[] arts = new Artifact[list.size()];
393         for (int i = 0; i < list.size(); i++) {
394             String s = (String) list.get(i);
395             arts[i] = Artifact.create(s);
396         }
397         for (Iterator it = watchers.iterator(); it.hasNext();) {
398             DeploymentWatcher watcher = (DeploymentWatcher) it.next();
399             for (int i = 0; i < arts.length; i++) {
400                 Artifact art = arts[i];
401                 watcher.deployed(art);
402             }
403         }
404     }
405 
406     private void cleanupConfigurations(List configurations) {
407         for (Iterator iterator = configurations.iterator(); iterator.hasNext();) {
408             ConfigurationData configurationData = (ConfigurationData) iterator.next();
409             File configurationDir = configurationData.getConfigurationDir();
410             if (!DeploymentUtil.recursiveDelete(configurationDir)) {
411                 pendingDeletionIndex.setProperty(configurationDir.getName(), "delete");
412                 log.debug("Queued deployment directory to be reaped " + configurationDir);
413             }
414         }
415     }
416 
417     /**
418      * Thread to cleanup unused temporary Deployer directories (and files).
419      * On Windows, open files can't be deleted. Until MultiParentClassLoaders
420      * are GC'ed, we won't be able to delete Config Store directories/files.
421      */
422     class DeployerReaper implements Runnable {
423         private final int reaperInterval;
424         private volatile boolean done = false;
425 
426         public DeployerReaper(int reaperInterval) {
427             this.reaperInterval = reaperInterval;
428         }
429 
430         public void close() {
431             this.done = true;
432         }
433 
434         public void run() {
435             log.debug("ConfigStoreReaper started");
436             while (!done) {
437                 try {
438                     Thread.sleep(reaperInterval);
439                 } catch (InterruptedException e) {
440                     continue;
441                 }
442                 reap();
443             }
444         }
445 
446         /**
447          * For every directory in the pendingDeletionIndex, attempt to delete all
448          * sub-directories and files.
449          */
450         public void reap() {
451             // return, if there's nothing to do
452             if (pendingDeletionIndex.size() == 0)
453                 return;
454             // Otherwise, attempt to delete all of the directories
455             Enumeration list = pendingDeletionIndex.propertyNames();
456             while (list.hasMoreElements()) {
457                 String dirName = (String) list.nextElement();
458                 File deleteDir = new File(dirName);
459 
460                 if (!DeploymentUtil.recursiveDelete(deleteDir)) {
461                     pendingDeletionIndex.remove(deleteDir);
462                     log.debug("Reaped deployment directory " + deleteDir);
463                 }
464             }
465         }
466     }
467 
468     public static final GBeanInfo GBEAN_INFO;
469 
470     private static final String DEPLOYER = "Deployer";
471 
472     static {
473         GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(Deployer.class, DEPLOYER);
474 
475         infoFactory.addAttribute("kernel", Kernel.class, false);
476         infoFactory.addAttribute("remoteDeployUploadURL", String.class, false);
477         infoFactory.addOperation("deploy", new Class[]{boolean.class, File.class, File.class});
478         infoFactory.addOperation("deploy", new Class[]{boolean.class, File.class, File.class, String.class});
479         infoFactory.addOperation("deploy", new Class[]{boolean.class, File.class, File.class, File.class, boolean.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class, String.class});
480 
481         infoFactory.addReference("Builders", ConfigurationBuilder.class, "ConfigBuilder");
482         infoFactory.addReference("Store", ConfigurationStore.class, "ConfigurationStore");
483         infoFactory.addReference("Watchers", DeploymentWatcher.class);
484 
485         infoFactory.setConstructor(new String[]{"Builders", "Store", "Watchers", "kernel"});
486 
487         GBEAN_INFO = infoFactory.getBeanInfo();
488     }
489 
490     public static GBeanInfo getGBeanInfo() {
491         return GBEAN_INFO;
492     }
493 }