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
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
108
109
110
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
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
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
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
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
248 if (!configID.isResolved()) {
249 configID = idBuilder.resolve(configID, "car");
250 }
251
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
257 }
258
259
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
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
313
314
315
316
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
350 throw new DeploymentException(e);
351 } catch (Throwable e) {
352
353 configsCleanupRequired = true;
354 throw e;
355 } finally {
356 thread.setContextClassLoader(oldCl);
357 if (context != null) {
358 context.close();
359 }
360 if (configsCleanupRequired) {
361
362 cleanupConfigurations(configurations);
363 }
364 }
365 } catch (Throwable e) {
366
367
368
369
370
371
372
373
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
452 if (pendingDeletionIndex.size() == 0)
453 return;
454
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 }