001 /** 002 * 003 * Copyright 2005 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 package org.apache.geronimo.deployment; 018 019 import java.io.File; 020 import java.io.IOException; 021 import java.util.ArrayList; 022 import java.util.Arrays; 023 import java.util.Collection; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.LinkedList; 027 import java.util.jar.JarFile; 028 029 import org.apache.commons.logging.Log; 030 import org.apache.commons.logging.LogFactory; 031 import org.apache.geronimo.common.DeploymentException; 032 import org.apache.geronimo.deployment.util.DeploymentUtil; 033 import org.apache.geronimo.kernel.config.ConfigurationData; 034 import org.apache.geronimo.kernel.config.ConfigurationInfo; 035 import org.apache.geronimo.kernel.config.ConfigurationManager; 036 import org.apache.geronimo.kernel.config.ConfigurationStore; 037 import org.apache.geronimo.kernel.config.InvalidConfigException; 038 import org.apache.geronimo.kernel.config.NoSuchConfigException; 039 import org.apache.geronimo.kernel.repository.Artifact; 040 import org.apache.geronimo.kernel.repository.ArtifactResolver; 041 import org.apache.geronimo.kernel.repository.Version; 042 import org.apache.geronimo.system.serverinfo.ServerInfo; 043 import org.apache.geronimo.gbean.GBeanInfo; 044 import org.apache.geronimo.gbean.GBeanInfoBuilder; 045 046 /** 047 * @version $Rev: 410741 $ $Date: 2006-05-31 21:35:48 -0700 (Wed, 31 May 2006) $ 048 */ 049 public class SingleFileHotDeployer { 050 private static final Log log = LogFactory.getLog(SingleFileHotDeployer.class); 051 private static final String LINE_SEP = System.getProperty("line.separator"); 052 private final File dir; 053 private final String[] watchPaths; 054 private final Collection builders; 055 private final ConfigurationStore store; 056 private final ConfigurationManager configurationManager; 057 private final boolean forceDeploy; 058 private final Artifact configurationId; 059 private boolean wasDeployed; 060 061 public SingleFileHotDeployer(String path, ServerInfo serverInfo, String[] watchPaths, Collection builders, ConfigurationStore store, ConfigurationManager configurationManager, boolean forceDeploy) throws DeploymentException { 062 this(serverInfo.resolve(path), watchPaths, builders, store, configurationManager, forceDeploy); 063 } 064 065 public SingleFileHotDeployer(File dir, String[] watchPaths, Collection builders, ConfigurationStore store, ConfigurationManager configurationManager, boolean forceDeploy) throws DeploymentException { 066 this.dir = dir; 067 this.watchPaths = watchPaths; 068 this.builders = builders; 069 this.store = store; 070 this.configurationManager = configurationManager; 071 this.forceDeploy = forceDeploy; 072 073 configurationId = start(dir); 074 } 075 076 private Artifact start(File dir) throws DeploymentException { 077 if (!dir.exists()) { 078 throw new IllegalArgumentException("Directory does not exist " + dir.getAbsolutePath()); 079 } 080 if (!dir.isDirectory()) { 081 throw new IllegalArgumentException("Directory is not a directory " + dir.getAbsolutePath()); 082 } 083 084 // take no action if there is nothing in the directory to deploy. Perhaps we should 085 // consider doing an undeploy in this case if the application is already deployed. Howevr 086 // for now this is to handle the case where the application is not already laid down at the 087 // time of the initial deploy of this gbean. 088 if (dir.list().length == 0) { 089 return null; 090 } 091 092 // get the existing inplace configuration if there is one 093 ConfigurationInfo existingConfiguration = null; 094 List list = configurationManager.listConfigurations(); 095 for (Iterator iterator = list.iterator(); iterator.hasNext();) { 096 ConfigurationInfo configurationInfo = (ConfigurationInfo) iterator.next(); 097 if (dir.equals(configurationInfo.getInPlaceLocation())) { 098 existingConfiguration = configurationInfo; 099 } 100 } 101 Artifact existingConfigurationId = (existingConfiguration == null) ? null : existingConfiguration.getConfigID(); 102 103 if (!forceDeploy && existingConfiguration != null && !isModifiedSince(existingConfiguration.getCreated())) { 104 try { 105 configurationManager.loadConfiguration(existingConfigurationId); 106 configurationManager.startConfiguration(existingConfigurationId); 107 } catch (Exception e) { 108 throw new DeploymentException("Unable to load and start " + dir, e); 109 } 110 return existingConfigurationId; 111 } 112 113 // if the current id and the new id only differ by version, we can reload, otherwise we need to load and start 114 if (existingConfigurationId != null && configurationManager.isLoaded(existingConfigurationId)) { 115 try { 116 configurationManager.unloadConfiguration(existingConfigurationId); 117 } catch (NoSuchConfigException e) { 118 throw new DeploymentException("Unable to unload existing configuration " + existingConfigurationId); 119 } 120 } 121 122 ModuleIDBuilder idBuilder = new ModuleIDBuilder(); 123 124 JarFile module = null; 125 try { 126 module = DeploymentUtil.createJarFile(dir); 127 } catch (IOException e) { 128 throw new DeploymentException("Cound not open module file: " + dir.getAbsolutePath(), e); 129 } 130 131 try { 132 // get the builder and plan 133 Object plan = null; 134 ConfigurationBuilder builder = null; 135 for (Iterator i = builders.iterator(); i.hasNext();) { 136 ConfigurationBuilder candidate = (ConfigurationBuilder) i.next(); 137 plan = candidate.getDeploymentPlan(null, module, idBuilder); 138 if (plan != null) { 139 builder = candidate; 140 break; 141 } 142 } 143 if (builder == null) { 144 throw new DeploymentException("Cannot deploy the requested application module because no builder is able to handle it (dir=" + dir.getAbsolutePath() + ")"); 145 } 146 147 // determine the new configuration id 148 Artifact configurationId = builder.getConfigurationID(plan, module, idBuilder); 149 150 // if the new configuration id isn't fully resolved, populate it with defaults 151 if (!configurationId.isResolved()) { 152 configurationId = resolve(configurationId); 153 } 154 155 // If we didn't find a previous configuration based upon the path then check one more time to 156 // see if one exists by the same configurationID. This will catch situations where the target 157 // path was renamed or moved such that the path associated with the previous configuration no longer 158 // matches the patch associated with the new configuration. 159 if ((existingConfigurationId == null) && configurationManager.isInstalled(configurationId)) { 160 log.info("Existing Module found by moduleId"); 161 existingConfigurationId = configurationId; 162 } 163 164 // if we are deploying over the exisitng version we need to uninstall first 165 if(configurationId.equals(existingConfigurationId)) { 166 log.info("Undeploying " + existingConfigurationId); 167 configurationManager.uninstallConfiguration(existingConfigurationId); 168 } 169 170 // deploy it 171 deployConfiguration(builder, store, configurationId, plan, module, Arrays.asList(configurationManager.getStores()), configurationManager.getArtifactResolver()); 172 wasDeployed = true; 173 174 configurationManager.loadConfiguration(configurationId); 175 configurationManager.startConfiguration(configurationId); 176 177 log.info("Successfully deployed and started " + configurationId + " in location " + dir); 178 179 return configurationId; 180 } catch (Exception e) { 181 throw new DeploymentException("Unable to deploy " + dir, e); 182 } finally { 183 DeploymentUtil.close(module); 184 } 185 186 } 187 188 private boolean isModifiedSince(long created) { 189 for (int i = 0; i < watchPaths.length; i++) { 190 String path = watchPaths[i]; 191 File file = new File(dir, path); 192 if (!file.exists()) { 193 log.warn("Watched file does not exist " + file); 194 } 195 if (file.isFile() && file.lastModified() > created) { 196 log.info("Redeploying " + dir + " because file " + file + " was modified;"); 197 return true; 198 } 199 } 200 return false; 201 } 202 203 private Artifact resolve(Artifact configID) throws DeploymentException { 204 String group = configID.getGroupId(); 205 if (group == null) { 206 group = Artifact.DEFAULT_GROUP_ID; 207 } 208 String artifactId = configID.getArtifactId(); 209 if (artifactId == null) { 210 throw new DeploymentException("Every configuration to deploy must have a ConfigID with an ArtifactID (not " + configID + ")"); 211 } 212 Version version = configID.getVersion(); 213 if (version == null) { 214 version = new Version(Long.toString(System.currentTimeMillis())); 215 } 216 String type = configID.getType(); 217 if (type == null) { 218 type = "car"; 219 } 220 return new Artifact(group, artifactId, version, type); 221 } 222 223 private List deployConfiguration(ConfigurationBuilder builder, ConfigurationStore store, Artifact configurationId, Object plan, JarFile module, Collection stores, ArtifactResolver artifactResolver) throws DeploymentException { 224 try { 225 // It's our responsibility to close this context, once we're done with it... 226 DeploymentContext context = builder.buildConfiguration(true, configurationId, plan, module, stores, artifactResolver, store); 227 228 List configurations = new ArrayList(); 229 try { 230 configurations.add(context.getConfigurationData()); 231 configurations.addAll(context.getAdditionalDeployment()); 232 233 if (configurations.isEmpty()) { 234 throw new DeploymentException("Deployer did not create any configurations"); 235 } 236 List deployedURIs = new ArrayList(); 237 for (Iterator iterator = configurations.iterator(); iterator.hasNext();) { 238 ConfigurationData configurationData = (ConfigurationData) iterator.next(); 239 configurationData.setAutoStart(false); 240 store.install(configurationData); 241 deployedURIs.add(configurationData.getId().toString()); 242 } 243 return deployedURIs; 244 } catch (IOException e) { 245 cleanupConfigurations(configurations); 246 throw e; 247 } catch (InvalidConfigException e) { 248 cleanupConfigurations(configurations); 249 // unlikely as we just built this 250 throw new DeploymentException(e); 251 } finally { 252 if (context != null) { 253 context.close(); 254 } 255 } 256 } catch (Throwable e) { 257 if (e instanceof Error) { 258 log.error("Deployment failed due to ", e); 259 throw (Error) e; 260 } else if (e instanceof DeploymentException) { 261 throw (DeploymentException) e; 262 } else if (e instanceof Exception) { 263 log.error("Deployment failed due to ", e); 264 throw new DeploymentException(e); 265 } 266 throw new Error(e); 267 } finally { 268 DeploymentUtil.close(module); 269 } 270 } 271 272 private void cleanupConfigurations(List configurations) { 273 LinkedList cannotBeDeletedList = new LinkedList(); 274 for (Iterator iterator = configurations.iterator(); iterator.hasNext();) { 275 ConfigurationData configurationData = (ConfigurationData) iterator.next(); 276 File dir = configurationData.getConfigurationDir(); 277 cannotBeDeletedList.clear(); 278 if (!DeploymentUtil.recursiveDelete(dir,cannotBeDeletedList)) { 279 // Output a message to help user track down file problem 280 log.warn("Unable to delete " + cannotBeDeletedList.size() + 281 " files while recursively deleting directory " 282 + dir + LINE_SEP + 283 "The first file that could not be deleted was:" + LINE_SEP + " "+ 284 ( !cannotBeDeletedList.isEmpty() ? cannotBeDeletedList.getFirst() : "") ); 285 } 286 } 287 } 288 289 public File getDir() { 290 return dir; 291 } 292 293 public Artifact getConfigurationId() { 294 return configurationId; 295 } 296 297 public boolean isForceDeploy() { 298 return forceDeploy; 299 } 300 301 public boolean wasDeployed() { 302 return wasDeployed; 303 } 304 305 public static final GBeanInfo GBEAN_INFO; 306 307 static { 308 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(SingleFileHotDeployer.class); 309 310 infoFactory.addAttribute("path", String.class, true); 311 infoFactory.addReference("ServerInfo", ServerInfo.class); 312 infoFactory.addAttribute("watchPaths", String[].class, true); 313 infoFactory.addReference("Builders", ConfigurationBuilder.class); 314 infoFactory.addReference("Store", ConfigurationStore.class); 315 infoFactory.addReference("ConfigurationManager", ConfigurationManager.class); 316 infoFactory.addAttribute("forceDeploy", boolean.class, true); 317 318 infoFactory.setConstructor(new String[]{"path", "ServerInfo", "watchPaths", "Builders", "Store", "ConfigurationManager", "forceDeploy"}); 319 320 GBEAN_INFO = infoFactory.getBeanInfo(); 321 } 322 323 public static GBeanInfo getGBeanInfo() { 324 return GBEAN_INFO; 325 } 326 }