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 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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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 }