blob: 71bfa2848b1be01212829231a1160c5f5cd17269 [file] [log] [blame]
/*
* 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.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;
import javax.servlet.annotation.MultipartConfig;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerServlet;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.InstanceEvent;
import org.apache.catalina.InstanceListener;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Wrapper;
import org.apache.catalina.security.SecurityUtil;
import org.apache.catalina.util.InstanceSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.PeriodicEventListener;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.log.SystemLogHandler;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.modeler.Util;
/**
* Standard implementation of the <b>Wrapper</b> interface that represents
* an individual servlet definition. No child Containers are allowed, and
* the parent Container must be a Context.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
@SuppressWarnings("deprecation") // SingleThreadModel
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
private final Log log = LogFactory.getLog(StandardWrapper.class); // must not be static
protected static final String[] DEFAULT_SERVLET_METHODS = new String[] {
"GET", "HEAD", "POST" };
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardWrapper component with the default basic Valve.
*/
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
// ----------------------------------------------------- Instance Variables
/**
* The date and time at which this servlet will become available (in
* milliseconds since the epoch), or zero if the servlet is available.
* If this value equals Long.MAX_VALUE, the unavailability of this
* servlet is considered permanent.
*/
protected long available = 0L;
/**
* The broadcaster that sends j2ee notifications.
*/
protected final NotificationBroadcasterSupport broadcaster;
/**
* The count of allocations that are currently active (even if they
* are for the same instance, as will be true on a non-STM servlet).
*/
protected final AtomicInteger countAllocated = new AtomicInteger(0);
/**
* The facade associated with this wrapper.
*/
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
/**
* The (single) possibly uninitialized instance of this servlet.
*/
protected volatile Servlet instance = null;
/**
* Flag that indicates if this instance has been initialized
*/
protected volatile boolean instanceInitialized = false;
/**
* The support object for our instance listeners.
*
* @deprecated Will be removed in 8.5.x onwards
*/
@Deprecated
protected final InstanceSupport instanceSupport = new InstanceSupport(this);
/**
* The load-on-startup order value (negative value means load on
* first call) for this servlet.
*/
protected int loadOnStartup = -1;
/**
* Mappings associated with the wrapper.
*/
protected final ArrayList<String> mappings = new ArrayList<>();
/**
* The initialization parameters for this servlet, keyed by
* parameter name.
*/
protected HashMap<String, String> parameters = new HashMap<>();
/**
* The security role references for this servlet, keyed by role name
* used in the servlet. The corresponding value is the role name of
* the web application itself.
*/
protected HashMap<String, String> references = new HashMap<>();
/**
* The run-as identity for this servlet.
*/
protected String runAs = null;
/**
* The notification sequence number.
*/
protected long sequenceNumber = 0;
/**
* The fully qualified servlet class name for this servlet.
*/
protected String servletClass = null;
/**
* Does this servlet implement the SingleThreadModel interface?
*/
protected volatile boolean singleThreadModel = false;
/**
* Are we unloading our servlet instance at the moment?
*/
protected volatile boolean unloading = false;
/**
* Maximum number of STM instances.
*/
protected int maxInstances = 20;
/**
* Number of instances currently loaded for a STM servlet.
*/
protected int nInstances = 0;
/**
* Stack containing the STM instances.
*/
protected Stack<Servlet> instancePool = null;
/**
* Wait time for servlet unload in ms.
*/
protected long unloadDelay = 2000;
/**
* True if this StandardWrapper is for the JspServlet
*/
protected boolean isJspServlet;
/**
* The ObjectName of the JSP monitoring mbean
*/
protected ObjectName jspMonitorON;
/**
* Should we swallow System.out
*/
protected boolean swallowOutput = false;
// To support jmx attributes
protected StandardWrapperValve swValve;
protected long loadTime=0;
protected int classLoadTime=0;
/**
* Multipart config
*/
protected MultipartConfigElement multipartConfigElement = null;
/**
* Async support
*/
protected boolean asyncSupported = false;
/**
* Enabled
*/
protected boolean enabled = true;
private boolean overridable = false;
/**
* Static class array used when the SecurityManager is turned on and
* <code>Servlet.init</code> is invoked.
*/
protected static Class<?>[] classType = new Class[]{ServletConfig.class};
private final ReentrantReadWriteLock parametersLock =
new ReentrantReadWriteLock();
private final ReentrantReadWriteLock mappingsLock =
new ReentrantReadWriteLock();
private final ReentrantReadWriteLock referencesLock =
new ReentrantReadWriteLock();
// ------------------------------------------------------------- Properties
@Override
public boolean isOverridable() {
return overridable;
}
@Override
public void setOverridable(boolean overridable) {
this.overridable = overridable;
}
/**
* Return the available date/time for this servlet, in milliseconds since
* the epoch. If this date/time is Long.MAX_VALUE, it is considered to mean
* that unavailability is permanent and any request for this servlet will return
* an SC_NOT_FOUND error. If this date/time is in the future, any request for
* this servlet will return an SC_SERVICE_UNAVAILABLE error. If it is zero,
* the servlet is currently available.
*/
@Override
public long getAvailable() {
return (this.available);
}
/**
* Set the available date/time for this servlet, in milliseconds since the
* epoch. If this date/time is Long.MAX_VALUE, it is considered to mean
* that unavailability is permanent and any request for this servlet will return
* an SC_NOT_FOUND error. If this date/time is in the future, any request for
* this servlet will return an SC_SERVICE_UNAVAILABLE error.
*
* @param available The new available date/time
*/
@Override
public void setAvailable(long available) {
long oldAvailable = this.available;
if (available > System.currentTimeMillis())
this.available = available;
else
this.available = 0L;
support.firePropertyChange("available", Long.valueOf(oldAvailable),
Long.valueOf(this.available));
}
/**
* Return the number of active allocations of this servlet, even if they
* are all for the same instance (as will be true for servlets that do
* not implement <code>SingleThreadModel</code>.
*/
public int getCountAllocated() {
return (this.countAllocated.get());
}
/**
* Return the InstanceSupport object for this Wrapper instance.
*
* @deprecated Will be removed in 8.5.x onwards
*/
@Deprecated
public InstanceSupport getInstanceSupport() {
return (this.instanceSupport);
}
/**
* Return the load-on-startup order value (negative value means
* load on first call).
*/
@Override
public int getLoadOnStartup() {
if (isJspServlet && loadOnStartup < 0) {
/*
* JspServlet must always be preloaded, because its instance is
* used during registerJMX (when registering the JSP
* monitoring mbean)
*/
return Integer.MAX_VALUE;
} else {
return (this.loadOnStartup);
}
}
/**
* Set the load-on-startup order value (negative value means
* load on first call).
*
* @param value New load-on-startup value
*/
@Override
public void setLoadOnStartup(int value) {
int oldLoadOnStartup = this.loadOnStartup;
this.loadOnStartup = value;
support.firePropertyChange("loadOnStartup",
Integer.valueOf(oldLoadOnStartup),
Integer.valueOf(this.loadOnStartup));
}
/**
* Set the load-on-startup order value from a (possibly null) string.
* Per the specification, any missing or non-numeric value is converted
* to a zero, so that this servlet will still be loaded at startup
* time, but in an arbitrary order.
*
* @param value New load-on-startup value
*/
public void setLoadOnStartupString(String value) {
try {
setLoadOnStartup(Integer.parseInt(value));
} catch (NumberFormatException e) {
setLoadOnStartup(0);
}
}
public String getLoadOnStartupString() {
return Integer.toString( getLoadOnStartup());
}
/**
* Return maximum number of instances that will be allocated when a single
* thread model servlet is used.
*/
public int getMaxInstances() {
return (this.maxInstances);
}
/**
* Set the maximum number of instances that will be allocated when a single
* thread model servlet is used.
*
* @param maxInstances New value of maxInstances
*/
public void setMaxInstances(int maxInstances) {
int oldMaxInstances = this.maxInstances;
this.maxInstances = maxInstances;
support.firePropertyChange("maxInstances", oldMaxInstances,
this.maxInstances);
}
/**
* Set the parent Container of this Wrapper, but only if it is a Context.
*
* @param container Proposed parent Container
*/
@Override
public void setParent(Container container) {
if ((container != null) &&
!(container instanceof Context))
throw new IllegalArgumentException
(sm.getString("standardWrapper.notContext"));
if (container instanceof StandardContext) {
swallowOutput = ((StandardContext)container).getSwallowOutput();
unloadDelay = ((StandardContext)container).getUnloadDelay();
}
super.setParent(container);
}
/**
* Return the run-as identity for this servlet.
*/
@Override
public String getRunAs() {
return (this.runAs);
}
/**
* Set the run-as identity for this servlet.
*
* @param runAs New run-as identity value
*/
@Override
public void setRunAs(String runAs) {
String oldRunAs = this.runAs;
this.runAs = runAs;
support.firePropertyChange("runAs", oldRunAs, this.runAs);
}
/**
* Return the fully qualified servlet class name for this servlet.
*/
@Override
public String getServletClass() {
return (this.servletClass);
}
/**
* Set the fully qualified servlet class name for this servlet.
*
* @param servletClass Servlet class name
*/
@Override
public void setServletClass(String servletClass) {
String oldServletClass = this.servletClass;
this.servletClass = servletClass;
support.firePropertyChange("servletClass", oldServletClass,
this.servletClass);
if (Constants.JSP_SERVLET_CLASS.equals(servletClass)) {
isJspServlet = true;
}
}
/**
* Set the name of this servlet. This is an alias for the normal
* <code>Container.setName()</code> method, and complements the
* <code>getServletName()</code> method required by the
* <code>ServletConfig</code> interface.
*
* @param name The new name of this servlet
*/
public void setServletName(String name) {
setName(name);
}
/**
* Does the servlet class represented by this component implement the
* <code>SingleThreadModel</code> interface? This can only be determined
* once the class is loaded. Calling this method will not trigger loading
* the class since that may cause the application to behave unexpectedly.
*
* @return {@code null} if the class has not been loaded, otherwise {@code
* true} if the servlet does implement {@code SingleThreadModel} and
* {@code false} if it does not.
*/
public Boolean isSingleThreadModel() {
// If the servlet has been loaded either singleThreadModel will be true
// or instance will be non-null
if (singleThreadModel || instance != null) {
return Boolean.valueOf(singleThreadModel);
}
return null;
}
/**
* Is this servlet currently unavailable?
*/
@Override
public boolean isUnavailable() {
if (!isEnabled())
return true;
else if (available == 0L)
return false;
else if (available <= System.currentTimeMillis()) {
available = 0L;
return false;
} else
return true;
}
@Override
public String[] getServletMethods() throws ServletException {
instance = loadServlet();
Class<? extends Servlet> servletClazz = instance.getClass();
if (!javax.servlet.http.HttpServlet.class.isAssignableFrom(
servletClazz)) {
return DEFAULT_SERVLET_METHODS;
}
HashSet<String> allow = new HashSet<>();
allow.add("TRACE");
allow.add("OPTIONS");
Method[] methods = getAllDeclaredMethods(servletClazz);
for (int i=0; methods != null && i<methods.length; i++) {
Method m = methods[i];
if (m.getName().equals("doGet")) {
allow.add("GET");
allow.add("HEAD");
} else if (m.getName().equals("doPost")) {
allow.add("POST");
} else if (m.getName().equals("doPut")) {
allow.add("PUT");
} else if (m.getName().equals("doDelete")) {
allow.add("DELETE");
}
}
String[] methodNames = new String[allow.size()];
return allow.toArray(methodNames);
}
/**
* Return the associated servlet instance.
*/
@Override
public Servlet getServlet() {
return instance;
}
/**
* Set the associated servlet instance.
*/
@Override
public void setServlet(Servlet servlet) {
instance = servlet;
}
/**
* {@inheritDoc}
*/
@Override
public void setServletSecurityAnnotationScanRequired(boolean b) {
// NO-OP
}
// --------------------------------------------------------- Public Methods
/**
* 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() {
super.backgroundProcess();
if (!getState().isAvailable())
return;
if (getServlet() != null && (getServlet() instanceof PeriodicEventListener)) {
((PeriodicEventListener) getServlet()).periodicEvent();
}
}
/**
* Extract the root cause from a servlet exception.
*
* @param e The servlet exception
*/
public static Throwable getRootCause(ServletException e) {
Throwable rootCause = e;
Throwable rootCauseCheck = null;
// Extra aggressive rootCause finding
int loops = 0;
do {
loops++;
rootCauseCheck = rootCause.getCause();
if (rootCauseCheck != null)
rootCause = rootCauseCheck;
} while (rootCauseCheck != null && (loops < 20));
return rootCause;
}
/**
* Refuse to add a child Container, because Wrappers are the lowest level
* of the Container hierarchy.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
throw new IllegalStateException
(sm.getString("standardWrapper.notChild"));
}
/**
* Add a new servlet initialization parameter for this servlet.
*
* @param name Name of this initialization parameter to add
* @param value Value of this initialization parameter to add
*/
@Override
public void addInitParameter(String name, String value) {
parametersLock.writeLock().lock();
try {
parameters.put(name, value);
} finally {
parametersLock.writeLock().unlock();
}
fireContainerEvent("addInitParameter", name);
}
/**
* Add a new listener interested in InstanceEvents.
*
* @param listener The new listener
*
* @deprecated Will be removed in 8.5.x onwards
*/
@Deprecated
@Override
public void addInstanceListener(InstanceListener listener) {
instanceSupport.addInstanceListener(listener);
}
/**
* Add a mapping associated with the Wrapper.
*
* @param mapping The new wrapper mapping
*/
@Override
public void addMapping(String mapping) {
mappingsLock.writeLock().lock();
try {
mappings.add(mapping);
} finally {
mappingsLock.writeLock().unlock();
}
if(parent.getState().equals(LifecycleState.STARTED))
fireContainerEvent(ADD_MAPPING_EVENT, mapping);
}
/**
* Add a new security role reference record to the set of records for
* this servlet.
*
* @param name Role name used within this servlet
* @param link Role name used within the web application
*/
@Override
public void addSecurityReference(String name, String link) {
referencesLock.writeLock().lock();
try {
references.put(name, link);
} finally {
referencesLock.writeLock().unlock();
}
fireContainerEvent("addSecurityReference", name);
}
/**
* Allocate an initialized instance of this Servlet that is ready to have
* its <code>service()</code> method called. If the servlet class does
* not implement <code>SingleThreadModel</code>, the (only) initialized
* instance may be returned immediately. If the servlet class implements
* <code>SingleThreadModel</code>, the Wrapper implementation must ensure
* that this instance is not allocated again until it is deallocated by a
* call to <code>deallocate()</code>.
*
* @exception ServletException if the servlet init() method threw
* an exception
* @exception ServletException if a loading error occurs
*/
@Override
public Servlet allocate() throws ServletException {
// If we are currently unloading this servlet, throw an exception
if (unloading) {
throw new ServletException(sm.getString("standardWrapper.unloading", getName()));
}
boolean newInstance = false;
// If not SingleThreadedModel, return the same instance every time
if (!singleThreadModel) {
// Load and initialize our instance if necessary
if (instance == null || !instanceInitialized) {
synchronized (this) {
if (instance == null) {
try {
if (log.isDebugEnabled()) {
log.debug("Allocating non-STM instance");
}
// Note: We don't know if the Servlet implements
// SingleThreadModel until we have loaded it.
instance = loadServlet();
newInstance = true;
if (!singleThreadModel) {
// For non-STM, increment here to prevent a race
// condition with unload. Bug 43683, test case
// #3
countAllocated.incrementAndGet();
}
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("standardWrapper.allocate"), e);
}
}
if (!instanceInitialized) {
initServlet(instance);
}
}
}
if (singleThreadModel) {
if (newInstance) {
// Have to do this outside of the sync above to prevent a
// possible deadlock
synchronized (instancePool) {
instancePool.push(instance);
nInstances++;
}
}
} else {
if (log.isTraceEnabled()) {
log.trace(" Returning non-STM instance");
}
// For new instances, count will have been incremented at the
// time of creation
if (!newInstance) {
countAllocated.incrementAndGet();
}
return instance;
}
}
synchronized (instancePool) {
while (countAllocated.get() >= nInstances) {
// Allocate a new instance if possible, or else wait
if (nInstances < maxInstances) {
try {
instancePool.push(loadServlet());
nInstances++;
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("standardWrapper.allocate"), e);
}
} else {
try {
instancePool.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
if (log.isTraceEnabled()) {
log.trace(" Returning allocated STM instance");
}
countAllocated.incrementAndGet();
return instancePool.pop();
}
}
/**
* Return this previously allocated servlet to the pool of available
* instances. If this servlet class does not implement SingleThreadModel,
* no action is actually required.
*
* @param servlet The servlet to be returned
*
* @exception ServletException if a deallocation error occurs
*/
@Override
public void deallocate(Servlet servlet) throws ServletException {
// If not SingleThreadModel, no action is required
if (!singleThreadModel) {
countAllocated.decrementAndGet();
return;
}
// Unlock and free this instance
synchronized (instancePool) {
countAllocated.decrementAndGet();
instancePool.push(servlet);
instancePool.notify();
}
}
/**
* Return the value for the specified initialization parameter name,
* if any; otherwise return <code>null</code>.
*
* @param name Name of the requested initialization parameter
*/
@Override
public String findInitParameter(String name) {
parametersLock.readLock().lock();
try {
return parameters.get(name);
} finally {
parametersLock.readLock().unlock();
}
}
/**
* Return the names of all defined initialization parameters for this
* servlet.
*/
@Override
public String[] findInitParameters() {
parametersLock.readLock().lock();
try {
String results[] = new String[parameters.size()];
return parameters.keySet().toArray(results);
} finally {
parametersLock.readLock().unlock();
}
}
/**
* Return the mappings associated with this wrapper.
*/
@Override
public String[] findMappings() {
mappingsLock.readLock().lock();
try {
return mappings.toArray(new String[mappings.size()]);
} finally {
mappingsLock.readLock().unlock();
}
}
/**
* Return the security role link for the specified security role
* reference name, if any; otherwise return <code>null</code>.
*
* @param name Security role reference used within this servlet
*/
@Override
public String findSecurityReference(String name) {
referencesLock.readLock().lock();
try {
return references.get(name);
} finally {
referencesLock.readLock().unlock();
}
}
/**
* Return the set of security role reference names associated with
* this servlet, if any; otherwise return a zero-length array.
*/
@Override
public String[] findSecurityReferences() {
referencesLock.readLock().lock();
try {
String results[] = new String[references.size()];
return references.keySet().toArray(results);
} finally {
referencesLock.readLock().unlock();
}
}
/**
* Load and initialize an instance of this servlet, if there is not already
* at least one initialized instance. This can be used, for example, to
* load servlets that are marked in the deployment descriptor to be loaded
* at server startup time.
* <p>
* <b>IMPLEMENTATION NOTE</b>: Servlets whose classnames begin with
* <code>org.apache.catalina.</code> (so-called "container" servlets)
* are loaded by the same classloader that loaded this class, rather than
* the classloader for the current web application.
* This gives such classes access to Catalina internals, which are
* prevented for classes loaded for web applications.
*
* @exception ServletException if the servlet init() method threw
* an exception
* @exception ServletException if some other loading problem occurs
*/
@Override
public synchronized void load() throws ServletException {
instance = loadServlet();
if (!instanceInitialized) {
initServlet(instance);
}
if (isJspServlet) {
StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name=");
oname.append(getName());
oname.append(getJ2EEKeyProperties());
try {
jspMonitorON = new ObjectName(oname.toString());
Registry.getRegistry(null, null)
.registerComponent(instance, jspMonitorON, null);
} catch( Exception ex ) {
log.info("Error registering JSP monitoring with jmx " +
instance);
}
}
}
/**
* Load and initialize an instance of this servlet, if there is not already
* at least one initialized instance. This can be used, for example, to
* load servlets that are marked in the deployment descriptor to be loaded
* at server startup time.
*/
public synchronized Servlet loadServlet() throws ServletException {
if (unloading) {
throw new ServletException(
sm.getString("standardWrapper.unloading", getName()));
}
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) &&
(isContainerProvidedServlet(servletClass) ||
((Context) getParent()).getPrivileged() )) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
/**
* {@inheritDoc}
*/
@Override
public void servletSecurityAnnotationScan() throws ServletException {
// NO-OP
}
private synchronized void initServlet(Servlet servlet)
throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this servlet
try {
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
servlet);
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",
servlet,
classType,
args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
servlet.init(facade);
}
instanceInitialized = true;
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet);
} catch (UnavailableException f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
unavailable(f);
throw f;
} catch (ServletException f) {
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
getServletContext().log("StandardWrapper.Throwable", f );
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
servlet, f);
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException
(sm.getString("standardWrapper.initException", getName()), f);
}
}
/**
* Remove the specified initialization parameter from this servlet.
*
* @param name Name of the initialization parameter to remove
*/
@Override
public void removeInitParameter(String name) {
parametersLock.writeLock().lock();
try {
parameters.remove(name);
} finally {
parametersLock.writeLock().unlock();
}
fireContainerEvent("removeInitParameter", name);
}
/**
* Remove a listener no longer interested in InstanceEvents.
*
* @param listener The listener to remove
*
* @deprecated Will be removed in 8.5.x onwards
*/
@Deprecated
@Override
public void removeInstanceListener(InstanceListener listener) {
instanceSupport.removeInstanceListener(listener);
}
/**
* Remove a mapping associated with the wrapper.
*
* @param mapping The pattern to remove
*/
@Override
public void removeMapping(String mapping) {
mappingsLock.writeLock().lock();
try {
mappings.remove(mapping);
} finally {
mappingsLock.writeLock().unlock();
}
if(parent.getState().equals(LifecycleState.STARTED))
fireContainerEvent(REMOVE_MAPPING_EVENT, mapping);
}
/**
* Remove any security role reference for the specified role name.
*
* @param name Security role used within this servlet to be removed
*/
@Override
public void removeSecurityReference(String name) {
referencesLock.writeLock().lock();
try {
references.remove(name);
} finally {
referencesLock.writeLock().unlock();
}
fireContainerEvent("removeSecurityReference", name);
}
/**
* Return a String representation of this component.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (getParent() != null) {
sb.append(getParent().toString());
sb.append(".");
}
sb.append("StandardWrapper[");
sb.append(getName());
sb.append("]");
return (sb.toString());
}
/**
* Process an UnavailableException, marking this servlet as unavailable
* for the specified amount of time.
*
* @param unavailable The exception that occurred, or <code>null</code>
* to mark this servlet as permanently unavailable
*/
@Override
public void unavailable(UnavailableException unavailable) {
getServletContext().log(sm.getString("standardWrapper.unavailable", getName()));
if (unavailable == null)
setAvailable(Long.MAX_VALUE);
else if (unavailable.isPermanent())
setAvailable(Long.MAX_VALUE);
else {
int unavailableSeconds = unavailable.getUnavailableSeconds();
if (unavailableSeconds <= 0)
unavailableSeconds = 60; // Arbitrary default
setAvailable(System.currentTimeMillis() +
(unavailableSeconds * 1000L));
}
}
/**
* Unload all initialized instances of this servlet, after calling the
* <code>destroy()</code> method for each instance. This can be used,
* for example, prior to shutting down the entire servlet engine, or
* prior to reloading all of the classes from the Loader associated with
* our Loader's repository.
*
* @exception ServletException if an exception is thrown by the
* destroy() method
*/
@Override
public synchronized void unload() throws ServletException {
// Nothing to do if we have never loaded the instance
if (!singleThreadModel && (instance == null))
return;
unloading = true;
// Loaf a while if the current instance is allocated
// (possibly more than once if non-STM)
if (countAllocated.get() > 0) {
int nRetries = 0;
long delay = unloadDelay / 20;
while ((nRetries < 21) && (countAllocated.get() > 0)) {
if ((nRetries % 10) == 0) {
log.info(sm.getString("standardWrapper.waiting",
countAllocated.toString(),
getName()));
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// Ignore
}
nRetries++;
}
}
if (instanceInitialized) {
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
// Call the servlet destroy() method
try {
instanceSupport.fireInstanceEvent
(InstanceEvent.BEFORE_DESTROY_EVENT, instance);
if( Globals.IS_SECURITY_ENABLED) {
try {
SecurityUtil.doAsPrivilege("destroy",
instance);
} finally {
SecurityUtil.remove(instance);
}
} else {
instance.destroy();
}
instanceSupport.fireInstanceEvent
(InstanceEvent.AFTER_DESTROY_EVENT, instance);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
instanceSupport.fireInstanceEvent
(InstanceEvent.AFTER_DESTROY_EVENT, instance, t);
instance = null;
instancePool = null;
nInstances = 0;
fireContainerEvent("unload", this);
unloading = false;
throw new ServletException
(sm.getString("standardWrapper.destroyException", getName()),
t);
} finally {
// Annotation processing
if (!((Context) getParent()).getIgnoreAnnotations()) {
try {
((Context)getParent()).getInstanceManager().destroyInstance(instance);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardWrapper.destroyInstance", getName()), t);
}
}
// Write captured output
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
}
// Deregister the destroyed instance
instance = null;
instanceInitialized = false;
if (isJspServlet && jspMonitorON != null ) {
Registry.getRegistry(null, null).unregisterComponent(jspMonitorON);
}
if (singleThreadModel && (instancePool != null)) {
try {
while (!instancePool.isEmpty()) {
Servlet s = instancePool.pop();
if (Globals.IS_SECURITY_ENABLED) {
try {
SecurityUtil.doAsPrivilege("destroy", s);
} finally {
SecurityUtil.remove(s);
}
} else {
s.destroy();
}
// Annotation processing
if (!((Context) getParent()).getIgnoreAnnotations()) {
((StandardContext)getParent()).getInstanceManager().destroyInstance(s);
}
}
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
instancePool = null;
nInstances = 0;
unloading = false;
fireContainerEvent("unload", this);
throw new ServletException
(sm.getString("standardWrapper.destroyException",
getName()), t);
}
instancePool = null;
nInstances = 0;
}
singleThreadModel = false;
unloading = false;
fireContainerEvent("unload", this);
}
// -------------------------------------------------- ServletConfig Methods
/**
* Return the initialization parameter value for the specified name,
* if any; otherwise return <code>null</code>.
*
* @param name Name of the initialization parameter to retrieve
*/
@Override
public String getInitParameter(String name) {
return (findInitParameter(name));
}
/**
* Return the set of initialization parameter names defined for this
* servlet. If none are defined, an empty Enumeration is returned.
*/
@Override
public Enumeration<String> getInitParameterNames() {
parametersLock.readLock().lock();
try {
return Collections.enumeration(parameters.keySet());
} finally {
parametersLock.readLock().unlock();
}
}
/**
* Return the servlet context with which this servlet is associated.
*/
@Override
public ServletContext getServletContext() {
if (parent == null)
return (null);
else if (!(parent instanceof Context))
return (null);
else
return (((Context) parent).getServletContext());
}
/**
* Return the name of this servlet.
*/
@Override
public String getServletName() {
return (getName());
}
public long getProcessingTime() {
return swValve.getProcessingTime();
}
public long getMaxTime() {
return swValve.getMaxTime();
}
public long getMinTime() {
return swValve.getMinTime();
}
public int getRequestCount() {
return swValve.getRequestCount();
}
public int getErrorCount() {
return swValve.getErrorCount();
}
/**
* Increment the error count used for monitoring.
*/
@Override
public void incrementErrorCount(){
swValve.incrementErrorCount();
}
public long getLoadTime() {
return loadTime;
}
public int getClassLoadTime() {
return classLoadTime;
}
@Override
public MultipartConfigElement getMultipartConfigElement() {
return multipartConfigElement;
}
@Override
public void setMultipartConfigElement(
MultipartConfigElement multipartConfigElement) {
this.multipartConfigElement = multipartConfigElement;
}
@Override
public boolean isAsyncSupported() {
return asyncSupported;
}
@Override
public void setAsyncSupported(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
// -------------------------------------------------------- Package Methods
// -------------------------------------------------------- protected Methods
/**
* Return <code>true</code> if the specified class name represents a
* container provided servlet class that should be loaded by the
* server class loader.
*
* @param classname Name of the class to be checked
*/
protected boolean isContainerProvidedServlet(String classname) {
if (classname.startsWith("org.apache.catalina.")) {
return (true);
}
try {
Class<?> clazz =
this.getClass().getClassLoader().loadClass(classname);
return (ContainerServlet.class.isAssignableFrom(clazz));
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
return (false);
}
}
protected Method[] getAllDeclaredMethods(Class<?> c) {
if (c.equals(javax.servlet.http.HttpServlet.class)) {
return null;
}
Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
Method[] thisMethods = c.getDeclaredMethods();
if (thisMethods.length == 0) {
return parentMethods;
}
if ((parentMethods != null) && (parentMethods.length > 0)) {
Method[] allMethods =
new Method[parentMethods.length + thisMethods.length];
System.arraycopy(parentMethods, 0, allMethods, 0,
parentMethods.length);
System.arraycopy(thisMethods, 0, allMethods, parentMethods.length,
thisMethods.length);
thisMethods = allMethods;
}
return thisMethods;
}
// ------------------------------------------------------ Lifecycle Methods
/**
* 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 {
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
// Start up this component
super.startInternal();
setAvailable(0L);
// Send j2ee.state.running notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
}
/**
* 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 {
setAvailable(Long.MAX_VALUE);
// Send j2ee.state.stopping notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.stopping", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
// Shut down our servlet instance (if it has been initialized)
try {
unload();
} catch (ServletException e) {
getServletContext().log(sm.getString
("standardWrapper.unloadException", getName()), e);
}
// Shut down this component
super.stopInternal();
// Send j2ee.state.stopped notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.stopped", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
// Send j2ee.object.deleted notification
Notification notification =
new Notification("j2ee.object.deleted", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
@Override
protected String getObjectNameKeyProperties() {
StringBuilder keyProperties =
new StringBuilder("j2eeType=Servlet");
keyProperties.append(getWebModuleKeyProperties());
keyProperties.append(",name=");
String name = getName();
if (Util.objectNameValueNeedsQuote(name)) {
name = ObjectName.quote(name);
}
keyProperties.append(name);
keyProperties.append(getJ2EEKeyProperties());
return keyProperties.toString();
}
private String getWebModuleKeyProperties() {
StringBuilder keyProperties = new StringBuilder(",WebModule=//");
String hostName = getParent().getParent().getName();
if (hostName == null) {
keyProperties.append("DEFAULT");
} else {
keyProperties.append(hostName);
}
String contextName = ((Context) getParent()).getName();
if (!contextName.startsWith("/")) {
keyProperties.append('/');
}
keyProperties.append(contextName);
return keyProperties.toString();
}
private String getJ2EEKeyProperties() {
StringBuilder keyProperties = new StringBuilder(",J2EEApplication=");
StandardContext ctx = null;
if (parent instanceof StandardContext) {
ctx = (StandardContext) getParent();
}
if (ctx == null) {
keyProperties.append("none");
} else {
keyProperties.append(ctx.getJ2EEApplication());
}
keyProperties.append(",J2EEServer=");
if (ctx == null) {
keyProperties.append("none");
} else {
keyProperties.append(ctx.getJ2EEServer());
}
return keyProperties.toString();
}
/**
* JSR 77. Always return false.
*
* @deprecated The JSR-77 implementation is incomplete and will be removed
* in 8.5.x
*/
@Deprecated
public boolean isStateManageable() {
return false;
}
/* Remove a JMX notificationListener
* @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public void removeNotificationListener(NotificationListener listener,
NotificationFilter filter, Object object) throws ListenerNotFoundException {
broadcaster.removeNotificationListener(listener,filter,object);
}
protected MBeanNotificationInfo[] notificationInfo;
/* Get JMX Broadcaster Info
* @TODO use StringManager for international support!
* @TODO This two events we not send j2ee.state.failed and j2ee.attribute.changed!
* @see javax.management.NotificationBroadcaster#getNotificationInfo()
*/
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
if(notificationInfo == null) {
notificationInfo = new MBeanNotificationInfo[]{
new MBeanNotificationInfo(new String[] {
"j2ee.object.created"},
Notification.class.getName(),
"servlet is created"
),
new MBeanNotificationInfo(new String[] {
"j2ee.state.starting"},
Notification.class.getName(),
"servlet is starting"
),
new MBeanNotificationInfo(new String[] {
"j2ee.state.running"},
Notification.class.getName(),
"servlet is running"
),
new MBeanNotificationInfo(new String[] {
"j2ee.state.stopped"},
Notification.class.getName(),
"servlet start to stopped"
),
new MBeanNotificationInfo(new String[] {
"j2ee.object.stopped"},
Notification.class.getName(),
"servlet is stopped"
),
new MBeanNotificationInfo(new String[] {
"j2ee.object.deleted"},
Notification.class.getName(),
"servlet is deleted"
)
};
}
return notificationInfo;
}
/* Add a JMX-NotificationListener
* @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public void addNotificationListener(NotificationListener listener,
NotificationFilter filter, Object object) throws IllegalArgumentException {
broadcaster.addNotificationListener(listener,filter,object);
}
/**
* Remove a JMX-NotificationListener
* @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
*/
@Override
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException {
broadcaster.removeNotificationListener(listener);
}
}