/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.startup;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.cert.Certificate;
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;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.management.ObjectName;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.DistributedManager;
import org.apache.catalina.Globals;
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.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.security.DeployXmlPermission;
import org.apache.catalina.util.ContextName;
import org.apache.catalina.util.IOTools;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.res.StringManager;

Startup event listener for a Host that configures the properties of that Host, and the associated defined contexts.
Author:Craig R. McClanahan, Remy Maucherat
/** * Startup event listener for a <b>Host</b> that configures the properties * of that Host, and the associated defined contexts. * * @author Craig R. McClanahan * @author Remy Maucherat */
public class HostConfig implements LifecycleListener { private static final Log log = LogFactory.getLog(HostConfig.class);
The string resources for this package.
/** * The string resources for this package. */
protected static final StringManager sm = StringManager.getManager(HostConfig.class);
The resolution, in milliseconds, of file modification times.
/** * The resolution, in milliseconds, of file modification times. */
protected static final long FILE_MODIFICATION_RESOLUTION_MS = 1000; // ----------------------------------------------------- Instance Variables
The Java class name of the Context implementation we should use.
/** * The Java class name of the Context implementation we should use. */
protected String contextClass = "org.apache.catalina.core.StandardContext";
The Host we are associated with.
/** * The Host we are associated with. */
protected Host host = null;
The JMX ObjectName of this component.
/** * The JMX ObjectName of this component. */
protected ObjectName oname = null;
Should we deploy XML Context config files packaged with WAR files and directories?
/** * Should we deploy XML Context config files packaged with WAR files and * directories? */
protected boolean deployXML = false;
Should XML files be copied to $CATALINA_BASE/conf/<engine>/<host> by default when a web application is deployed?
/** * Should XML files be copied to * $CATALINA_BASE/conf/&lt;engine&gt;/&lt;host&gt; by default when * a web application is deployed? */
protected boolean copyXML = false;
Should we unpack WAR files when auto-deploying applications in the appBase directory?
/** * Should we unpack WAR files when auto-deploying applications in the * <code>appBase</code> directory? */
protected boolean unpackWARs = false;
Map of deployed applications.
/** * Map of deployed applications. */
protected final Map<String, DeployedApplication> deployed = new ConcurrentHashMap<>();
List of applications which are being serviced, and shouldn't be deployed/undeployed/redeployed at the moment.
/** * List of applications which are being serviced, and shouldn't be * deployed/undeployed/redeployed at the moment. */
protected final ArrayList<String> serviced = new ArrayList<>();
The Digester instance used to parse context descriptors.
/** * The <code>Digester</code> instance used to parse context descriptors. */
protected Digester digester = createDigester(contextClass); private final Object digesterLock = new Object();
The list of Wars in the appBase to be ignored because they are invalid (e.g. contain /../ sequences).
/** * The list of Wars in the appBase to be ignored because they are invalid * (e.g. contain /../ sequences). */
protected final Set<String> invalidWars = new HashSet<>(); // ------------------------------------------------------------- Properties
Returns:the Context implementation class name.
/** * @return the Context implementation class name. */
public String getContextClass() { return this.contextClass; }
Set the Context implementation class name.
Params:
  • contextClass – The new Context implementation class name.
/** * Set the Context implementation class name. * * @param contextClass The new Context implementation class name. */
public void setContextClass(String contextClass) { String oldContextClass = this.contextClass; this.contextClass = contextClass; if (!oldContextClass.equals(contextClass)) { synchronized (digesterLock) { digester = createDigester(getContextClass()); } } }
Returns:the deploy XML config file flag for this component.
/** * @return the deploy XML config file flag for this component. */
public boolean isDeployXML() { return this.deployXML; }
Set the deploy XML config file flag for this component.
Params:
  • deployXML – The new deploy XML flag
/** * Set the deploy XML config file flag for this component. * * @param deployXML The new deploy XML flag */
public void setDeployXML(boolean deployXML) { this.deployXML = deployXML; } private boolean isDeployThisXML(File docBase, ContextName cn) { boolean deployThisXML = isDeployXML(); if (Globals.IS_SECURITY_ENABLED && !deployThisXML) { // When running under a SecurityManager, deployXML may be overridden // on a per Context basis by the granting of a specific permission Policy currentPolicy = Policy.getPolicy(); if (currentPolicy != null) { URL contextRootUrl; try { contextRootUrl = docBase.toURI().toURL(); CodeSource cs = new CodeSource(contextRootUrl, (Certificate[]) null); PermissionCollection pc = currentPolicy.getPermissions(cs); Permission p = new DeployXmlPermission(cn.getBaseName()); if (pc.implies(p)) { deployThisXML = true; } } catch (MalformedURLException e) { // Should never happen log.warn(sm.getString("hostConfig.docBaseUrlInvalid"), e); } } } return deployThisXML; }
Returns:the copy XML config file flag for this component.
/** * @return the copy XML config file flag for this component. */
public boolean isCopyXML() { return this.copyXML; }
Set the copy XML config file flag for this component.
Params:
  • copyXML – The new copy XML flag
/** * Set the copy XML config file flag for this component. * * @param copyXML The new copy XML flag */
public void setCopyXML(boolean copyXML) { this.copyXML= copyXML; }
Returns:the unpack WARs flag.
/** * @return the unpack WARs flag. */
public boolean isUnpackWARs() { return this.unpackWARs; }
Set the unpack WARs flag.
Params:
  • unpackWARs – The new unpack WARs flag
/** * Set the unpack WARs flag. * * @param unpackWARs The new unpack WARs flag */
public void setUnpackWARs(boolean unpackWARs) { this.unpackWARs = unpackWARs; } // --------------------------------------------------------- Public Methods
Process the START event for an associated Host.
Params:
  • event – The lifecycle event that has occurred
/** * Process the START event for an associated Host. * * @param event The lifecycle event that has occurred */
@Override public void lifecycleEvent(LifecycleEvent event) { // Identify the host we are associated with try { host = (Host) event.getLifecycle(); if (host instanceof StandardHost) { setCopyXML(((StandardHost) host).isCopyXML()); setDeployXML(((StandardHost) host).isDeployXML()); setUnpackWARs(((StandardHost) host).isUnpackWARs()); setContextClass(((StandardHost) host).getContextClass()); } } catch (ClassCastException e) { log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) { check(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { stop(); } }
Add a serviced application to the list.
Params:
  • name – the context name
/** * Add a serviced application to the list. * @param name the context name */
public synchronized void addServiced(String name) { serviced.add(name); }
Is application serviced ?
Params:
  • name – the context name
Returns:state of the application
/** * Is application serviced ? * @param name the context name * @return state of the application */
public synchronized boolean isServiced(String name) { return serviced.contains(name); }
Removed a serviced application from the list.
Params:
  • name – the context name
/** * Removed a serviced application from the list. * @param name the context name */
public synchronized void removeServiced(String name) { serviced.remove(name); }
Get the instant where an application was deployed.
Params:
  • name – the context name
Returns:0L if no application with that name is deployed, or the instant on which the application was deployed
/** * Get the instant where an application was deployed. * @param name the context name * @return 0L if no application with that name is deployed, or the instant * on which the application was deployed */
public long getDeploymentTime(String name) { DeployedApplication app = deployed.get(name); 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.
Params:
  • name – the context name
Returns:true if the application has been deployed and false if the application has not been deployed or does not exist
/** * Has the specified application been deployed? Note applications defined * in server.xml will not have been deployed. * @param name the context name * @return <code>true</code> if the application has been deployed and * <code>false</code> if the application has not been deployed or does not * exist */
public boolean isDeployed(String name) { return deployed.containsKey(name); } // ------------------------------------------------------ Protected Methods
Create the digester which will be used to parse context config files.
Params:
  • contextClassName – The class which will be used to create the context instance
Returns:the digester
/** * Create the digester which will be used to parse context config files. * @param contextClassName The class which will be used to create the * context instance * @return the digester */
protected static Digester createDigester(String contextClassName) { Digester digester = new Digester(); digester.setValidating(false); // Add object creation rule digester.addObjectCreate("Context", contextClassName, "className"); // 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); if (!file.isAbsolute()) file = new File(host.getCatalinaBase(), path); try { return file.getCanonicalFile(); } catch (IOException e) { return file; } }
Get the name of the configBase. For use with JMX management.
Returns:the config base
/** * Get the name of the configBase. * For use with JMX management. * @return the config base */
public String getConfigBaseName() { return host.getConfigBaseFile().getAbsolutePath(); }
Deploy applications for any directories or WAR files that are found in our "application root" directory.
/** * Deploy applications for any directories or WAR files that are found * in our "application root" directory. */
protected void deployApps() { File appBase = host.getAppBaseFile(); File configBase = host.getConfigBaseFile(); String[] filteredAppPaths = filterAppPaths(appBase.list()); // Deploy XML descriptors from configBase deployDescriptors(configBase, configBase.list()); // Deploy WARs 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 Host.getDeployIgnore().
Params:
  • unfilteredAppPaths – The list of application paths to filter
Returns: The filtered list of application paths
/** * 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 filter * * @return The filtered list of application paths */
protected String[] filterAppPaths(String[] unfilteredAppPaths) { Pattern filter = host.getDeployIgnorePattern(); if (filter == null || unfilteredAppPaths == null) { return unfilteredAppPaths; } List<String> filteredList = new ArrayList<>(); Matcher matcher = null; for (String appPath : unfilteredAppPaths) { if (matcher == null) { matcher = filter.matcher(appPath); } else { matcher.reset(appPath); } if (matcher.matches()) { if (log.isDebugEnabled()) { log.debug(sm.getString("hostConfig.ignorePath", appPath)); } } else { filteredList.add(appPath); } } return filteredList.toArray(new String[0]); }
Deploy applications for any directories or WAR files that are found in our "application root" directory.
Params:
  • name – The context name which should be deployed
/** * Deploy applications for any directories or WAR files that are found * in our "application root" directory. * @param name The context name which should be deployed */
protected void deployApps(String name) { File appBase = host.getAppBaseFile(); File configBase = host.getConfigBaseFile(); ContextName cn = new ContextName(name, false); String baseName = cn.getBaseName(); if (deploymentExists(cn.getName())) { return; } // Deploy XML descriptor from configBase File xml = new File(configBase, baseName + ".xml"); if (xml.exists()) { deployDescriptor(cn, xml); return; } // Deploy WAR File war = new File(appBase, baseName + ".war"); if (war.exists()) { deployWAR(cn, war); return; } // Deploy expanded folder File dir = new File(appBase, baseName); if (dir.exists()) deployDirectory(cn, dir); }
Deploy XML context descriptors.
Params:
  • configBase – The config base
  • files – The XML descriptors which should be deployed
/** * Deploy XML context descriptors. * @param configBase The config base * @param files The XML descriptors which should be deployed */
protected void deployDescriptors(File configBase, String[] files) { if (files == null) return; ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<>(); for (String file : files) { File contextXml = new File(configBase, file); if (file.toLowerCase(Locale.ENGLISH).endsWith(".xml")) { ContextName cn = new ContextName(file, true); if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; results.add( es.submit(new DeployDescriptor(this, cn, contextXml))); } } for (Future<?> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.threaded.error"), e); } } }
Deploy specified context descriptor.
Params:
  • cn – The context name
  • contextXml – The descriptor
/** * Deploy specified context descriptor. * @param cn The context name * @param contextXml The descriptor */
@SuppressWarnings("null") // context is not null protected void deployDescriptor(ContextName cn, File contextXml) { DeployedApplication deployedApp = new DeployedApplication(cn.getName(), true); long startTime = 0; // Assume this is a configuration descriptor and deploy it if(log.isInfoEnabled()) { startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployDescriptor", contextXml.getAbsolutePath())); } Context context = null; boolean isExternalWar = false; boolean isExternal = false; File expandedDocBase = null; try (FileInputStream fis = new FileInputStream(contextXml)) { synchronized (digesterLock) { try { context = (Context) digester.parse(fis); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.error", contextXml.getAbsolutePath()), e); } finally { digester.reset(); if (context == null) { context = new FailedContext(); } } } if (context.getPath() != null) { log.warn(sm.getString("hostConfig.deployDescriptor.path", context.getPath(), contextXml.getAbsolutePath())); } Class<?> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance(); context.addLifecycleListener(listener); context.setConfigFile(contextXml.toURI().toURL()); context.setName(cn.getName()); context.setPath(cn.getPath()); context.setWebappVersion(cn.getVersion()); // Add the associated docBase to the redeployed list if it's a WAR if (context.getDocBase() != null) { File docBase = new File(context.getDocBase()); if (!docBase.isAbsolute()) { docBase = new File(host.getAppBaseFile(), context.getDocBase()); } // If external docBase, register .xml as redeploy first if (!docBase.getCanonicalPath().startsWith( host.getAppBaseFile().getAbsolutePath() + File.separator)) { isExternal = true; deployedApp.redeployResources.put( contextXml.getAbsolutePath(), Long.valueOf(contextXml.lastModified())); deployedApp.redeployResources.put(docBase.getAbsolutePath(), Long.valueOf(docBase.lastModified())); if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) { isExternalWar = true; } // Check that a WAR or DIR in the appBase is not 'hidden' File war = new File(host.getAppBaseFile(), cn.getBaseName() + ".war"); if (war.exists()) { log.warn(sm.getString("hostConfig.deployDescriptor.hiddenWar", contextXml.getAbsolutePath(), war.getAbsolutePath())); } File dir = new File(host.getAppBaseFile(), cn.getBaseName()); if (dir.exists()) { log.warn(sm.getString("hostConfig.deployDescriptor.hiddenDir", contextXml.getAbsolutePath(), dir.getAbsolutePath())); } } else { log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified", docBase)); // Ignore specified docBase context.setDocBase(null); } } host.addChild(context); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("hostConfig.deployDescriptor.error", contextXml.getAbsolutePath()), t); } finally { // Get paths for WAR and expanded WAR in appBase // default to appBase dir + name expandedDocBase = new File(host.getAppBaseFile(), cn.getBaseName()); if (context.getDocBase() != null && !context.getDocBase().toLowerCase(Locale.ENGLISH).endsWith(".war")) { // first assume docBase is absolute expandedDocBase = new File(context.getDocBase()); if (!expandedDocBase.isAbsolute()) { // if docBase specified and relative, it must be relative to appBase expandedDocBase = new File(host.getAppBaseFile(), context.getDocBase()); } } boolean unpackWAR = unpackWARs; if (unpackWAR && context instanceof StandardContext) { unpackWAR = ((StandardContext) context).getUnpackWAR(); } // Add the eventual unpacked WAR and all the resources which will be // watched inside it if (isExternalWar) { if (unpackWAR) { deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(), Long.valueOf(expandedDocBase.lastModified())); addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context); } else { addWatchedResources(deployedApp, null, context); } } else { // Find an existing matching war and expanded folder if (!isExternal) { File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war"); if (warDocBase.exists()) { deployedApp.redeployResources.put(warDocBase.getAbsolutePath(), Long.valueOf(warDocBase.lastModified())); } else { // Trigger a redeploy if a WAR is added deployedApp.redeployResources.put( warDocBase.getAbsolutePath(), Long.valueOf(0)); } } if (unpackWAR) { deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(), Long.valueOf(expandedDocBase.lastModified())); addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context); } else { addWatchedResources(deployedApp, null, context); } if (!isExternal) { // For external docBases, the context.xml will have been // added above. deployedApp.redeployResources.put( contextXml.getAbsolutePath(), Long.valueOf(contextXml.lastModified())); } } // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process addGlobalRedeployResources(deployedApp); } if (host.findChild(context.getName()) != null) { deployed.put(context.getName(), deployedApp); } if (log.isInfoEnabled()) { log.info(sm.getString("hostConfig.deployDescriptor.finished", contextXml.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime))); } }
Deploy WAR files.
Params:
  • appBase – The base path for applications
  • files – The WARs to deploy
/** * Deploy WAR files. * @param appBase The base path for applications * @param files The WARs to deploy */
protected void deployWARs(File appBase, String[] files) { if (files == null) return; ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<>(); for (String file : files) { if (file.equalsIgnoreCase("META-INF")) continue; if (file.equalsIgnoreCase("WEB-INF")) continue; File war = new File(appBase, file); if (file.toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && !invalidWars.contains(file)) { ContextName cn = new ContextName(file, true); if (isServiced(cn.getName())) { continue; } if (deploymentExists(cn.getName())) { DeployedApplication app = deployed.get(cn.getName()); boolean unpackWAR = unpackWARs; if (unpackWAR && host.findChild(cn.getName()) instanceof StandardContext) { unpackWAR = ((StandardContext) host.findChild(cn.getName())).getUnpackWAR(); } if (!unpackWAR && app != null) { // Need to check for a directory that should not be // there File dir = new File(appBase, cn.getBaseName()); if (dir.exists()) { if (!app.loggedDirWarning) { log.warn(sm.getString( "hostConfig.deployWar.hiddenDir", dir.getAbsoluteFile(), war.getAbsoluteFile())); app.loggedDirWarning = true; } } else { app.loggedDirWarning = false; } } continue; } // Check for WARs with /../ /./ or similar sequences in the name if (!validateContextPath(appBase, cn.getBaseName())) { log.error(sm.getString( "hostConfig.illegalWarName", file)); invalidWars.add(file); continue; } results.add(es.submit(new DeployWar(this, cn, war))); } } for (Future<?> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployWar.threaded.error"), e); } } } 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); if (canonicalAppBase.endsWith(File.separator)) { docBase.append(contextPath.substring(1).replace( '/', File.separatorChar)); } else { docBase.append(contextPath.replace('/', File.separatorChar)); } // 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)) { docBase.append(File.separator); } } catch (IOException ioe) { return false; } // Compare the two. If they are not the same, the contextPath must // have /../ like sequences in it return canonicalDocBase.equals(docBase.toString()); }
Deploy packed WAR.
Params:
  • cn – The context name
  • war – The WAR file
/** * Deploy packed WAR. * @param cn The context name * @param war The WAR file */
protected void deployWAR(ContextName cn, File war) { File xml = new File(host.getAppBaseFile(), cn.getBaseName() + "/" + Constants.ApplicationContextXml); File warTracker = new File(host.getAppBaseFile(), cn.getBaseName() + Constants.WarTracker); boolean xmlInWar = false; try (JarFile jar = new JarFile(war)) { JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml); if (entry != null) { xmlInWar = true; } } catch (IOException e) { /* Ignore */ } // If there is an expanded directory then any xml in that directory // should only be used if the directory is not out of date and // unpackWARs is true. Note the code below may apply further limits boolean useXml = false; // If the xml file exists then expandedDir must exists so no need to // test that here if (xml.exists() && unpackWARs && (!warTracker.exists() || warTracker.lastModified() == war.lastModified())) { useXml = true; } Context context = null; boolean deployThisXML = isDeployThisXML(war, cn); try { if (deployThisXML && useXml && !copyXML) { synchronized (digesterLock) { try { context = (Context) digester.parse(xml); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.error", war.getAbsolutePath()), e); } finally { digester.reset(); if (context == null) { context = new FailedContext(); } } } context.setConfigFile(xml.toURI().toURL()); } else if (deployThisXML && xmlInWar) { synchronized (digesterLock) { try (JarFile jar = new JarFile(war)) { JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml); try (InputStream istream = jar.getInputStream(entry)) { context = (Context) digester.parse(istream); } } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.error", war.getAbsolutePath()), e); } finally { digester.reset(); if (context == null) { context = new FailedContext(); } context.setConfigFile( UriUtil.buildJarUrl(war, Constants.ApplicationContextXml)); } } } else if (!deployThisXML && xmlInWar) { // Block deployment as META-INF/context.xml may contain security // configuration necessary for a secure deployment. log.error(sm.getString("hostConfig.deployDescriptor.blocked", cn.getPath(), Constants.ApplicationContextXml, new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"))); } else { context = (Context) Class.forName(contextClass).getConstructor().newInstance(); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("hostConfig.deployWar.error", war.getAbsolutePath()), t); } finally { if (context == null) { context = new FailedContext(); } } boolean copyThisXml = false; if (deployThisXML) { if (host instanceof StandardHost) { copyThisXml = ((StandardHost) host).isCopyXML(); } // If Host is using default value Context can override it. if (!copyThisXml && context instanceof StandardContext) { copyThisXml = ((StandardContext) context).getCopyXML(); } if (xmlInWar && copyThisXml) { // Change location of XML file to config base xml = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"); try (JarFile jar = new JarFile(war)) { JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml); try (InputStream istream = jar.getInputStream(entry); OutputStream ostream = new FileOutputStream(xml)) { IOTools.flow(istream, ostream); } } catch (IOException e) { /* Ignore */ } } } DeployedApplication deployedApp = new DeployedApplication(cn.getName(), xml.exists() && deployThisXML && copyThisXml); long startTime = 0; // Deploy the application in this WAR file if(log.isInfoEnabled()) { startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployWar", war.getAbsolutePath())); } try { // Populate redeploy resources with the WAR file deployedApp.redeployResources.put (war.getAbsolutePath(), Long.valueOf(war.lastModified())); if (deployThisXML && xml.exists() && copyThisXml) { deployedApp.redeployResources.put(xml.getAbsolutePath(), Long.valueOf(xml.lastModified())); } else { // In case an XML file is added to the config base later deployedApp.redeployResources.put( (new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml")).getAbsolutePath(), Long.valueOf(0)); } Class<?> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance(); context.addLifecycleListener(listener); context.setName(cn.getName()); context.setPath(cn.getPath()); context.setWebappVersion(cn.getVersion()); context.setDocBase(cn.getBaseName() + ".war"); host.addChild(context); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("hostConfig.deployWar.error", war.getAbsolutePath()), t); } finally { // If we're unpacking WARs, the docBase will be mutated after // starting the context boolean unpackWAR = unpackWARs; if (unpackWAR && context instanceof StandardContext) { unpackWAR = ((StandardContext) context).getUnpackWAR(); } if (unpackWAR && context.getDocBase() != null) { File docBase = new File(host.getAppBaseFile(), cn.getBaseName()); deployedApp.redeployResources.put(docBase.getAbsolutePath(), Long.valueOf(docBase.lastModified())); addWatchedResources(deployedApp, docBase.getAbsolutePath(), context); if (deployThisXML && !copyThisXml && (xmlInWar || xml.exists())) { deployedApp.redeployResources.put(xml.getAbsolutePath(), Long.valueOf(xml.lastModified())); } } else { // Passing null for docBase means that no resources will be // watched. This will be logged at debug level. addWatchedResources(deployedApp, null, context); } // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process addGlobalRedeployResources(deployedApp); } deployed.put(cn.getName(), deployedApp); if (log.isInfoEnabled()) { log.info(sm.getString("hostConfig.deployWar.finished", war.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime))); } }
Deploy exploded webapps.
Params:
  • appBase – The base path for applications
  • files – The exploded webapps that should be deployed
/** * Deploy exploded webapps. * @param appBase The base path for applications * @param files The exploded webapps that should be deployed */
protected void deployDirectories(File appBase, String[] files) { if (files == null) return; ExecutorService es = host.getStartStopExecutor(); List<Future<?>> results = new ArrayList<>(); for (String file : files) { if (file.equalsIgnoreCase("META-INF")) continue; if (file.equalsIgnoreCase("WEB-INF")) continue; File dir = new File(appBase, file); if (dir.isDirectory()) { ContextName cn = new ContextName(file, false); if (isServiced(cn.getName()) || deploymentExists(cn.getName())) continue; results.add(es.submit(new DeployDirectory(this, cn, dir))); } } for (Future<?> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDir.threaded.error"), e); } } }
Deploy exploded webapp.
Params:
  • cn – The context name
  • dir – The path to the root folder of the weapp
/** * Deploy exploded webapp. * @param cn The context name * @param dir The path to the root folder of the weapp */
protected void deployDirectory(ContextName cn, File dir) { long startTime = 0; // Deploy the application in this directory if( log.isInfoEnabled() ) { startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployDir", dir.getAbsolutePath())); } Context context = null; File xml = new File(dir, Constants.ApplicationContextXml); File xmlCopy = new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml"); DeployedApplication deployedApp; boolean copyThisXml = isCopyXML(); boolean deployThisXML = isDeployThisXML(dir, cn); try { if (deployThisXML && xml.exists()) { synchronized (digesterLock) { try { context = (Context) digester.parse(xml); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.error", xml), e); context = new FailedContext(); } finally { digester.reset(); if (context == null) { context = new FailedContext(); } } } if (copyThisXml == false && context instanceof StandardContext) { // Host is using default value. Context may override it. copyThisXml = ((StandardContext) context).getCopyXML(); } if (copyThisXml) { Files.copy(xml.toPath(), xmlCopy.toPath()); context.setConfigFile(xmlCopy.toURI().toURL()); } else { context.setConfigFile(xml.toURI().toURL()); } } else if (!deployThisXML && xml.exists()) { // Block deployment as META-INF/context.xml may contain security // configuration necessary for a secure deployment. log.error(sm.getString("hostConfig.deployDescriptor.blocked", cn.getPath(), xml, xmlCopy)); context = new FailedContext(); } else { context = (Context) Class.forName(contextClass).getConstructor().newInstance(); } Class<?> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance(); context.addLifecycleListener(listener); context.setName(cn.getName()); context.setPath(cn.getPath()); context.setWebappVersion(cn.getVersion()); context.setDocBase(cn.getBaseName()); host.addChild(context); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("hostConfig.deployDir.error", dir.getAbsolutePath()), t); } finally { deployedApp = new DeployedApplication(cn.getName(), xml.exists() && deployThisXML && copyThisXml); // Fake re-deploy resource to detect if a WAR is added at a later // point deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war", Long.valueOf(0)); deployedApp.redeployResources.put(dir.getAbsolutePath(), Long.valueOf(dir.lastModified())); if (deployThisXML && xml.exists()) { if (copyThisXml) { deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(xmlCopy.lastModified())); } else { deployedApp.redeployResources.put( xml.getAbsolutePath(), Long.valueOf(xml.lastModified())); // Fake re-deploy resource to detect if a context.xml file is // added at a later point deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(0)); } } else { // Fake re-deploy resource to detect if a context.xml file is // added at a later point deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(0)); if (!xml.exists()) { deployedApp.redeployResources.put( xml.getAbsolutePath(), Long.valueOf(0)); } } addWatchedResources(deployedApp, dir.getAbsolutePath(), context); // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process addGlobalRedeployResources(deployedApp); } deployed.put(cn.getName(), deployedApp); if( log.isInfoEnabled() ) { log.info(sm.getString("hostConfig.deployDir.finished", dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime))); } }
Check if a webapp is already deployed in this host.
Params:
  • contextName – of the context which will be checked
Returns:true if the specified deployment exists
/** * Check if a webapp is already deployed in this host. * * @param contextName of the context which will be checked * @return <code>true</code> if the specified deployment exists */
protected boolean deploymentExists(String contextName) { return (deployed.containsKey(contextName) || (host.findChild(contextName) != null)); }
Add watched resources to the specified Context.
Params:
  • app – HostConfig deployed app
  • docBase – web app docBase
  • context – web application context
/** * Add watched resources to the specified Context. * @param app HostConfig deployed app * @param docBase web app docBase * @param context web application context */
protected void addWatchedResources(DeployedApplication app, String docBase, Context context) { // FIXME: Feature idea. Add support for patterns (ex: WEB-INF/*, // WEB-INF/*.xml), where we would only check if at least one // resource is newer than app.timestamp File docBaseFile = null; if (docBase != null) { docBaseFile = new File(docBase); if (!docBaseFile.isAbsolute()) { docBaseFile = new File(host.getAppBaseFile(), docBase); } } String[] watchedResources = context.findWatchedResources(); for (String watchedResource : watchedResources) { File resource = new File(watchedResource); if (!resource.isAbsolute()) { if (docBase != null) { resource = new File(docBaseFile, watchedResource); } else { if (log.isDebugEnabled()) log.debug("Ignoring non-existent WatchedResource '" + resource.getAbsolutePath() + "'"); continue; } } if (log.isDebugEnabled()) log.debug("Watching WatchedResource '" + 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 File hostContextXml = new File(getConfigBaseName(), Constants.HostContextXml); if (hostContextXml.isFile()) { app.redeployResources.put(hostContextXml.getAbsolutePath(), Long.valueOf(hostContextXml.lastModified())); } // Redeploy resources in CATALINA_BASE/conf are never deleted File globalContextXml = returnCanonicalPath(Constants.DefaultContextXml); if (globalContextXml.isFile()) { app.redeployResources.put(globalContextXml.getAbsolutePath(), Long.valueOf(globalContextXml.lastModified())); } }
Check resources for redeployment and reloading.
Params:
  • app – The web application to check
  • skipFileModificationResolutionCheck – When checking files for modification should the check that requires that any file modification must have occurred at least as long ago as the resolution of the file time stamp be skipped
/** * Check resources for redeployment and reloading. * * @param app The web application to check * @param skipFileModificationResolutionCheck * When checking files for modification should the check that * requires that any file modification must have occurred at * least as long ago as the resolution of the file time stamp * be skipped */
protected synchronized void checkResources(DeployedApplication app, boolean skipFileModificationResolutionCheck) { String[] resources = app.redeployResources.keySet().toArray(new String[0]); // Offset the current time by the resolution of File.lastModified() long currentTimeWithResolutionOffset = System.currentTimeMillis() - FILE_MODIFICATION_RESOLUTION_MS; for (int i = 0; i < resources.length; i++) { File resource = new File(resources[i]); if (log.isDebugEnabled()) log.debug("Checking context[" + app.name + "] redeploy resource " + resource); long lastModified = app.redeployResources.get(resources[i]).longValue(); if (resource.exists() || lastModified == 0) { // File.lastModified() has a resolution of 1s (1000ms). The last // modified time has to be more than 1000ms ago to ensure that // modifications that take place in the same second are not // missed. See Bug 57765. if (resource.lastModified() != lastModified && (!host.getAutoDeploy() || resource.lastModified() < currentTimeWithResolutionOffset || skipFileModificationResolutionCheck)) { if (resource.isDirectory()) { // No action required for modified directory app.redeployResources.put(resources[i], Long.valueOf(resource.lastModified())); } else if (app.hasDescriptor && resource.getName().toLowerCase( Locale.ENGLISH).endsWith(".war")) { // Modified WAR triggers a reload if there is an XML // file present // The only resource that should be deleted is the // expanded WAR (if any) Context context = (Context) host.findChild(app.name); String docBase = context.getDocBase(); if (!docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) { // This is an expanded directory File docBaseFile = new File(docBase); if (!docBaseFile.isAbsolute()) { docBaseFile = new File(host.getAppBaseFile(), docBase); } reload(app, docBaseFile, resource.getAbsolutePath()); } else { reload(app, null, null); } // Update times app.redeployResources.put(resources[i], Long.valueOf(resource.lastModified())); app.timestamp = System.currentTimeMillis(); boolean unpackWAR = unpackWARs; if (unpackWAR && context instanceof StandardContext) { unpackWAR = ((StandardContext) context).getUnpackWAR(); } if (unpackWAR) { addWatchedResources(app, context.getDocBase(), context); } else { addWatchedResources(app, null, context); } return; } else { // Everything else triggers a redeploy // (just need to undeploy here, deploy will follow) undeploy(app); deleteRedeployResources(app, resources, i, false); return; } } } else { // There is a chance the the resource was only missing // temporarily eg renamed during a text editor save try { Thread.sleep(500); } catch (InterruptedException e1) { // Ignore } // Recheck the resource to see if it was really deleted if (resource.exists()) { continue; } // Undeploy application undeploy(app); deleteRedeployResources(app, resources, i, true); return; } } resources = app.reloadResources.keySet().toArray(new String[0]); boolean update = false; for (String s : resources) { File resource = new File(s); if (log.isDebugEnabled()) { log.debug("Checking context[" + app.name + "] reload resource " + resource); } long lastModified = app.reloadResources.get(s).longValue(); // File.lastModified() has a resolution of 1s (1000ms). The last // modified time has to be more than 1000ms ago to ensure that // modifications that take place in the same second are not // missed. See Bug 57765. if ((resource.lastModified() != lastModified && (!host.getAutoDeploy() || resource.lastModified() < currentTimeWithResolutionOffset || skipFileModificationResolutionCheck)) || update) { if (!update) { // Reload application reload(app, null, null); update = true; } // Update times. More than one file may have been updated. We // don't want to trigger a series of reloads. app.reloadResources.put(s, Long.valueOf(resource.lastModified())); } app.timestamp = System.currentTimeMillis(); } } /* * Note: If either of fileToRemove and newDocBase are null, both will be * ignored. */ private void reload(DeployedApplication app, File fileToRemove, String newDocBase) { if(log.isInfoEnabled()) log.info(sm.getString("hostConfig.reload", app.name)); Context context = (Context) host.findChild(app.name); if (context.getState().isAvailable()) { if (fileToRemove != null && newDocBase != null) { context.addLifecycleListener( new ExpandedDirectoryRemovalListener(fileToRemove, newDocBase)); } // Reload catches and logs exceptions context.reload(); } else { // If the context was not started (for example an error // in web.xml) we'll still get to try to start if (fileToRemove != null && newDocBase != null) { ExpandWar.delete(fileToRemove); context.setDocBase(newDocBase); } try { context.start(); } catch (Exception e) { log.error(sm.getString("hostConfig.context.restart", app.name), e); } } } private void undeploy(DeployedApplication app) { 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); } deployed.remove(app.name); } private void deleteRedeployResources(DeployedApplication app, String[] resources, int i, boolean deleteReloadResources) { // Delete other redeploy resources for (int j = i + 1; j < resources.length; j++) { File current = new File(resources[j]); // 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 (isDeletableResource(app, current)) { if (log.isDebugEnabled()) { log.debug("Delete " + current); } ExpandWar.delete(current); } } // Delete reload resources (to remove any remaining .xml descriptor) if (deleteReloadResources) { String[] resources2 = app.reloadResources.keySet().toArray(new String[0]); for (String s : resources2) { File current = new File(s); // 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 (isDeletableResource(app, current)) { if (log.isDebugEnabled()) { log.debug("Delete " + current); } ExpandWar.delete(current); } } } } /* * Delete any resource that would trigger the automatic deployment code to * re-deploy the application. This means deleting: * - any resource located in the appBase * - any deployment descriptor located under the configBase * - symlinks in the appBase or configBase for either of the above */ private boolean isDeletableResource(DeployedApplication app, File resource) { // The resource may be a file, a directory or a symlink to a file or // directory. // Check that the resource is absolute. This should always be the case. if (!resource.isAbsolute()) { log.warn(sm.getString("hostConfig.resourceNotAbsolute", app.name, resource)); return false; } // Determine where the resource is located String canonicalLocation; try { canonicalLocation = resource.getParentFile().getCanonicalPath(); } catch (IOException e) { log.warn(sm.getString( "hostConfig.canonicalizing", resource.getParentFile(), app.name), e); return false; } String canonicalAppBase; try { canonicalAppBase = host.getAppBaseFile().getCanonicalPath(); } catch (IOException e) { log.warn(sm.getString( "hostConfig.canonicalizing", host.getAppBaseFile(), app.name), e); return false; } if (canonicalLocation.equals(canonicalAppBase)) { // Resource is located in the appBase so it may be deleted return true; } String canonicalConfigBase; try { canonicalConfigBase = host.getConfigBaseFile().getCanonicalPath(); } catch (IOException e) { log.warn(sm.getString( "hostConfig.canonicalizing", host.getConfigBaseFile(), app.name), e); return false; } if (canonicalLocation.equals(canonicalConfigBase) && resource.getName().endsWith(".xml")) { // Resource is an xml file in the configBase so it may be deleted return true; } // All other resources should not be deleted return false; } public void beforeStart() { if (host.getCreateDirs()) { File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()}; for (File dir : dirs) { if (!dir.mkdirs() && !dir.isDirectory()) { log.error(sm.getString("hostConfig.createDirs", dir)); } } } }
Process a "start" event for this Host.
/** * Process a "start" event for this Host. */
public void start() { if (log.isDebugEnabled()) log.debug(sm.getString("hostConfig.start")); try { ObjectName hostON = host.getObjectName(); oname = new ObjectName (hostON.getDomain() + ":type=Deployer,host=" + host.getName()); Registry.getRegistry(null, null).registerComponent (this, oname, this.getClass().getName()); } catch (Exception e) { log.warn(sm.getString("hostConfig.jmx.register", oname), e); } if (!host.getAppBaseFile().isDirectory()) { log.error(sm.getString("hostConfig.appBase", host.getName(), host.getAppBaseFile().getPath())); host.setDeployOnStartup(false); host.setAutoDeploy(false); } if (host.getDeployOnStartup()) deployApps(); }
Process a "stop" event for this Host.
/** * Process a "stop" event for this Host. */
public void stop() { if (log.isDebugEnabled()) log.debug(sm.getString("hostConfig.stop")); if (oname != null) { try { Registry.getRegistry(null, null).unregisterComponent(oname); } catch (Exception e) { log.warn(sm.getString("hostConfig.jmx.unregister", oname), e); } } oname = null; }
Check status of all webapps.
/** * Check status of all webapps. */
protected void check() { if (host.getAutoDeploy()) { // Check for resources modification to trigger redeployment DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (DeployedApplication app : apps) { if (!isServiced(app.name)) checkResources(app, false); } // Check for old versions of applications that can now be undeployed if (host.getUndeployOldVersions()) { checkUndeploy(); } // Hotdeploy applications deployApps(); } }
Check status of a specific web application and reload, redeploy or deploy it as necessary. This method is for use with functionality such as management web applications that upload new/updated web applications and need to trigger the appropriate action to deploy them. This method assumes that the web application is currently marked as serviced and that any uploading/updating has been completed before this method is called. Any action taken as a result of the checks will complete before this method returns.
Params:
  • name – The name of the web application to check
/** * Check status of a specific web application and reload, redeploy or deploy * it as necessary. This method is for use with functionality such as * management web applications that upload new/updated web applications and * need to trigger the appropriate action to deploy them. This method * assumes that the web application is currently marked as serviced and that * any uploading/updating has been completed before this method is called. * Any action taken as a result of the checks will complete before this * method returns. * * @param name The name of the web application to check */
public void check(String name) { DeployedApplication app = deployed.get(name); if (app != null) { checkResources(app, true); } deployApps(name); }
Check for old versions of applications using parallel deployment that are now unused (have no active sessions) and undeploy any that are found.
/** * Check for old versions of applications using parallel deployment that are * now unused (have no active sessions) and undeploy any that are found. */
public synchronized void checkUndeploy() { if (deployed.size() < 2) { return; } // Need ordered set of names SortedSet<String> sortedAppNames = new TreeSet<>(deployed.keySet()); Iterator<String> iter = sortedAppNames.iterator(); ContextName previous = new ContextName(iter.next(), false); do { ContextName current = new ContextName(iter.next(), false); if (current.getPath().equals(previous.getPath())) { // Current and previous are same path - current will always // be a later version Context previousContext = (Context) host.findChild(previous.getName()); Context currentContext = (Context) host.findChild(current.getName()); if (previousContext != null && currentContext != null && currentContext.getState().isAvailable() && !isServiced(previous.getName())) { Manager manager = previousContext.getManager(); if (manager != null) { int sessionCount; if (manager instanceof DistributedManager) { sessionCount = ((DistributedManager) manager).getActiveSessionsFull(); } else { sessionCount = manager.getActiveSessions(); } if (sessionCount == 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 undeploy(app); 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.
Params:
  • context – The context instance
/** * Add a new Context to be managed by us. * Entry point for the admin webapp, and other JMX Context controllers. * @param context The context instance */
public void manageApp(Context context) { String contextName = context.getName(); if (deployed.containsKey(contextName)) return; DeployedApplication deployedApp = new DeployedApplication(contextName, false); // Add the associated docBase to the redeployed list if it's a WAR boolean isWar = false; if (context.getDocBase() != null) { File docBase = new File(context.getDocBase()); if (!docBase.isAbsolute()) { docBase = new File(host.getAppBaseFile(), context.getDocBase()); } deployedApp.redeployResources.put(docBase.getAbsolutePath(), Long.valueOf(docBase.lastModified())); if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) { isWar = true; } } host.addChild(context); // Add the eventual unpacked WAR and all the resources which will be // watched inside it boolean unpackWAR = unpackWARs; if (unpackWAR && context instanceof StandardContext) { unpackWAR = ((StandardContext) context).getUnpackWAR(); } if (isWar && unpackWAR) { File docBase = new File(host.getAppBaseFile(), context.getBaseName()); deployedApp.redeployResources.put(docBase.getAbsolutePath(), Long.valueOf(docBase.lastModified())); addWatchedResources(deployedApp, docBase.getAbsolutePath(), context); } else { addWatchedResources(deployedApp, null, context); } deployed.put(contextName, deployedApp); }
Remove a webapp from our control. Entry point for the admin webapp, and other JMX Context controllers.
Params:
  • contextName – The context name
/** * Remove a webapp from our control. * Entry point for the admin webapp, and other JMX Context controllers. * @param contextName The context name */
public void unmanageApp(String contextName) { if(isServiced(contextName)) { deployed.remove(contextName); host.removeChild(host.findChild(contextName)); } } // ----------------------------------------------------- Instance Variables
This class represents the state of a deployed application, as well as the monitored resources.
/** * This class represents the state of a deployed application, as well as * the monitored resources. */
protected static class DeployedApplication { public DeployedApplication(String name, boolean hasDescriptor) { this.name = name; this.hasDescriptor = hasDescriptor; }
Application context path. The assertion is that (host.getChild(name) != null).
/** * Application context path. The assertion is that * (host.getChild(name) != null). */
public final String name;
Does this application have a context.xml descriptor file on the host's configBase?
/** * Does this application have a context.xml descriptor file on the * host's configBase? */
public final boolean hasDescriptor;
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. The value is the last modification time.
/** * 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. * The value is the last modification time. */
public final LinkedHashMap<String, Long> redeployResources = new LinkedHashMap<>();
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. The value is the last modification time.
/** * 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. * The value is the last modification time. */
public final HashMap<String, Long> reloadResources = new HashMap<>();
Instant where the application was last put in service.
/** * Instant where the application was last put in service. */
public long timestamp = System.currentTimeMillis();
In some circumstances, such as when unpackWARs is true, a directory may be added to the appBase that is ignored. This flag indicates that the user has been warned so that the warning is not logged on every run of the auto deployer.
/** * In some circumstances, such as when unpackWARs is true, a directory * may be added to the appBase that is ignored. This flag indicates that * the user has been warned so that the warning is not logged on every * run of the auto deployer. */
public boolean loggedDirWarning = false; } private static class DeployDescriptor implements Runnable { private HostConfig config; private ContextName cn; private File descriptor; public DeployDescriptor(HostConfig config, ContextName cn, File descriptor) { this.config = config; this.cn = cn; this.descriptor= descriptor; } @Override public void run() { config.deployDescriptor(cn, descriptor); } } private static class DeployWar implements Runnable { private HostConfig config; private ContextName cn; private File war; public DeployWar(HostConfig config, ContextName cn, File war) { this.config = config; this.cn = cn; this.war = war; } @Override public void run() { config.deployWAR(cn, war); } } private static class DeployDirectory implements Runnable { private HostConfig config; private ContextName cn; private File dir; public DeployDirectory(HostConfig config, ContextName cn, File dir) { this.config = config; this.cn = cn; this.dir = dir; } @Override public void run() { config.deployDirectory(cn, dir); } } /* * The purpose of this class is to provide a way for HostConfig to get * a Context to delete an expanded WAR after the Context stops. This is to * resolve this issue described in Bug 57772. The alternative solutions * require either duplicating a lot of the Context.reload() code in * HostConfig or adding a new reload(boolean) method to Context that allows * the caller to optionally delete any expanded WAR. * * The LifecycleListener approach offers greater flexibility and enables the * behaviour to be changed / extended / removed in future without changing * the Context API. */ private static class ExpandedDirectoryRemovalListener implements LifecycleListener { private final File toDelete; private final String newDocBase;
Create a listener that will ensure that any expanded WAR is removed and the docBase set to the specified WAR.
Params:
  • toDelete – The file (a directory representing an expanded WAR) to be deleted
  • newDocBase – The new docBase for the Context
/** * Create a listener that will ensure that any expanded WAR is removed * and the docBase set to the specified WAR. * * @param toDelete The file (a directory representing an expanded WAR) * to be deleted * @param newDocBase The new docBase for the Context */
public ExpandedDirectoryRemovalListener(File toDelete, String newDocBase) { this.toDelete = toDelete; this.newDocBase = newDocBase; } @Override public void lifecycleEvent(LifecycleEvent event) { if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) { // The context has stopped. Context context = (Context) event.getLifecycle(); // Remove the old expanded WAR. ExpandWar.delete(toDelete); // Reset the docBase to trigger re-expansion of the WAR. context.setDocBase(newDocBase); // Remove this listener from the Context else it will run every // time the Context is stopped. context.removeLifecycleListener(this); } } } }