001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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: 427123 $ $Date: 2006-07-31 07:16:46 -0700 (Mon, 31 Jul 2006) $ 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 Collection builders; 071 private final Collection stores; 072 private final Collection watchers; 073 private final ArtifactResolver artifactResolver; 074 private final Kernel kernel; 075 076 public Deployer(Collection builders, Collection stores, Collection watchers, Kernel kernel) { 077 this(builders, stores, watchers, getArtifactResolver(kernel), kernel); 078 } 079 080 private static ArtifactResolver getArtifactResolver(Kernel kernel) { 081 ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel); 082 return configurationManager.getArtifactResolver(); 083 } 084 085 public Deployer(Collection builders, Collection stores, Collection watchers, ArtifactResolver artifactResolver, Kernel kernel) { 086 this.builders = builders; 087 this.stores = stores; 088 this.watchers = watchers; 089 this.artifactResolver = artifactResolver; 090 this.kernel = kernel; 091 092 // Create and start the reaper... 093 this.reaper = new DeployerReaper(REAPER_INTERVAL); 094 Thread t = new Thread(reaper, "Geronimo Config Store Reaper"); 095 t.setDaemon(true); 096 t.start(); 097 } 098 099 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 }