org.eclipse.jetty.servlet.ServletHandler 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.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletSecurityElement;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.MatchedPath;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ServletPathMapping;
import org.eclipse.jetty.server.ServletRequestHttpWrapper;
import org.eclipse.jetty.server.ServletResponseHttpWrapper;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ScopedHandler;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Servlet HttpHandler.
*
* This handler maps requests to servlets that implement the
* jakarta.servlet.http.HttpServlet API.
*
* This handler does not implement the full J2EE features and is intended to
* be used directly when a full web application is not required. If a Web application is required,
* then this handler should be used as part of a org.eclipse.jetty.webapp.WebAppContext
.
*
* Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
* method must be called manually after start().
*/
@ManagedObject("Servlet Handler")
public class ServletHandler extends ScopedHandler
{
private static final Logger LOG = LoggerFactory.getLogger(ServletHandler.class);
private final AutoLock _lock = new AutoLock();
private ServletContextHandler _contextHandler;
private ServletContext _servletContext;
private final List _filters = new ArrayList<>();
private final List _filterMappings = new ArrayList<>();
private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
private int _matchAfterIndex = -1; //index of 1st programmatic FilterMapping with isMatchAfter=true
private boolean _filterChainsCached = true;
private int _maxFilterChainsCacheSize = 1024;
private boolean _startWithUnavailable = false;
private boolean _ensureDefaultServlet = true;
private IdentityService _identityService;
private boolean _allowDuplicateMappings = false;
private final List _servlets = new ArrayList<>();
private final List _servletMappings = new ArrayList<>();
private final Map _filterNameMap = new HashMap<>();
private List _filterPathMappings;
private MultiMap _filterNameMappings;
private List _wildFilterNameMappings;
private final List> _durable = new ArrayList<>();
private final Map _servletNameMap = new HashMap<>();
private PathMappings _servletPathMap;
private final List _listeners = new ArrayList<>();
private boolean _initialized = false;
@SuppressWarnings("unchecked")
protected final ConcurrentMap[] _chainCache = new ConcurrentMap[FilterMapping.ALL];
/**
* Constructor.
*/
public ServletHandler()
{
}
AutoLock lock()
{
return _lock.lock();
}
private void updateAndSet(Collection target, Collection values)
{
updateBeans(target, values);
target.clear();
target.addAll(values);
}
@Override
public boolean isDumpable(Object o)
{
return !(o instanceof BaseHolder || o instanceof FilterMapping || o instanceof ServletMapping);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent,
DumpableCollection.from("listeners " + this, _listeners),
DumpableCollection.from("filters " + this, _filters),
DumpableCollection.from("filterMappings " + this, _filterMappings),
DumpableCollection.from("servlets " + this, _servlets),
DumpableCollection.from("servletMappings " + this, _servletMappings),
DumpableCollection.from("durable " + this, _durable));
}
@Override
protected void doStart() throws Exception
{
try (AutoLock ignored = lock())
{
ContextHandler.Context context = ContextHandler.getCurrentContext();
_servletContext = context == null ? new ContextHandler.StaticContext() : context;
_contextHandler = (ServletContextHandler)(context == null ? null : context.getContextHandler());
if (_contextHandler != null)
{
SecurityHandler securityHandler = _contextHandler.getChildHandlerByClass(SecurityHandler.class);
if (securityHandler != null)
_identityService = securityHandler.getIdentityService();
}
_durable.clear();
_durable.addAll(Arrays.asList(getFilters()));
_durable.addAll(Arrays.asList(getServlets()));
_durable.addAll(Arrays.asList(getListeners()));
updateNameMappings();
updateMappings();
if (getServletMapping("/") == null && isEnsureDefaultServlet())
{
if (LOG.isDebugEnabled())
LOG.debug("Adding Default404Servlet to {}", this);
addServletWithMapping(Default404Servlet.class, "/");
updateMappings();
getServletMapping("/").setFromDefaultDescriptor(true);
}
if (isFilterChainsCached())
{
_chainCache[FilterMapping.REQUEST] = new ConcurrentHashMap<>();
_chainCache[FilterMapping.FORWARD] = new ConcurrentHashMap<>();
_chainCache[FilterMapping.INCLUDE] = new ConcurrentHashMap<>();
_chainCache[FilterMapping.ERROR] = new ConcurrentHashMap<>();
_chainCache[FilterMapping.ASYNC] = new ConcurrentHashMap<>();
}
if (_contextHandler == null)
initialize();
super.doStart();
}
}
/**
* @return true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
* default servlet is configured.
*/
public boolean isEnsureDefaultServlet()
{
return _ensureDefaultServlet;
}
/**
* @param ensureDefaultServlet true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
* default servlet is configured.
*/
public void setEnsureDefaultServlet(boolean ensureDefaultServlet)
{
_ensureDefaultServlet = ensureDefaultServlet;
}
@Override
protected void start(LifeCycle l) throws Exception
{
//Don't start the whole object tree (ie all the servlet and filter Holders) when
//this handler starts. They have a slightly special lifecycle, and should only be
//started AFTER the handlers have all started (and the ContextHandler has called
//the context listeners).
if (!(l instanceof Holder))
super.start(l);
}
@Override
protected void stop(LifeCycle l) throws Exception
{
if (!(l instanceof Holder))
super.stop(l);
}
@Override
protected void doStop() throws Exception
{
try (AutoLock ignored = lock())
{
super.doStop();
// Stop filters
List filterHolders = new ArrayList<>();
for (int i = _filters.size(); i-- > 0; )
{
FilterHolder filter = _filters.get(i);
try
{
filter.stop();
}
catch (Exception e)
{
LOG.warn("Unable to stop filter {}", filter, e);
}
if (_durable.contains(filter))
{
filterHolders.add(filter); //only retain durable
}
}
//Retain only durable filters
updateBeans(_filters, filterHolders);
_filters.clear();
_filters.addAll(filterHolders);
// Stop servlets
List servletHolders = new ArrayList<>(); //will be remaining servlets
for (int i = _servlets.size(); i-- > 0; )
{
ServletHolder servlet = _servlets.get(i);
try
{
servlet.stop();
}
catch (Exception e)
{
LOG.warn("Unable to stop servlet {}", servlet, e);
}
if (_durable.contains(servlet))
{
servletHolders.add(servlet); //only retain embedded
}
}
//Retain only durable Servlets
updateBeans(_servlets, servletHolders);
_servlets.clear();
_servlets.addAll(servletHolders);
updateNameMappings();
updateAndSet(_servletMappings, _servletMappings.stream().filter(m -> _servletNameMap.containsKey(m.getServletName())).collect(Collectors.toList()));
updateAndSet(_filterMappings, _filterMappings.stream().filter(m -> _filterNameMap.containsKey(m.getFilterName())).collect(Collectors.toList()));
updateMappings();
if (_contextHandler != null)
_contextHandler.contextDestroyed();
//Retain only Listeners added via jetty apis (is Source.EMBEDDED)
List listenerHolders = new ArrayList<>();
for (int i = _listeners.size(); i-- > 0; )
{
ListenerHolder listener = _listeners.get(i);
try
{
listener.stop();
}
catch (Exception e)
{
LOG.warn("Unable to stop listener {}", listener, e);
}
if (_durable.contains(listener))
listenerHolders.add(listener);
}
updateBeans(_listeners, listenerHolders);
_listeners.clear();
_listeners.addAll(listenerHolders);
// Update indexes for prepending filters
_matchAfterIndex = (_filterMappings.size() == 0 ? -1 : _filterMappings.size() - 1);
_matchBeforeIndex = -1;
_durable.clear();
_filterPathMappings = null;
_filterNameMappings = null;
_servletPathMap = null;
_initialized = false;
}
}
protected IdentityService getIdentityService()
{
return _identityService;
}
@ManagedAttribute(value = "filters", readonly = true)
public FilterMapping[] getFilterMappings()
{
return _filterMappings.toArray(new FilterMapping[0]);
}
@ManagedAttribute(value = "filters", readonly = true)
public FilterHolder[] getFilters()
{
return _filters.toArray(new FilterHolder[0]);
}
/**
* ServletHolder matching path.
*
* @param target Path within _context or servlet name
* @return PathMap Entries pathspec to ServletHolder
* @deprecated Use {@link #getMatchedServlet(String)} instead
*/
@Deprecated
public MappedResource getHolderEntry(String target)
{
if (target.startsWith("/"))
{
MatchedResource matchedResource = getMatchedServlet(target);
return new MappedResource<>(matchedResource.getPathSpec(), matchedResource.getResource());
}
return null;
}
public ServletContext getServletContext()
{
return _servletContext;
}
public ServletContextHandler getServletContextHandler()
{
return _contextHandler;
}
@ManagedAttribute(value = "mappings of servlets", readonly = true)
public ServletMapping[] getServletMappings()
{
return _servletMappings.toArray(new ServletMapping[0]);
}
/**
* Get the ServletMapping matching the path
*
* @param pathSpec the path spec
* @return the servlet mapping for the path spec (or null if not found)
*/
public ServletMapping getServletMapping(String pathSpec)
{
if (pathSpec == null)
return null;
ServletMapping mapping = null;
for (int i = 0; i < _servletMappings.size() && mapping == null; i++)
{
ServletMapping m = _servletMappings.get(i);
if (m.getPathSpecs() != null)
{
for (String p : m.getPathSpecs())
{
if (pathSpec.equals(p))
{
mapping = m;
break;
}
}
}
}
return mapping;
}
@ManagedAttribute(value = "servlets", readonly = true)
public ServletHolder[] getServlets()
{
return _servlets.toArray(new ServletHolder[0]);
}
public List getServlets(Class> clazz)
{
List holders = null;
for (ServletHolder holder : _servlets)
{
Class extends Servlet> held = holder.getHeldClass();
if ((held == null && holder.getClassName() != null && holder.getClassName().equals(clazz.getName())) ||
(held != null && clazz.isAssignableFrom(holder.getHeldClass())))
{
if (holders == null)
holders = new ArrayList<>();
holders.add(holder);
}
}
return holders == null ? Collections.emptyList() : holders;
}
public ServletHolder getServlet(String name)
{
MappedServlet mapped = _servletNameMap.get(name);
if (mapped != null)
return mapped.getServletHolder();
return null;
}
@Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// Get the base requests
final ServletPathMapping old_servlet_path_mapping = baseRequest.getServletPathMapping();
ServletHolder servletHolder = null;
UserIdentity.Scope oldScope = null;
MatchedResource matched = getMatchedServlet(target);
if (matched != null)
{
MappedServlet mappedServlet = matched.getResource();
servletHolder = mappedServlet.getServletHolder();
ServletPathMapping servletPathMapping = mappedServlet.getServletPathMapping(target, matched.getMatchedPath());
if (servletPathMapping != null)
{
baseRequest.setServletPathMapping(servletPathMapping);
}
}
if (LOG.isDebugEnabled())
LOG.debug("servlet {}|{}|{}|{} -> {}", baseRequest.getContextPath(), baseRequest.getServletPath(), baseRequest.getPathInfo(), baseRequest.getHttpServletMapping(), servletHolder);
try
{
// Do the filter/handling thang
oldScope = baseRequest.getUserIdentityScope();
baseRequest.setUserIdentityScope(servletHolder);
nextScope(target, baseRequest, request, response);
}
finally
{
if (oldScope != null)
baseRequest.setUserIdentityScope(oldScope);
baseRequest.setServletPathMapping(old_servlet_path_mapping);
}
}
@Override
public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
ServletHolder servletHolder = (ServletHolder)baseRequest.getUserIdentityScope();
FilterChain chain = null;
// find the servlet
if (servletHolder != null && _filterMappings.size() > 0)
chain = getFilterChain(baseRequest, target.startsWith("/") ? target : null, servletHolder);
if (LOG.isDebugEnabled())
LOG.debug("chain={}", chain);
try
{
if (servletHolder == null)
notFound(baseRequest, request, response);
else
{
// unwrap any tunnelling of base Servlet request/responses
ServletRequest req = request;
if (req instanceof ServletRequestHttpWrapper)
req = ((ServletRequestHttpWrapper)req).getRequest();
ServletResponse res = response;
if (res instanceof ServletResponseHttpWrapper)
res = ((ServletResponseHttpWrapper)res).getResponse();
// Do the filter/handling thang
servletHolder.prepare(baseRequest, req, res);
if (chain != null)
chain.doFilter(req, res);
else
servletHolder.handle(baseRequest, req, res);
}
}
finally
{
if (servletHolder != null)
baseRequest.setHandled(true);
}
}
/**
* ServletHolder matching target path.
*
* @param target Path within _context or servlet name
* @return MatchedResource, pointing to the {@link MappedResource} for the {@link ServletHolder}, and also the pathspec specific name/info sections for the match.
* Named servlets have a null PathSpec and {@link MatchedResource}.
*/
public MatchedResource getMatchedServlet(String target)
{
if (target.startsWith("/"))
{
if (_servletPathMap == null)
return null;
return _servletPathMap.getMatched(target);
}
MappedServlet holder = _servletNameMap.get(target);
if (holder == null)
return null;
return new MatchedResource<>(holder, null, MatchedPath.EMPTY);
}
/**
* ServletHolder matching path.
*
* @param target Path within _context or servlet name
* @return MappedResource to the ServletHolder. Named servlets have a null PathSpec
* @deprecated use {@link #getMatchedServlet(String)} instead
*/
@Deprecated
public MappedServlet getMappedServlet(String target)
{
MatchedResource matchedResource = getMatchedServlet(target);
return matchedResource.getResource();
}
protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
{
Objects.requireNonNull(servletHolder);
String key = pathInContext == null ? servletHolder.getName() : pathInContext;
int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
if (_filterChainsCached)
{
FilterChain chain = _chainCache[dispatch].get(key);
if (chain != null)
return chain;
}
// Build the filter chain from the inside out.
// ie first wrap the servlet with the last filter to be applied.
// The mappings lists have been reversed to make this simple and fast.
FilterChain chain = null;
if (_filterNameMappings != null && !_filterNameMappings.isEmpty())
{
if (_wildFilterNameMappings != null)
for (FilterMapping mapping : _wildFilterNameMappings)
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
List nameMappings = _filterNameMappings.get(servletHolder.getName());
if (nameMappings != null)
{
for (FilterMapping mapping : nameMappings)
{
if (mapping.appliesTo(dispatch))
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
}
}
}
if (pathInContext != null && _filterPathMappings != null)
{
for (FilterMapping mapping : _filterPathMappings)
{
if (mapping.appliesTo(pathInContext, dispatch))
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
}
}
if (_filterChainsCached)
{
final Map cache = _chainCache[dispatch];
// Do we have too many cached chains?
if (_maxFilterChainsCacheSize > 0 && cache.size() >= _maxFilterChainsCacheSize)
{
// flush the cache
LOG.debug("{} flushed filter chain cache for {}", this, baseRequest.getDispatcherType());
cache.clear();
}
chain = chain == null ? new ChainEnd(servletHolder) : chain;
// flush the cache
LOG.debug("{} cached filter chain for {}: {}", this, baseRequest.getDispatcherType(), chain);
cache.put(key, chain);
}
return chain;
}
/**
* Create a FilterChain that calls the passed filter with the passed chain
* @param filterHolder The filter to invoke
* @param chain The chain to pass to the filter
* @return A FilterChain that invokes the filter with the chain
*/
protected FilterChain newFilterChain(FilterHolder filterHolder, FilterChain chain)
{
return new Chain(filterHolder, chain);
}
protected void invalidateChainsCache()
{
if (_chainCache[FilterMapping.REQUEST] != null)
{
_chainCache[FilterMapping.REQUEST].clear();
_chainCache[FilterMapping.FORWARD].clear();
_chainCache[FilterMapping.INCLUDE].clear();
_chainCache[FilterMapping.ERROR].clear();
_chainCache[FilterMapping.ASYNC].clear();
}
}
/**
* @return true if the handler is started and there are no unavailable servlets
*/
public boolean isAvailable()
{
if (!isStarted())
return false;
ServletHolder[] holders = getServlets();
for (ServletHolder holder : holders)
{
if (holder != null && !holder.isAvailable())
return false;
}
return true;
}
/**
* @param start True if this handler will start with unavailable servlets
*/
public void setStartWithUnavailable(boolean start)
{
_startWithUnavailable = start;
}
/**
* @return the allowDuplicateMappings
*/
public boolean isAllowDuplicateMappings()
{
return _allowDuplicateMappings;
}
/**
* @param allowDuplicateMappings the allowDuplicateMappings to set
*/
public void setAllowDuplicateMappings(boolean allowDuplicateMappings)
{
_allowDuplicateMappings = allowDuplicateMappings;
}
/**
* @return True if this handler will start with unavailable servlets
*/
public boolean isStartWithUnavailable()
{
return _startWithUnavailable;
}
/**
* Initialize filters and load-on-startup servlets.
*
* @throws Exception if unable to initialize
*/
public void initialize()
throws Exception
{
MultiException mx = new MultiException();
Consumer> c = h ->
{
try
{
if (!h.isStarted())
{
h.start();
h.initialize();
}
}
catch (Throwable e)
{
LOG.debug("Unable to start {}", h, e);
mx.add(e);
}
};
//Start the listeners so we can call them
_listeners.forEach(c);
//call listeners contextInitialized
if (_contextHandler != null)
_contextHandler.contextInitialized();
//Only set initialized true AFTER the listeners have been called
_initialized = true;
//Start the filters then the servlets
Stream.concat(
_filters.stream(),
_servlets.stream().sorted())
.forEach(c);
mx.ifExceptionThrow();
}
/**
* @return true if initialized has been called, false otherwise
*/
public boolean isInitialized()
{
return _initialized;
}
protected void initializeHolders(Collection extends BaseHolder>> holders)
{
for (BaseHolder> holder : holders)
{
holder.setServletHandler(this);
if (isInitialized())
{
try
{
if (!holder.isStarted())
{
holder.start();
holder.initialize();
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
}
/**
* @return whether the filter chains are cached.
*/
public boolean isFilterChainsCached()
{
return _filterChainsCached;
}
/**
* Add a holder for a listener
*
* @param listener the listener for the holder
*/
public void addListener(ListenerHolder listener)
{
if (listener != null)
setListeners(ArrayUtil.addToArray(getListeners(), listener, ListenerHolder.class));
}
public ListenerHolder[] getListeners()
{
return _listeners.toArray(new ListenerHolder[0]);
}
public void setListeners(ListenerHolder[] holders)
{
List listeners = holders == null ? Collections.emptyList() : Arrays.asList(holders);
initializeHolders(listeners);
updateBeans(_listeners, listeners);
_listeners.clear();
_listeners.addAll(listeners);
}
public ListenerHolder newListenerHolder(Source source)
{
return new ListenerHolder(source);
}
/**
* Add a new servlet holder
*
* @param source the holder source
* @return the servlet holder
*/
public ServletHolder newServletHolder(Source source)
{
return new ServletHolder(source);
}
/**
* Convenience method to add a servlet.
*
* @param className the class name
* @param pathSpec the path spec
* @return The servlet holder.
*/
public ServletHolder addServletWithMapping(String className, String pathSpec)
{
ServletHolder holder = newServletHolder(Source.EMBEDDED);
holder.setClassName(className);
addServletWithMapping(holder, pathSpec);
return holder;
}
/**
* Convenience method to add a servlet.
*
* @param servlet the servlet class
* @param pathSpec the path spec
* @return The servlet holder.
*/
public ServletHolder addServletWithMapping(Class extends Servlet> servlet, String pathSpec)
{
ServletHolder holder = newServletHolder(Source.EMBEDDED);
holder.setHeldClass(servlet);
addServletWithMapping(holder, pathSpec);
return holder;
}
/**
* Convenience method to add a servlet.
*
* @param servlet servlet holder to add
* @param pathSpec servlet mappings for the servletHolder
*/
public void addServletWithMapping(ServletHolder servlet, String pathSpec)
{
Objects.requireNonNull(servlet);
ServletHolder[] holders = getServlets();
try
{
try (AutoLock ignored = lock())
{
if (!containsServletHolder(servlet))
setServlets(ArrayUtil.addToArray(holders, servlet, ServletHolder.class));
}
ServletMapping mapping = new ServletMapping();
mapping.setServletName(servlet.getName());
mapping.setPathSpec(pathSpec);
setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
}
catch (RuntimeException e)
{
setServlets(holders);
throw e;
}
}
/**
* Convenience method to add a pre-constructed ServletHolder.
*
* @param holder the servlet holder
*/
public void addServlet(ServletHolder holder)
{
if (holder == null)
return;
try (AutoLock ignored = lock())
{
if (!containsServletHolder(holder))
setServlets(ArrayUtil.addToArray(getServlets(), holder, ServletHolder.class));
}
}
/**
* Convenience method to add a pre-constructed ServletMapping.
*
* @param mapping the servlet mapping
*/
public void addServletMapping(ServletMapping mapping)
{
setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
}
public Set setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
{
if (_contextHandler != null)
{
return _contextHandler.setServletSecurity(registration, servletSecurityElement);
}
return Collections.emptySet();
}
public FilterHolder newFilterHolder(Source source)
{
return new FilterHolder(source);
}
public FilterHolder getFilter(String name)
{
return _filterNameMap.get(name);
}
/**
* Convenience method to add a filter.
*
* @param filter class of filter to create
* @param pathSpec filter mappings for filter
* @param dispatches see {@link FilterMapping#setDispatches(int)}
* @return The filter holder.
*/
public FilterHolder addFilterWithMapping(Class extends Filter> filter, String pathSpec, EnumSet dispatches)
{
FilterHolder holder = newFilterHolder(Source.EMBEDDED);
holder.setHeldClass(filter);
addFilterWithMapping(holder, pathSpec, dispatches);
return holder;
}
/**
* Convenience method to add a filter.
*
* @param className of filter
* @param pathSpec filter mappings for filter
* @param dispatches see {@link FilterMapping#setDispatches(int)}
* @return The filter holder.
*/
public FilterHolder addFilterWithMapping(String className, String pathSpec, EnumSet dispatches)
{
FilterHolder holder = newFilterHolder(Source.EMBEDDED);
holder.setClassName(className);
addFilterWithMapping(holder, pathSpec, dispatches);
return holder;
}
/**
* Convenience method to add a filter.
*
* @param holder filter holder to add
* @param pathSpec filter mappings for filter
* @param dispatches see {@link FilterMapping#setDispatches(int)}
*/
public void addFilterWithMapping(FilterHolder holder, String pathSpec, EnumSet dispatches)
{
Objects.requireNonNull(holder);
FilterHolder[] holders = getFilters();
try
{
try (AutoLock ignored = lock())
{
if (!containsFilterHolder(holder))
setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
}
FilterMapping mapping = new FilterMapping();
mapping.setFilterName(holder.getName());
mapping.setPathSpec(pathSpec);
mapping.setDispatcherTypes(dispatches);
addFilterMapping(mapping);
}
catch (Throwable e)
{
setFilters(holders);
throw e;
}
}
/**
* Convenience method to add a filter.
*
* @param filter class of filter to create
* @param pathSpec filter mappings for filter
* @param dispatches see {@link FilterMapping#setDispatches(int)}
* @return The filter holder.
*/
public FilterHolder addFilterWithMapping(Class extends Filter> filter, String pathSpec, int dispatches)
{
FilterHolder holder = newFilterHolder(Source.EMBEDDED);
holder.setHeldClass(filter);
addFilterWithMapping(holder, pathSpec, dispatches);
return holder;
}
/**
* Convenience method to add a filter.
*
* @param className of filter
* @param pathSpec filter mappings for filter
* @param dispatches see {@link FilterMapping#setDispatches(int)}
* @return The filter holder.
*/
public FilterHolder addFilterWithMapping(String className, String pathSpec, int dispatches)
{
FilterHolder holder = newFilterHolder(Source.EMBEDDED);
holder.setClassName(className);
addFilterWithMapping(holder, pathSpec, dispatches);
return holder;
}
/**
* Convenience method to add a filter.
*
* @param holder filter holder to add
* @param pathSpec filter mappings for filter
* @param dispatches see {@link FilterMapping#setDispatches(int)}
*/
public void addFilterWithMapping(FilterHolder holder, String pathSpec, int dispatches)
{
Objects.requireNonNull(holder);
FilterHolder[] holders = getFilters();
if (holders != null)
holders = holders.clone();
try
{
try (AutoLock ignored = lock())
{
if (!containsFilterHolder(holder))
setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
}
FilterMapping mapping = new FilterMapping();
mapping.setFilterName(holder.getName());
mapping.setPathSpec(pathSpec);
mapping.setDispatches(dispatches);
addFilterMapping(mapping);
}
catch (Throwable e)
{
setFilters(holders);
throw e;
}
}
/**
* Convenience method to add a filter and mapping
*
* @param filter the filter holder
* @param filterMapping the filter mapping
*/
public void addFilter(FilterHolder filter, FilterMapping filterMapping)
{
if (filter != null)
{
try (AutoLock ignored = lock())
{
if (!containsFilterHolder(filter))
setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
}
}
if (filterMapping != null)
addFilterMapping(filterMapping);
}
/**
* Convenience method to add a preconstructed FilterHolder
*
* @param filter the filter holder
*/
public void addFilter(FilterHolder filter)
{
if (filter == null)
return;
try (AutoLock ignored = lock())
{
if (!containsFilterHolder(filter))
setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
}
}
/**
* Convenience method to add a preconstructed FilterHolder
*
* @param filter the filter holder
*/
public void prependFilter(FilterHolder filter)
{
if (filter == null)
return;
try (AutoLock ignored = lock())
{
if (!containsFilterHolder(filter))
setFilters(ArrayUtil.prependToArray(filter, getFilters(), FilterHolder.class));
}
}
/**
* Convenience method to add a preconstructed FilterMapping
*
* @param mapping the filter mapping
*/
public void addFilterMapping(FilterMapping mapping)
{
if (mapping == null)
return;
try (AutoLock ignored = lock())
{
Source source = (mapping.getFilterHolder() == null ? null : mapping.getFilterHolder().getSource());
if (_filterMappings.isEmpty())
{
_filterMappings.add(mapping);
if (source == Source.JAVAX_API)
_matchAfterIndex = 0;
}
else
{
//there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list.
//If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals),
//but before the first matchAfter filtermapping.
if (Source.JAVAX_API == source)
{
_filterMappings.add(mapping);
if (_matchAfterIndex < 0)
_matchAfterIndex = getFilterMappings().length - 1;
}
else
{
//insert non-programmatic filter mappings before any matchAfters, if any
if (_matchAfterIndex < 0)
_filterMappings.add(mapping);
else
_filterMappings.add(_matchAfterIndex++, mapping);
}
}
addBean(mapping);
if (isRunning())
updateMappings();
invalidateChainsCache();
}
}
/**
* Convenience method to add a preconstructed FilterMapping
*
* @param mapping the filter mapping
*/
public void prependFilterMapping(FilterMapping mapping)
{
if (mapping == null)
return;
try (AutoLock ignored = lock())
{
Source source = (mapping.getFilterHolder() == null ? null : mapping.getFilterHolder().getSource());
if (_filterMappings.isEmpty())
{
_filterMappings.add(mapping);
if (Source.JAVAX_API == source)
_matchBeforeIndex = 0;
}
else
{
if (Source.JAVAX_API == source)
{
//programmatically defined filter mappings are prepended to mapping list in the order
//in which they were defined. In other words, insert this mapping at the tail of the
//programmatically prepended filter mappings, BEFORE the first web.xml defined filter mapping.
if (_matchBeforeIndex < 0)
{
//no programmatically defined prepended filter mappings yet, prepend this one
_matchBeforeIndex = 0;
_filterMappings.add(0, mapping);
}
else
{
_filterMappings.add(1 + _matchBeforeIndex++, mapping);
}
}
else
{
//non programmatically defined, just prepend to list
_filterMappings.add(0, mapping);
}
//adjust matchAfterIndex ptr to take account of the mapping we just prepended
if (_matchAfterIndex >= 0)
++_matchAfterIndex;
}
addBean(mapping);
if (isRunning())
updateMappings();
invalidateChainsCache();
}
}
public void removeFilterHolder(FilterHolder holder)
{
if (holder == null)
return;
try (AutoLock ignored = lock())
{
FilterHolder[] holders = Arrays.stream(getFilters())
.filter(h -> h != holder)
.toArray(FilterHolder[]::new);
setFilters(holders);
}
}
public void removeFilterMapping(FilterMapping mapping)
{
if (mapping == null)
return;
try (AutoLock ignored = lock())
{
FilterMapping[] mappings = Arrays.stream(getFilterMappings())
.filter(m -> m != mapping)
.toArray(FilterMapping[]::new);
setFilterMappings(mappings);
}
}
protected void updateNameMappings()
{
try (AutoLock ignored = lock())
{
// update filter name map
_filterNameMap.clear();
for (FilterHolder filter : _filters)
{
_filterNameMap.put(filter.getName(), filter);
filter.setServletHandler(this);
}
// Map servlet names to holders
_servletNameMap.clear();
// update the maps
for (ServletHolder servlet : _servlets)
{
_servletNameMap.put(servlet.getName(), new MappedServlet(null, servlet));
servlet.setServletHandler(this);
}
}
}
protected PathSpec asPathSpec(String pathSpec)
{
// By default only allow servlet path specs
return new ServletPathSpec(pathSpec);
}
protected void updateMappings()
{
try (AutoLock ignored = lock())
{
// update filter mappings
_filterPathMappings = new ArrayList<>();
_filterNameMappings = new MultiMap<>();
for (FilterMapping filtermapping : _filterMappings)
{
FilterHolder filterHolder = _filterNameMap.get(filtermapping.getFilterName());
if (filterHolder == null)
throw new IllegalStateException("No filter named " + filtermapping.getFilterName());
filtermapping.setFilterHolder(filterHolder);
if (filtermapping.getPathSpecs() != null)
_filterPathMappings.add(filtermapping);
if (filtermapping.getServletNames() != null)
{
String[] names = filtermapping.getServletNames();
for (String name : names)
{
if (name != null)
_filterNameMappings.add(name, filtermapping);
}
}
}
// Reverse filter mappings to apply as wrappers last filter wrapped first
for (Map.Entry> entry : _filterNameMappings.entrySet())
Collections.reverse(entry.getValue());
Collections.reverse(_filterPathMappings);
_wildFilterNameMappings = _filterNameMappings.get("*");
if (_wildFilterNameMappings != null)
Collections.reverse(_wildFilterNameMappings);
// Map servlet paths to holders
PathMappings pm = new PathMappings<>();
//create a map of paths to set of ServletMappings that define that mapping
HashMap> sms = new HashMap<>();
for (ServletMapping servletMapping : _servletMappings)
{
String[] pathSpecs = servletMapping.getPathSpecs();
if (pathSpecs != null)
{
for (String pathSpec : pathSpecs)
{
List mappings = sms.computeIfAbsent(pathSpec, k -> new ArrayList<>());
mappings.add(servletMapping);
}
}
}
//evaluate path to servlet map based on servlet mappings
for (String pathSpec : sms.keySet())
{
//for each path, look at the mappings where it is referenced
//if a mapping is for a servlet that is not enabled, skip it
List mappings = sms.get(pathSpec);
ServletMapping finalMapping = null;
for (ServletMapping mapping : mappings)
{
//Get servlet associated with the mapping and check it is enabled
ServletHolder servletHolder = getServlet(mapping.getServletName());
if (servletHolder == null)
throw new IllegalStateException("No such servlet: " + mapping.getServletName());
//if the servlet related to the mapping is not enabled, skip it from consideration
if (!servletHolder.isEnabled())
continue;
//only accept a default mapping if we don't have any other
if (finalMapping == null)
finalMapping = mapping;
else
{
//already have a candidate - only accept another one
//if the candidate is a default, or we're allowing duplicate mappings
if (finalMapping.isFromDefaultDescriptor())
finalMapping = mapping;
else if (isAllowDuplicateMappings())
{
LOG.warn("Multiple servlets map to path {}: {} and {}, choosing {}", pathSpec, finalMapping.getServletName(), mapping.getServletName(), mapping);
finalMapping = mapping;
}
else
{
//existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error
if (!mapping.isFromDefaultDescriptor())
{
ServletHolder finalMappedServlet = getServlet(finalMapping.getServletName());
throw new IllegalStateException("Multiple servlets map to path " +
pathSpec + ": " +
finalMappedServlet.getName() + "[mapped:" + finalMapping.getSource() + "]," +
mapping.getServletName() + "[mapped:" + mapping.getSource() + "]");
}
}
}
}
if (finalMapping == null)
throw new IllegalStateException("No acceptable servlet mappings for " + pathSpec);
if (LOG.isDebugEnabled())
LOG.debug("Path={}[{}] mapped to servlet={}[{}]",
pathSpec,
finalMapping.getSource(),
finalMapping.getServletName(),
getServlet(finalMapping.getServletName()).getSource());
PathSpec ps = asPathSpec(pathSpec);
MappedServlet mappedServlet = new MappedServlet(ps, getServlet(finalMapping.getServletName()));
pm.put(ps, mappedServlet);
}
_servletPathMap = pm;
// flush filter chain cache
for (int i = _chainCache.length; i-- > 0; )
{
if (_chainCache[i] != null)
_chainCache[i].clear();
}
if (LOG.isDebugEnabled())
{
LOG.debug("filterNameMap={} pathFilters={} servletFilterMap={} servletPathMap={} servletNameMap={}",
_filterNameMap, _filterPathMappings, _filterNameMappings, _servletPathMap, _servletNameMap);
}
}
}
protected void notFound(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (LOG.isDebugEnabled())
LOG.debug("Not Found {}", request.getRequestURI());
if (getHandler() != null)
nextHandle(baseRequest.getPathInContext(), baseRequest, request, response);
}
protected boolean containsFilterHolder(FilterHolder holder)
{
try (AutoLock ignored = lock())
{
return _filters.contains(holder);
}
}
protected boolean containsServletHolder(ServletHolder holder)
{
try (AutoLock ignored = lock())
{
return _servlets.contains(holder);
}
}
/**
* @param filterChainsCached The filterChainsCached to set.
*/
public void setFilterChainsCached(boolean filterChainsCached)
{
_filterChainsCached = filterChainsCached;
}
/**
* @param filterMappings The filterMappings to set.
*/
public void setFilterMappings(FilterMapping[] filterMappings)
{
try (AutoLock ignored = lock())
{
List mappings = filterMappings == null ? Collections.emptyList() : Arrays.asList(filterMappings);
updateAndSet(_filterMappings, mappings);
if (isRunning())
updateMappings();
invalidateChainsCache();
}
}
public void setFilters(FilterHolder[] holders)
{
try (AutoLock ignored = lock())
{
List filters = holders == null ? Collections.emptyList() : Arrays.asList(holders);
initializeHolders(filters);
updateAndSet(_filters, filters);
updateNameMappings();
invalidateChainsCache();
}
}
/**
* @param servletMappings The servletMappings to set.
*/
public void setServletMappings(ServletMapping[] servletMappings)
{
List mappings = servletMappings == null ? Collections.emptyList() : Arrays.asList(servletMappings);
updateAndSet(_servletMappings, mappings);
if (isRunning())
updateMappings();
invalidateChainsCache();
}
/**
* Set Servlets.
*
* @param holders Array of servlets to define
*/
public void setServlets(ServletHolder[] holders)
{
try (AutoLock ignored = lock())
{
List servlets = holders == null ? Collections.emptyList() : Arrays.asList(holders);
initializeHolders(servlets);
updateAndSet(_servlets, servlets);
updateNameMappings();
invalidateChainsCache();
}
}
/**
* @return The maximum entries in a filter chain cache.
*/
public int getMaxFilterChainsCacheSize()
{
return _maxFilterChainsCacheSize;
}
/**
* Set the maximum filter chain cache size.
* Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
* is greater than zero, then the cache is flushed whenever it grows to be this size.
*
* @param maxFilterChainsCacheSize the maximum number of entries in a filter chain cache.
*/
public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
{
_maxFilterChainsCacheSize = maxFilterChainsCacheSize;
}
void destroyServlet(Servlet servlet)
{
if (_contextHandler != null)
_contextHandler.destroyServlet(servlet);
}
void destroyFilter(Filter filter)
{
if (_contextHandler != null)
_contextHandler.destroyFilter(filter);
}
void destroyListener(EventListener listener)
{
if (_contextHandler != null)
_contextHandler.destroyListener(listener);
}
/**
* A mapping of a servlet by pathSpec or by name
*/
public static class MappedServlet
{
private final PathSpec _pathSpec;
private final ServletHolder _servletHolder;
private final ServletPathMapping _servletPathMapping;
MappedServlet(PathSpec pathSpec, ServletHolder servletHolder)
{
_pathSpec = pathSpec;
_servletHolder = servletHolder;
// Create the HttpServletMapping only once if possible.
if (pathSpec instanceof ServletPathSpec)
{
switch (pathSpec.getGroup())
{
case EXACT:
_servletPathMapping = new ServletPathMapping(_pathSpec, _servletHolder.getName(), _pathSpec.getDeclaration());
break;
case ROOT:
_servletPathMapping = new ServletPathMapping(_pathSpec, _servletHolder.getName(), "/");
break;
default:
_servletPathMapping = null;
break;
}
}
else
{
_servletPathMapping = null;
}
}
public PathSpec getPathSpec()
{
return _pathSpec;
}
public ServletHolder getServletHolder()
{
return _servletHolder;
}
public ServletPathMapping getServletPathMapping(String pathInContext, MatchedPath matchedPath)
{
if (_servletPathMapping != null)
return _servletPathMapping;
if (_pathSpec != null)
return new ServletPathMapping(_pathSpec, _servletHolder.getName(), pathInContext, matchedPath);
return null;
}
@Override
public String toString()
{
return String.format("MappedServlet%x{%s->%s}",
hashCode(), _pathSpec == null ? null : _pathSpec.getDeclaration(), _servletHolder);
}
}
@SuppressWarnings("serial")
public static class Default404Servlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
static class Chain implements FilterChain
{
private final FilterHolder _filterHolder;
private final FilterChain _filterChain;
Chain(FilterHolder filter, FilterChain chain)
{
_filterHolder = filter;
_filterChain = chain;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
{
_filterHolder.doFilter(request, response, _filterChain);
}
@Override
public String toString()
{
return String.format("Chain@%x(%s)->%s", hashCode(), _filterHolder, _filterChain);
}
}
static class ChainEnd implements FilterChain
{
private final ServletHolder _servletHolder;
ChainEnd(ServletHolder holder)
{
Objects.requireNonNull(holder);
_servletHolder = holder;
}
public ServletHolder getServletHolder()
{
return _servletHolder;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
{
Request baseRequest = Request.getBaseRequest(request);
Objects.requireNonNull(baseRequest);
_servletHolder.handle(baseRequest, request, response);
}
@Override
public String toString()
{
return String.format("ChainEnd@%x(%s)", hashCode(), _servletHolder);
}
}
}