/* * 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.core; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.management.ObjectName; import org.apache.catalina.AccessLog; import org.apache.catalina.Cluster; import org.apache.catalina.Container; import org.apache.catalina.ContainerEvent; import org.apache.catalina.ContainerListener; import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.Globals; import org.apache.catalina.Host; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.Loader; import org.apache.catalina.Pipeline; import org.apache.catalina.Realm; import org.apache.catalina.Valve; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.ContextName; import org.apache.catalina.util.LifecycleMBeanBase; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.res.StringManager; import org.apache.tomcat.util.threads.InlineExecutorService; /** * Abstract implementation of the <b>Container</b> interface, providing common * functionality required by nearly every implementation. Classes extending * this base class must may implement a replacement for <code>invoke()</code>. * <p> * All subclasses of this abstract base class will include support for a * Pipeline object that defines the processing to be performed for each request * received by the <code>invoke()</code> method of this class, utilizing the * "Chain of Responsibility" design pattern. A subclass should encapsulate its * own processing functionality as a <code>Valve</code>, and configure this * Valve into the pipeline by calling <code>setBasic()</code>. * <p> * This implementation fires property change events, per the JavaBeans design * pattern, for changes in singleton properties. In addition, it fires the * following <code>ContainerEvent</code> events to listeners who register * themselves with <code>addContainerListener()</code>: * <table border=1> * <caption>ContainerEvents fired by this implementation</caption> * <tr> * <th>Type</th> * <th>Data</th> * <th>Description</th> * </tr> * <tr> * <td align=center><code>addChild</code></td> * <td align=center><code>Container</code></td> * <td>Child container added to this Container.</td> * </tr> * <tr> * <td align=center><code>{@link #getPipeline() pipeline}.addValve</code></td> * <td align=center><code>Valve</code></td> * <td>Valve added to this Container.</td> * </tr> * <tr> * <td align=center><code>removeChild</code></td> * <td align=center><code>Container</code></td> * <td>Child container removed from this Container.</td> * </tr> * <tr> * <td align=center><code>{@link #getPipeline() pipeline}.removeValve</code></td> * <td align=center><code>Valve</code></td> * <td>Valve removed from this Container.</td> * </tr> * <tr> * <td align=center><code>start</code></td> * <td align=center><code>null</code></td> * <td>Container was started.</td> * </tr> * <tr> * <td align=center><code>stop</code></td> * <td align=center><code>null</code></td> * <td>Container was stopped.</td> * </tr> * </table> * Subclasses that fire additional events should document them in the * class comments of the implementation class. * * @author Craig R. McClanahan */ public abstract class ContainerBase extends LifecycleMBeanBase implements Container { private static final Log log = LogFactory.getLog(ContainerBase.class); /** * Perform addChild with the permissions of this class. * addChild can be called with the XML parser on the stack, * this allows the XML parser to have fewer privileges than * Tomcat. */ protected class PrivilegedAddChild implements PrivilegedAction<Void> { private final Container child; PrivilegedAddChild(Container child) { this.child = child; } @Override public Void run() { addChildInternal(child); return null; } } // ----------------------------------------------------- Instance Variables /** * The child Containers belonging to this Container, keyed by name. */ protected final HashMap<String, Container> children = new HashMap<>(); /** * The processor delay for this component. */ protected int backgroundProcessorDelay = -1; /** * The container event listeners for this Container. Implemented as a * CopyOnWriteArrayList since listeners may invoke methods to add/remove * themselves or other listeners and with a ReadWriteLock that would trigger * a deadlock. */ protected final List<ContainerListener> listeners = new CopyOnWriteArrayList<>(); /** * The Logger implementation with which this Container is associated. */ protected Log logger = null; /** * Associated logger name. */ protected String logName = null; /** * The cluster with which this Container is associated. */ protected Cluster cluster = null; private final ReadWriteLock clusterLock = new ReentrantReadWriteLock(); /** * The human-readable name of this Container. */ protected String name = null; /** * The parent Container to which this Container is a child. */ protected Container parent = null; /** * The parent class loader to be configured when we install a Loader. */ protected ClassLoader parentClassLoader = null; /** * The Pipeline object with which this Container is associated. */ protected final Pipeline pipeline = new StandardPipeline(this); /** * The Realm with which this Container is associated. */ private volatile Realm realm = null; /** * Lock used to control access to the Realm. */ private final ReadWriteLock realmLock = new ReentrantReadWriteLock(); /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); /** * Will children be started automatically when they are added. */ protected boolean startChildren = true; /** * The property change support for this component. */ protected final PropertyChangeSupport support = new PropertyChangeSupport(this); /** * The background thread. */ private Thread thread = null; /** * The background thread completion semaphore. */ private volatile boolean threadDone = false; /** * The access log to use for requests normally handled by this container * that have been handled earlier in the processing chain. */ protected volatile AccessLog accessLog = null; private volatile boolean accessLogScanComplete = false; /** * The number of threads available to process start and stop events for any * children associated with this container. */ private int startStopThreads = 1; protected ExecutorService startStopExecutor; // ------------------------------------------------------------- Properties @Override public int getStartStopThreads() { return startStopThreads; } /** * Handles the special values. */ private int getStartStopThreadsInternal() { int result = getStartStopThreads(); // Positive values are unchanged if (result > 0) { return result; } // Zero == Runtime.getRuntime().availableProcessors() // -ve == Runtime.getRuntime().availableProcessors() + value // These two are the same result = Runtime.getRuntime().availableProcessors() + result; if (result < 1) { result = 1; } return result; } @Override public void setStartStopThreads(int startStopThreads) { int oldStartStopThreads = this.startStopThreads; this.startStopThreads = startStopThreads; // Use local copies to ensure thread safety if (oldStartStopThreads != startStopThreads && startStopExecutor != null) { reconfigureStartStopExecutor(getStartStopThreadsInternal()); } } /** * Get the delay between the invocation of the backgroundProcess method on * this container and its children. Child containers will not be invoked * if their delay value is not negative (which would mean they are using * their own thread). Setting this to a positive value will cause * a thread to be spawn. After waiting the specified amount of time, * the thread will invoke the executePeriodic method on this container * and all its children. */ @Override public int getBackgroundProcessorDelay() { return backgroundProcessorDelay; } /** * Set the delay between the invocation of the execute method on this * container and its children. * * @param delay The delay in seconds between the invocation of * backgroundProcess methods */ @Override public void setBackgroundProcessorDelay(int delay) { backgroundProcessorDelay = delay; } /** * Return the Logger for this Container. */ @Override public Log getLogger() { if (logger != null) return logger; logger = LogFactory.getLog(getLogName()); return logger; } /** * @return the abbreviated name of this container for logging messages */ @Override public String getLogName() { if (logName != null) { return logName; } String loggerName = null; Container current = this; while (current != null) { String name = current.getName(); if ((name == null) || (name.equals(""))) { name = "/"; } else if (name.startsWith("##")) { name = "/" + name; } loggerName = "[" + name + "]" + ((loggerName != null) ? ("." + loggerName) : ""); current = current.getParent(); } logName = ContainerBase.class.getName() + "." + loggerName; return logName; } /** * Return the Cluster with which this Container is associated. If there is * no associated Cluster, return the Cluster associated with our parent * Container (if any); otherwise return <code>null</code>. */ @Override public Cluster getCluster() { Lock readLock = clusterLock.readLock(); readLock.lock(); try { if (cluster != null) return cluster; if (parent != null) return parent.getCluster(); return null; } finally { readLock.unlock(); } } /* * Provide access to just the cluster component attached to this container. */ protected Cluster getClusterInternal() { Lock readLock = clusterLock.readLock(); readLock.lock(); try { return cluster; } finally { readLock.unlock(); } } /** * Set the Cluster with which this Container is associated. * * @param cluster The newly associated Cluster */ @Override public void setCluster(Cluster cluster) { Cluster oldCluster = null; Lock writeLock = clusterLock.writeLock(); writeLock.lock(); try { // Change components if necessary oldCluster = this.cluster; if (oldCluster == cluster) return; this.cluster = cluster; // Stop the old component if necessary if (getState().isAvailable() && (oldCluster != null) && (oldCluster instanceof Lifecycle)) { try { ((Lifecycle) oldCluster).stop(); } catch (LifecycleException e) { log.error("ContainerBase.setCluster: stop: ", e); } } // Start the new component if necessary if (cluster != null) cluster.setContainer(this); if (getState().isAvailable() && (cluster != null) && (cluster instanceof Lifecycle)) { try { ((Lifecycle) cluster).start(); } catch (LifecycleException e) { log.error("ContainerBase.setCluster: start: ", e); } } } finally { writeLock.unlock(); } // Report this property change to interested listeners support.firePropertyChange("cluster", oldCluster, cluster); } /** * Return a name string (suitable for use by humans) that describes this * Container. Within the set of child containers belonging to a particular * parent, Container names must be unique. */ @Override public String getName() { return name; } /** * Set a name string (suitable for use by humans) that describes this * Container. Within the set of child containers belonging to a particular * parent, Container names must be unique. * * @param name New name of this container * * @exception IllegalStateException if this Container has already been * added to the children of a parent Container (after which the name * may not be changed) */ @Override public void setName(String name) { String oldName = this.name; this.name = name; support.firePropertyChange("name", oldName, this.name); } /** * Return if children of this container will be started automatically when * they are added to this container. * * @return <code>true</code> if the children will be started */ public boolean getStartChildren() { return startChildren; } /** * Set if children of this container will be started automatically when * they are added to this container. * * @param startChildren New value of the startChildren flag */ public void setStartChildren(boolean startChildren) { boolean oldStartChildren = this.startChildren; this.startChildren = startChildren; support.firePropertyChange("startChildren", oldStartChildren, this.startChildren); } /** * Return the Container for which this Container is a child, if there is * one. If there is no defined parent, return <code>null</code>. */ @Override public Container getParent() { return parent; } /** * Set the parent Container to which this Container is being added as a * child. This Container may refuse to become attached to the specified * Container by throwing an exception. * * @param container Container to which this Container is being added * as a child * * @exception IllegalArgumentException if this Container refuses to become * attached to the specified Container */ @Override public void setParent(Container container) { Container oldParent = this.parent; this.parent = container; support.firePropertyChange("parent", oldParent, this.parent); } /** * Return the parent class loader (if any) for this web application. * This call is meaningful only <strong>after</strong> a Loader has * been configured. */ @Override public ClassLoader getParentClassLoader() { if (parentClassLoader != null) return parentClassLoader; if (parent != null) { return parent.getParentClassLoader(); } return ClassLoader.getSystemClassLoader(); } /** * Set the parent class loader (if any) for this web application. * This call is meaningful only <strong>before</strong> a Loader has * been configured, and the specified value (if non-null) should be * passed as an argument to the class loader constructor. * * * @param parent The new parent class loader */ @Override public void setParentClassLoader(ClassLoader parent) { ClassLoader oldParentClassLoader = this.parentClassLoader; this.parentClassLoader = parent; support.firePropertyChange("parentClassLoader", oldParentClassLoader, this.parentClassLoader); } /** * Return the Pipeline object that manages the Valves associated with * this Container. */ @Override public Pipeline getPipeline() { return this.pipeline; } /** * Return the Realm with which this Container is associated. If there is * no associated Realm, return the Realm associated with our parent * Container (if any); otherwise return <code>null</code>. */ @Override public Realm getRealm() { Lock l = realmLock.readLock(); l.lock(); try { if (realm != null) return realm; if (parent != null) return parent.getRealm(); return null; } finally { l.unlock(); } } protected Realm getRealmInternal() { Lock l = realmLock.readLock(); l.lock(); try { return realm; } finally { l.unlock(); } } /** * Set the Realm with which this Container is associated. * * @param realm The newly associated Realm */ @Override public void setRealm(Realm realm) { Lock l = realmLock.writeLock(); l.lock(); try { // Change components if necessary Realm oldRealm = this.realm; if (oldRealm == realm) return; this.realm = realm; // Stop the old component if necessary if (getState().isAvailable() && (oldRealm != null) && (oldRealm instanceof Lifecycle)) { try { ((Lifecycle) oldRealm).stop(); } catch (LifecycleException e) { log.error("ContainerBase.setRealm: stop: ", e); } } // Start the new component if necessary if (realm != null) realm.setContainer(this); if (getState().isAvailable() && (realm != null) && (realm instanceof Lifecycle)) { try { ((Lifecycle) realm).start(); } catch (LifecycleException e) { log.error("ContainerBase.setRealm: start: ", e); } } // Report this property change to interested listeners support.firePropertyChange("realm", oldRealm, this.realm); } finally { l.unlock(); } } // ------------------------------------------------------ Container Methods /** * Add a new child Container to those associated with this Container, * if supported. Prior to adding this Container to the set of children, * the child's <code>setParent()</code> method must be called, with this * Container as an argument. This method may thrown an * <code>IllegalArgumentException</code> if this Container chooses not * to be attached to the specified Container, in which case it is not added * * @param child New child Container to be added * * @exception IllegalArgumentException if this exception is thrown by * the <code>setParent()</code> method of the child Container * @exception IllegalArgumentException if the new child does not have * a name unique from that of existing children of this Container * @exception IllegalStateException if this Container does not support * child Containers */ @Override public void addChild(Container child) { if (Globals.IS_SECURITY_ENABLED) { PrivilegedAction<Void> dp = new PrivilegedAddChild(child); AccessController.doPrivileged(dp); } else { addChildInternal(child); } } private void addChildInternal(Container child) { if( log.isDebugEnabled() ) log.debug("Add child " + child + " " + this); synchronized(children) { if (children.get(child.getName()) != null) throw new IllegalArgumentException("addChild: Child name '" + child.getName() + "' is not unique"); child.setParent(this); // May throw IAE children.put(child.getName(), child); } // Start child // Don't do this inside sync block - start can be a slow process and // locking the children object can cause problems elsewhere try { if ((getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) && startChildren) { child.start(); } } catch (LifecycleException e) { log.error("ContainerBase.addChild: start: ", e); throw new IllegalStateException("ContainerBase.addChild: start: " + e); } finally { fireContainerEvent(ADD_CHILD_EVENT, child); } } /** * Add a container event listener to this component. * * @param listener The listener to add */ @Override public void addContainerListener(ContainerListener listener) { listeners.add(listener); } /** * Add a property change listener to this component. * * @param listener The listener to add */ @Override public void addPropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } /** * Return the child Container, associated with this Container, with * the specified name (if any); otherwise, return <code>null</code> * * @param name Name of the child Container to be retrieved */ @Override public Container findChild(String name) { if (name == null) { return null; } synchronized (children) { return children.get(name); } } /** * Return the set of children Containers associated with this Container. * If this Container has no children, a zero-length array is returned. */ @Override public Container[] findChildren() { synchronized (children) { Container results[] = new Container[children.size()]; return children.values().toArray(results); } } /** * Return the set of container listeners associated with this Container. * If this Container has no registered container listeners, a zero-length * array is returned. */ @Override public ContainerListener[] findContainerListeners() { ContainerListener[] results = new ContainerListener[0]; return listeners.toArray(results); } /** * Remove an existing child Container from association with this parent * Container. * * @param child Existing child Container to be removed */ @Override public void removeChild(Container child) { if (child == null) { return; } try { if (child.getState().isAvailable()) { child.stop(); } } catch (LifecycleException e) { log.error("ContainerBase.removeChild: stop: ", e); } try { // child.destroy() may have already been called which would have // triggered this call. If that is the case, no need to destroy the // child again. if (!LifecycleState.DESTROYING.equals(child.getState())) { child.destroy(); } } catch (LifecycleException e) { log.error("ContainerBase.removeChild: destroy: ", e); } synchronized(children) { if (children.get(child.getName()) == null) return; children.remove(child.getName()); } fireContainerEvent(REMOVE_CHILD_EVENT, child); } /** * Remove a container event listener from this component. * * @param listener The listener to remove */ @Override public void removeContainerListener(ContainerListener listener) { listeners.remove(listener); } /** * Remove a property change listener from this component. * * @param listener The listener to remove */ @Override public void removePropertyChangeListener(PropertyChangeListener listener) { support.removePropertyChangeListener(listener); } @Override protected void initInternal() throws LifecycleException { reconfigureStartStopExecutor(getStartStopThreadsInternal()); super.initInternal(); } /* * Implementation note: If there is a demand for more control than this then * it is likely that the best solution will be to reference an external * executor. */ private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { if (!(startStopExecutor instanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else { if (startStopExecutor instanceof ThreadPoolExecutor) { ((ThreadPoolExecutor) startStopExecutor).setMaximumPoolSize(threads); ((ThreadPoolExecutor) startStopExecutor).setCorePoolSize(threads); } else { BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>(); ThreadPoolExecutor tpe = new ThreadPoolExecutor(threads, threads, 10, TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-")); tpe.allowCoreThreadTimeOut(true); startStopExecutor = tpe; } } } /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any logger = null; getLogger(); Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).start(); } Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } boolean fail = false; for (Future<Void> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); fail = true; } } if (fail) { throw new LifecycleException( sm.getString("containerBase.threadedStartFailed")); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); setState(LifecycleState.STARTING); // Start our thread threadStart(); } /** * Stop this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void stopInternal() throws LifecycleException { // Stop our thread threadStop(); setState(LifecycleState.STOPPING); // Stop the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle && ((Lifecycle) pipeline).getState().isAvailable()) { ((Lifecycle) pipeline).stop(); } // Stop our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StopChild(children[i]))); } boolean fail = false; for (Future<Void> result : results) { try { result.get(); } catch (Exception e) { log.error(sm.getString("containerBase.threadedStopFailed"), e); fail = true; } } if (fail) { throw new LifecycleException( sm.getString("containerBase.threadedStopFailed")); } // Stop our subordinate components, if any Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).stop(); } Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).stop(); } } @Override protected void destroyInternal() throws LifecycleException { Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).destroy(); } Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).destroy(); } // Stop the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).destroy(); } // Remove children now this container is being destroyed for (Container child : findChildren()) { removeChild(child); } // Required if the child is destroyed directly. if (parent != null) { parent.removeChild(this); } // If init fails, this may be null if (startStopExecutor != null) { startStopExecutor.shutdownNow(); } super.destroyInternal(); } /** * Check this container for an access log and if none is found, look to the * parent. If there is no parent and still none is found, use the NoOp * access log. */ @Override public void logAccess(Request request, Response response, long time, boolean useDefault) { boolean logged = false; if (getAccessLog() != null) { getAccessLog().log(request, response, time); logged = true; } if (getParent() != null) { // No need to use default logger once request/response has been logged // once getParent().logAccess(request, response, time, (useDefault && !logged)); } } @Override public AccessLog getAccessLog() { if (accessLogScanComplete) { return accessLog; } AccessLogAdapter adapter = null; Valve valves[] = getPipeline().getValves(); for (Valve valve : valves) { if (valve instanceof AccessLog) { if (adapter == null) { adapter = new AccessLogAdapter((AccessLog) valve); } else { adapter.add((AccessLog) valve); } } } if (adapter != null) { accessLog = adapter; } accessLogScanComplete = true; return accessLog; } // ------------------------------------------------------- Pipeline Methods /** * Convenience method, intended for use by the digester to simplify the * process of adding Valves to containers. See * {@link Pipeline#addValve(Valve)} for full details. Components other than * the digester should use {@link #getPipeline()}.{@link #addValve(Valve)} in case a * future implementation provides an alternative method for the digester to * use. * * @param valve Valve to be added * * @exception IllegalArgumentException if this Container refused to * accept the specified Valve * @exception IllegalArgumentException if the specified Valve refuses to be * associated with this Container * @exception IllegalStateException if the specified Valve is already * associated with a different Container */ public synchronized void addValve(Valve valve) { pipeline.addValve(valve); } /** * Execute a periodic task, such as reloading, etc. This method will be * invoked inside the classloading context of this container. Unexpected * throwables will be caught and logged. */ @Override public void backgroundProcess() { if (!getState().isAvailable()) return; Cluster cluster = getClusterInternal(); if (cluster != null) { try { cluster.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e); } } Realm realm = getRealmInternal(); if (realm != null) { try { realm.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e); } } Valve current = pipeline.getFirst(); while (current != null) { try { current.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e); } current = current.getNext(); } fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); } @Override public File getCatalinaBase() { if (parent == null) { return null; } return parent.getCatalinaBase(); } @Override public File getCatalinaHome() { if (parent == null) { return null; } return parent.getCatalinaHome(); } // ------------------------------------------------------ Protected Methods /** * Notify all container event listeners that a particular event has * occurred for this Container. The default implementation performs * this notification synchronously using the calling thread. * * @param type Event type * @param data Event data */ @Override public void fireContainerEvent(String type, Object data) { if (listeners.size() < 1) return; ContainerEvent event = new ContainerEvent(this, type, data); // Note for each uses an iterator internally so this is safe for (ContainerListener listener : listeners) { listener.containerEvent(event); } } // -------------------- JMX and Registration -------------------- @Override protected String getDomainInternal() { Container p = this.getParent(); if (p == null) { return null; } else { return p.getDomain(); } } @Override public String getMBeanKeyProperties() { Container c = this; StringBuilder keyProperties = new StringBuilder(); int containerCount = 0; // Work up container hierarchy, add a component to the name for // each container while (!(c instanceof Engine)) { if (c instanceof Wrapper) { keyProperties.insert(0, ",servlet="); keyProperties.insert(9, c.getName()); } else if (c instanceof Context) { keyProperties.insert(0, ",context="); ContextName cn = new ContextName(c.getName(), false); keyProperties.insert(9,cn.getDisplayName()); } else if (c instanceof Host) { keyProperties.insert(0, ",host="); keyProperties.insert(6, c.getName()); } else if (c == null) { // May happen in unit testing and/or some embedding scenarios keyProperties.append(",container"); keyProperties.append(containerCount++); keyProperties.append("=null"); break; } else { // Should never happen... keyProperties.append(",container"); keyProperties.append(containerCount++); keyProperties.append('='); keyProperties.append(c.getName()); } c = c.getParent(); } return keyProperties.toString(); } public ObjectName[] getChildren() { List<ObjectName> names = new ArrayList<>(children.size()); for (Container next : children.values()) { if (next instanceof ContainerBase) { names.add(next.getObjectName()); } } return names.toArray(new ObjectName[names.size()]); } // -------------------- Background Thread -------------------- /** * Start the background thread that will periodically check for * session timeouts. */ protected void threadStart() { if (thread != null) return; if (backgroundProcessorDelay <= 0) return; threadDone = false; String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start(); } /** * Stop the background thread that is periodically checking for * session timeouts. */ protected void threadStop() { if (thread == null) return; threadDone = true; thread.interrupt(); try { thread.join(); } catch (InterruptedException e) { // Ignore } thread = null; } @Override public final String toString() { StringBuilder sb = new StringBuilder(); Container parent = getParent(); if (parent != null) { sb.append(parent.toString()); sb.append('.'); } sb.append(this.getClass().getSimpleName()); sb.append('['); sb.append(getName()); sb.append(']'); return sb.toString(); } // -------------------------------------- ContainerExecuteDelay Inner Class /** * Private thread class to invoke the backgroundProcess method * of this container and its children after a fixed delay. */ protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { Throwable t = null; String unexpectedDeathMessage = sm.getString( "containerBase.backgroundProcess.unexpectedThreadDeath", Thread.currentThread().getName()); try { while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { // Ignore } if (!threadDone) { processChildren(ContainerBase.this); } } } catch (RuntimeException|Error e) { t = e; throw e; } finally { if (!threadDone) { log.error(unexpectedDeathMessage, t); } } } protected void processChildren(Container container) { ClassLoader originalClassLoader = null; try { if (container instanceof Context) { Loader loader = ((Context) container).getLoader(); // Loader will be null for FailedContext instances if (loader == null) { return; } // Ensure background processing for Contexts and Wrappers // is performed under the web app's class loader originalClassLoader = ((Context) container).bind(false, null); } container.backgroundProcess(); Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i]); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error("Exception invoking periodic operation: ", t); } finally { if (container instanceof Context) { ((Context) container).unbind(false, originalClassLoader); } } } } // ----------------------------- Inner classes used with start/stop Executor private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { child.start(); return null; } } private static class StopChild implements Callable<Void> { private Container child; public StopChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { if (child.getState().isAvailable()) { child.stop(); } return null; } } private static class StartStopThreadFactory implements ThreadFactory { private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public StartStopThreadFactory(String namePrefix) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); this.namePrefix = namePrefix; } @Override public Thread newThread(Runnable r) { Thread thread = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()); thread.setDaemon(true); return thread; } } }