org.eclipse.jetty.ee9.webapp.WebAppContext Maven / Gradle / Ivy
Show all versions of jetty-ee9-webapp Show documentation
//
// ========================================================================
// 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.ee9.webapp;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.PermissionCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration.Dynamic;
import jakarta.servlet.ServletSecurityElement;
import jakarta.servlet.http.HttpSessionActivationListener;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingListener;
import jakarta.servlet.http.HttpSessionIdListener;
import jakarta.servlet.http.HttpSessionListener;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.ee9.nested.ContextHandler;
import org.eclipse.jetty.ee9.nested.ErrorHandler;
import org.eclipse.jetty.ee9.nested.HandlerWrapper;
import org.eclipse.jetty.ee9.nested.SessionHandler;
import org.eclipse.jetty.ee9.security.ConstraintAware;
import org.eclipse.jetty.ee9.security.ConstraintMapping;
import org.eclipse.jetty.ee9.security.ConstraintSecurityHandler;
import org.eclipse.jetty.ee9.security.SecurityHandler;
import org.eclipse.jetty.ee9.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
import org.eclipse.jetty.ee9.servlet.ServletHandler;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
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.ClassLoaderDump;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Web Application Context Handler.
*
* The WebAppContext handler is an extension of ContextHandler that
* coordinates the construction and configuration of nested handlers:
* {@link ConstraintSecurityHandler}, {@link SessionHandler}
* and {@link ServletHandler}.
* The handlers are configured by pluggable configuration classes, with
* the default being {@link WebXmlConfiguration} and
* {@link JettyWebXmlConfiguration}.
*
*/
@ManagedObject("Web Application ContextHandler")
public class WebAppContext extends ServletContextHandler implements WebAppClassLoader.Context, Deployable
{
static final Logger LOG = LoggerFactory.getLogger(WebAppContext.class);
public static final String WEB_DEFAULTS_XML = "org/eclipse/jetty/ee9/webapp/webdefault-ee9.xml";
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
/**
* @deprecated use {@link WebAppClassLoading#PROTECTED_CLASSES_ATTRIBUTE} instead.
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final String SERVER_SYS_CLASSES = WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE;
/**
* @deprecated use {@link WebAppClassLoading#HIDDEN_CLASSES_ATTRIBUTE} instead.
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final String SERVER_SRV_CLASSES = WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE;
private static final String[] __dftProtectedTargets = {"/WEB-INF", "/META-INF"};
/**
* @deprecated use {@link WebAppClassLoading#DEFAULT_PROTECTED_CLASSES}
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final org.eclipse.jetty.ee9.webapp.ClassMatcher __dftSystemClasses =
new org.eclipse.jetty.ee9.webapp.ClassMatcher(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES);
/**
* @deprecated use {@link WebAppClassLoading#DEFAULT_HIDDEN_CLASSES}
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final org.eclipse.jetty.ee9.webapp.ClassMatcher __dftServerClasses =
new org.eclipse.jetty.ee9.webapp.ClassMatcher(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES);
private final ClassMatcher _systemClasses = new ClassMatcher(WebAppClassLoading.getProtectedClasses(ServletContextHandler.ENVIRONMENT));
private final ClassMatcher _serverClasses = new ClassMatcher(WebAppClassLoading.getHiddenClasses(ServletContextHandler.ENVIRONMENT));
private Configurations _configurations;
private String _defaultsDescriptor = WEB_DEFAULTS_XML;
private String _descriptor = null;
private final List _overrideDescriptors = new ArrayList<>();
private boolean _distributable = false;
private boolean _extractWAR = true;
private boolean _copyDir = false;
private boolean _copyWebInf = false;
private boolean _logUrlOnStart = false;
private boolean _parentLoaderPriority = Boolean.getBoolean("org.eclipse.jetty.server.webapp.parentLoaderPriority");
private PermissionCollection _permissions;
private String[] _contextWhiteList = null;
private String _war;
private List _extraClasspath;
private Throwable _unavailableException;
private Map _resourceAliases;
private ClassLoader _initialClassLoader;
private boolean _configurationDiscovered = true;
private boolean _allowDuplicateFragmentNames = false;
private boolean _throwUnavailableOnStartupException = false;
private MetaData _metadata = new MetaData();
private boolean _defaultContextPath = true;
public static WebAppContext getCurrentWebAppContext()
{
ContextHandler.APIContext context = ContextHandler.getCurrentContext();
if (context != null)
{
ContextHandler handler = context.getContextHandler();
if (handler instanceof WebAppContext)
return (WebAppContext)handler;
}
return null;
}
public WebAppContext()
{
this(null, null, null, null, null, new ErrorPageErrorHandler(), SESSIONS | SECURITY);
}
/**
* @param contextPath The context path
* @param webApp The URL or filename of the webapp directory or war file.
*/
public WebAppContext(String webApp, String contextPath)
{
this(null, contextPath, null, null, null, new ErrorPageErrorHandler(), SESSIONS | SECURITY);
setWar(webApp);
}
/**
* @param contextPath The context path
* @param webApp The URL or filename of the webapp directory or war file.
*/
public WebAppContext(Resource webApp, String contextPath)
{
this(null, contextPath, null, null, null, new ErrorPageErrorHandler(), SESSIONS | SECURITY);
setWarResource(webApp);
}
/**
* @param parent The parent HandlerContainer.
* @param contextPath The context path
* @param webApp The URL or filename of the webapp directory or war file.
*/
public WebAppContext(Handler.Container parent, String webApp, String contextPath)
{
this(parent, contextPath, null, null, null, new ErrorPageErrorHandler(), SESSIONS | SECURITY);
setWar(webApp);
}
/**
* @param parent The parent HandlerContainer.
* @param contextPath The context path
* @param webApp The webapp directory or war file.
*/
public WebAppContext(Handler.Container parent, Resource webApp, String contextPath)
{
this(parent, contextPath, null, null, null, new ErrorPageErrorHandler(), SESSIONS | SECURITY);
setWarResource(webApp);
}
/**
* This constructor is used in the geronimo integration.
*
* @param sessionHandler SessionHandler for this web app
* @param securityHandler SecurityHandler for this web app
* @param servletHandler ServletHandler for this web app
* @param errorHandler ErrorHandler for this web app
*/
public WebAppContext(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
{
this(null, null, sessionHandler, securityHandler, servletHandler, errorHandler, 0);
}
/**
* This constructor is used in the geronimo integration.
*
* @param parent the parent handler
* @param contextPath the context path
* @param sessionHandler SessionHandler for this web app
* @param securityHandler SecurityHandler for this web app
* @param servletHandler ServletHandler for this web app
* @param errorHandler ErrorHandler for this web app
* @param options the options ({@link ServletContextHandler#SESSIONS} and/or {@link ServletContextHandler#SECURITY})
*/
public WebAppContext(Handler.Container parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler, int options)
{
// always pass parent as null and then set below, so that any resulting setServer call
// is done after this instance is constructed.
super(null, contextPath, sessionHandler, securityHandler, servletHandler, errorHandler, options);
_apiContext = new Context();
setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler());
setProtectedTargets(__dftProtectedTargets);
HandlerWrapper.setAsParent(parent, this.get());
}
@Override
public void initializeDefaults(Map properties)
{
for (String property : properties.keySet())
{
String value = properties.get(property);
if (LOG.isDebugEnabled())
LOG.debug("init {}: {}", property, value);
switch (property)
{
case Deployable.WAR -> setWar(value);
case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value));
case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses(value == null ? null : value.split(","));
case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value);
case Deployable.EXTRACT_WARS -> setExtractWAR(Boolean.parseBoolean(value));
case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority(Boolean.parseBoolean(value));
case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value);
case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor(value);
case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value);
case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value);
default ->
{
if (LOG.isDebugEnabled() && StringUtil.isNotBlank(value))
LOG.debug("unknown property {}={}", property, value);
}
}
}
_defaultContextPath = true;
}
public boolean isContextPathDefault()
{
return _defaultContextPath;
}
@Override
public void setContextPath(String contextPath)
{
super.setContextPath(contextPath);
_defaultContextPath = false;
}
public void setDefaultContextPath(String contextPath)
{
super.setContextPath(contextPath);
_defaultContextPath = true;
}
/**
* @param displayName The servletContextName to set.
*/
@Override
public void setDisplayName(String displayName)
{
super.setDisplayName(displayName);
ClassLoader cl = getClassLoader();
if (cl instanceof WebAppClassLoader && displayName != null)
((WebAppClassLoader)cl).setName(displayName);
}
/**
* Get an exception that caused the webapp to be unavailable
*
* @return A throwable if the webapp is unavailable or null
*/
public Throwable getUnavailableException()
{
return _unavailableException;
}
/**
* Set Resource Alias.
* Resource aliases map resource uri's within a context.
* They may optionally be used by a handler when looking for
* a resource.
*
* @param alias the alias for a resource
* @param uri the uri for the resource
*/
public void setResourceAlias(String alias, String uri)
{
if (_resourceAliases == null)
_resourceAliases = new HashMap<>(5);
_resourceAliases.put(alias, uri);
}
public Map getResourceAliases()
{
if (_resourceAliases == null)
return null;
return _resourceAliases;
}
public void setResourceAliases(Map map)
{
_resourceAliases = map;
}
public String getResourceAlias(String path)
{
if (_resourceAliases == null)
return null;
String alias = _resourceAliases.get(path);
int slash = path.length();
while (alias == null)
{
slash = path.lastIndexOf("/", slash - 1);
if (slash < 0)
break;
String match = _resourceAliases.get(path.substring(0, slash + 1));
if (match != null)
alias = match + path.substring(slash + 1);
}
return alias;
}
public String removeResourceAlias(String alias)
{
if (_resourceAliases == null)
return null;
return _resourceAliases.remove(alias);
}
@Override
public void setClassLoader(ClassLoader classLoader)
{
super.setClassLoader(classLoader);
String name = getDisplayName();
if (name == null)
name = getContextPath();
if (classLoader instanceof WebAppClassLoader && getDisplayName() != null)
((WebAppClassLoader)classLoader).setName(name);
}
public ResourceFactory getResourceFactory()
{
return ResourceFactory.of(this);
}
@Override
public Resource getResource(String pathInContext) throws MalformedURLException
{
if (pathInContext == null || !pathInContext.startsWith("/"))
throw new MalformedURLException(pathInContext);
MalformedURLException mue = null;
Resource resource = null;
int loop = 0;
while (pathInContext != null && loop++ < 100)
{
try
{
resource = super.getResource(pathInContext);
if (Resources.exists(resource))
return resource;
pathInContext = getResourceAlias(pathInContext);
}
catch (MalformedURLException e)
{
LOG.trace("IGNORED", e);
if (mue == null)
mue = e;
}
}
if (mue != null)
throw mue;
return resource;
}
/**
* Is the context Automatically configured.
*
* @return true if configuration discovery.
*/
public boolean isConfigurationDiscovered()
{
return _configurationDiscovered;
}
/**
* Set the configuration discovery mode.
* If configuration discovery is set to true, then the JSR315
* servlet 3.0 discovered configuration features are enabled.
* These are:
* - Web Fragments
* - META-INF/resource directories
*
*
* @param discovered true if configuration discovery is enabled for automatic configuration from the context
*/
public void setConfigurationDiscovered(boolean discovered)
{
_configurationDiscovered = discovered;
}
/**
* Pre configure the web application.
*
* The method is normally called from {@link #start()}. It performs
* the discovery of the configurations to be applied to this context,
* specifically:
*
* - Instantiate the {@link Configuration} instances with a call to {@link #loadConfigurations()}.
*
- Instantiates a classloader (if one is not already set)
*
- Calls the {@link Configuration#preConfigure(WebAppContext)} method of all
* Configuration instances.
*
*
* @throws Exception if unable to pre configure
*/
public void preConfigure() throws Exception
{
// Add the known server class inclusions for all known configurations
for (Configuration configuration : Configurations.getKnown())
{
_serverClasses.include(configuration.getServerClasses().getInclusions());
}
// Setup Configuration classes for this webapp!
loadConfigurations();
_configurations.sort();
for (Configuration configuration : _configurations)
{
_systemClasses.add(configuration.getSystemClasses().getPatterns());
_serverClasses.exclude(configuration.getServerClasses().getExclusions());
}
// Configure classloader
_initialClassLoader = getClassLoader();
ClassLoader loader = configureClassLoader(_initialClassLoader);
if (loader != _initialClassLoader)
setClassLoader(loader);
if (LOG.isDebugEnabled())
{
LOG.debug("Thread Context classloader {}", loader);
loader = loader.getParent();
while (loader != null)
{
LOG.debug("Parent class loader: {} ", loader);
loader = loader.getParent();
}
}
_configurations.preConfigure(this);
}
/**
* Configure the context {@link ClassLoader}, potentially wrapping it.
* @param loader The loader initially set on this context by {@link #setClassLoader(ClassLoader)}
* @return Either the configured loader, or a new {@link ClassLoader} that uses the loader.
*/
protected ClassLoader configureClassLoader(ClassLoader loader) throws IOException
{
if (loader instanceof WebAppClassLoader)
return loader;
return new WebAppClassLoader(loader, this);
}
public boolean configure() throws Exception
{
return _configurations.configure(this);
}
public void postConfigure() throws Exception
{
_configurations.postConfigure(this);
}
@Override
protected void doStart() throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
try
{
_metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
Boolean validate = (Boolean)getAttribute(MetaData.VALIDATE_XML);
_metadata.setValidateXml((validate != null && validate));
wrapConfigurations();
preConfigure();
Thread.currentThread().setContextClassLoader(getClassLoader());
super.doStart();
postConfigure();
if (isLogUrlOnStart())
dumpUrl();
}
catch (Throwable t)
{
// start up of the webapp context failed, make sure it is not started
LOG.warn("Failed startup of context {}", this, t);
_unavailableException = t;
setAvailable(false); // webapp cannot be accessed (results in status code 503)
if (isThrowUnavailableOnStartupException())
throw t;
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
private void wrapConfigurations()
{
Collection wrappers = getBeans(Configuration.WrapperFunction.class);
if (wrappers == null || wrappers.isEmpty())
return;
List configs = new ArrayList<>(_configurations.getConfigurations());
_configurations.clear();
for (Configuration config : configs)
{
Configuration wrapped = config;
for (Configuration.WrapperFunction wrapperFunction : getBeans(Configuration.WrapperFunction.class))
{
wrapped = wrapperFunction.wrapConfiguration(wrapped);
}
_configurations.add(wrapped);
}
}
@Override
public void destroy()
{
// Prepare for configuration
Throwable multiException = null;
if (_configurations != null)
{
for (Configuration configuration : _configurations)
{
try
{
configuration.destroy(this);
}
catch (Exception e)
{
multiException = ExceptionUtil.combine(multiException, e);
}
}
}
_configurations = null;
super.destroy();
ExceptionUtil.ifExceptionThrowUnchecked(multiException);
}
/*
* Dumps the current web app name and URL to the log
*/
private void dumpUrl()
{
Connector[] connectors = getServer().getConnectors();
for (int i = 0; i < connectors.length; i++)
{
String displayName = getDisplayName();
if (displayName == null)
displayName = "WebApp@" + Arrays.hashCode(connectors);
LOG.info("{} at http://{}{}", displayName, connectors[i].toString(), getContextPath());
}
}
/**
* @return Returns the configurations.
*/
@ManagedAttribute(value = "configuration classes used to configure webapp", readonly = true)
public String[] getConfigurationClasses()
{
loadConfigurations();
return _configurations.toArray();
}
/**
* @return Returns the configurations.
*/
public Configurations getConfigurations()
{
loadConfigurations();
return _configurations;
}
/**
* The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
*
* @return Returns the defaultsDescriptor.
*/
@ManagedAttribute(value = "default web.xml deascriptor applied before standard web.xml", readonly = true)
public String getDefaultsDescriptor()
{
return _defaultsDescriptor;
}
/**
* The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
*
* @return Returns the Override Descriptor.
*/
public String getOverrideDescriptor()
{
if (_overrideDescriptors.size() != 1)
return null;
return _overrideDescriptors.get(0);
}
/**
* An override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
*
* @return Returns the Override Descriptor list
*/
@ManagedAttribute(value = "web.xml deascriptors applied after standard web.xml", readonly = true)
public List getOverrideDescriptors()
{
return Collections.unmodifiableList(_overrideDescriptors);
}
/**
* @return Returns the permissions.
*/
@Override
public PermissionCollection getPermissions()
{
return _permissions;
}
/**
* Set the server classes patterns.
*
* These classes/packages are used to implement the server and are hidden
* from the context. If the context needs to load these classes, it must have its
* own copy of them in WEB-INF/lib or WEB-INF/classes.
*
* @param serverClasses the server classes pattern
*/
public void setServerClassMatcher(ClassMatcher serverClasses)
{
_serverClasses.clear();
_serverClasses.add(serverClasses.getPatterns());
}
/**
* Set the system classes patterns.
*
* These classes/packages are provided by the JVM and
* cannot be replaced by classes of the same name from WEB-INF,
* regardless of the value of {@link #setParentLoaderPriority(boolean)}.
*
* @param systemClasses the system classes pattern
*/
public void setSystemClassMatcher(ClassMatcher systemClasses)
{
_systemClasses.clear();
_systemClasses.add(systemClasses.getPatterns());
}
/**
* Add a ClassMatcher for server classes by combining with
* any existing matcher.
*
* @param serverClasses The class matcher of patterns to add to the server ClassMatcher
*/
public void addServerClassMatcher(ClassMatcher serverClasses)
{
_serverClasses.add(serverClasses.getPatterns());
}
/**
* Add a ClassMatcher for system classes by combining with
* any existing matcher.
*
* @param systemClasses The class matcher of patterns to add to the system ClassMatcher
*/
public void addSystemClassMatcher(ClassMatcher systemClasses)
{
_systemClasses.add(systemClasses.getPatterns());
}
/**
* @return The ClassMatcher used to match System (protected) classes
*/
public ClassMatcher getSystemClassMatcher()
{
return _systemClasses;
}
/**
* @return The ClassMatcher used to match Server (hidden) classes
*/
public ClassMatcher getServerClassMatcher()
{
return _serverClasses;
}
@ManagedAttribute(value = "classes and packages protected by context classloader", readonly = true)
public String[] getSystemClasses()
{
return _systemClasses.getPatterns();
}
@ManagedAttribute(value = "classes and packages hidden by the context classloader", readonly = true)
public String[] getServerClasses()
{
return _serverClasses.getPatterns();
}
@Override
public boolean isHiddenClass(Class> clazz)
{
return isServerClass(clazz);
}
@Override
public boolean isProtectedClass(Class> clazz)
{
return isSystemClass(clazz);
}
@Override
public boolean isServerClass(Class> clazz)
{
return _serverClasses.match(clazz);
}
@Override
public boolean isSystemClass(Class> clazz)
{
return _systemClasses.match(clazz);
}
@Override
public boolean isServerResource(String name, URL url)
{
return _serverClasses.match(name, url);
}
@Override
public boolean isSystemResource(String name, URL url)
{
return _systemClasses.match(name, url);
}
@Override
public void setServer(Server server)
{
super.setServer(server);
if (server != null)
{
_systemClasses.add(WebAppClassLoading.getProtectedClasses(server).getPatterns());
_serverClasses.add(WebAppClassLoading.getHiddenClasses(server).getPatterns());
}
}
/**
* @return Returns the war as a file or URL string (Resource).
* The war may be different to the @link {@link #getResourceBase()}
* if the war has been expanded and/or copied.
*/
@ManagedAttribute(value = "war file location", readonly = true)
public String getWar()
{
if (_war == null)
_war = getResourceBase();
return _war;
}
public Resource getWebInf() throws IOException
{
if (super.getBaseResource() == null)
return null;
// Is there a WEB-INF directory anywhere in the Resource Base?
// ResourceBase could be a ResourceCollection
// The result could be a ResourceCollection with multiple WEB-INF directories
// Can return from WEB-INF/lib/foo.jar!/WEB-INF
// Can also never return from a META-INF/versions/#/WEB-INF location
Resource webInf = super.getBaseResource().resolve("WEB-INF/");
if (Resources.isReadableDirectory(webInf))
return webInf;
return null;
}
/**
* @return Returns the distributable.
*/
@ManagedAttribute("web application distributable")
public boolean isDistributable()
{
return _distributable;
}
/**
* @return Returns the extractWAR.
*/
@ManagedAttribute(value = "extract war", readonly = true)
public boolean isExtractWAR()
{
return _extractWAR;
}
/**
* @return True if the webdir is copied (to allow hot replacement of jars on windows)
*/
@ManagedAttribute(value = "webdir copied on deploy (allows hot replacement on windows)", readonly = true)
public boolean isCopyWebDir()
{
return _copyDir;
}
/**
* @return True if the web-inf lib and classes directories are copied (to allow hot replacement of jars on windows)
*/
public boolean isCopyWebInf()
{
return _copyWebInf;
}
/**
* @return True if the classloader should delegate first to the parent
* classloader (standard java behaviour) or false if the classloader
* should first try to load from WEB-INF/lib or WEB-INF/classes (servlet
* spec recommendation). Default is false or can be set by the system
* property org.eclipse.jetty.server.webapp.parentLoaderPriority
*/
@Override
@ManagedAttribute(value = "parent classloader given priority", readonly = true)
public boolean isParentLoaderPriority()
{
return _parentLoaderPriority;
}
protected void loadConfigurations()
{
//if the configuration instances have been set explicitly, use them
if (_configurations != null)
return;
if (isStarted())
throw new IllegalStateException();
_configurations = newConfigurations();
}
protected Configurations newConfigurations()
{
Configurations configurations = new Configurations();
configurations.add(Configurations.getServerDefault(getServer()).toArray());
return configurations;
}
@Override
public String toString()
{
if (_war != null)
return super.toString() + "{" + _war + "}";
return super.toString();
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
List systemClasses = null;
if (_systemClasses != null)
{
systemClasses = new ArrayList<>(_systemClasses);
Collections.sort(systemClasses);
}
List serverClasses = null;
if (_serverClasses != null)
{
serverClasses = new ArrayList<>(_serverClasses);
Collections.sort(serverClasses);
}
String name = getDisplayName();
if (name == null)
{
if (_war != null)
{
int webapps = _war.indexOf("/webapps/");
if (webapps >= 0)
name = _war.substring(webapps + 8);
else
name = _war;
}
else if (getResourceBase() != null)
{
name = getResourceBase();
int webapps = name.indexOf("/webapps/");
if (webapps >= 0)
name = name.substring(webapps + 8);
}
else
{
name = this.getClass().getSimpleName();
}
}
name = String.format("%s@%x", name, hashCode());
dumpObjects(out, indent,
Dumpable.named("environment", ContextHandler.ENVIRONMENT.getName()),
new ClassLoaderDump(getClassLoader()),
new DumpableCollection("Systemclasses " + name, systemClasses),
new DumpableCollection("Serverclasses " + name, serverClasses),
new DumpableCollection("Configurations " + name, _configurations),
new DumpableCollection("Handler attributes " + name, getAttributes().asAttributeMap().entrySet()),
new DumpableCollection("Context attributes " + name, getServletContext().getContextHandler().asAttributeMap().entrySet()),
new DumpableCollection("Environment attributes " + name, ContextHandler.ENVIRONMENT.asAttributeMap().entrySet()),
new DumpableCollection("EventListeners " + this, getEventListeners()),
new DumpableCollection("Initparams " + name, getInitParams().entrySet())
);
}
/**
* @param configurations The configuration class names. If setConfigurations is not called
* these classes are used to create a configurations array.
*/
public void setConfigurationClasses(String[] configurations)
{
if (_configurations == null)
_configurations = new Configurations();
_configurations.set(configurations);
}
public void setConfigurationClasses(List configurations)
{
setConfigurationClasses(configurations.toArray(new String[0]));
}
/**
* @param configurations The configurations to set.
*/
public void setConfigurations(Configuration[] configurations)
{
if (_configurations == null)
_configurations = new Configurations();
_configurations.set(configurations);
}
public void addConfiguration(Configuration... configuration)
{
loadConfigurations();
_configurations.add(configuration);
}
public T getConfiguration(Class extends T> configClass)
{
loadConfigurations();
return _configurations.get(configClass);
}
public void removeConfiguration(Configuration... configurations)
{
if (_configurations != null)
_configurations.remove(configurations);
}
public void removeConfiguration(Class extends Configuration>... configurations)
{
if (_configurations != null)
_configurations.remove(configurations);
}
/**
* The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
*
* @param defaultsDescriptor The defaultsDescriptor to set.
*/
public void setDefaultsDescriptor(String defaultsDescriptor)
{
_defaultsDescriptor = defaultsDescriptor;
}
/**
* The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
*
* @param overrideDescriptor The overrideDescritpor to set.
*/
public void setOverrideDescriptor(String overrideDescriptor)
{
_overrideDescriptors.clear();
_overrideDescriptors.add(overrideDescriptor);
}
/**
* The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
*
* @param overrideDescriptors The overrideDescriptors (file or URL) to set.
*/
public void setOverrideDescriptors(List overrideDescriptors)
{
_overrideDescriptors.clear();
_overrideDescriptors.addAll(overrideDescriptors);
}
/**
* The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
*
* @param overrideDescriptor The overrideDescriptor (file or URL) to add.
*/
public void addOverrideDescriptor(String overrideDescriptor)
{
_overrideDescriptors.add(overrideDescriptor);
}
/**
* @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
*/
@ManagedAttribute(value = "standard web.xml descriptor", readonly = true)
public String getDescriptor()
{
return _descriptor;
}
/**
* Set the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists..
* @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
*/
public void setDescriptor(String descriptor)
{
_descriptor = descriptor;
}
/**
* @param distributable The distributable to set.
*/
public void setDistributable(boolean distributable)
{
this._distributable = distributable;
}
@Override
public boolean removeEventListener(EventListener listener)
{
if (super.removeEventListener(listener))
{
if ((listener instanceof HttpSessionActivationListener) ||
(listener instanceof HttpSessionAttributeListener) ||
(listener instanceof HttpSessionBindingListener) ||
(listener instanceof HttpSessionListener) ||
(listener instanceof HttpSessionIdListener))
{
if (_sessionHandler != null)
_sessionHandler.removeEventListener(listener);
}
return true;
}
return false;
}
/**
* @param extractWAR True if war files are extracted
*/
public void setExtractWAR(boolean extractWAR)
{
_extractWAR = extractWAR;
}
/**
* @param copy True if the webdir is copied (to allow hot replacement of jars)
*/
public void setCopyWebDir(boolean copy)
{
_copyDir = copy;
}
/**
* @param copyWebInf True if the web-inf lib and classes directories are copied (to allow hot replacement of jars on windows)
*/
public void setCopyWebInf(boolean copyWebInf)
{
_copyWebInf = copyWebInf;
}
/**
* @param java2compliant True if the classloader should delegate first to the parent
* classloader (standard java behaviour) or false if the classloader
* should first try to load from WEB-INF/lib or WEB-INF/classes (servlet
* spec recommendation). Default is false or can be set by the system
* property org.eclipse.jetty.server.webapp.parentLoaderPriority
*/
public void setParentLoaderPriority(boolean java2compliant)
{
_parentLoaderPriority = java2compliant;
}
/**
* @param permissions The permissions to set.
*/
public void setPermissions(PermissionCollection permissions)
{
_permissions = permissions;
}
/**
* Set the context white list
*
* In certain circumstances you want may want to deny access of one webapp from another
* when you may not fully trust the webapp. Setting this white list will enable a
* check when a servlet called {@link ServletContextHandler.ServletAPIContext#getContext(String)}, validating that the uriInPath
* for the given webapp has been declaratively allows access to the context.
*
* @param contextWhiteList the whitelist of contexts for {@link ServletContextHandler.ServletAPIContext#getContext(String)}
*/
public void setContextWhiteList(String... contextWhiteList)
{
_contextWhiteList = contextWhiteList;
}
/**
* Set temporary directory for context.
* The {@value jakarta.servlet.ServletContext#TEMPDIR} attribute is also set.
*
* @param dir Writable temporary directory.
*/
public void setTempDirectory(File dir)
{
getCoreContextHandler().setTempDirectory(dir);
}
@ManagedAttribute(value = "temporary directory location", readonly = true)
public File getTempDirectory()
{
return getCoreContextHandler().getTempDirectory();
}
protected void makeTempDirectory() throws Exception
{
getCoreContextHandler().makeTempDirectory();
}
protected String getCanonicalNameForTmpDir()
{
return getCoreContextHandler().getCanonicalNameForTmpDir();
}
@Override
public Resource getNestedResourceForTempDirName()
{
Resource resource = super.getNestedResourceForTempDirName();
if (resource == null)
{
if (getWar() == null || getWar().length() == 0)
throw new IllegalStateException("No resourceBase or war set for context");
// Use name of given resource in the temporary dirname
resource = getResourceFactory().newResource(getWar());
}
return resource;
}
/**
* If true the temp directory for this
* webapp will be kept when the webapp stops. Otherwise,
* it will be deleted.
*
* @param persist true to persist the temp directory on shutdown / exit of the webapp
*/
public void setPersistTempDirectory(boolean persist)
{
getCoreContextHandler().setTempDirectoryPersistent(persist);
}
/**
* @return true if tmp directory will persist between startups of the webapp
*/
public boolean isPersistTempDirectory()
{
return getCoreContextHandler().isTempDirectoryPersistent();
}
/**
* Set the war of the webapp. From this value a {@link #setResourceBase(String)}
* value is computed by {@link WebInfConfiguration}, which may be changed from
* the war URI by unpacking and/or copying.
*
* @param war The war to set as a file name or URL.
*/
public void setWar(String war)
{
_war = war;
}
/**
* Set the war of the webapp as a {@link Resource}.
*
* @param war The war to set as a Resource.
* @see #setWar(String)
*/
public void setWarResource(Resource war)
{
setWar(war == null ? null : war.toString());
}
/**
* @return Comma or semicolon separated path of filenames or URLs
* pointing to directories or jar files. Directories should end
* with '/'.
*/
@Override
@ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
public List getExtraClasspath()
{
return _extraClasspath == null ? Collections.emptyList() : _extraClasspath;
}
/**
* Set the Extra ClassPath via delimited String.
*
* This is a convenience method for {@link #setExtraClasspath(List)} )}
*
*
* @param extraClasspath Comma or semicolon separated path of filenames or URLs
* pointing to directories or jar files. Directories should end
* with '/'.
* @throws IOException if unable to resolve the resources referenced
* @see #setExtraClasspath(List)
*/
public void setExtraClasspath(String extraClasspath) throws IOException
{
setExtraClasspath(getResourceFactory().split(extraClasspath));
}
public void setExtraClasspath(List extraClasspath)
{
_extraClasspath = extraClasspath;
}
public boolean isLogUrlOnStart()
{
return _logUrlOnStart;
}
/**
* Sets whether or not the web app name and URL is logged on startup
*
* @param logOnStart whether or not the log message is created
*/
public void setLogUrlOnStart(boolean logOnStart)
{
this._logUrlOnStart = logOnStart;
}
public boolean isAllowDuplicateFragmentNames()
{
return _allowDuplicateFragmentNames;
}
public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames)
{
_allowDuplicateFragmentNames = allowDuplicateFragmentNames;
}
public void setThrowUnavailableOnStartupException(boolean throwIfStartupException)
{
_throwUnavailableOnStartupException = throwIfStartupException;
}
public boolean isThrowUnavailableOnStartupException()
{
return _throwUnavailableOnStartupException;
}
@Override
protected void startContext()
throws Exception
{
if (configure())
{
//resolve the metadata
_metadata.resolve(this);
startWebapp();
}
}
@Override
protected void stopContext() throws Exception
{
stopWebapp();
try
{
for (int i = _configurations.size(); i-- > 0; )
{
_configurations.get(i).deconfigure(this);
}
if (_metadata != null)
_metadata.clear();
_metadata = new MetaData();
}
finally
{
ClassLoader loader = getClassLoader();
if (loader != _initialClassLoader)
{
if (loader instanceof URLClassLoader urlClassLoader)
urlClassLoader.close();
setClassLoader(_initialClassLoader);
}
_unavailableException = null;
}
}
/**
* Continue the {@link #startContext()} before calling {@code super.startContext()}.
* @throws Exception If there was a problem starting
*/
protected void startWebapp() throws Exception
{
super.startContext();
}
/**
* Continue the {@link #stopContext()} before calling {@code super.stopContext()}.
* @throws Exception If there was a problem stopping
*/
protected void stopWebapp() throws Exception
{
super.stopContext();
}
@Override
public Set setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
{
Set unchangedURLMappings = new HashSet<>();
//From javadoc for ServletSecurityElement:
/*
If a URL pattern of this ServletRegistration is an exact target of a security-constraint that
was established via the portable deployment descriptor, then this method does not change the
security-constraint for that pattern, and the pattern will be included in the return value.
If a URL pattern of this ServletRegistration is an exact target of a security constraint
that was established via the ServletSecurity annotation or a previous call to this method,
then this method replaces the security constraint for that pattern.
If a URL pattern of this ServletRegistration is neither the exact target of a security constraint
that was established via the ServletSecurity annotation or a previous call to this method,
nor the exact target of a security-constraint in the portable deployment descriptor, then
this method establishes the security constraint for that pattern from the argument ServletSecurityElement.
*/
Collection pathMappings = registration.getMappings();
if (pathMappings != null)
{
ConstraintSecurityHandler.createConstraint(registration.getName(), servletSecurityElement);
for (String pathSpec : pathMappings)
{
Origin origin = getMetaData().getOrigin("constraint.url." + pathSpec);
switch (origin)
{
case NotSet:
{
//No mapping for this url already established
List mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
for (ConstraintMapping m : mappings)
{
((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
}
((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
getMetaData().setOriginAPI("constraint.url." + pathSpec);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
case WebFragment:
{
//a mapping for this url was created in a descriptor, which overrides everything
unchangedURLMappings.add(pathSpec);
break;
}
case Annotation:
case API:
{
//mapping established via an annotation or by previous call to this method,
//replace the security constraint for this pattern
List constraintMappings = ConstraintSecurityHandler.removeConstraintMappingsForPath(pathSpec, ((ConstraintAware)getSecurityHandler()).getConstraintMappings());
List freshMappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
constraintMappings.addAll(freshMappings);
((ConstraintSecurityHandler)getSecurityHandler()).setConstraintMappings(constraintMappings);
((ConstraintAware)getSecurityHandler()).checkPathsWithUncoveredHttpMethods();
break;
}
default:
throw new IllegalStateException(origin.toString());
}
}
}
return unchangedURLMappings;
}
public class Context extends ServletContextHandler.ServletAPIContext
{
@Override
public void checkListener(Class extends EventListener> listener) throws IllegalStateException
{
try
{
super.checkListener(listener);
}
catch (IllegalArgumentException e)
{
// not one of the standard servlet listeners, check our extended session listener types
throw new IllegalArgumentException("Inappropriate listener type " + listener.getName());
}
}
@Override
public URL getResource(String path) throws MalformedURLException
{
if (path == null)
return null;
// Assumption is that the resource base has been properly setup.
// Spec requirement is that the WAR file is interrogated first.
// If a WAR file is mounted, or is extracted to a temp directory,
// then the first entry of the resource base must be the WAR file.
Resource resource = WebAppContext.this.getResource(path);
if (Resources.missing(resource))
return null;
for (Resource r: resource)
{
// return first entry
if (Resources.exists(r))
return r.getURI().toURL();
}
// A Resource was returned, but did not exist
return null;
}
@Override
public ServletContext getContext(String uripath)
{
ServletContext servletContext = super.getContext(uripath);
if (servletContext != null && _contextWhiteList != null)
{
for (String context : _contextWhiteList)
{
if (context.equals(uripath))
{
return servletContext;
}
}
return null;
}
else
{
return servletContext;
}
}
}
public MetaData getMetaData()
{
return _metadata;
}
/**
* Add a Server Class pattern to use for all ee9 WebAppContexts.
* @param attributes The {@link Server} instance to add classes to
* @param patterns the patterns to use
* @see #getServerClassMatcher()
* @see #getServerClasses()
* @deprecated use {@link WebAppClassLoading#addProtectedClasses(Server, String...)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public static void addServerClasses(Attributes attributes, String... patterns)
{
WebAppClassLoading.addHiddenClasses(attributes, patterns);
}
/**
* Add a System Class pattern to use for all ee9 WebAppContexts.
* @param attributes The {@link Server} instance to add classes to
* @param patterns the patterns to use
* @see #getSystemClassMatcher()
* @see #getSystemClasses()
* @deprecated use {@link WebAppClassLoading#addHiddenClasses(Server, String...)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public static void addSystemClasses(Attributes attributes, String... patterns)
{
WebAppClassLoading.addProtectedClasses(attributes, patterns);
}
}