All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.core.ContainerBase Maven / Gradle / Ivy

There is a newer version: 11.0.0-M24
Show newest version
/*
 * 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
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.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.Server;
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.MultiThrowable;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.threads.InlineExecutorService;


/**
 * Abstract implementation of the Container interface, providing common functionality required by nearly every
 * implementation. Classes extending this base class must 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(): *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ContainerEvents fired by this implementation
TypeDataDescription
addChildContainerChild container added to this Container.
{@link #getPipeline() pipeline}.addValveValveValve added to this Container.
removeChildContainerChild container removed from this Container.
{@link #getPipeline() pipeline}.removeValveValveValve removed from this Container.
startnullContainer was started.
stopnullContainer was stopped.
* 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); // ----------------------------------------------------- Instance Variables /** * The child Containers belonging to this Container, keyed by name. */ protected final HashMap children = new HashMap<>(); private final ReadWriteLock childrenLock = new ReentrantReadWriteLock(); /** * The processor delay for this component. */ protected int backgroundProcessorDelay = -1; /** * The future allowing control of the background processor. */ protected ScheduledFuture backgroundProcessorFuture; protected ScheduledFuture monitorFuture; /** * 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 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(ContainerBase.class); /** * 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 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; } @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(getStartStopThreads()); } } @Override public int getBackgroundProcessorDelay() { return backgroundProcessorDelay; } @Override public void setBackgroundProcessorDelay(int delay) { backgroundProcessorDelay = delay; } @Override public Log getLogger() { if (logger != null) { return logger; } logger = LogFactory.getLog(getLogName()); return logger; } @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; } @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(); } } @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; // Start the new component if necessary if (cluster != null) { cluster.setContainer(this); } } finally { writeLock.unlock(); } // Stop the old component if necessary if (getState().isAvailable() && (oldCluster instanceof Lifecycle)) { try { ((Lifecycle) oldCluster).stop(); } catch (LifecycleException e) { log.error(sm.getString("containerBase.cluster.stop"), e); } } if (getState().isAvailable() && (cluster instanceof Lifecycle)) { try { ((Lifecycle) cluster).start(); } catch (LifecycleException e) { log.error(sm.getString("containerBase.cluster.start"), e); } } // Report this property change to interested listeners support.firePropertyChange("cluster", oldCluster, cluster); } @Override public String getName() { return name; } @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. * * @return true 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); } @Override public Container getParent() { return parent; } @Override public void setParent(Container container) { Container oldParent = this.parent; this.parent = container; support.firePropertyChange("parent", oldParent, this.parent); } @Override public ClassLoader getParentClassLoader() { if (parentClassLoader != null) { return parentClassLoader; } if (parent != null) { return parent.getParentClassLoader(); } return ClassLoader.getSystemClassLoader(); } @Override public void setParentClassLoader(ClassLoader parent) { ClassLoader oldParentClassLoader = this.parentClassLoader; this.parentClassLoader = parent; support.firePropertyChange("parentClassLoader", oldParentClassLoader, this.parentClassLoader); } @Override public Pipeline getPipeline() { return this.pipeline; } @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(); } } @Override public void setRealm(Realm realm) { Realm oldRealm = null; Lock l = realmLock.writeLock(); l.lock(); try { // Change components if necessary oldRealm = this.realm; if (oldRealm == realm) { return; } this.realm = realm; // Start the new component if necessary if (realm != null) { realm.setContainer(this); } } finally { l.unlock(); } // Stop the old component if necessary if (getState().isAvailable() && oldRealm instanceof Lifecycle) { try { ((Lifecycle) oldRealm).stop(); } catch (LifecycleException e) { log.error(sm.getString("containerBase.realm.stop"), e); } } if (getState().isAvailable() && realm instanceof Lifecycle) { try { ((Lifecycle) realm).start(); } catch (LifecycleException e) { log.error(sm.getString("containerBase.realm.start"), e); } } // Report this property change to interested listeners support.firePropertyChange("realm", oldRealm, this.realm); } // ------------------------------------------------------ Container Methods @Override public void addChild(Container child) { if (log.isDebugEnabled()) { log.debug(sm.getString("containerBase.child.add", child, this)); } childrenLock.writeLock().lock(); try { if (children.get(child.getName()) != null) { throw new IllegalArgumentException(sm.getString("containerBase.child.notUnique", child.getName())); } child.setParent(this); // May throw IAE children.put(child.getName(), child); } finally { childrenLock.writeLock().unlock(); } fireContainerEvent(ADD_CHILD_EVENT, 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) { throw new IllegalStateException(sm.getString("containerBase.child.start"), e); } } @Override public void addContainerListener(ContainerListener listener) { listeners.add(listener); } @Override public void addPropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } @Override public Container findChild(String name) { if (name == null) { return null; } childrenLock.readLock().lock(); try { return children.get(name); } finally { childrenLock.readLock().unlock(); } } @Override public Container[] findChildren() { childrenLock.readLock().lock(); try { return children.values().toArray(new Container[0]); } finally { childrenLock.readLock().unlock(); } } @Override public ContainerListener[] findContainerListeners() { return listeners.toArray(new ContainerListener[0]); } @Override public void removeChild(Container child) { if (child == null) { return; } try { if (child.getState().isAvailable()) { child.stop(); } } catch (LifecycleException e) { log.error(sm.getString("containerBase.child.stop"), e); } boolean destroy = false; 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(); destroy = true; } } catch (LifecycleException e) { log.error(sm.getString("containerBase.child.destroy"), e); } if (!destroy) { fireContainerEvent(REMOVE_CHILD_EVENT, child); } childrenLock.writeLock().lock(); try { children.remove(child.getName()); } finally { childrenLock.writeLock().unlock(); } } @Override public void removeContainerListener(ContainerListener listener) { listeners.remove(listener); } @Override public void removePropertyChangeListener(PropertyChangeListener listener) { support.removePropertyChangeListener(listener); } private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { // Use a fake executor if (!(startStopExecutor instanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else { // Delegate utility execution to the Service Server server = Container.getService(this).getServer(); server.setUtilityThreads(threads); startStopExecutor = server.getUtilityExecutor(); } } /** * 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 void startInternal() throws LifecycleException { reconfigureStartStopExecutor(getStartStopThreads()); // 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> results = new ArrayList<>(children.length); for (Container child : children) { results.add(startStopExecutor.submit(new StartChild(child))); } 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 if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer().getUtilityExecutor() .scheduleWithFixedDelay(new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } } /** * 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 void stopInternal() throws LifecycleException { // Stop our thread if (monitorFuture != null) { monitorFuture.cancel(true); monitorFuture = null; } 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<>(children.length); for (Container child : children) { results.add(startStopExecutor.submit(new StopChild(child))); } 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 Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).stop(); } Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).stop(); } // If init fails, this may be null if (startStopExecutor != null) { startStopExecutor.shutdownNow(); startStopExecutor = null; } } @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); } super.destroyInternal(); } @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); } @Override public synchronized 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(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 @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 names; childrenLock.readLock().lock(); try { names = new ArrayList<>(children.size()); for (Container next : children.values()) { if (next instanceof ContainerBase) { names.add(next.getObjectName()); } } } finally { childrenLock.readLock().unlock(); } return names.toArray(new ObjectName[0]); } // -------------------- Background Thread -------------------- /** * Start the background thread that will periodically check for session timeouts. */ protected void threadStart() { if (backgroundProcessorDelay > 0 && (getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) && (backgroundProcessorFuture == null || backgroundProcessorFuture.isDone())) { if (backgroundProcessorFuture != null && backgroundProcessorFuture.isDone()) { // There was an error executing the scheduled task, get it and log it try { backgroundProcessorFuture.get(); } catch (InterruptedException | ExecutionException e) { log.error(sm.getString("containerBase.backgroundProcess.error"), e); } } backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor() .scheduleWithFixedDelay(new ContainerBackgroundProcessor(), backgroundProcessorDelay, backgroundProcessorDelay, TimeUnit.SECONDS); } } /** * Stop the background thread that is periodically checking for session timeouts. */ protected void threadStop() { if (backgroundProcessorFuture != null) { backgroundProcessorFuture.cancel(true); backgroundProcessorFuture = 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(); } // ------------------------------- ContainerBackgroundProcessor Inner Class protected class ContainerBackgroundProcessorMonitor implements Runnable { @Override public void run() { if (getState().isAvailable()) { threadStart(); } } } /** * Private runnable 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() { processChildren(ContainerBase.this); } 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(null); } container.backgroundProcess(); Container[] children = container.findChildren(); for (Container child : children) { if (child.getBackgroundProcessorDelay() <= 0) { processChildren(child); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("containerBase.backgroundProcess.error"), t); } finally { if (container instanceof Context) { ((Context) container).unbind(originalClassLoader); } } } } // ---------------------------- Inner classes used with start/stop Executor private static class StartChild implements Callable { private Container child; 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; StopChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { if (child.getState().isAvailable()) { child.stop(); } return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy