| /* |
| * 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.Iterator; |
| import java.util.List; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| 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; |
| |
| |
| /** |
| * 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. |
| * |
| * TODO: Review synchronisation around background processing. See bug 47024. |
| * |
| * @author Craig R. McClanahan |
| */ |
| public abstract class ContainerBase extends LifecycleMBeanBase |
| implements Container { |
| |
| private static final org.apache.juli.logging.Log log= |
| org.apache.juli.logging.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 ThreadPoolExecutor 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) { |
| this.startStopThreads = startStopThreads; |
| |
| // Use local copies to ensure thread safety |
| ThreadPoolExecutor executor = startStopExecutor; |
| if (executor != null) { |
| int newThreads = getStartStopThreadsInternal(); |
| executor.setMaximumPoolSize(newThreads); |
| executor.setCorePoolSize(newThreads); |
| } |
| } |
| |
| |
| /** |
| * 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(logName()); |
| return (logger); |
| |
| } |
| |
| |
| /** |
| * 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. |
| */ |
| 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 |
| if ((getState().isAvailable() || |
| LifecycleState.STARTING_PREP.equals(getState())) && |
| startChildren) { |
| try { |
| child.start(); |
| } catch (LifecycleException e) { |
| log.error("ContainerBase.addChild: start: ", e); |
| throw new IllegalStateException |
| ("ContainerBase.addChild: start: " + e); |
| } |
| } |
| |
| 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; |
| } |
| |
| synchronized(children) { |
| if (children.get(child.getName()) == null) |
| return; |
| children.remove(child.getName()); |
| } |
| |
| try { |
| if (child.getState().isAvailable()) { |
| child.stop(); |
| } |
| } catch (LifecycleException e) { |
| log.error("ContainerBase.removeChild: stop: ", e); |
| } |
| |
| fireContainerEvent(REMOVE_CHILD_EVENT, child); |
| |
| 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); |
| } |
| |
| } |
| |
| |
| /** |
| * 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 { |
| BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>(); |
| startStopExecutor = new ThreadPoolExecutor( |
| getStartStopThreadsInternal(), |
| getStartStopThreadsInternal(), 10, TimeUnit.SECONDS, |
| startStopQueue, |
| new StartStopThreadFactory(getName() + "-startStop-")); |
| startStopExecutor.allowCoreThreadTimeOut(true); |
| super.initInternal(); |
| } |
| |
| |
| /** |
| * 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 != null) && (cluster instanceof Lifecycle)) |
| ((Lifecycle) cluster).start(); |
| Realm realm = getRealmInternal(); |
| if ((realm != null) && (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 != null) && (realm instanceof Lifecycle)) { |
| ((Lifecycle) realm).stop(); |
| } |
| Cluster cluster = getClusterInternal(); |
| if ((cluster != null) && (cluster instanceof Lifecycle)) { |
| ((Lifecycle) cluster).stop(); |
| } |
| } |
| |
| @Override |
| protected void destroyInternal() throws LifecycleException { |
| |
| Realm realm = getRealmInternal(); |
| if ((realm != null) && (realm instanceof Lifecycle)) { |
| ((Lifecycle) realm).destroy(); |
| } |
| Cluster cluster = getClusterInternal(); |
| if ((cluster != null) && (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); |
| } |
| } |
| |
| |
| /** |
| * Return the abbreviated name of this container for logging messages |
| */ |
| protected String logName() { |
| |
| 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; |
| |
| } |
| |
| |
| // -------------------- 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()); |
| Iterator<Container> it = children.values().iterator(); |
| while (it.hasNext()) { |
| Object next = it.next(); |
| if (next instanceof ContainerBase) { |
| names.add(((ContainerBase)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; |
| |
| } |
| |
| |
| // -------------------------------------- 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; |
| } |
| } |
| } |