org.eclipse.jetty.ee8.servlet.ServletHolder Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee8.servlet;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.GenericServlet;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletSecurityElement;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee8.nested.ContextHandler;
import org.eclipse.jetty.ee8.nested.Request;
import org.eclipse.jetty.ee8.nested.UserIdentityScope;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.IdentityService.Association;
import org.eclipse.jetty.security.IdentityService.RunAsToken;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Servlet Instance and Context Holder.
*
* Holds the name, params and some state of a javax.servlet.Servlet
* instance. It implements the ServletConfig interface.
* This class will organise the loading of the servlet when needed or
* requested.
*/
@ManagedObject("Servlet Holder")
public class ServletHolder extends Holder implements UserIdentityScope, Comparable {
private static final Logger LOG = LoggerFactory.getLogger(ServletHolder.class);
private int _initOrder = -1;
private boolean _initOnStartup = false;
private Map _roleMap;
private String _forcedPath;
private String _runAsRole;
private ServletRegistration.Dynamic _registration;
private JspContainer _jspContainer;
private volatile Servlet _servlet;
private Config _config;
private boolean _enabled = true;
public static final String APACHE_SENTINEL_CLASS = "org.apache.tomcat.InstanceManager";
public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
public enum JspContainer {
APACHE, OTHER
}
/**
* Constructor .
*/
public ServletHolder() {
this(Source.EMBEDDED);
}
/**
* Constructor .
*
* @param creator the holder source
*/
public ServletHolder(Source creator) {
super(creator);
}
/**
* Constructor for existing servlet.
*
* @param servlet the servlet
*/
public ServletHolder(Servlet servlet) {
this(Source.EMBEDDED);
setServlet(servlet);
}
/**
* Constructor for servlet class.
*
* @param name the name of the servlet
* @param servlet the servlet class
*/
public ServletHolder(String name, Class extends Servlet> servlet) {
this(Source.EMBEDDED);
setName(name);
setHeldClass(servlet);
}
/**
* Constructor for servlet class.
*
* @param name the servlet name
* @param servlet the servlet
*/
public ServletHolder(String name, Servlet servlet) {
this(Source.EMBEDDED);
setName(name);
setServlet(servlet);
}
/**
* Constructor for servlet class.
*
* @param servlet the servlet class
*/
public ServletHolder(Class extends Servlet> servlet) {
this(Source.EMBEDDED);
setHeldClass(servlet);
}
/**
* @return The unavailable exception or null if not unavailable
*/
public UnavailableException getUnavailableException() {
Servlet servlet = _servlet;
if (servlet instanceof UnavailableServlet)
return ((UnavailableServlet) servlet).getUnavailableException();
return null;
}
public void setServlet(Servlet servlet) {
if (servlet == null || servlet instanceof SingleThreadModel)
throw new IllegalArgumentException(SingleThreadModel.class.getName() + " has been deprecated since Servlet API 2.4");
setInstance(servlet);
}
@ManagedAttribute(value = "initialization order", readonly = true)
public int getInitOrder() {
return _initOrder;
}
/**
* Set the initialize order.
*
* Holders with order<0, are initialized on use. Those with
* order>=0 are initialized in increasing order when the handler
* is started.
*
* @param order the servlet init order
*/
public void setInitOrder(int order) {
_initOnStartup = order >= 0;
_initOrder = order;
}
/**
* Comparator by init order.
*/
@Override
public int compareTo(ServletHolder sh) {
if (sh == this)
return 0;
if (sh._initOrder < _initOrder)
return 1;
if (sh._initOrder > _initOrder)
return -1;
// consider getClassName(), need to position properly when one is configured but not the other
int c;
if (getClassName() == null && sh.getClassName() == null)
c = 0;
else if (getClassName() == null)
c = -1;
else if (sh.getClassName() == null)
c = 1;
else
c = getClassName().compareTo(sh.getClassName());
// if _initOrder and getClassName() are the same, consider the getName()
if (c == 0)
c = getName().compareTo(sh.getName());
return c;
}
@Override
public boolean equals(Object o) {
return o instanceof ServletHolder && compareTo((ServletHolder) o) == 0;
}
@Override
public int hashCode() {
return getName() == null ? System.identityHashCode(this) : getName().hashCode();
}
/**
* Link a user role.
* Translate the role name used by a servlet, to the link name
* used by the container.
*
* @param name The role name as used by the servlet
* @param link The role name as used by the container.
*/
public void setUserRoleLink(String name, String link) {
try (AutoLock l = lock()) {
if (_roleMap == null)
_roleMap = new HashMap<>();
_roleMap.put(name, link);
}
}
/**
* get a user role link.
*
* @param name The name of the role
* @return The name as translated by the link. If no link exists,
* the name is returned.
*/
public String getUserRoleLink(String name) {
try (AutoLock l = lock()) {
if (_roleMap == null)
return name;
String link = _roleMap.get(name);
return (link == null) ? name : link;
}
}
/**
* @return Returns the forcedPath.
*/
@ManagedAttribute(value = "forced servlet path", readonly = true)
public String getForcedPath() {
return _forcedPath;
}
/**
* @param forcedPath The forcedPath to set.
*/
public void setForcedPath(String forcedPath) {
_forcedPath = forcedPath;
}
private void setClassFrom(ServletHolder holder) {
if (_servlet != null || getInstance() != null)
throw new IllegalStateException();
this.setClassName(holder.getClassName());
this.setHeldClass(holder.getHeldClass());
}
public boolean isEnabled() {
return _enabled;
}
public void setEnabled(boolean enabled) {
_enabled = enabled;
}
@Override
public void doStart() throws Exception {
if (!_enabled)
return;
// Handle JSP file forced paths
if (_forcedPath != null) {
// Look for a precompiled JSP Servlet
String precompiled = getClassNameForJsp(_forcedPath);
if (!StringUtil.isBlank(precompiled)) {
if (LOG.isDebugEnabled())
LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
ServletHolder jsp = getServletHandler().getServlet(precompiled);
if (jsp != null && jsp.getClassName() != null) {
if (LOG.isDebugEnabled())
LOG.debug("JSP file {} for {} mapped to Servlet {}", _forcedPath, getName(), jsp.getClassName());
// set the className/servlet/instance for this servlet to the precompiled one
setClassFrom(jsp);
} else {
// Look for normal JSP servlet
jsp = getServletHandler().getServlet("jsp");
if (jsp != null) {
if (LOG.isDebugEnabled())
LOG.debug("JSP file {} for {} mapped to JspServlet class {}", _forcedPath, getName(), jsp.getClassName());
setClassFrom(jsp);
//copy jsp init params that don't exist for this servlet
for (Map.Entry entry : jsp.getInitParameters().entrySet()) {
if (!getInitParameters().containsKey(entry.getKey()))
setInitParameter(entry.getKey(), entry.getValue());
}
//jsp specific: set up the jsp-file on the JspServlet. If load-on-startup is >=0 and the jsp container supports
//precompilation, the jsp will be compiled when this holder is initialized. If not load on startup, or the
//container does not support startup precompilation, it will be compiled at runtime when handling a request for this jsp.
//See also adaptForcedPathToJspContainer
setInitParameter("jspFile", _forcedPath);
}
}
} else
LOG.warn("Bad jsp-file {} conversion to classname in holder {}", _forcedPath, getName());
}
//check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
try {
super.doStart();
} catch (UnavailableException ex) {
makeUnavailable(ex);
if (getServletHandler().isStartWithUnavailable()) {
LOG.trace("IGNORED", ex);
return;
} else
throw ex;
}
//servlet is not an instance of jakarta.servlet.Servlet
try {
checkServletType();
} catch (UnavailableException ex) {
makeUnavailable(ex);
if (getServletHandler().isStartWithUnavailable()) {
LOG.trace("IGNORED", ex);
return;
} else
throw ex;
}
//check if we need to forcibly set load-on-startup
checkInitOnStartup();
_config = new Config();
}
@Override
public void initialize() throws Exception {
try (AutoLock l = lock()) {
if (_servlet == null && (_initOnStartup || isInstance())) {
super.initialize();
initServlet();
}
}
}
@Override
public void doStop() throws Exception {
try (AutoLock l = lock()) {
Servlet servlet = _servlet;
if (servlet != null) {
_servlet = null;
try {
destroyInstance(servlet);
} catch (Exception e) {
LOG.warn("Unable to destroy servlet {}", servlet, e);
}
}
_config = null;
}
}
@Override
public void destroyInstance(Object o) {
if (o == null)
return;
Servlet servlet = (Servlet) o;
// call any predestroy callbacks
predestroyServlet(servlet);
// Call the servlet destroy
servlet.destroy();
}
private void predestroyServlet(Servlet servlet) {
// TODO We should only predestroy instnaces that we created
// TODO But this breaks tests in jetty-9, so review behaviour in jetty-10
// Need to use the unwrapped servlet because lifecycle callbacks such as
// postconstruct and predestroy are based off the classname and the wrapper
// classes are unknown outside the ServletHolder
getServletHandler().destroyServlet(unwrap(servlet));
}
/**
* Get the servlet.
*
* @return The servlet
* @throws ServletException if unable to init the servlet on first use
*/
public Servlet getServlet() throws ServletException {
Servlet servlet = _servlet;
if (servlet == null) {
try (AutoLock l = lock()) {
if (_servlet == null && isRunning()) {
if (getHeldClass() != null)
initServlet();
}
servlet = _servlet;
}
}
return servlet;
}
/**
* Get the servlet instance (no initialization done).
*
* @return The servlet or null
*/
public Servlet getServletInstance() {
return _servlet;
}
/**
* Check to ensure class of servlet is acceptable.
*
* @throws UnavailableException if Servlet class is not of type {@link javax.servlet.Servlet}
*/
public void checkServletType() throws UnavailableException {
if (getHeldClass() == null || !javax.servlet.Servlet.class.isAssignableFrom(getHeldClass())) {
throw new UnavailableException("Servlet " + getHeldClass() + " is not a javax.servlet.Servlet");
}
}
/**
* @return true if the holder is started and is not unavailable
*/
public boolean isAvailable() {
return (isStarted() && !(_servlet instanceof UnavailableServlet));
}
/**
* Check if there is a javax.servlet.annotation.ServletSecurity
* annotation on the servlet class. If there is, then we force
* it to be loaded on startup, because all of the security
* constraints must be calculated as the container starts.
*/
private void checkInitOnStartup() {
if (getHeldClass() == null)
return;
if ((getHeldClass().getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
setInitOrder(Integer.MAX_VALUE);
}
private Servlet makeUnavailable(UnavailableException e) {
try (AutoLock l = lock()) {
if (_servlet instanceof UnavailableServlet) {
Throwable cause = ((UnavailableServlet) _servlet).getUnavailableException();
if (cause != e)
cause.addSuppressed(e);
} else {
_servlet = new UnavailableServlet(e, _servlet);
}
return _servlet;
}
}
private void makeUnavailable(final Throwable e) {
if (e instanceof UnavailableException)
makeUnavailable((UnavailableException) e);
else {
ServletContext ctx = getServletHandler().getServletContext();
if (ctx == null)
LOG.warn("unavailable", e);
else
ctx.log("unavailable", e);
UnavailableException unavailable = new UnavailableException(String.valueOf(e), -1) {
{
initCause(e);
}
};
makeUnavailable(unavailable);
}
}
private void initServlet() throws ServletException {
// must be called with lock held and _servlet==null
if (!lockIsHeldByCurrentThread())
throw new IllegalStateException("Lock not held");
if (_servlet != null)
throw new IllegalStateException("Servlet already initialised: " + _servlet);
Servlet servlet = null;
try {
servlet = getInstance();
if (servlet == null)
servlet = newInstance();
if (servlet instanceof SingleThreadModel) {
predestroyServlet(servlet);
servlet = new SingleThreadedWrapper();
}
if (_config == null)
_config = new Config();
//check run-as rolename and convert to token from IdentityService
if (_runAsRole != null) {
IdentityService identityService = getServletHandler().getIdentityService();
if (identityService != null) {
RunAsToken runAsToken = identityService.newRunAsToken(_runAsRole);
servlet = new RunAs(servlet, identityService, runAsToken);
}
}
if (!isAsyncSupported())
servlet = new NotAsync(servlet);
// Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
if (isJspServlet()) {
initJspServlet();
detectJspContainer();
} else if (_forcedPath != null)
detectJspContainer();
servlet = wrap(servlet, WrapFunction.class, WrapFunction::wrapServlet);
if (LOG.isDebugEnabled())
LOG.debug("Servlet.init {} for {}", _servlet, getName());
try {
servlet.init(_config);
_servlet = servlet;
} catch (UnavailableException e) {
_servlet = new UnavailableServlet(e, servlet);
}
} catch (ServletException e) {
makeUnavailable(e.getCause() == null ? e : e.getCause());
predestroyServlet(servlet);
throw e;
} catch (Exception e) {
makeUnavailable(e);
predestroyServlet(servlet);
throw new ServletException(this.toString(), e);
}
}
/**
* @throws Exception if unable to init the JSP Servlet
*/
protected void initJspServlet() throws Exception {
ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
/* Set the webapp's classpath for Jasper */
ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
/* Set up other classpath attribute */
if ("?".equals(getInitParameter("classpath"))) {
String classpath = ch.getClassPath();
if (LOG.isDebugEnabled())
LOG.debug("classpath={}", classpath);
if (classpath != null)
setInitParameter("classpath", classpath);
}
/* ensure scratch dir */
File scratch;
if (getInitParameter("scratchdir") == null) {
File tmp = (File) getServletHandler().getServletContext().getAttribute(ServletContext.TEMPDIR);
scratch = new File(tmp, "jsp");
setInitParameter("scratchdir", scratch.getAbsolutePath());
}
scratch = new File(getInitParameter("scratchdir"));
if (!scratch.exists() && !scratch.mkdir())
throw new IllegalStateException("Could not create JSP scratch directory");
}
@Override
public ContextHandler getContextHandler() {
return ContextHandler.getContextHandler(_config.getServletContext());
}
@Override
public String getContextPath() {
return _config.getServletContext().getContextPath();
}
@Override
public Map getRoleRefMap() {
return _roleMap;
}
@ManagedAttribute(value = "role to run servlet as", readonly = true)
public String getRunAsRole() {
return _runAsRole;
}
public void setRunAsRole(String role) {
_runAsRole = role;
}
/**
* Prepare to service a request.
*
* @param baseRequest the base request
* @param request the request
* @param response the response
* @throws ServletException if unable to prepare the servlet
* @throws UnavailableException if not available
*/
protected void prepare(Request baseRequest, ServletRequest request, ServletResponse response) throws ServletException, UnavailableException {
// Ensure the servlet is initialized prior to any filters being invoked
getServlet();
// Check for multipart config
if (_registration != null) {
MultipartConfigElement mpce = ((Registration) _registration).getMultipartConfig();
if (mpce != null)
baseRequest.setAttribute(Request.MULTIPART_CONFIG_ELEMENT, mpce);
}
}
/**
* Service a request with this servlet.
*
* @param baseRequest the base request
* @param request the request
* @param response the response
* @throws ServletException if unable to process the servlet
* @throws UnavailableException if servlet is unavailable
* @throws IOException if unable to process the request or response
*/
public void handle(Request baseRequest, ServletRequest request, ServletResponse response) throws ServletException, UnavailableException, IOException {
try {
Servlet servlet = getServletInstance();
if (servlet == null)
throw new UnavailableException("Servlet Not Initialized");
servlet.service(request, response);
} catch (UnavailableException e) {
makeUnavailable(e).service(request, response);
}
}
protected boolean isJspServlet() {
Servlet servlet = getServletInstance();
Class> c = servlet == null ? getHeldClass() : servlet.getClass();
while (c != null) {
if (isJspServlet(c.getName()))
return true;
c = c.getSuperclass();
}
return false;
}
protected boolean isJspServlet(String classname) {
if (classname == null)
return false;
return ("org.apache.jasper.servlet.JspServlet".equals(classname));
}
private void detectJspContainer() {
if (_jspContainer == null) {
try {
//check for apache
Loader.loadClass(APACHE_SENTINEL_CLASS);
if (LOG.isDebugEnabled())
LOG.debug("Apache jasper detected");
_jspContainer = JspContainer.APACHE;
} catch (ClassNotFoundException x) {
if (LOG.isDebugEnabled())
LOG.debug("Other jasper detected");
_jspContainer = JspContainer.OTHER;
}
}
}
/**
* @param jsp the jsp-file
* @return the simple classname of the jsp
*/
public String getNameOfJspClass(String jsp) {
if (StringUtil.isBlank(jsp))
//empty
return "";
jsp = jsp.trim();
if ("/".equals(jsp))
//only slash
return "";
int i = jsp.lastIndexOf('/');
if (i == jsp.length() - 1)
//ends with slash
return "";
jsp = jsp.substring(i + 1);
try {
Class> jspUtil = Loader.loadClass("org.apache.jasper.compiler.JspUtil");
Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
return (String) makeJavaIdentifier.invoke(null, jsp);
} catch (Exception e) {
String tmp = StringUtil.replace(jsp, '.', '_');
if (LOG.isDebugEnabled()) {
LOG.warn("JspUtil.makeJavaIdentifier failed for jsp {} using {} instead", jsp, tmp, e);
}
return tmp;
}
}
public String getPackageOfJspClass(String jsp) {
if (jsp == null)
return "";
int i = jsp.lastIndexOf('/');
if (i <= 0)
return "";
try {
Class> jspUtil = Loader.loadClass("org.apache.jasper.compiler.JspUtil");
Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
return (String) makeJavaPackage.invoke(null, jsp.substring(0, i));
} catch (Exception e) {
String tmp = jsp;
//remove any leading slash
int s = 0;
if ('/' == (tmp.charAt(0)))
s = 1;
//remove the element after last slash, which should be name of jsp
tmp = tmp.substring(s, i).trim();
tmp = StringUtil.replace(tmp, '/', '.');
tmp = (".".equals(tmp) ? "" : tmp);
if (LOG.isDebugEnabled()) {
LOG.warn("JspUtil.makeJavaPackage failed for {} using {} instead", jsp, tmp, e);
}
return tmp;
}
}
/**
* Get the package for all jsps.
* @return the package for all jsps
*/
public String getJspPackagePrefix() {
String jspPackageName = null;
if (getServletHandler() != null && getServletHandler().getServletContext() != null)
jspPackageName = (String) getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME);
if (jspPackageName == null)
jspPackageName = "org.apache.jsp";
return jspPackageName;
}
/**
* @param jsp the jsp-file from web.xml
* @return the fully qualified classname
*/
public String getClassNameForJsp(String jsp) {
if (jsp == null)
return null;
String name = getNameOfJspClass(jsp);
if (StringUtil.isBlank(name))
return null;
StringBuffer fullName = new StringBuffer();
appendPath(fullName, getJspPackagePrefix());
appendPath(fullName, getPackageOfJspClass(jsp));
appendPath(fullName, name);
return fullName.toString();
}
/**
* Concatenate an element on to fully qualified classname.
*
* @param path the path under construction
* @param element the element of the name to add
*/
protected void appendPath(StringBuffer path, String element) {
if (StringUtil.isBlank(element))
return;
if (path.length() > 0)
path.append(".");
path.append(element);
}
protected class Config extends HolderConfig implements ServletConfig {
@Override
public String getServletName() {
return getName();
}
}
public class Registration extends HolderRegistration implements ServletRegistration.Dynamic {
protected MultipartConfigElement _multipartConfig;
@Override
public Set addMapping(String... urlPatterns) {
illegalStateIfContextStarted();
Set clash = null;
for (String pattern : urlPatterns) {
ServletMapping mapping = getServletHandler().getServletMapping(pattern);
if (mapping != null) {
//if the servlet mapping was from a default descriptor, then allow it to be overridden
if (!mapping.isFromDefaultDescriptor()) {
if (clash == null)
clash = new HashSet<>();
clash.add(pattern);
}
}
}
//if there were any clashes amongst the urls, return them
if (clash != null)
return clash;
//otherwise apply all of them
ServletMapping mapping = new ServletMapping(Source.JAKARTA_API);
mapping.setServletName(ServletHolder.this.getName());
mapping.setPathSpecs(urlPatterns);
getServletHandler().addServletMapping(mapping);
return Collections.emptySet();
}
@Override
public Collection getMappings() {
ServletMapping[] mappings = getServletHandler().getServletMappings();
List patterns = new ArrayList<>();
if (mappings != null) {
for (ServletMapping mapping : mappings) {
if (!mapping.getServletName().equals(getName()))
continue;
String[] specs = mapping.getPathSpecs();
if (specs != null && specs.length > 0)
patterns.addAll(Arrays.asList(specs));
}
}
return patterns;
}
@Override
public String getRunAsRole() {
return _runAsRole;
}
@Override
public void setLoadOnStartup(int loadOnStartup) {
illegalStateIfContextStarted();
ServletHolder.this.setInitOrder(loadOnStartup);
}
public int getInitOrder() {
return ServletHolder.this.getInitOrder();
}
@Override
public void setMultipartConfig(MultipartConfigElement element) {
_multipartConfig = element;
}
public MultipartConfigElement getMultipartConfig() {
return _multipartConfig;
}
@Override
public void setRunAsRole(String role) {
_runAsRole = role;
}
@Override
public Set setServletSecurity(ServletSecurityElement securityElement) {
return getServletHandler().setServletSecurity(this, securityElement);
}
}
public ServletRegistration.Dynamic getRegistration() {
if (_registration == null)
_registration = new Registration();
return _registration;
}
private class SingleThreadedWrapper implements Servlet {
Stack _stack = new Stack<>();
@Override
public void destroy() {
try (AutoLock l = lock()) {
while (_stack.size() > 0) {
Servlet servlet = _stack.pop();
try {
servlet.destroy();
} catch (Exception e) {
LOG.warn("Unable to destroy servlet {}", servlet, e);
}
}
}
}
@Override
public ServletConfig getServletConfig() {
return _config;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void init(ServletConfig config) throws ServletException {
try (AutoLock l = lock()) {
if (_stack.size() == 0) {
try {
Servlet s = newInstance();
s.init(config);
_stack.push(s);
} catch (ServletException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
}
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
Servlet s;
try (AutoLock l = lock()) {
if (_stack.size() > 0)
s = (Servlet) _stack.pop();
else {
try {
s = newInstance();
s.init(_config);
} catch (ServletException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
}
try {
s.service(req, res);
} finally {
try (AutoLock l = lock()) {
_stack.push(s);
}
}
}
}
/**
* @return the newly created Servlet instance
* @throws ServletException if unable to create a new instance
* @throws IllegalAccessException if not allowed to create a new instance
* @throws InstantiationException if creating new instance resulted in error
* @throws NoSuchMethodException if creating new instance resulted in error
* @throws InvocationTargetException If creating new instance throws an exception
*/
protected Servlet newInstance() throws Exception {
return createInstance();
}
@Override
protected Servlet createInstance() throws Exception {
try (AutoLock l = lock()) {
Servlet servlet = super.createInstance();
if (servlet == null) {
ServletContext ctx = getServletContext();
if (ctx != null)
servlet = ctx.createServlet(getHeldClass());
}
return servlet;
}
}
@Override
public void dump(Appendable out, String indent) throws IOException {
if (getInitParameters().isEmpty())
Dumpable.dumpObjects(out, indent, this, _servlet == null ? getHeldClass() : _servlet);
else
Dumpable.dumpObjects(out, indent, this, _servlet == null ? getHeldClass() : _servlet, new DumpableCollection("initParams", getInitParameters().entrySet()));
}
@Override
public String toString() {
return String.format("%s==%s@%x{jsp=%s,order=%d,inst=%b,async=%b,src=%s,%s}", getName(), getClassName(), hashCode(), _forcedPath, _initOrder, _servlet != null, isAsyncSupported(), getSource(), getState());
}
private class UnavailableServlet extends Wrapper {
final UnavailableException _unavailableException;
final AtomicLong _unavailableStart;
public UnavailableServlet(UnavailableException unavailableException, Servlet servlet) {
super(servlet != null ? servlet : new GenericServlet() {
@Override
public void service(ServletRequest req, ServletResponse res) throws IOException {
((HttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
}
});
_unavailableException = unavailableException;
if (unavailableException.isPermanent())
_unavailableStart = null;
else {
long start = NanoTime.now();
while (start == 0) start = NanoTime.now();
_unavailableStart = new AtomicLong(start);
}
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (LOG.isDebugEnabled())
LOG.debug("Unavailable {}", req, _unavailableException);
if (_unavailableStart == null) {
((HttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
long start = _unavailableStart.get();
if (start == 0 || NanoTime.secondsSince(start) < _unavailableException.getUnavailableSeconds()) {
((HttpServletResponse) res).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
} else if (_unavailableStart.compareAndSet(start, 0)) {
try (AutoLock l = lock()) {
_servlet = getWrapped();
}
Request baseRequest = Request.getBaseRequest(req);
ServletHolder.this.prepare(baseRequest, req, res);
ServletHolder.this.handle(baseRequest, req, res);
} else {
((HttpServletResponse) res).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
}
}
public UnavailableException getUnavailableException() {
return _unavailableException;
}
}
/**
* Experimental Wrapper mechanism for Servlet objects.
*
* Beans in {@code ServletContextHandler} or {@code WebAppContext} that implement this interface
* will be called to optionally wrap any newly created Servlets
* (before their {@link Servlet#init(ServletConfig)} method is called)
*
*/
public interface WrapFunction {
/**
* Optionally wrap the Servlet.
*
* @param servlet the servlet being passed in.
* @return the servlet (extend from {@link ServletHolder.Wrapper} if you do wrap the Servlet)
*/
Servlet wrapServlet(Servlet servlet);
}
public static class Wrapper implements Servlet, Wrapped {
private final Servlet _wrappedServlet;
public Wrapper(Servlet servlet) {
_wrappedServlet = Objects.requireNonNull(servlet, "Servlet cannot be null");
}
@Override
public Servlet getWrapped() {
return _wrappedServlet;
}
@Override
public void init(ServletConfig config) throws ServletException {
_wrappedServlet.init(config);
}
@Override
public ServletConfig getServletConfig() {
return _wrappedServlet.getServletConfig();
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
_wrappedServlet.service(req, res);
}
@Override
public String getServletInfo() {
return _wrappedServlet.getServletInfo();
}
@Override
public void destroy() {
_wrappedServlet.destroy();
}
@Override
public String toString() {
return String.format("%s:%s", this.getClass().getSimpleName(), _wrappedServlet.toString());
}
}
private static class RunAs extends Wrapper {
final IdentityService _identityService;
final RunAsToken _runAsToken;
public RunAs(Servlet servlet, IdentityService identityService, RunAsToken runAsToken) {
super(servlet);
_identityService = identityService;
_runAsToken = runAsToken;
}
@Override
public void init(ServletConfig config) throws ServletException {
try (Association ignored = _identityService.associate(_identityService.getSystemUserIdentity(), _runAsToken)) {
getWrapped().init(config);
}
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
try (Association ignored = _identityService.associate(_identityService.getSystemUserIdentity(), _runAsToken)) {
getWrapped().service(req, res);
}
}
@Override
public void destroy() {
try (Association ignored = _identityService.associate(_identityService.getSystemUserIdentity(), _runAsToken)) {
getWrapped().destroy();
}
}
}
private static class NotAsync extends Wrapper {
public NotAsync(Servlet servlet) {
super(servlet);
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req.isAsyncSupported()) {
Request baseRequest = Request.getBaseRequest(req);
try {
baseRequest.setAsyncSupported(false, this.toString());
getWrapped().service(req, res);
} finally {
baseRequest.setAsyncSupported(true, null);
}
} else {
getWrapped().service(req, res);
}
}
}
}