org.apache.catalina.core.ContainerBase Maven / Gradle / Ivy
/*
* 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.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
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 javax.naming.directory.DirContext;
import javax.servlet.ServletException;
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.Globals;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Loader;
import org.apache.catalina.Manager;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.mbeans.MBeanUtils;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.naming.resources.ProxyDirContext;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.MultiThrowable;
import org.apache.tomcat.util.res.StringManager;
/**
* Abstract implementation of the Container interface, providing common
* functionality required by nearly every implementation. Classes extending
* this base class must implement getInfo()
, and may implement
* a replacement for invoke()
.
*
* 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 invoke()
method of this class, utilizing the
* "Chain of Responsibility" design pattern. A subclass should encapsulate its
* own processing functionality as a Valve
, and configure this
* Valve into the pipeline by calling setBasic()
.
*
* This implementation fires property change events, per the JavaBeans design
* pattern, for changes in singleton properties. In addition, it fires the
* following ContainerEvent
events to listeners who register
* themselves with addContainerListener()
:
*
*
* Type
* Data
* Description
*
*
* addChild
* Container
* Child container added to this Container.
*
*
* {@link #getPipeline() pipeline}.addValve
* Valve
* Valve added to this Container.
*
*
* removeChild
* Container
* Child container removed from this Container.
*
*
* {@link #getPipeline() pipeline}.removeValve
* Valve
* Valve removed from this Container.
*
*
* start
* null
* Container was started.
*
*
* stop
* null
* Container was stopped.
*
*
* 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 {
private 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 HashMap 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 List listeners =
new CopyOnWriteArrayList();
/**
* The Loader implementation with which this Container is associated.
*/
protected Loader loader = null;
private final ReadWriteLock loaderLock = new ReentrantReadWriteLock();
/**
* The Logger implementation with which this Container is associated.
*/
protected Log logger = null;
/**
* Associated logger name.
*/
protected String logName = null;
/**
* The Manager implementation with which this Container is associated.
*/
protected Manager manager = null;
private final ReadWriteLock managerLock = new ReentrantReadWriteLock();
/**
* 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 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 resources DirContext object with which this Container is associated.
*/
protected DirContext resources = null;
private final ReadWriteLock resourcesLock = 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 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 descriptive information about this Container implementation and
* the corresponding version number, in the format
* <description>/<version>
.
*/
@Override
public String getInfo() {
return this.getClass().getName();
}
/**
* Return the Loader with which this Container is associated. If there is
* no associated Loader, return the Loader associated with our parent
* Container (if any); otherwise, return null
.
*/
@Override
public Loader getLoader() {
Lock readLock = loaderLock.readLock();
readLock.lock();
try {
if (loader != null)
return loader;
if (parent != null)
return parent.getLoader();
return null;
} finally {
readLock.unlock();
}
}
/*
* Provide access to just the loader component attached to this container.
*/
protected Loader getLoaderInternal() {
Lock readLock = loaderLock.readLock();
readLock.lock();
try {
return loader;
} finally {
readLock.unlock();
}
}
/**
* Set the Loader with which this Container is associated.
*
* @param loader The newly associated loader
*/
@Override
public void setLoader(Loader loader) {
Lock writeLock = loaderLock.writeLock();
writeLock.lock();
Loader oldLoader = null;
try {
// Change components if necessary
oldLoader = this.loader;
if (oldLoader == loader)
return;
this.loader = loader;
// Stop the old component if necessary
if (getState().isAvailable() && (oldLoader != null) &&
(oldLoader instanceof Lifecycle)) {
try {
((Lifecycle) oldLoader).stop();
} catch (LifecycleException e) {
log.error("ContainerBase.setLoader: stop: ", e);
}
}
// Start the new component if necessary
if (loader != null)
loader.setContainer(this);
if (getState().isAvailable() && (loader != null) &&
(loader instanceof Lifecycle)) {
try {
((Lifecycle) loader).start();
} catch (LifecycleException e) {
log.error("ContainerBase.setLoader: start: ", e);
}
}
} finally {
writeLock.unlock();
}
// Report this property change to interested listeners
support.firePropertyChange("loader", oldLoader, loader);
}
/**
* Return the Logger for this Container.
*/
@Override
public Log getLogger() {
if (logger != null)
return (logger);
logger = LogFactory.getLog(logName());
return (logger);
}
/**
* Return the Manager with which this Container is associated. If there is
* no associated Manager, return the Manager associated with our parent
* Container (if any); otherwise return null
.
*/
@Override
public Manager getManager() {
Lock readLock = managerLock.readLock();
readLock.lock();
try {
if (manager != null)
return manager;
if (parent != null)
return parent.getManager();
return null;
} finally {
readLock.unlock();
}
}
/*
* Provide access to just the manager component attached to this container.
*/
protected Manager getManagerInternal() {
Lock readLock = managerLock.readLock();
readLock.lock();
try {
return manager;
} finally {
readLock.unlock();
}
}
/**
* Set the Manager with which this Container is associated.
*
* @param manager The newly associated Manager
*/
@Override
public void setManager(Manager manager) {
Lock writeLock = managerLock.writeLock();
writeLock.lock();
Manager oldManager = null;
try {
// Change components if necessary
oldManager = this.manager;
if (oldManager == manager)
return;
this.manager = manager;
// Stop the old component if necessary
if (oldManager instanceof Lifecycle) {
try {
((Lifecycle) oldManager).stop();
((Lifecycle) oldManager).destroy();
} catch (LifecycleException e) {
log.error("ContainerBase.setManager: stop-destroy: ", e);
}
}
// Start the new component if necessary
if (manager != null)
manager.setContainer(this);
if (getState().isAvailable() && manager instanceof Lifecycle) {
try {
((Lifecycle) manager).start();
} catch (LifecycleException e) {
log.error("ContainerBase.setManager: start: ", e);
}
}
} finally {
writeLock.unlock();
}
// Report this property change to interested listeners
support.firePropertyChange("manager", oldManager, manager);
}
/**
* Return an object which may be utilized for mapping to this component.
*/
@Deprecated
@Override
public Object getMappingObject() {
return this;
}
/**
* 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 null
.
*/
@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) {
if (name == null) {
throw new IllegalArgumentException(sm.getString("containerBase.nullName"));
}
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 null
.
*/
@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 after 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 before 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 null
.
*/
@Override
public Realm getRealm() {
Lock l = realmLock.readLock();
try {
l.lock();
if (realm != null)
return (realm);
if (parent != null)
return (parent.getRealm());
return null;
} finally {
l.unlock();
}
}
protected Realm getRealmInternal() {
Lock l = realmLock.readLock();
try {
l.lock();
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();
try {
l.lock();
// 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();
}
}
/**
* Return the resources DirContext object with which this Container is
* associated. If there is no associated resources object, return the
* resources associated with our parent Container (if any); otherwise
* return null
.
*/
@Override
public DirContext getResources() {
Lock readLock = resourcesLock.readLock();
readLock.lock();
try {
if (resources != null)
return resources;
if (parent != null)
return parent.getResources();
return null;
} finally {
readLock.unlock();
}
}
/*
* Provide access to just the resources component attached to this container.
*/
protected DirContext getResourcesInternal() {
Lock readLock = resourcesLock.readLock();
readLock.lock();
try {
return resources;
} finally {
readLock.unlock();
}
}
/**
* Set the resources DirContext object with which this Container is
* associated.
*
* @param resources The newly associated DirContext
*/
@Override
public void setResources(DirContext resources) {
// Called from StandardContext.setResources()
// <- StandardContext.start()
// <- ContainerBase.addChildInternal()
Lock writeLock = resourcesLock.writeLock();
writeLock.lock();
DirContext oldResources = null;
try {
// Change components if necessary
oldResources = this.resources;
if (oldResources == resources)
return;
// null resources don't need to be wrapped. Neither do resources
// that are already wrapped.
if (resources == null || resources instanceof ProxyDirContext) {
this.resources = resources;
} else {
Hashtable env = new Hashtable();
if (getParent() != null)
env.put(ProxyDirContext.HOST, getParent().getName());
env.put(ProxyDirContext.CONTEXT, getName());
this.resources = new ProxyDirContext(env, resources);
}
} finally {
writeLock.unlock();
}
// Report this property change to interested listeners
support.firePropertyChange("resources", oldResources, resources);
}
// ------------------------------------------------------ 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 setParent()
method must be called, with this
* Container as an argument. This method may thrown an
* IllegalArgumentException
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 setParent()
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 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 null
*
* @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);
}
/**
* Process the specified Request, to produce the corresponding Response,
* by invoking the first Valve in our pipeline (if any), or the basic
* Valve otherwise.
*
* @param request Request to be processed
* @param response Response to be produced
*
* @exception IllegalStateException if neither a pipeline or a basic
* Valve have been configured for this Container
* @exception IOException if an input/output error occurred while
* processing
* @exception ServletException if a ServletException was thrown
* while processing this request
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.getFirst().invoke(request, response);
}
/**
* 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 {
BlockingQueue 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
Loader loader = getLoaderInternal();
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
logger = null;
getLogger();
Manager manager = getManagerInternal();
if ((manager != null) && (manager instanceof Lifecycle))
((Lifecycle) manager).start();
Cluster cluster = getClusterInternal();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
DirContext resources = getResourcesInternal();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Start our child containers, if any
Container children[] = findChildren();
List> results = new ArrayList>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
MultiThrowable multiThrowable = null;
for (Future result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// 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> results = new ArrayList>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StopChild(children[i])));
}
boolean fail = false;
for (Future 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
DirContext resources = getResourcesInternal();
if ((resources != null) && (resources instanceof Lifecycle)) {
((Lifecycle) resources).stop();
}
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle)) {
((Lifecycle) realm).stop();
}
Cluster cluster = getClusterInternal();
if ((cluster != null) && (cluster instanceof Lifecycle)) {
((Lifecycle) cluster).stop();
}
Manager manager = getManagerInternal();
if ((manager != null) && (manager instanceof Lifecycle) &&
((Lifecycle) manager).getState().isAvailable() ) {
((Lifecycle) manager).stop();
}
Loader loader = getLoaderInternal();
if ((loader != null) && (loader instanceof Lifecycle)) {
((Lifecycle) loader).stop();
}
}
@Override
protected void destroyInternal() throws LifecycleException {
Manager manager = getManagerInternal();
if ((manager != null) && (manager instanceof Lifecycle)) {
((Lifecycle) manager).destroy();
}
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle)) {
((Lifecycle) realm).destroy();
}
Cluster cluster = getClusterInternal();
if ((cluster != null) && (cluster instanceof Lifecycle)) {
((Lifecycle) cluster).destroy();
}
Loader loader = getLoaderInternal();
if ((loader != null) && (loader instanceof Lifecycle)) {
((Lifecycle) loader).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);
}
}
Loader loader = getLoaderInternal();
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
Manager manager = getManagerInternal();
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), 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);
}
// ------------------------------------------------------ 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() {
return MBeanUtils.getDomain(this);
}
public ObjectName[] getChildren() {
List names = new ArrayList(children.size());
Iterator 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) {
Container parent = (Container) getMappingObject();
ClassLoader cl =
Thread.currentThread().getContextClassLoader();
if (parent.getLoader() != null) {
cl = parent.getLoader().getClassLoader();
}
processChildren(parent, cl);
}
}
} catch (RuntimeException e) {
t = e;
throw e;
} catch (Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
container.backgroundProcess();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i], cl);
}
}
}
}
// ----------------------------- Inner classes used with start/stop Executor
private static class StartChild implements Callable {
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 {
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;
}
}
}