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    }