001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.deployment; 019 020 import java.io.File; 021 import java.io.IOException; 022 import java.net.URI; 023 import java.util.ArrayList; 024 import java.util.Collection; 025 import java.util.Collections; 026 import java.util.Enumeration; 027 import java.util.Hashtable; 028 import java.util.Iterator; 029 import java.util.List; 030 import java.util.Properties; 031 import java.util.Set; 032 import java.util.jar.Attributes; 033 import java.util.jar.JarFile; 034 import java.util.jar.Manifest; 035 036 import javax.management.ObjectName; 037 038 import org.apache.commons.logging.Log; 039 import org.apache.commons.logging.LogFactory; 040 import org.apache.geronimo.common.DeploymentException; 041 import org.apache.geronimo.deployment.util.DeploymentUtil; 042 import org.apache.geronimo.gbean.AbstractName; 043 import org.apache.geronimo.gbean.AbstractNameQuery; 044 import org.apache.geronimo.gbean.GBeanInfo; 045 import org.apache.geronimo.gbean.GBeanInfoBuilder; 046 import org.apache.geronimo.kernel.GBeanNotFoundException; 047 import org.apache.geronimo.kernel.Kernel; 048 import org.apache.geronimo.kernel.config.Configuration; 049 import org.apache.geronimo.kernel.config.ConfigurationData; 050 import org.apache.geronimo.kernel.config.ConfigurationManager; 051 import org.apache.geronimo.kernel.config.ConfigurationStore; 052 import org.apache.geronimo.kernel.config.ConfigurationUtil; 053 import org.apache.geronimo.kernel.config.InvalidConfigException; 054 import org.apache.geronimo.kernel.config.DeploymentWatcher; 055 import org.apache.geronimo.kernel.repository.Artifact; 056 import org.apache.geronimo.kernel.repository.ArtifactResolver; 057 import org.apache.geronimo.system.configuration.ExecutableConfigurationUtil; 058 import org.apache.geronimo.system.main.CommandLineManifest; 059 060 /** 061 * GBean that knows how to deploy modules (by consulting available module builders) 062 * 063 * @version $Rev: 562021 $ $Date: 2007-08-02 01:51:18 -0400 (Thu, 02 Aug 2007) $ 064 */ 065 public class Deployer { 066 private static final Log log = LogFactory.getLog(Deployer.class); 067 private final int REAPER_INTERVAL = 60 * 1000; 068 private final Properties pendingDeletionIndex = new Properties(); 069 private DeployerReaper reaper; 070 private final String remoteDeployAddress; 071 private final Collection builders; 072 private final Collection stores; 073 private final Collection watchers; 074 private final ArtifactResolver artifactResolver; 075 private final Kernel kernel; 076 077 public Deployer(String remoteDeployAddress, Collection builders, Collection stores, Collection watchers, Kernel kernel) { 078 this(remoteDeployAddress, builders, stores, watchers, getArtifactResolver(kernel), kernel); 079 } 080 081 private static ArtifactResolver getArtifactResolver(Kernel kernel) { 082 ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel); 083 return configurationManager.getArtifactResolver(); 084 } 085 086 public Deployer(String remoteDeployAddress, Collection builders, Collection stores, Collection watchers, ArtifactResolver artifactResolver, Kernel kernel) { 087 this.remoteDeployAddress = remoteDeployAddress; 088 this.builders = builders; 089 this.stores = stores; 090 this.watchers = watchers; 091 this.artifactResolver = artifactResolver; 092 this.kernel = kernel; 093 094 // Create and start the reaper... 095 this.reaper = new DeployerReaper(REAPER_INTERVAL); 096 Thread t = new Thread(reaper, "Geronimo Config Store Reaper"); 097 t.setDaemon(true); 098 t.start(); 099 } 100 101 public List deploy(boolean inPlace, File moduleFile, File planFile) throws DeploymentException { 102 return deploy(inPlace, moduleFile, planFile, null); 103 } 104 105 public List deploy(boolean inPlace, File moduleFile, File planFile, String targetConfigStore) throws DeploymentException { 106 File originalModuleFile = moduleFile; 107 File tmpDir = null; 108 if (moduleFile != null && !moduleFile.isDirectory()) { 109 // todo jar url handling with Sun's VM on Windows leaves a lock on the module file preventing rebuilds 110 // to address this we use a gross hack and copy the file to a temporary directory 111 // the lock on the file will prevent that being deleted properly until the URLJarFile has 112 // been GC'ed. 113 try { 114 tmpDir = File.createTempFile("geronimo-deployer", ".tmpdir"); 115 tmpDir.delete(); 116 tmpDir.mkdir(); 117 File tmpFile = new File(tmpDir, moduleFile.getName()); 118 DeploymentUtil.copyFile(moduleFile, tmpFile); 119 moduleFile = tmpFile; 120 } catch (IOException e) { 121 throw new DeploymentException(e); 122 } 123 } 124 125 try { 126 return deploy(inPlace, moduleFile, planFile, null, true, null, null, null, null, null, null, null, targetConfigStore); 127 } catch (DeploymentException e) { 128 log.debug("Deployment failed: plan=" + planFile + ", module=" + originalModuleFile, e); 129 throw e.cleanse(); 130 } finally { 131 if (tmpDir != null) { 132 if (!DeploymentUtil.recursiveDelete(tmpDir)) { 133 pendingDeletionIndex.setProperty(tmpDir.getName(), "delete"); 134 } 135 } 136 } 137 } 138 139 /** 140 * Gets a URL that a remote deploy client can use to upload files to the 141 * server. Looks up a remote deploy web application by searching for a 142 * particular GBean and figuring out a reference to the web application 143 * based on that. Then constructs a URL pointing to that web application 144 * based on available connectors for the web container and the context 145 * root for the web application. 146 * 147 * @return The URL that clients should use for deployment file uploads. 148 */ 149 public String getRemoteDeployUploadURL() { 150 // Get the token GBean from the remote deployment configuration 151 Set set = kernel.listGBeans(new AbstractNameQuery("org.apache.geronimo.deployment.remote.RemoteDeployToken")); 152 if (set.size() == 0) { 153 return null; 154 } 155 AbstractName token = (AbstractName) set.iterator().next(); 156 // Identify the parent configuration for that GBean 157 set = kernel.getDependencyManager().getParents(token); 158 ObjectName config = null; 159 for (Iterator it = set.iterator(); it.hasNext();) { 160 AbstractName name = (AbstractName) it.next(); 161 if (Configuration.isConfigurationObjectName(name.getObjectName())) { 162 config = name.getObjectName(); 163 break; 164 } 165 } 166 if (config == null) { 167 log.warn("Unable to find remote deployment configuration; is the remote deploy web application running?"); 168 return null; 169 } 170 // Generate the URL based on the remote deployment configuration 171 Hashtable hash = new Hashtable(); 172 hash.put("J2EEApplication", token.getObjectName().getKeyProperty("J2EEApplication")); 173 hash.put("j2eeType", "WebModule"); 174 try { 175 hash.put("name", Configuration.getConfigurationID(config).toString()); 176 Set names = kernel.listGBeans(new AbstractNameQuery(null, hash)); 177 if (names.size() != 1) { 178 log.error("Unable to look up remote deploy upload URL"); 179 return null; 180 } 181 AbstractName module = (AbstractName) names.iterator().next(); 182 return remoteDeployAddress + "/" + kernel.getAttribute(module, "contextPath") + "/upload"; 183 } catch (Exception e) { 184 log.error("Unable to look up remote deploy upload URL", e); 185 return null; 186 } 187 } 188 189 public List deploy(boolean inPlace, 190 File moduleFile, 191 File planFile, 192 File targetFile, 193 boolean install, 194 String mainClass, 195 String mainGBean, String mainMethod, String manifestConfigurations, String classPath, 196 String endorsedDirs, 197 String extensionDirs, 198 String targetConfigurationStore) throws DeploymentException { 199 if (planFile == null && moduleFile == null) { 200 throw new DeploymentException("No plan or module specified"); 201 } 202 203 if (planFile != null) { 204 if (!planFile.exists()) { 205 throw new DeploymentException("Plan file does not exist: " + planFile.getAbsolutePath()); 206 } 207 if (!planFile.isFile()) { 208 throw new DeploymentException("Plan file is not a regular file: " + planFile.getAbsolutePath()); 209 } 210 } 211 212 JarFile module = null; 213 if (moduleFile != null) { 214 if (inPlace && !moduleFile.isDirectory()) { 215 throw new DeploymentException("In place deployment is not allowed for packed module"); 216 } 217 if (!moduleFile.exists()) { 218 throw new DeploymentException("Module file does not exist: " + moduleFile.getAbsolutePath()); 219 } 220 try { 221 module = DeploymentUtil.createJarFile(moduleFile); 222 } catch (IOException e) { 223 throw new DeploymentException("Cound not open module file: " + moduleFile.getAbsolutePath(), e); 224 } 225 } 226 227 // File configurationDir = null; 228 ModuleIDBuilder idBuilder = new ModuleIDBuilder(); 229 try { 230 Object plan = null; 231 ConfigurationBuilder builder = null; 232 for (Iterator i = builders.iterator(); i.hasNext();) { 233 ConfigurationBuilder candidate = (ConfigurationBuilder) i.next(); 234 plan = candidate.getDeploymentPlan(planFile, module, idBuilder); 235 if (plan != null) { 236 builder = candidate; 237 break; 238 } 239 } 240 if (builder == null) { 241 throw new DeploymentException("Cannot deploy the requested application module because no deployer is able to handle it. " + 242 " 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" + 243 " EJB module on a minimal Geronimo server that does not have EJB support installed. (" + 244 (planFile == null ? "" : "planFile=" + planFile.getAbsolutePath()) + 245 (moduleFile == null ? "" : (planFile == null ? "" : ", ") + "moduleFile=" + moduleFile.getAbsolutePath()) + ")"); 246 } 247 248 Artifact configID = builder.getConfigurationID(plan, module, idBuilder); 249 // If the Config ID isn't fully resolved, populate it with defaults 250 if (!configID.isResolved()) { 251 configID = idBuilder.resolve(configID, "car"); 252 } 253 // Make sure this configuration doesn't already exist 254 try { 255 kernel.getGBeanState(Configuration.getConfigurationAbstractName(configID)); 256 throw new DeploymentException("Module " + configID + " already exists in the server. Try to undeploy it first or use the redeploy command."); 257 } catch (GBeanNotFoundException e) { 258 // this is good 259 } 260 261 // create the manifest 262 Manifest manifest; 263 if (mainClass != null) { 264 manifest = new Manifest(); 265 Attributes mainAttributes = manifest.getMainAttributes(); 266 mainAttributes.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0"); 267 if (mainClass != null) { 268 mainAttributes.putValue(Attributes.Name.MAIN_CLASS.toString(), mainClass); 269 } 270 if (mainGBean != null) { 271 mainAttributes.putValue(CommandLineManifest.MAIN_GBEAN.toString(), mainGBean); 272 } 273 if (mainMethod != null) { 274 mainAttributes.putValue(CommandLineManifest.MAIN_METHOD.toString(), mainMethod); 275 } 276 if (manifestConfigurations != null) { 277 mainAttributes.putValue(CommandLineManifest.CONFIGURATIONS.toString(), manifestConfigurations); 278 } 279 if (classPath != null) { 280 mainAttributes.putValue(Attributes.Name.CLASS_PATH.toString(), classPath); 281 } 282 if (endorsedDirs != null) { 283 mainAttributes.putValue(CommandLineManifest.ENDORSED_DIRS.toString(), endorsedDirs); 284 } 285 if (extensionDirs != null) { 286 mainAttributes.putValue(CommandLineManifest.EXTENSION_DIRS.toString(), extensionDirs); 287 } 288 } else { 289 manifest = null; 290 } 291 292 if (stores.isEmpty()) { 293 throw new DeploymentException("No ConfigurationStores!"); 294 } 295 ConfigurationStore store; 296 if (targetConfigurationStore != null) { 297 AbstractName targetStoreName = new AbstractName(new URI(targetConfigurationStore)); 298 store = (ConfigurationStore) kernel.getGBean(targetStoreName); 299 } else { 300 store = (ConfigurationStore) stores.iterator().next(); 301 } 302 303 // It's our responsibility to close this context, once we're done with it... 304 DeploymentContext context = builder.buildConfiguration(inPlace, configID, plan, module, stores, artifactResolver, store); 305 List configurations = new ArrayList(); 306 boolean configsCleanupRequired = false; 307 configurations.add(context.getConfigurationData()); 308 configurations.addAll(context.getAdditionalDeployment()); 309 310 if (configurations.isEmpty()) { 311 throw new DeploymentException("Deployer did not create any configurations"); 312 } 313 314 // Set TCCL to the classloader for the configuration being deployed 315 // so that any static blocks invoked during the loading of classes 316 // during serialization of the configuration have the correct TCCL 317 // ( a TCCL that is consistent with what is set when the same 318 // classes are loaded when the configuration is started. 319 Thread thread = Thread.currentThread(); 320 ClassLoader oldCl = thread.getContextClassLoader(); 321 thread.setContextClassLoader( context.getConfiguration().getConfigurationClassLoader()); 322 try { 323 if (targetFile != null) { 324 if (configurations.size() > 1) { 325 throw new DeploymentException("Deployer created more than one configuration"); 326 } 327 ConfigurationData configurationData = (ConfigurationData) configurations.get(0); 328 ExecutableConfigurationUtil.createExecutableConfiguration(configurationData, manifest, targetFile); 329 } 330 if (install) { 331 List deployedURIs = new ArrayList(); 332 for (Iterator iterator = configurations.iterator(); iterator.hasNext();) { 333 ConfigurationData configurationData = (ConfigurationData) iterator.next(); 334 store.install(configurationData); 335 deployedURIs.add(configurationData.getId().toString()); 336 } 337 notifyWatchers(deployedURIs); 338 return deployedURIs; 339 } else { 340 configsCleanupRequired = true; 341 return Collections.EMPTY_LIST; 342 } 343 } catch (DeploymentException e) { 344 configsCleanupRequired = true; 345 throw e; 346 } catch (IOException e) { 347 configsCleanupRequired = true; 348 throw e; 349 } catch (InvalidConfigException e) { 350 configsCleanupRequired = true; 351 // unlikely as we just built this 352 throw new DeploymentException(e); 353 } catch (Throwable e) { 354 // Could get here if serialization of the configuration failed (GERONIMO-1996) 355 configsCleanupRequired = true; 356 throw e; 357 } finally { 358 thread.setContextClassLoader(oldCl); 359 if (context != null) { 360 context.close(); 361 } 362 if (configsCleanupRequired) { 363 // We do this after context is closed so the module jar isn't open 364 cleanupConfigurations(configurations); 365 } 366 } 367 } catch (Throwable e) { 368 //TODO not clear all errors will result in total cleanup 369 // File configurationDir = configurationData.getConfigurationDir(); 370 // if (!DeploymentUtil.recursiveDelete(configurationDir)) { 371 // pendingDeletionIndex.setProperty(configurationDir.getName(), new String("delete")); 372 // log.debug("Queued deployment directory to be reaped " + configurationDir); 373 // } 374 // if (targetFile != null) { 375 // targetFile.delete(); 376 // } 377 378 if (e instanceof Error) { 379 log.error("Deployment failed due to ", e); 380 throw (Error) e; 381 } else if (e instanceof DeploymentException) { 382 throw (DeploymentException) e; 383 } else if (e instanceof Exception) { 384 log.error("Deployment failed due to ", e); 385 throw new DeploymentException(e); 386 } 387 throw new Error(e); 388 } finally { 389 DeploymentUtil.close(module); 390 } 391 } 392 393 private void notifyWatchers(List list) { 394 Artifact[] arts = new Artifact[list.size()]; 395 for (int i = 0; i < list.size(); i++) { 396 String s = (String) list.get(i); 397 arts[i] = Artifact.create(s); 398 } 399 for (Iterator it = watchers.iterator(); it.hasNext();) { 400 DeploymentWatcher watcher = (DeploymentWatcher) it.next(); 401 for (int i = 0; i < arts.length; i++) { 402 Artifact art = arts[i]; 403 watcher.deployed(art); 404 } 405 } 406 } 407 408 private void cleanupConfigurations(List configurations) { 409 for (Iterator iterator = configurations.iterator(); iterator.hasNext();) { 410 ConfigurationData configurationData = (ConfigurationData) iterator.next(); 411 File configurationDir = configurationData.getConfigurationDir(); 412 if (!DeploymentUtil.recursiveDelete(configurationDir)) { 413 pendingDeletionIndex.setProperty(configurationDir.getName(), "delete"); 414 log.debug("Queued deployment directory to be reaped " + configurationDir); 415 } 416 } 417 } 418 419 /** 420 * Thread to cleanup unused temporary Deployer directories (and files). 421 * On Windows, open files can't be deleted. Until MultiParentClassLoaders 422 * are GC'ed, we won't be able to delete Config Store directories/files. 423 */ 424 class DeployerReaper implements Runnable { 425 private final int reaperInterval; 426 private volatile boolean done = false; 427 428 public DeployerReaper(int reaperInterval) { 429 this.reaperInterval = reaperInterval; 430 } 431 432 public void close() { 433 this.done = true; 434 } 435 436 public void run() { 437 log.debug("ConfigStoreReaper started"); 438 while (!done) { 439 try { 440 Thread.sleep(reaperInterval); 441 } catch (InterruptedException e) { 442 continue; 443 } 444 reap(); 445 } 446 } 447 448 /** 449 * For every directory in the pendingDeletionIndex, attempt to delete all 450 * sub-directories and files. 451 */ 452 public void reap() { 453 // return, if there's nothing to do 454 if (pendingDeletionIndex.size() == 0) 455 return; 456 // Otherwise, attempt to delete all of the directories 457 Enumeration list = pendingDeletionIndex.propertyNames(); 458 while (list.hasMoreElements()) { 459 String dirName = (String) list.nextElement(); 460 File deleteDir = new File(dirName); 461 462 if (!DeploymentUtil.recursiveDelete(deleteDir)) { 463 pendingDeletionIndex.remove(deleteDir); 464 log.debug("Reaped deployment directory " + deleteDir); 465 } 466 } 467 } 468 } 469 470 public static final GBeanInfo GBEAN_INFO; 471 472 private static final String DEPLOYER = "Deployer"; 473 474 static { 475 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(Deployer.class, DEPLOYER); 476 477 infoFactory.addAttribute("kernel", Kernel.class, false); 478 infoFactory.addAttribute("remoteDeployAddress", String.class, true, true); 479 infoFactory.addAttribute("remoteDeployUploadURL", String.class, false); 480 infoFactory.addOperation("deploy", new Class[]{boolean.class, File.class, File.class}); 481 infoFactory.addOperation("deploy", new Class[]{boolean.class, File.class, File.class, String.class}); 482 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}); 483 484 infoFactory.addReference("Builders", ConfigurationBuilder.class, "ConfigBuilder"); 485 infoFactory.addReference("Store", ConfigurationStore.class, "ConfigurationStore"); 486 infoFactory.addReference("Watchers", DeploymentWatcher.class); 487 488 infoFactory.setConstructor(new String[]{"remoteDeployAddress", "Builders", "Store", "Watchers", "kernel"}); 489 490 GBEAN_INFO = infoFactory.getBeanInfo(); 491 } 492 493 public static GBeanInfo getGBeanInfo() { 494 return GBEAN_INFO; 495 } 496 }