Author: markt Date: Sat Sep 8 21:11:46 2012 New Revision: 1382367 URL: http://svn.apache.org/viewvc?rev=1382367&view=rev Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=52777 Implement optional automatic removal of old applications where parallel deployment has been used.
Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/catalina/Host.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardHost.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/HostConfig.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/mbeans-descriptors.xml tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml tomcat/tc7.0.x/trunk/webapps/docs/config/context.xml tomcat/tc7.0.x/trunk/webapps/docs/config/host.xml Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1382366 Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/Host.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/Host.java?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/Host.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/Host.java Sat Sep 8 21:11:46 2012 @@ -180,6 +180,22 @@ public interface Host extends Container public ExecutorService getStartStopExecutor(); + /** + * Returns true of the Host is configured to automatically undeploy old + * versions of applications deployed using parallel deployment. This only + * takes effect is {@link #getAutoDeploy()} also returns true. + */ + public boolean getUndeployOldVersions(); + + + /** + * Set to true if the Host should automatically undeploy old versions of + * applications deployed using parallel deployment. This only takes effect + * if {@link #getAutoDeploy()} returns true. + */ + public void setUndeployOldVersions(boolean undeployOldVersions); + + // --------------------------------------------------------- Public Methods /** Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardHost.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardHost.java?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardHost.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/StandardHost.java Sat Sep 8 21:11:46 2012 @@ -5,9 +5,9 @@ * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -53,7 +53,7 @@ public class StandardHost extends Contai private static final org.apache.juli.logging.Log log= org.apache.juli.logging.LogFactory.getLog( StandardHost.class ); - + // ----------------------------------------------------------- Constructors @@ -75,7 +75,7 @@ public class StandardHost extends Contai * The set of aliases for this Host. */ private String[] aliases = new String[0]; - + private final Object aliasesLock = new Object(); @@ -132,7 +132,7 @@ public class StandardHost extends Contai /** - * The Java class name of the default error reporter implementation class + * The Java class name of the default error reporter implementation class * for deployed web applications. */ private String errorReportValveClass = @@ -162,15 +162,15 @@ public class StandardHost extends Contai */ private boolean createDirs = true; - + /** * Track the class loaders for the child web applications so memory leaks * can be detected. */ private Map<ClassLoader, String> childClassLoaders = new WeakHashMap<ClassLoader, String>(); - - + + /** * Any file or directory in {@link #appBase} that this pattern matches will * be ignored by the automatic deployment process (both @@ -179,12 +179,27 @@ public class StandardHost extends Contai private Pattern deployIgnore = null; + private boolean undeployOldVersions = false; + + // ------------------------------------------------------------- Properties - @Override - public ExecutorService getStartStopExecutor() { - return startStopExecutor; - } + @Override + public boolean getUndeployOldVersions() { + return undeployOldVersions; + } + + + @Override + public void setUndeployOldVersions(boolean undeployOldVersions) { + this.undeployOldVersions = undeployOldVersions; + } + + + @Override + public ExecutorService getStartStopExecutor() { + return startStopExecutor; + } /** @@ -225,7 +240,7 @@ public class StandardHost extends Contai return (this.xmlBase); } - + /** * Set the Xml root for this Host. This can be an absolute @@ -264,7 +279,7 @@ public class StandardHost extends Contai } /** - * Return the value of the auto deploy flag. If true, it indicates that + * Return the value of the auto deploy flag. If true, it indicates that * this host's child webapps will be dynamically deployed. */ @Override @@ -277,7 +292,7 @@ public class StandardHost extends Contai /** * Set the auto deploy flag value for this host. - * + * * @param autoDeploy The new auto deploy flag */ @Override @@ -285,7 +300,7 @@ public class StandardHost extends Contai boolean oldAutoDeploy = this.autoDeploy; this.autoDeploy = autoDeploy; - support.firePropertyChange("autoDeploy", oldAutoDeploy, + support.firePropertyChange("autoDeploy", oldAutoDeploy, this.autoDeploy); } @@ -348,8 +363,8 @@ public class StandardHost extends Contai /** - * Return the value of the deploy on startup flag. If true, it indicates - * that this host's child webapps should be discovered and automatically + * Return the value of the deploy on startup flag. If true, it indicates + * that this host's child webapps should be discovered and automatically * deployed at startup time. */ @Override @@ -362,7 +377,7 @@ public class StandardHost extends Contai /** * Set the deploy on startup flag value for this host. - * + * * @param deployOnStartup The new deploy on startup flag */ @Override @@ -370,7 +385,7 @@ public class StandardHost extends Contai boolean oldDeployOnStartup = this.deployOnStartup; this.deployOnStartup = deployOnStartup; - support.firePropertyChange("deployOnStartup", oldDeployOnStartup, + support.firePropertyChange("deployOnStartup", oldDeployOnStartup, this.deployOnStartup); } @@ -416,8 +431,8 @@ public class StandardHost extends Contai this.copyXML= copyXML; } - - + + /** * Return the Java class name of the error report valve class * for new web applications. @@ -440,12 +455,12 @@ public class StandardHost extends Contai String oldErrorReportValveClassClass = this.errorReportValveClass; this.errorReportValveClass = errorReportValveClass; support.firePropertyChange("errorReportValveClass", - oldErrorReportValveClassClass, + oldErrorReportValveClassClass, this.errorReportValveClass); } - - + + /** * Return the canonical, fully qualified, name of the virtual host * this Container represents. @@ -529,7 +544,7 @@ public class StandardHost extends Contai public String getDeployIgnore() { if (deployIgnore == null) { return null; - } + } return this.deployIgnore.toString(); } @@ -564,7 +579,7 @@ public class StandardHost extends Contai this.deployIgnore = Pattern.compile(deployIgnore); } support.firePropertyChange("deployIgnore", - oldDeployIgnore, + oldDeployIgnore, deployIgnore); } @@ -636,8 +651,8 @@ public class StandardHost extends Contai } } } - - + + /** * Attempt to identify the contexts that have a class loader memory leak. * This is usually triggered on context reload. Note: This method attempts @@ -645,11 +660,11 @@ public class StandardHost extends Contai * caution on a production system. */ public String[] findReloadedContextMemoryLeaks() { - + System.gc(); - + List<String> result = new ArrayList<String>(); - + for (Map.Entry<ClassLoader, String> entry : childClassLoaders.entrySet()) { ClassLoader cl = entry.getKey(); @@ -659,7 +674,7 @@ public class StandardHost extends Contai } } } - + return result.toArray(new String[result.size()]); } @@ -747,7 +762,7 @@ public class StandardHost extends Contai return (sb.toString()); } - + /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. @@ -757,7 +772,7 @@ public class StandardHost extends Contai */ @Override protected synchronized void startInternal() throws LifecycleException { - + // Set error report valve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { @@ -821,5 +836,5 @@ public class StandardHost extends Contai return keyProperties.toString(); } - + } Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/mbeans-descriptors.xml Sat Sep 8 21:11:46 2012 @@ -1211,6 +1211,10 @@ type="java.lang.String" writeable="false"/> + <attribute name="undeployOldVersions" + description="Determines if old versions of applications deployed using parallel deployment are automatically undeployed when no longer used. Requires autoDeploy to be enabled." + type="boolean"/> + <attribute name="unpackWARs" description="Unpack WARs property" is="true" Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/HostConfig.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/HostConfig.java?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/HostConfig.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/HostConfig.java Sat Sep 8 21:11:46 2012 @@ -5,9 +5,9 @@ * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,11 +28,14 @@ import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -51,6 +54,7 @@ import org.apache.catalina.Host; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Manager; import org.apache.catalina.core.StandardHost; import org.apache.catalina.util.ContextName; import org.apache.catalina.util.IOTools; @@ -72,7 +76,7 @@ import org.apache.tomcat.util.res.String */ public class HostConfig implements LifecycleListener { - + private static final Log log = LogFactory.getLog( HostConfig.class ); // ----------------------------------------------------- Instance Variables @@ -107,12 +111,12 @@ public class HostConfig */ protected Host host = null; - + /** * The JMX ObjectName of this component. */ protected ObjectName oname = null; - + /** * The string resources for this package. @@ -134,8 +138,8 @@ public class HostConfig * a web application is deployed? */ protected boolean copyXML = false; - - + + /** * Should we unpack WAR files when auto-deploying applications in the * <code>appBase</code> directory? @@ -149,13 +153,13 @@ public class HostConfig protected Map<String, DeployedApplication> deployed = new ConcurrentHashMap<String, DeployedApplication>(); - + /** - * List of applications which are being serviced, and shouldn't be + * List of applications which are being serviced, and shouldn't be * deployed/undeployed/redeployed at the moment. */ protected ArrayList<String> serviced = new ArrayList<String>(); - + /** * The <code>Digester</code> instance used to parse context descriptors. @@ -279,8 +283,8 @@ public class HostConfig this.unpackWARs = unpackWARs; } - - + + // --------------------------------------------------------- Public Methods @@ -316,15 +320,15 @@ public class HostConfig } - + /** * Add a serviced application to the list. */ public synchronized void addServiced(String name) { serviced.add(name); } - - + + /** * Is application serviced ? * @return state of the application @@ -332,7 +336,7 @@ public class HostConfig public synchronized boolean isServiced(String name) { return (serviced.contains(name)); } - + /** * Removed a serviced application from the list. @@ -341,7 +345,7 @@ public class HostConfig serviced.remove(name); } - + /** * Get the instant where an application was deployed. * @return 0L if no application with that name is deployed, or the instant @@ -352,11 +356,11 @@ public class HostConfig if (app == null) { return 0L; } - + return app.timestamp; } - - + + /** * Has the specified application been deployed? Note applications defined * in server.xml will not have been deployed. @@ -369,14 +373,14 @@ public class HostConfig if (app == null) { return false; } - + return true; } - - + + // ------------------------------------------------------ Protected Methods - + /** * Create the digester which will be used to parse context config files. */ @@ -386,12 +390,12 @@ public class HostConfig // Add object creation rule digester.addObjectCreate("Context", "org.apache.catalina.core.StandardContext", "className"); - // Set the properties on that object (it doesn't matter if extra + // Set the properties on that object (it doesn't matter if extra // properties are set) digester.addSetProperties("Context"); return (digester); } - + protected File returnCanonicalPath(String path) { File file = new File(path); File base = new File(System.getProperty(Globals.CATALINA_BASE_PROP)); @@ -403,7 +407,7 @@ public class HostConfig return file; } } - + /** * Return a File object representing the "application root" directory @@ -414,7 +418,7 @@ public class HostConfig if (appBase != null) { return appBase; } - + appBase = returnCanonicalPath(host.getAppBase()); return appBase; @@ -430,7 +434,7 @@ public class HostConfig if (configBase != null) { return configBase; } - + if (host.getXmlBase()!=null) { configBase = returnCanonicalPath(host.getXmlBase()); } else { @@ -472,16 +476,16 @@ public class HostConfig deployWARs(appBase, filteredAppPaths); // Deploy expanded folders deployDirectories(appBase, filteredAppPaths); - + } /** * Filter the list of application file paths to remove those that match * the regular expression defined by {@link Host#getDeployIgnore()}. - * + * * @param unfilteredAppPaths The list of application paths to filtert - * + * * @return The filtered list of application paths */ protected String[] filterAppPaths(String[] unfilteredAppPaths) { @@ -520,7 +524,7 @@ public class HostConfig File configBase = configBase(); ContextName cn = new ContextName(name); String baseName = cn.getBaseName(); - + if (deploymentExists(baseName)) { return; } @@ -551,7 +555,7 @@ public class HostConfig if (files == null) return; - + ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<Future<?>>(); @@ -563,7 +567,7 @@ public class HostConfig if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; - + results.add( es.submit(new DeployDescriptor(this, cn, contextXml))); } @@ -585,7 +589,7 @@ public class HostConfig * @param contextXml */ protected void deployDescriptor(ContextName cn, File contextXml) { - + DeployedApplication deployedApp = new DeployedApplication(cn.getName()); // Assume this is a configuration descriptor and deploy it @@ -686,7 +690,7 @@ public class HostConfig if (expandedDocBase.exists()) { deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(), Long.valueOf(expandedDocBase.lastModified())); - addWatchedResources(deployedApp, + addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context); } else { addWatchedResources(deployedApp, null, context); @@ -713,15 +717,15 @@ public class HostConfig * Deploy WAR files. */ protected void deployWARs(File appBase, String[] files) { - + if (files == null) return; - + ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<Future<?>>(); for (int i = 0; i < files.length; i++) { - + if (files[i].equalsIgnoreCase("META-INF")) continue; if (files[i].equalsIgnoreCase("WEB-INF")) @@ -729,9 +733,9 @@ public class HostConfig File war = new File(appBase, files[i]); if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && !invalidWars.contains(files[i]) ) { - + ContextName cn = new ContextName(files[i]); - + // Check for WARs with /../ /./ or similar sequences in the name if (!validateContextPath(appBase, cn.getBaseName())) { log.error(sm.getString( @@ -742,7 +746,7 @@ public class HostConfig if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; - + results.add(es.submit(new DeployWar(this, cn, war))); } } @@ -761,10 +765,10 @@ public class HostConfig private boolean validateContextPath(File appBase, String contextPath) { // More complicated than the ideal as the canonical path may or may // not end with File.separator for a directory - + StringBuilder docBase; String canonicalDocBase = null; - + try { String canonicalAppBase = appBase.getCanonicalPath(); docBase = new StringBuilder(canonicalAppBase); @@ -776,10 +780,10 @@ public class HostConfig } // At this point docBase should be canonical but will not end // with File.separator - + canonicalDocBase = (new File(docBase.toString())).getCanonicalPath(); - + // If the canonicalDocBase ends with File.separator, add one to // docBase before they are compared if (canonicalDocBase.endsWith(File.separator)) { @@ -788,9 +792,9 @@ public class HostConfig } catch (IOException ioe) { return false; } - + // Compare the two. If they are not the same, the contextPath must - // have /../ like sequences in it + // have /../ like sequences in it return canonicalDocBase.equals(docBase.toString()); } @@ -799,7 +803,7 @@ public class HostConfig * @param war */ protected void deployWAR(ContextName cn, File war) { - + // Checking for a nested /META-INF/context.xml JarFile jar = null; JarEntry entry = null; @@ -814,7 +818,7 @@ public class HostConfig cn.getBaseName() + "/META-INF/context.xml"); } boolean xmlInWar = false; - + if (deployXML && !xml.exists()) { try { jar = new JarFile(war); @@ -824,7 +828,7 @@ public class HostConfig } if ((copyXML || unpackWARs) && xmlInWar) { istream = jar.getInputStream(entry); - + fos = new FileOutputStream(xml); ostream = new BufferedOutputStream(fos, 1024); byte buffer[] = new byte[1024]; @@ -875,11 +879,11 @@ public class HostConfig } } } - + DeployedApplication deployedApp = new DeployedApplication(cn.getName()); - + // Deploy the application in this WAR file - if(log.isInfoEnabled()) + if(log.isInfoEnabled()) log.info(sm.getString("hostConfig.deployWar", war.getAbsolutePath())); @@ -985,7 +989,7 @@ public class HostConfig // the end so they don't interfere with the deletion process addGlobalRedeployResources(deployedApp); } - + deployed.put(cn.getName(), deployedApp); } @@ -997,7 +1001,7 @@ public class HostConfig if (files == null) return; - + ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<Future<?>>(); @@ -1028,17 +1032,17 @@ public class HostConfig } } - + /** * @param cn * @param dir */ protected void deployDirectory(ContextName cn, File dir) { - + DeployedApplication deployedApp = new DeployedApplication(cn.getName()); // Deploy the application in this directory - if( log.isInfoEnabled() ) + if( log.isInfoEnabled() ) log.info(sm.getString("hostConfig.deployDir", dir.getAbsolutePath())); @@ -1125,17 +1129,17 @@ public class HostConfig deployed.put(cn.getName(), deployedApp); } - + /** * Check if a webapp is already deployed in this host. - * + * * @param contextName of the context which will be checked */ protected boolean deploymentExists(String contextName) { return (deployed.containsKey(contextName) || (host.findChild(contextName) != null)); } - + /** * Add watched resources to the specified Context. @@ -1171,11 +1175,11 @@ public class HostConfig if(log.isDebugEnabled()) log.debug("Watching WatchedResource '" + resource.getAbsolutePath() + "'"); - app.reloadResources.put(resource.getAbsolutePath(), + app.reloadResources.put(resource.getAbsolutePath(), Long.valueOf(resource.lastModified())); } } - + protected void addGlobalRedeployResources(DeployedApplication app) { // Redeploy resources processing is hard-coded to never delete this file @@ -1213,43 +1217,7 @@ public class HostConfig if ((!resource.isDirectory()) && resource.lastModified() > lastModified) { // Undeploy application - if (log.isInfoEnabled()) - log.info(sm.getString("hostConfig.undeploy", app.name)); - Container context = host.findChild(app.name); - try { - host.removeChild(context); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - log.warn(sm.getString - ("hostConfig.context.remove", app.name), t); - } - // Delete other redeploy resources - for (int j = i + 1; j < resources.length; j++) { - try { - File current = new File(resources[j]); - current = current.getCanonicalFile(); - // Never delete per host context.xml defaults - if (Constants.HostContextXml.equals( - current.getName())) { - continue; - } - // Only delete resources in the appBase or the - // host's configBase - if ((current.getAbsolutePath().startsWith( - appBase().getAbsolutePath() + - File.separator)) - || (current.getAbsolutePath().startsWith( - configBase().getAbsolutePath()))) { - if (log.isDebugEnabled()) - log.debug("Delete " + current); - ExpandWar.delete(current); - } - } catch (IOException e) { - log.warn(sm.getString - ("hostConfig.canonicalizing", app.name), e); - } - } - deployed.remove(app.name); + deleteRedeployResources(app, resources, i, false); return; } } else { @@ -1270,71 +1238,7 @@ public class HostConfig continue; } // Undeploy application - if (log.isInfoEnabled()) - log.info(sm.getString("hostConfig.undeploy", app.name)); - Container context = host.findChild(app.name); - try { - host.removeChild(context); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - log.warn(sm.getString - ("hostConfig.context.remove", app.name), t); - } - // Delete all redeploy resources - for (int j = i + 1; j < resources.length; j++) { - try { - File current = new File(resources[j]); - current = current.getCanonicalFile(); - // Never delete per host context.xml defaults - if (Constants.HostContextXml.equals( - current.getName())) { - continue; - } - // Only delete resources in the appBase or the host's - // configBase - if ((current.getAbsolutePath().startsWith( - appBase().getAbsolutePath() + File.separator)) - || (current.getAbsolutePath().startsWith( - configBase().getAbsolutePath()))) { - if (log.isDebugEnabled()) - log.debug("Delete " + current); - ExpandWar.delete(current); - } - } catch (IOException e) { - log.warn(sm.getString - ("hostConfig.canonicalizing", app.name), e); - } - } - // Delete reload resources as well (to remove any remaining .xml - // descriptor) - String[] resources2 = - app.reloadResources.keySet().toArray(new String[0]); - for (int j = 0; j < resources2.length; j++) { - try { - File current = new File(resources2[j]); - current = current.getCanonicalFile(); - // Never delete per host context.xml defaults - if (Constants.HostContextXml.equals( - current.getName())) { - continue; - } - // Only delete resources in the appBase or the host's - // configBase - if ((current.getAbsolutePath().startsWith( - appBase().getAbsolutePath() + File.separator)) - || ((current.getAbsolutePath().startsWith( - configBase().getAbsolutePath()) - && (current.getAbsolutePath().endsWith(".xml"))))) { - if (log.isDebugEnabled()) - log.debug("Delete " + current); - ExpandWar.delete(current); - } - } catch (IOException e) { - log.warn(sm.getString - ("hostConfig.canonicalizing", app.name), e); - } - } - deployed.remove(app.name); + deleteRedeployResources(app, resources, i, true); return; } } @@ -1346,7 +1250,7 @@ public class HostConfig "] reload resource " + resource); long lastModified = app.reloadResources.get(resources[i]).longValue(); - if ((!resource.exists() && lastModified != 0L) + if ((!resource.exists() && lastModified != 0L) || (resource.lastModified() != lastModified)) { // Reload application if(log.isInfoEnabled()) @@ -1373,8 +1277,83 @@ public class HostConfig } } } - - + + + private void deleteRedeployResources(DeployedApplication app, + String[] resources, int i, boolean deleteReloadResources) { + + // Delete redeploy resources + if (log.isInfoEnabled()) + log.info(sm.getString("hostConfig.undeploy", app.name)); + Container context = host.findChild(app.name); + try { + host.removeChild(context); + } catch (Throwable t) { + ExceptionUtils.handleThrowable(t); + log.warn(sm.getString + ("hostConfig.context.remove", app.name), t); + } + // Delete other redeploy resources + for (int j = i + 1; j < resources.length; j++) { + try { + File current = new File(resources[j]); + current = current.getCanonicalFile(); + // Never delete per host context.xml defaults + if (Constants.HostContextXml.equals( + current.getName())) { + continue; + } + // Only delete resources in the appBase or the + // host's configBase + if ((current.getAbsolutePath().startsWith( + appBase().getAbsolutePath() + + File.separator)) + || (current.getAbsolutePath().startsWith( + configBase().getAbsolutePath()))) { + if (log.isDebugEnabled()) + log.debug("Delete " + current); + ExpandWar.delete(current); + } + } catch (IOException e) { + log.warn(sm.getString + ("hostConfig.canonicalizing", app.name), e); + } + } + + // Delete reload resources (to remove any remaining .xml descriptor) + if (deleteReloadResources) { + String[] resources2 = + app.reloadResources.keySet().toArray(new String[0]); + for (int j = 0; j < resources2.length; j++) { + try { + File current = new File(resources2[j]); + current = current.getCanonicalFile(); + // Never delete per host context.xml defaults + if (Constants.HostContextXml.equals( + current.getName())) { + continue; + } + // Only delete resources in the appBase or the host's + // configBase + if ((current.getAbsolutePath().startsWith( + appBase().getAbsolutePath() + File.separator)) + || ((current.getAbsolutePath().startsWith( + configBase().getAbsolutePath()) + && (current.getAbsolutePath().endsWith(".xml"))))) { + if (log.isDebugEnabled()) + log.debug("Delete " + current); + ExpandWar.delete(current); + } + } catch (IOException e) { + log.warn(sm.getString + ("hostConfig.canonicalizing", app.name), e); + } + } + + } + deployed.remove(app.name); + } + /** * Process a "start" event for this Host. */ @@ -1392,7 +1371,7 @@ public class HostConfig } catch (Exception e) { log.error(sm.getString("hostConfig.jmx.register", oname), e); } - + if (host.getCreateDirs()) { File[] dirs = new File[] {appBase(),configBase()}; for (int i=0; i<dirs.length; i++) { @@ -1411,7 +1390,7 @@ public class HostConfig if (host.getDeployOnStartup()) deployApps(); - + } @@ -1444,19 +1423,24 @@ public class HostConfig if (host.getAutoDeploy()) { // Check for resources modification to trigger redeployment - DeployedApplication[] apps = + DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (int i = 0; i < apps.length; i++) { if (!isServiced(apps[i].name)) checkResources(apps[i]); } + + // Check for old versions of applications that can now be undeployed + if (host.getUndeployOldVersions()) { + checkUndeploy(); + } + // Hotdeploy applications deployApps(); } - } - + /** * Check status of a specific webapp, for use with stuff like management webapps. */ @@ -1470,18 +1454,64 @@ public class HostConfig } /** + * Check for old versions of applications using parallel deployment that are + * now unused (have no active sessions) and undeploy any that are found. + */ + public void checkUndeploy() { + // Need ordered set of names + SortedSet<String> sortedAppNames = new TreeSet<String>(); + sortedAppNames.addAll(deployed.keySet()); + + if (sortedAppNames.size() < 2) { + return; + } + Iterator<String> iter = sortedAppNames.iterator(); + + ContextName previous = new ContextName(iter.next()); + do { + ContextName current = new ContextName(iter.next()); + + if (current.getPath().equals(previous.getPath())) { + // Current and previous are same version - current will always + // be a later version + Context context = (Context) host.findChild(previous.getName()); + if (context != null) { + Manager manager = context.getManager(); + if (manager != null && manager.getActiveSessions() == 0) { + if (log.isInfoEnabled()) { + log.info(sm.getString("hostConfig.undeployVersion", + previous.getName())); + } + DeployedApplication app = + deployed.get(previous.getName()); + String[] resources = + app.redeployResources.keySet().toArray( + new String[0]); + // Version is unused - undeploy it completely + // The -1 is a 'trick' to ensure all redeploy resources + // are removed + deleteRedeployResources(app, resources, -1, + true); + } + } + } + previous = current; + } while (iter.hasNext()); + } + + /** * Add a new Context to be managed by us. * Entry point for the admin webapp, and other JMX Context controllers. */ - public void manageApp(Context context) { + public void manageApp(Context context) { String contextName = context.getName(); - + if (deployed.containsKey(contextName)) return; DeployedApplication deployedApp = new DeployedApplication(contextName); - + // Add the associated docBase to the redeployed list if it's a WAR boolean isWar = false; if (context.getDocBase() != null) { @@ -1524,22 +1554,22 @@ public class HostConfig /** - * This class represents the state of a deployed application, as well as + * This class represents the state of a deployed application, as well as * the monitored resources. */ protected static class DeployedApplication { public DeployedApplication(String name) { this.name = name; } - + /** - * Application context path. The assertion is that + * Application context path. The assertion is that * (host.getChild(name) != null). */ public String name; - + /** - * Any modification of the specified (static) resources will cause a + * Any modification of the specified (static) resources will cause a * redeployment of the application. If any of the specified resources is * removed, the application will be undeployed. Typically, this will * contain resources like the context.xml file, a compressed WAR path. @@ -1549,7 +1579,7 @@ public class HostConfig new LinkedHashMap<String, Long>(); /** - * Any modification of the specified (static) resources will cause a + * Any modification of the specified (static) resources will cause a * reload of the application. This will typically contain resources * such as the web.xml of a webapp, but can be configured to contain * additional descriptors. Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/LocalStrings.properties Sat Sep 8 21:11:46 2012 @@ -111,6 +111,7 @@ hostConfig.start=HostConfig: Processing hostConfig.stop=HostConfig: Processing STOP hostConfig.undeploy=Undeploying context [{0}] hostConfig.undeploy.error=Error undeploying web application at context path {0} +hostConfig.undeployVersion=Undeploying old version of context [{0}] which has no active session tldConfig.addListeners=Adding {0} listeners from TLD files tldConfig.cce=Lifecycle event data object {0} is not a Context tldConfig.dirFail=Failed to process directory [{0}] for TLD files Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/mbeans-descriptors.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/mbeans-descriptors.xml?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/mbeans-descriptors.xml (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/startup/mbeans-descriptors.xml Sat Sep 8 21:11:46 2012 @@ -109,6 +109,12 @@ type="java.lang.String"/> </operation> + <operation name="checkUndeploy" + description="Undeploy any old versions of applications deployed using parallel deployment that have no active sessions" + impact="ACTION" + returnType="void"> + </operation> + <operation name="getDeploymentTime" description="Get the instant where an application was deployed" impact="ACTION" Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Sat Sep 8 21:11:46 2012 @@ -60,6 +60,11 @@ Add one library from JDK 7 to the value of <code>jarsToSkip</code> property in the <code>catalina.properties</code> file. (kkolinko) </update> + <add> + <bug>52777</bug>: Add an option to automatically remove old, unused + versions (ones where there are no longer any active sessions) of + applications deployed using parallel deployment. (markt) + </add> <fix> <bug>53828</bug>: Use correct status code when closing a WebSocket connection normally in response to a close frame from a client. (markt) Modified: tomcat/tc7.0.x/trunk/webapps/docs/config/context.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/config/context.xml?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/config/context.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/config/context.xml Sat Sep 8 21:11:46 2012 @@ -82,6 +82,9 @@ <li>If session information is present in the request but no matching session can be found, use the latest version.</li> </ul> + <p>The <a href="host.html">Host</a> may be configured (via the + <code>undeployOldVersions</code>) to remove old versions deployed in this way + once they are no longer in use.</p> </subsection> <subsection name="Naming"> Modified: tomcat/tc7.0.x/trunk/webapps/docs/config/host.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/config/host.xml?rev=1382367&r1=1382366&r2=1382367&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/config/host.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/config/host.xml Sat Sep 8 21:11:46 2012 @@ -201,6 +201,14 @@ not specified, the default value of 1 will be used.</p> </attribute> + <attribute name="undeployOldVersions" required="false"> + <p>This flag determines if Tomcat, as part of the auto deployment + process, will check for old, unused versions of web applications + deployed using parallel deployment and, if any are found, remove them. + This flag only applies if <code>autoDeploy</code> is true. If not + specified the default value of false will be used.</p> + </attribute> + </attributes> </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org