Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-4340-ServiceLoader

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2020-03-13 17:02:26 +11:00
commit a6b2b3ff98
133 changed files with 4588 additions and 1804 deletions

View File

@ -355,6 +355,40 @@ jetty-10.0.0-alpha0 - 11 July 2019
+ 3849 ClosedChannelException from jetty-test-webapp javax websocket chat
example
jetty-9.4.27.v20200227 - 27 February 2020
+ 3247 Generate jetty-maven-plugin website
+ 4247 Cookie security attributes are going to mandated by Google Chrome
+ 4360 Upgrade to Apache Jasper 8.5.49
+ 4475 WebSocket JSR356 implementation not honoring javadoc of MessageHandler
on Whole<Reader>
+ 4495 Review ReservedThreadExecutor's concurrency model
+ 4504 X-Forwarded-Server header overwrites X-Forwarded-Host
+ 4520 Jetty jdbc session manager causing exceptions for violating primary key
in inserting session in the table
+ 4529 ErrorHandler showing servlet info, can not be disabled unless
overriding most of its functionality
+ 4533 Reinstate hard close in dispatcher
+ 4537 High CPU on Jetty Websocket thread
+ 4541 Jetty server always allocates maximum response header size
+ 4550 XmlConfiguration constructor selection based on number of arguments
+ 4567 Jetty logging supporting Throwable as last argument
+ 4573 Order dependency of X-Forwarded-Host and X-Forwarded-Port
+ 4575 Stopping ReservedThreadExecutor may hang
+ 4577 request getPathInfo returns null
+ 4594 ServletContextListeners added to destroyServletContextListeners in
ContextHandler::startContext() are not removed by
ContextHandler::removeEventListener()
+ 4606 DateCache.formatNow(long now) does not honor the passed in long
+ 4612 ReservedThreadExecutor hangs when the last reserved thread idles out
jetty-9.4.26.v20200117 - 17 January 2020
+ 2620 Exception from user endpoint onClose results in unclosed
WebSocketSession
+ 4383 Errors deleting multipart tmp files java.lang.NullPointerException
under heavy load
+ 4444 TLS Connection Timeout Intermittently
+ 4461 IllegalStateException in HttpOutput with Jersey
jetty-9.4.25.v20191220 - 20 December 2019
+ 995 UrlEncoded.encodeString should skip more characters
+ 2195 Add parameter expansion to start.jar --exec parameters

View File

@ -27,7 +27,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@ -59,7 +58,6 @@ import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaDataComplete;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
@ -336,11 +334,13 @@ public class AnnotationConfiguration extends AbstractConfiguration
@Override
public void configure(WebAppContext context) throws Exception
{
//handle introspectable annotations (postconstruct,predestroy, multipart etc etc)
context.getObjectFactory().addDecorator(new AnnotationDecorator(context));
if (!context.getMetaData().isMetaDataComplete())
{
//If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
//If web.xml not metadata-complete, if this is a servlet 3 webapp or above
//or configDiscovered is true, we need to search for annotations
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{
_discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
@ -409,7 +409,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
/**
* Perform scanning of classes for annotations
* Perform scanning of classes for discoverable
* annotations such as WebServlet/WebFilter/WebListener
*
* @param context the context for the scan
* @throws Exception if unable to scan
@ -432,6 +433,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
isUseMultiThreading(context),
getMaxScanWait(context));
//scan selected jars on the container classpath first
parseContainerPath(context, parser);
//email from Rajiv Mordani jsrs 315 7 April 2010
// If there is a <others/> then the ordering should be
@ -439,6 +441,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
// In case there is no others then it is
// WEB-INF/classes + order of the elements.
parseWebInfClasses(context, parser);
//scan non-excluded, non medatadata-complete jars in web-inf lib
parseWebInfLib(context, parser);
long start = System.nanoTime();
@ -692,14 +695,14 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
//If no ordering, nothing is excluded
if (context.getMetaData().getOrdering() == null)
if (!context.getMetaData().isOrdered())
{
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} no ordering", sci);
return false;
}
List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
List<Resource> orderedJars = context.getMetaData().getWebInfResources(true);
//there is an ordering, but there are no jars resulting from the ordering, everything excluded
if (orderedJars.isEmpty())
@ -711,17 +714,17 @@ public class AnnotationConfiguration extends AbstractConfiguration
//Check if it is excluded by an ordering
URI loadingJarURI = sciResource.getURI();
boolean found = false;
Iterator<Resource> itor = orderedJars.iterator();
while (!found && itor.hasNext())
boolean included = false;
for (Resource r : orderedJars)
{
Resource r = itor.next();
found = r.getURI().equals(loadingJarURI);
included = r.getURI().equals(loadingJarURI);
if (included)
break;
}
if (LOG.isDebugEnabled())
LOG.debug("{}Excluded {} found={}", found ? "!" : "", sci, found);
return !found;
LOG.debug("{}Excluded {} found={}", included ? "!" : "", sci, included);
return !included;
}
/**
@ -786,7 +789,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
*/
public boolean isFromWebInfClasses(WebAppContext context, Resource sci)
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
for (Resource dir : context.getMetaData().getWebInfClassesResources())
{
if (dir.equals(sci))
{
@ -856,7 +859,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
{
if (LOG.isDebugEnabled())
LOG.debug("Ordering ServletContainerInitializers with " + initializerOrdering);
LOG.debug("Ordering ServletContainerInitializers with {}", initializerOrdering);
//There is an ordering that is not just "*".
//Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
@ -883,7 +886,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
else
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
for (Resource dir : context.getMetaData().getWebInfClassesResources())
{
if (dir.equals(entry.getValue()))//from WEB-INF/classes so can't be ordered/excluded
{
@ -912,7 +915,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
LOG.debug("Ordering ServletContainerInitializers with ordering {}", context.getMetaData().getOrdering());
//add SCIs according to the ordering of its containing jar
for (Resource webInfJar : context.getMetaData().getOrderedWebInfJars())
for (Resource webInfJar : context.getMetaData().getWebInfResources(true))
{
for (Map.Entry<ServletContainerInitializer, Resource> entry : sciResourceMap.entrySet())
{
@ -966,7 +969,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
return null;
String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
if (tmp == null || "".equals(tmp.trim()))
if (StringUtil.isBlank(tmp))
return null;
return new ServletContainerInitializerOrdering(tmp);
@ -991,9 +994,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled())
_containerPathStats = new CounterStatistic();
//scan the container classpath jars that were selected by
//filtering in MetaInfConfiguration
for (Resource r : context.getMetaData().getContainerResources())
{
//queue it up for scanning if using multithreaded mode
if (_parserTasks != null)
{
ParserTask task = new ParserTask(parser, handlers, r);
@ -1008,7 +1012,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
/**
* Scan jars in WEB-INF/lib
* Scan jars in WEB-INF/lib.
*
* Only jars selected by MetaInfConfiguration, and that are not excluded
* by an ordering will be considered.
*
* @param context the context for the scan
* @param parser the annotation parser to use
@ -1016,20 +1023,13 @@ public class AnnotationConfiguration extends AbstractConfiguration
*/
public void parseWebInfLib(final WebAppContext context, final AnnotationParser parser) throws Exception
{
List<FragmentDescriptor> frags = context.getMetaData().getFragments();
//email from Rajiv Mordani jsrs 315 7 April 2010
//jars that do not have a web-fragment.xml are still considered fragments
//they have to participate in the ordering
ArrayList<URI> webInfUris = new ArrayList<URI>();
List<Resource> jars = null;
if (context.getMetaData().getOrdering() != null)
jars = context.getMetaData().getOrderedWebInfJars();
else
//No ordering just use the jars in any order
jars = context.getMetaData().getWebInfJars();
//if there is an ordering, the ordered jars should be used.
//If there is no ordering, jars will be unordered.
List<Resource> jars = context.getMetaData().getWebInfResources(context.getMetaData().isOrdered());
if (LOG.isDebugEnabled())
{
@ -1042,13 +1042,13 @@ public class AnnotationConfiguration extends AbstractConfiguration
//for each jar, we decide which set of annotations we need to parse for
final Set<Handler> handlers = new HashSet<Handler>();
FragmentDescriptor f = getFragmentFromJar(r, frags);
FragmentDescriptor f = context.getMetaData().getFragmentDescriptorForJar(r);
//if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
// but yet we still need to do the scanning for the classes on behalf of the servletcontainerinitializers
//if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
//or if it has a fragment we scan it if it is not metadata complete
if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
if (f == null || !WebDescriptor.isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{
//register the classinheritance handler if there is one
if (_classInheritanceHandler != null)
@ -1058,7 +1058,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
handlers.addAll(_containerInitializerAnnotationHandlers);
//only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
if (f == null || !isMetaDataComplete(f))
if (f == null || !WebDescriptor.isMetaDataComplete(f))
handlers.addAll(_discoverableAnnotationHandlers);
if (_parserTasks != null)
@ -1076,7 +1076,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
/**
* Scan classes in WEB-INF/classes
* Scan classes in WEB-INF/classes.
*
* @param context the context for the scan
* @param parser the annotation parser to use
@ -1094,7 +1094,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled())
_webInfClassesStats = new CounterStatistic();
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
for (Resource dir : context.getMetaData().getWebInfClassesResources())
{
if (_parserTasks != null)
{
@ -1109,39 +1109,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
}
/**
* Get the web-fragment.xml from a jar
*
* @param jar the jar to look in for a fragment
* @param frags the fragments previously found
* @return true if the fragment if found, or null of not found
* @throws Exception if unable to determine the the fragment contains
*/
public FragmentDescriptor getFragmentFromJar(Resource jar, List<FragmentDescriptor> frags)
throws Exception
{
//check if the jar has a web-fragment.xml
FragmentDescriptor d = null;
for (FragmentDescriptor frag : frags)
{
Resource fragResource = frag.getResource(); //eg jar:file:///a/b/c/foo.jar!/META-INF/web-fragment.xml
if (Resource.isContainedIn(fragResource, jar))
{
d = frag;
break;
}
}
return d;
}
public boolean isMetaDataComplete(WebDescriptor d)
{
return (d != null && d.getMetaDataComplete() == MetaDataComplete.True);
}
public static class ClassInheritanceMap extends ConcurrentHashMap<String, Set<String>>
{
@Override
public String toString()
{

View File

@ -18,6 +18,9 @@
package org.eclipse.jetty.annotations;
import java.util.Objects;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.Decorator;
import org.eclipse.jetty.webapp.WebAppContext;
@ -26,23 +29,26 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/
public class AnnotationDecorator implements Decorator
{
protected AnnotationIntrospector _introspector = new AnnotationIntrospector();
protected AnnotationIntrospector _introspector;
protected WebAppContext _context;
public AnnotationDecorator(WebAppContext context)
{
registerHandlers(context);
_context = Objects.requireNonNull(context);
_introspector = new AnnotationIntrospector(_context);
registerHandlers();
}
public void registerHandlers(WebAppContext context)
private void registerHandlers()
{
_introspector.registerHandler(new ResourceAnnotationHandler(context));
_introspector.registerHandler(new ResourcesAnnotationHandler(context));
_introspector.registerHandler(new RunAsAnnotationHandler(context));
_introspector.registerHandler(new PostConstructAnnotationHandler(context));
_introspector.registerHandler(new PreDestroyAnnotationHandler(context));
_introspector.registerHandler(new DeclareRolesAnnotationHandler(context));
_introspector.registerHandler(new MultiPartConfigAnnotationHandler(context));
_introspector.registerHandler(new ServletSecurityAnnotationHandler(context));
_introspector.registerHandler(new ResourceAnnotationHandler(_context));
_introspector.registerHandler(new ResourcesAnnotationHandler(_context));
_introspector.registerHandler(new RunAsAnnotationHandler(_context));
_introspector.registerHandler(new PostConstructAnnotationHandler(_context));
_introspector.registerHandler(new PreDestroyAnnotationHandler(_context));
_introspector.registerHandler(new DeclareRolesAnnotationHandler(_context));
_introspector.registerHandler(new MultiPartConfigAnnotationHandler(_context));
_introspector.registerHandler(new ServletSecurityAnnotationHandler(_context));
}
/**
@ -50,22 +56,28 @@ public class AnnotationDecorator implements Decorator
* <ul>
* <li> Resource </li>
* <li> Resources </li>
* <li> RunAs </li>
* <li> PostConstruct </li>
* <li> PreDestroy </li>
* <li> ServletSecurity? </li>
* <li> DeclareRoles </li>
* <li> MultiPart </li>
* <li> ServletSecurity</li>
* </ul>
*
* @param o the object ot introspect
* @param o the object to introspect
* @param metaInfo information about the object to introspect
*/
protected void introspect(Object o)
protected void introspect(Object o, Object metaInfo)
{
_introspector.introspect(o.getClass());
if (o == null)
return;
_introspector.introspect(o, metaInfo);
}
@Override
public Object decorate(Object o)
{
introspect(o);
introspect(o, DecoratedObjectFactory.getAssociatedInfo());
return o;
}

View File

@ -18,11 +18,21 @@
package org.eclipse.jetty.annotations;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jetty.servlet.BaseHolder;
import org.eclipse.jetty.servlet.Source.Origin;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
/**
* AnnotationIntrospector
* Introspects a class to find various types of
@ -30,8 +40,10 @@ import java.util.Set;
*/
public class AnnotationIntrospector
{
private static final Logger LOG = Log.getLogger(AnnotationIntrospector.class);
private final Set<Class<?>> _introspectedClasses = new HashSet<>();
private final List<IntrospectableAnnotationHandler> _handlers = new ArrayList<IntrospectableAnnotationHandler>();
private final WebAppContext _context;
/**
* IntrospectableAnnotationHandler
@ -51,12 +63,14 @@ public class AnnotationIntrospector
*/
public abstract static class AbstractIntrospectableAnnotationHandler implements IntrospectableAnnotationHandler
{
private boolean _introspectAncestors;
protected boolean _introspectAncestors;
protected WebAppContext _context;
public abstract void doHandle(Class<?> clazz);
public AbstractIntrospectableAnnotationHandler(boolean introspectAncestors)
public AbstractIntrospectableAnnotationHandler(boolean introspectAncestors, WebAppContext context)
{
_context = Objects.requireNonNull(context);
_introspectAncestors = introspectAncestors;
}
@ -75,20 +89,113 @@ public class AnnotationIntrospector
c = c.getSuperclass();
}
}
public WebAppContext getContext()
{
return _context;
}
}
public AnnotationIntrospector(WebAppContext context)
{
_context = Objects.requireNonNull(context);
}
public void registerHandler(IntrospectableAnnotationHandler handler)
{
_handlers.add(handler);
}
public void introspect(Class<?> clazz)
/**
* Test if an object should be introspected for some specific types of annotations
* like PostConstruct/PreDestroy/MultiPart etc etc.
*
* According to servlet 4.0, these types of annotations should only be evaluated iff any
* of the following are true:
* <ol>
* <li>the object was created by the javax.servlet.ServletContext.createServlet/Filter/Listener method</li>
* <li>the object comes either from a discovered annotation (WebServlet/Filter/Listener) or a declaration
* in a descriptor AND web.xml is NOT metadata-complete AND any web-fragment.xml associated with the location of
* the class is NOT metadata-complete</li>
* </ol>
*
* We also support evaluations of these types of annotations for objects that were created directly
* by the jetty api.
*
* @param o the object to check for its ability to be introspected for annotations
* @param metaInfo meta information about the object to be introspected
* @return true if it can be introspected according to servlet 4.0 rules
*/
public boolean isIntrospectable(Object o, Object metaInfo)
{
if (_handlers == null)
return;
if (clazz == null)
return;
if (o == null)
return false; //nothing to introspect
if (metaInfo == null)
return true; //no information about the object to introspect, assume introspectable
@SuppressWarnings("rawtypes")
BaseHolder holder = null;
try
{
holder = (BaseHolder)metaInfo;
}
catch (ClassCastException e)
{
LOG.warn(e);
return true; //not the type of information we were expecting, assume introspectable
}
Origin origin = (holder.getSource() == null ? null : holder.getSource().getOrigin());
if (origin == null)
return true; //assume introspectable
switch (origin)
{
case EMBEDDED:
case JAVAX_API:
{
return true; //objects created from the jetty or servlet api are always introspectable
}
case ANNOTATION:
{
return true; //we will have discovered annotations only if metadata-complete==false
}
default:
{
//must be from a descriptor. Only introspect if the descriptor with which it was associated
//is not metadata-complete
if (_context.getMetaData().isMetaDataComplete())
return false;
String descriptorLocation = holder.getSource().getResource();
if (descriptorLocation == null)
return true; //no descriptor, can't be metadata-complete
try
{
return !WebDescriptor.isMetaDataComplete(_context.getMetaData().getFragmentDescriptor(Resource.newResource(descriptorLocation)));
}
catch (IOException e)
{
LOG.warn("Unable to get Resource for descriptor {}", descriptorLocation, e);
return false; //something wrong with the descriptor
}
}
}
}
/**
* @param o
* @param metaInfo
*/
public void introspect(Object o, Object metaInfo)
{
if (!isIntrospectable(o, metaInfo))
return;
Class<?> clazz = o.getClass();
synchronized (_introspectedClasses)
{
//Synchronize on the set of already introspected classes.

View File

@ -35,12 +35,9 @@ public class DeclareRolesAnnotationHandler extends AbstractIntrospectableAnnotat
{
private static final Logger LOG = Log.getLogger(DeclareRolesAnnotationHandler.class);
protected WebAppContext _context;
public DeclareRolesAnnotationHandler(WebAppContext context)
{
super(false);
_context = context;
super(false, context);
}
@Override

View File

@ -33,13 +33,10 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/
public class MultiPartConfigAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
protected WebAppContext _context;
public MultiPartConfigAnnotationHandler(WebAppContext context)
{
//TODO verify that MultipartConfig is not inheritable
super(false);
_context = context;
super(false, context);
}
@Override

View File

@ -31,12 +31,9 @@ import org.eclipse.jetty.webapp.WebAppContext;
public class PostConstructAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
protected WebAppContext _context;
public PostConstructAnnotationHandler(WebAppContext wac)
{
super(true);
_context = wac;
super(true, wac);
}
@Override

View File

@ -31,12 +31,9 @@ import org.eclipse.jetty.webapp.WebAppContext;
public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
WebAppContext _context;
public PreDestroyAnnotationHandler(WebAppContext wac)
{
super(true);
_context = wac;
super(true, wac);
}
@Override

View File

@ -48,12 +48,9 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
Float.class
});
protected WebAppContext _context;
public ResourceAnnotationHandler(WebAppContext wac)
{
super(true);
_context = wac;
super(true, wac);
}
/**

View File

@ -31,12 +31,9 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
{
private static final Logger LOG = Log.getLogger(ResourcesAnnotationHandler.class);
protected WebAppContext _wac;
public ResourcesAnnotationHandler(WebAppContext wac)
{
super(true);
_wac = wac;
super(true, wac);
}
@Override
@ -64,8 +61,8 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
{
//TODO don't ignore the shareable, auth etc etc
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name, mappedName))
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.getServer(), name, mappedName))
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName))
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName))
LOG.warn("Skipping Resources(Resource) annotation on " + clazz.getName() + " for name " + name + ": No resource bound at " + (mappedName == null ? name : mappedName));
}
catch (NamingException e)

View File

@ -33,14 +33,11 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
{
private static final Logger LOG = Log.getLogger(RunAsAnnotationHandler.class);
protected WebAppContext _context;
public RunAsAnnotationHandler(WebAppContext wac)
{
//Introspect only the given class for a RunAs annotation, as it is a class level annotation,
//and according to Common Annotation Spec p2-6 a class-level annotation is not inheritable.
super(false);
_context = wac;
super(false, wac);
}
@Override

View File

@ -57,12 +57,9 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
{
private static final Logger LOG = Log.getLogger(ServletSecurityAnnotationHandler.class);
private WebAppContext _context;
public ServletSecurityAnnotationHandler(WebAppContext wac)
{
super(false);
_context = wac;
super(false, wac);
}
@Override

View File

@ -16,9 +16,15 @@
// ========================================================================
//
package org.eclipse.jetty.webapp;
package org.eclipse.jetty.annotations;
public enum MetaDataComplete
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServlet;
public class ServletE extends HttpServlet
{
NotSet, True, False
@PreDestroy
public void preDestroy()
{
}
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.RelativeOrdering;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -125,7 +126,7 @@ public class TestAnnotationConfiguration
context25.setClassLoader(Thread.currentThread().getContextClassLoader());
context25.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context25.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context25.getMetaData().setWebXml(Resource.newResource(web25));
context25.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25.getServletContext().setEffectiveMajorVersion(2);
context25.getServletContext().setEffectiveMinorVersion(5);
config25.configure(context25);
@ -138,7 +139,7 @@ public class TestAnnotationConfiguration
context25b.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context25b.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context25b.setConfigurationDiscovered(true);
context25b.getMetaData().setWebXml(Resource.newResource(web25));
context25b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25b.getServletContext().setEffectiveMajorVersion(2);
context25b.getServletContext().setEffectiveMinorVersion(5);
config25b.configure(context25b);
@ -150,7 +151,7 @@ public class TestAnnotationConfiguration
context31.setClassLoader(Thread.currentThread().getContextClassLoader());
context31.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context31.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context31.getMetaData().setWebXml(Resource.newResource(web31true));
context31.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true)));
context31.getServletContext().setEffectiveMajorVersion(3);
context31.getServletContext().setEffectiveMinorVersion(1);
config31.configure(context31);
@ -162,7 +163,7 @@ public class TestAnnotationConfiguration
context31b.setClassLoader(Thread.currentThread().getContextClassLoader());
context31b.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context31b.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
context31b.getMetaData().setWebXml(Resource.newResource(web31false));
context31b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31false)));
context31b.getServletContext().setEffectiveMajorVersion(3);
context31b.getServletContext().setEffectiveMinorVersion(1);
config31b.configure(context31b);
@ -183,9 +184,9 @@ public class TestAnnotationConfiguration
//test 3.1 webapp loads both server and app scis
context.setClassLoader(webAppLoader);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebXml(Resource.newResource(web31true));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true)));
context.getMetaData().setWebInfClassesResources(classes);
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
@ -215,9 +216,9 @@ public class TestAnnotationConfiguration
// test a 3.1 webapp with metadata-complete=false loads both server
// and webapp scis
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web31false));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31false)));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
@ -260,12 +261,12 @@ public class TestAnnotationConfiguration
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setClassLoader(orderedLoader);
context.getMetaData().setWebXml(Resource.newResource(web31true));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31true)));
RelativeOrdering ordering = new RelativeOrdering(context.getMetaData());
context.getMetaData().setOrdering(ordering);
context.getMetaData().addWebInfJar(Resource.newResource(orderedFragmentJar.toURI().toURL()));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfResource(Resource.newResource(orderedFragmentJar.toURI().toURL()));
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().orderFragments();
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
@ -295,9 +296,9 @@ public class TestAnnotationConfiguration
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
@ -332,9 +333,9 @@ public class TestAnnotationConfiguration
List<ServletContainerInitializer> scis;
context.setConfigurationDiscovered(true);
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context.getMetaData().setWebInfClassesResources(classes);
context.getMetaData().addWebInfResource(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
@ -349,24 +350,4 @@ public class TestAnnotationConfiguration
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testGetFragmentFromJar() throws Exception
{
String dir = MavenTestingUtils.getTargetTestingDir("getFragmentFromJar").getAbsolutePath();
File file = new File(dir);
file = new File(file.getCanonicalPath());
URL url = file.toURI().toURL();
Resource jar1 = Resource.newResource(url + "file.jar");
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext wac = new WebAppContext();
List<FragmentDescriptor> frags = new ArrayList<FragmentDescriptor>();
frags.add(new FragmentDescriptor(Resource.newResource("jar:" + url + "file.jar!/fooa.props")));
frags.add(new FragmentDescriptor(Resource.newResource("jar:" + url + "file2.jar!/foob.props")));
assertNotNull(config.getFragmentFromJar(jar1, frags));
}
}

View File

@ -0,0 +1,127 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.annotations;
import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.eclipse.jetty.xml.XmlParser;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAnnotationDecorator
{
public class TestWebDescriptor extends WebDescriptor
{
public TestWebDescriptor(MetaData.Complete metadata)
{
super(EmptyResource.INSTANCE);
_metaDataComplete = metadata;
}
@Override
public void parse(XmlParser parser) throws Exception
{
}
@Override
public void processVersion()
{
}
@Override
public void processOrdering()
{
}
@Override
public void processDistributable()
{
}
@Override
public int getMajorVersion()
{
return 4;
}
@Override
public int getMinorVersion()
{
return 0;
}
}
@Test
public void testAnnotationDecorator() throws Exception
{
assertThrows(NullPointerException.class, () ->
{
new AnnotationDecorator(null);
});
WebAppContext context = new WebAppContext();
AnnotationDecorator decorator = new AnnotationDecorator(context);
ServletE servlet = new ServletE();
//test without BaseHolder metadata
decorator.decorate(servlet);
LifeCycleCallbackCollection callbacks = (LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
assertNotNull(callbacks);
assertFalse(callbacks.getPreDestroyCallbacks().isEmpty());
//reset
context.removeAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
//test with BaseHolder metadata, should not introspect with metdata-complete==true
context.getMetaData().setWebDescriptor(new TestWebDescriptor(MetaData.Complete.True));
assertTrue(context.getMetaData().isMetaDataComplete());
ServletHolder holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, ""));
holder.setHeldClass(ServletE.class);
context.getServletHandler().addServlet(holder);
DecoratedObjectFactory.associateInfo(holder);
decorator = new AnnotationDecorator(context);
decorator.decorate(servlet);
DecoratedObjectFactory.disassociateInfo();
callbacks = (LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
assertNull(callbacks);
//reset
context.removeAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
//test with BaseHolder metadata, should introspect with metadata-complete==false
context.getMetaData().setWebDescriptor(new TestWebDescriptor(MetaData.Complete.False));
DecoratedObjectFactory.associateInfo(holder);
decorator = new AnnotationDecorator(context);
decorator.decorate(servlet);
DecoratedObjectFactory.disassociateInfo();
callbacks = (LifeCycleCallbackCollection)context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
assertNotNull(callbacks);
assertFalse(callbacks.getPreDestroyCallbacks().isEmpty());
}
}

View File

@ -0,0 +1,98 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.annotations;
import java.io.File;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAnnotationIntrospector
{
@Test
public void testIsIntrospectable() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(AnnotationIntrospector.class))
{
WebAppContext wac = new WebAppContext();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
//can't introspect nothing
assertFalse(introspector.isIntrospectable(null, null));
//can introspect if no metadata to say otherwise
assertTrue(introspector.isIntrospectable(new Object(), null));
//can introspect if metdata isn't a BaseHolder
assertTrue(introspector.isIntrospectable(new Object(), new Object()));
//an EMBEDDED sourced servlet can be introspected
ServletHolder holder = new ServletHolder();
holder.setHeldClass(ServletE.class);
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a JAVAX API sourced servlet can be introspected
holder = new ServletHolder(Source.JAVAX_API);
holder.setHeldClass(ServletE.class);
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//an ANNOTATION sourced servlet can be introspected
holder = new ServletHolder(new Source(Source.Origin.ANNOTATION, ServletE.class.getName()));
holder.setHeldClass(ServletE.class);
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet can be introspected if web.xml metdata-complete==false
File file = MavenTestingUtils.getTestResourceFile("web31false.xml");
Resource resource = Resource.newResource(file);
wac.getMetaData().setWebDescriptor(new WebDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet can be introspected if web-fragment.xml medata-complete==false && web.xml metadata-complete==false
file = MavenTestingUtils.getTestResourceFile("web-fragment4false.xml");
resource = Resource.newResource(file);
wac.getMetaData().addFragmentDescriptor(Resource.newResource(file.getParentFile()), new FragmentDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertTrue(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet cannot be introspected if web-fragment.xml medata-complete==true (&& web.xml metadata-complete==false)
file = MavenTestingUtils.getTestResourceFile("web-fragment4true.xml");
resource = Resource.newResource(file);
wac.getMetaData().addFragmentDescriptor(Resource.newResource(file.getParentFile()), new FragmentDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertFalse(introspector.isIntrospectable(new ServletE(), holder));
//a DESCRIPTOR sourced servlet cannot be introspected if web.xml medata-complete==true
file = MavenTestingUtils.getTestResourceFile("web31true.xml");
resource = Resource.newResource(file);
wac.getMetaData().setWebDescriptor(new WebDescriptor(resource));
holder = new ServletHolder(new Source(Source.Origin.DESCRIPTOR, resource.toString()));
assertFalse(introspector.isIntrospectable(new ServletE(), holder));
}
}
}

View File

@ -89,7 +89,7 @@ public class TestSecurityAnnotationConversions
//Assume we found 1 servlet with a @HttpConstraint with value=EmptyRoleSemantic.DENY security annotation
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
introspector.registerHandler(annotationHandler);
//set up the expected outcomes:
@ -108,7 +108,7 @@ public class TestSecurityAnnotationConversions
expectedMappings[1].setConstraint(expectedConstraint);
expectedMappings[1].setPathSpec("*.foo");
introspector.introspect(DenyServlet.class);
introspector.introspect(new DenyServlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -122,15 +122,15 @@ public class TestSecurityAnnotationConversions
});
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
introspector.registerHandler(annotationHandler);
//set up the expected outcomes - no constraints at all as per Servlet Spec 3.1 pg 129
//1 ConstraintMapping per ServletMapping pathSpec
ConstraintMapping[] expectedMappings = new ConstraintMapping[]{};
introspector.introspect(PermitServlet.class);
PermitServlet permit = new PermitServlet();
introspector.introspect(permit, null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -146,7 +146,7 @@ public class TestSecurityAnnotationConversions
});
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
introspector.registerHandler(annotationHandler);
//set up the expected outcomes:compareResults
@ -164,8 +164,7 @@ public class TestSecurityAnnotationConversions
expectedMappings[1] = new ConstraintMapping();
expectedMappings[1].setConstraint(expectedConstraint);
expectedMappings[1].setPathSpec("*.foo");
introspector.introspect(RolesServlet.class);
introspector.introspect(new RolesServlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -211,10 +210,10 @@ public class TestSecurityAnnotationConversions
expectedMappings[3].setPathSpec("*.foo");
expectedMappings[3].setMethod("GET");
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
introspector.registerHandler(annotationHandler);
introspector.introspect(Method1Servlet.class);
introspector.introspect(new Method1Servlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}
@ -227,7 +226,7 @@ public class TestSecurityAnnotationConversions
"/foo/*", "*.foo"
});
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
ServletSecurityAnnotationHandler annotationHandler = new ServletSecurityAnnotationHandler(wac);
introspector.registerHandler(annotationHandler);
@ -263,7 +262,7 @@ public class TestSecurityAnnotationConversions
expectedMappings[3].setPathSpec("*.foo");
expectedMappings[3].setMethod("GET");
introspector.introspect(Method2Servlet.class);
introspector.introspect(new Method2Servlet(), null);
compareResults(expectedMappings, ((ConstraintAware)wac.getSecurityHandler()).getConstraintMappings());
}

View File

@ -73,11 +73,14 @@ public class TestResourceAnnotations
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resA", objA, false);
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resB", objB, false);
AnnotationIntrospector parser = new AnnotationIntrospector();
AnnotationIntrospector parser = new AnnotationIntrospector(wac);
ResourceAnnotationHandler handler = new ResourceAnnotationHandler(wac);
parser.registerHandler(handler);
parser.introspect(ResourceA.class);
parser.introspect(ResourceB.class);
ResourceA resourceA = new ResourceA();
ResourceB resourceB = new ResourceB();
parser.introspect(resourceA, null);
parser.introspect(resourceB, null);
//processing classA should give us these jndi name bindings:
// java:comp/env/myf
@ -155,11 +158,13 @@ public class TestResourceAnnotations
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resA", objA, false);
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "resB", objB, false);
AnnotationIntrospector introspector = new AnnotationIntrospector();
AnnotationIntrospector introspector = new AnnotationIntrospector(wac);
ResourcesAnnotationHandler handler = new ResourcesAnnotationHandler(wac);
introspector.registerHandler(handler);
introspector.introspect(ResourceA.class);
introspector.introspect(ResourceB.class);
ResourceA resourceA = new ResourceA();
ResourceB resourceB = new ResourceB();
introspector.introspect(resourceA, null);
introspector.introspect(resourceB, null);
assertEquals(objA, env.lookup("peach"));
assertEquals(objB, env.lookup("pear"));

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-fragment_4_0.xsd"
version="4.0">
<name>ardvaark</name>
</web-fragment>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-fragment_4_0.xsd"
metadata-complete="true"
version="4.0">
<name>badger</name>
</web-fragment>

View File

@ -26,3 +26,4 @@ include::secure-passwords.adoc[]
include::setting-port80-access-for-non-root-user.adoc[]
include::jaas-support.adoc[]
include::spnego-support.adoc[]
include::openid-support.adoc[]

View File

@ -0,0 +1,139 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
[[openid-support]]
=== OpenID Support
==== External Setup
===== Registering an App with OpenID Provider
You must register the app with an OpenID Provider such as link:https://developers.google.com/identity/protocols/OpenIDConnect#authenticatingtheuser[Google] or link:https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf[Amazon.]
This will give you a Client ID and Client Secret.
Once set up you must also register all the possible URI's for your webapp with the path `/j_security_check` so that the OpenId Provider will allow redirection back to the webapp.
These may look like
* `http://localhost:8080/openid-webapp/j_security_check`
* `https://example.com/j_security_check`
==== Distribution Configuration
===== OpenID Provider Configuration
To enable OpenID support, you first need to activate the `openid` module in your implementation.
[source, screen, subs="{sub-order}"]
----
java -jar {JETTY_HOME}/start.jar --add-to-start=openid
----
To configure OpenID Authentication with Jetty you will need to specify the OpenID Provider's issuer identifier (case sensitive URL using the `https` scheme) and the OAuth 2.0 Client ID and Client Secret.
If the OpenID Provider does not allow metadata discovery you will also need to specify the token endpoint and authorization endpoint of the OpenID Provider.
These can be set as properties in the `start.ini` or `start.d/openid.ini` files.
===== WebApp Specific Configuration in web.xml
The `web.xml` file needs some specific configuration to use OpenID.
There must be a `login-config` element with an `auth-method` value of `OPENID`, and a `realm-name` value of the exact URL string used to set the OpenID Provider.
To set the error page, an init param is set at `"org.eclipse.jetty.security.openid.error_page"`, its value should be a path relative to the webapp where authentication errors should be redirected.
Example:
[source, xml, subs="{sub-order}"]
----
<login-config>
<auth-method>OPENID</auth-method>
<realm-name>https://accounts.google.com</realm-name>
</login-config>
<context-param>
<param-name>org.eclipse.jetty.security.openid.error_page</param-name>
<param-value>/error</param-value>
</context-param>
----
==== Embedded Configuration
===== Define the `OpenIdConfiguration` for a specific OpenID Provider.
If the OpenID Provider allows metadata discovery then you can use.
[source, java, subs="{sub-order}"]
----
OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, CLIENT_ID, CLIENT_SECRET);
----
Otherwise you can manually enter the necessary information:
[source, java, subs="{sub-order}"]
----
OpenIdConfiguration openIdConfig = new OpenIdConfiguration(ISSUER, TOKEN_ENDPOINT, AUTH_ENDPOINT, CLIENT_ID, CLIENT_SECRET);
----
===== Configuring an `OpenIdLoginService`
[source, java, subs="{sub-order}"]
----
LoginService loginService = new OpenIdLoginService(openIdConfig);
securityHandler.setLoginService(loginService);
----
===== Configuring an `OpenIdAuthenticator` with `OpenIdConfiguration` and Error Page Redirect
[source, java, subs="{sub-order}"]
----
Authenticator authenticator = new OpenIdAuthenticator(openIdConfig, "/error");
securityHandler.setAuthenticator(authenticator);
servletContextHandler.setSecurityHandler(securityHandler);
----
===== Usage
====== Claims and Access Token
Claims about the user can be found using attributes on the session attribute `"org.eclipse.jetty.security.openid.claims"`, and the full response containing the OAuth 2.0 Access Token can be found with the session attribute `"org.eclipse.jetty.security.openid.response"`.
Example:
[source, java, subs="{sub-order}"]
----
Map<String, Object> claims = (Map)request.getSession().getAttribute("org.eclipse.jetty.security.openid.claims");
String userId = claims.get("sub");
Map<String, Object> response = (Map)request.getSession().getAttribute("org.eclipse.jetty.security.openid.response");
String accessToken = response.get("access_token");
----
==== Scopes
The OpenID scope is always used but additional scopes can be requested which can give you additional resources or privileges.
For the Google OpenID Provider it can be useful to request the scopes `profile` and `email` which will give you additional user claims.
Additional scopes can be requested through the `start.ini` or `start.d/openid.ini` files, or with `OpenIdConfiguration.addScopes(...);` in embedded code.
==== Roles
If security roles are required they can be configured through a wrapped `LoginService` which is deferred to for role information by the `OpenIdLoginService`.
This can be configured in XML through `etc/openid-baseloginservice.xml` in the Distribution, or in embedded code using the constructor for the `OpenIdLoginService`.
[source, java, subs="{sub-order}"]
----
LoginService wrappedLoginService = ...; // Optional LoginService for Roles
LoginService loginService = new OpenIdLoginService(openIdConfig, wrappedLoginService);
----
When using authorization roles, the setting `authenticateNewUsers` becomes significant.
If set to `true` users not found by the wrapped `LoginService` will still be authenticated but will have no roles.
If set to `false` those users will be not be allowed to authenticate and are redirected to the error page.
This setting is configured through the property `jetty.openid.authenticateNewUsers` in the `start.ini` or `start.d/openid.ini` file, or with `OpenIdLoginService.setAuthenticateNewUsers(...);` in embedded code.

View File

@ -11,7 +11,7 @@
<name>Jetty :: Hazelcast Session Manager</name>
<properties>
<hazelcast.version>3.9.4</hazelcast.version>
<hazelcast.version>3.12.6</hazelcast.version>
<bundle-symbolic-name>${project.groupId}.hazelcast</bundle-symbolic-name>
</properties>

View File

@ -5,7 +5,7 @@
<!-- ===================================================================== -->
<!-- Configure a factory for HazelcastSessionDataStore using -->
<!-- an embedded Hazelcast Instance -->
<!-- an remote Hazelcast Instance -->
<!-- ===================================================================== -->
<Call name="addBean">
<Arg>
@ -16,6 +16,7 @@
<Set name="gracePeriodSec"><Property name="jetty.session.gracePeriod.seconds" default="3600" /></Set>
<Set name="savePeriodSec"><Property name="jetty.session.savePeriod.seconds" default="0" /></Set>
<Set name="onlyClient"><Property name="jetty.session.hazelcast.onlyClient" default="true" /></Set>
<Set name="addresses"><Property name="jetty.session.hazelcast.addresses" default="" /></Set>
</New>
</Arg>
</Call>

View File

@ -13,7 +13,7 @@ session-store
sessions
[files]
maven://com.hazelcast/hazelcast/3.9.4|lib/hazelcast/hazelcast-3.9.4.jar
maven://com.hazelcast/hazelcast/3.12.6|lib/hazelcast/hazelcast-3.12.6.jar
[xml]
etc/sessions/hazelcast/default.xml
@ -31,7 +31,6 @@ http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
#jetty.session.hazelcast.configurationLocation=
jetty.session.hazelcast.scavengeZombies=false
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0

View File

@ -13,8 +13,8 @@ session-store
sessions
[files]
maven://com.hazelcast/hazelcast/3.9.4|lib/hazelcast/hazelcast-3.9.4.jar
maven://com.hazelcast/hazelcast-client/3.9.4|lib/hazelcast/hazelcast-client-3.9.4.jar
maven://com.hazelcast/hazelcast/3.12.6|lib/hazelcast/hazelcast-3.12.6.jar
maven://com.hazelcast/hazelcast-client/3.12.6|lib/hazelcast/hazelcast-client-3.12.6.jar
[xml]
etc/sessions/hazelcast/remote.xml
@ -34,6 +34,6 @@ jetty.session.hazelcast.mapName=jetty-distributed-session-map
jetty.session.hazelcast.hazelcastInstanceName=JETTY_DISTRIBUTED_SESSION_INSTANCE
jetty.session.hazelcast.onlyClient=true
jetty.session.hazelcast.scavengeZombies=false
#jetty.session.hazelcast.configurationLocation=
jetty.session.gracePeriod.seconds=3600
jetty.session.savePeriod.seconds=0
#jetty.session.hazelcast.addresses=

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.hazelcast.session;
import java.io.IOException;
import java.util.Arrays;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
@ -29,6 +30,7 @@ import com.hazelcast.config.SerializerConfig;
import com.hazelcast.config.XmlConfigBuilder;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import org.eclipse.jetty.server.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataStore;
@ -57,6 +59,8 @@ public class HazelcastSessionDataStoreFactory
private boolean scavengeZombies = false;
private String addresses;
public boolean isScavengeZombies()
{
return scavengeZombies;
@ -82,6 +86,12 @@ public class HazelcastSessionDataStoreFactory
if (configurationLocation == null)
{
ClientConfig config = new ClientConfig();
if (addresses != null && !addresses.isEmpty())
{
config.getNetworkConfig().setAddresses(Arrays.asList(addresses.split(",")));
}
SerializerConfig sc = new SerializerConfig()
.setImplementation(new SessionDataSerializer())
.setTypeClass(SessionData.class);
@ -202,4 +212,14 @@ public class HazelcastSessionDataStoreFactory
{
this.hazelcastInstanceName = hazelcastInstanceName;
}
public String getAddresses()
{
return addresses;
}
public void setAddresses(String addresses)
{
this.addresses = addresses;
}
}

View File

@ -13,6 +13,9 @@
<Set name="umaskOctal"><Property name="jetty.setuid.umask" default="002"/></Set>
<Set name="username"><Property name="jetty.setuid.userName" default="jetty"/></Set>
<Set name="groupname"><Property name="jetty.setuid.groupName" default="jetty"/></Set>
<Set name="clearSupplementalGroups">
<Property name="jetty.setuid.clearSupplementalGroups" default="false" />
</Set>
<!-- uncomment to change the limits on number of open file descriptors for root -->
<!--
<Call name="setRLimitNoFiles">

View File

@ -1,4 +1,4 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Enables the unix setUID configuration so that the server
@ -9,7 +9,7 @@ changing to a restricted user (eg jetty).
server
[lib]
lib/setuid/jetty-setuid-java-1.0.3.jar
lib/setuid/jetty-setuid-java-1.0.4.jar
[xml]
etc/jetty-setuid.xml
@ -20,3 +20,4 @@ etc/jetty-setuid.xml
# jetty.setuid.userName=jetty
# jetty.setuid.groupName=jetty
# jetty.setuid.umask=002
# jetty.setuid.clearSupplementalGroups=false

View File

@ -25,9 +25,11 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -39,6 +41,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -59,6 +62,7 @@ import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.Graceful;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -66,6 +70,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -466,7 +471,7 @@ public class HTTP2Test extends AbstractTest
// The third stream must not be created.
MetaData.Request request3 = newRequest("GET", new HttpFields());
CountDownLatch maxStreamsLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(request3, null, false), new Promise.Adapter<Stream>()
session.newStream(new HeadersFrame(request3, null, false), new Promise.Adapter<>()
{
@Override
public void failed(Throwable x)
@ -494,7 +499,7 @@ public class HTTP2Test extends AbstractTest
// Create a fourth stream.
MetaData.Request request4 = newRequest("GET", new HttpFields());
CountDownLatch exchangeLatch4 = new CountDownLatch(2);
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<Stream>()
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>()
{
@Override
public void succeeded(Stream result)
@ -893,6 +898,125 @@ public class HTTP2Test extends AbstractTest
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testGracefulServerGoAway() throws Exception
{
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverSessionLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
{
@Override
public void onAccept(Session session)
{
serverSessionRef.set(session);
serverSessionLatch.countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
dataLatch.countDown();
if (frame.isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}
};
}
});
// Avoid aggressive idle timeout to allow the test verifications.
connector.setShutdownIdleTimeout(connector.getIdleTimeout());
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClient(new Session.Listener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
clientCloseLatch.countDown();
}
});
assertTrue(serverSessionLatch.await(5, TimeUnit.SECONDS));
Session serverSession = serverSessionRef.get();
// Start 2 requests without completing them yet.
CountDownLatch responseLatch = new CountDownLatch(2);
MetaData.Request metaData1 = newRequest("GET", new HttpFields());
HeadersFrame request1 = new HeadersFrame(metaData1, null, false);
FuturePromise<Stream> promise1 = new FuturePromise<>();
Stream.Listener.Adapter listener = new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (frame.isEndStream())
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
assertEquals(HttpStatus.OK_200, response.getStatus());
responseLatch.countDown();
}
}
};
clientSession.newStream(request1, promise1, listener);
Stream stream1 = promise1.get(5, TimeUnit.SECONDS);
stream1.data(new DataFrame(stream1.getId(), ByteBuffer.allocate(1), false), Callback.NOOP);
MetaData.Request metaData2 = newRequest("GET", new HttpFields());
HeadersFrame request2 = new HeadersFrame(metaData2, null, false);
FuturePromise<Stream> promise2 = new FuturePromise<>();
clientSession.newStream(request2, promise2, listener);
Stream stream2 = promise2.get(5, TimeUnit.SECONDS);
stream2.data(new DataFrame(stream2.getId(), ByteBuffer.allocate(1), false), Callback.NOOP);
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
// Both requests are now on the server, shutdown gracefully the server session.
int port = connector.getLocalPort();
CompletableFuture<Void> shutdown = Graceful.shutdown(server);
// GOAWAY should not arrive to the client yet.
assertFalse(clientCloseLatch.await(1, TimeUnit.SECONDS));
// New requests should be immediately rejected.
HostPortHttpField authority3 = new HostPortHttpField("localhost" + ":" + port);
MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP, authority3, servletPath, HttpVersion.HTTP_2, new HttpFields());
HeadersFrame request3 = new HeadersFrame(metaData3, null, false);
FuturePromise<Stream> promise3 = new FuturePromise<>();
CountDownLatch resetLatch = new CountDownLatch(1);
clientSession.newStream(request3, promise3, new Stream.Listener.Adapter()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
{
resetLatch.countDown();
}
});
Stream stream3 = promise3.get(5, TimeUnit.SECONDS);
stream3.data(new DataFrame(stream3.getId(), ByteBuffer.allocate(1), true), Callback.NOOP);
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
// Finish the previous requests and expect the responses.
stream1.data(new DataFrame(stream1.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
stream2.data(new DataFrame(stream2.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
assertNull(shutdown.get(5, TimeUnit.SECONDS));
// Now GOAWAY should arrive to the client.
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
// Wait to process the GOAWAY frames and close the EndPoints.
Thread.sleep(1000);
assertFalse(((HTTP2Session)clientSession).getEndPoint().isOpen());
assertFalse(((HTTP2Session)serverSession).getEndPoint().isOpen());
}
private static void sleep(long time)
{
try

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@ -77,6 +78,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private static final Logger LOG = Log.getLogger(HTTP2Session.class);
private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
private final AtomicBiInteger streamCount = new AtomicBiInteger(); // Hi = closed, Lo = stream count
private final AtomicInteger localStreamIds = new AtomicInteger();
private final AtomicInteger lastRemoteStreamId = new AtomicInteger();
private final AtomicInteger localStreamCount = new AtomicInteger();
@ -100,6 +102,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private boolean connectProtocolEnabled;
private long idleTime;
private GoAwayFrame closeFrame;
private Callback.Completable shutdownCallback;
public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId)
{
@ -436,31 +439,17 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
if (LOG.isDebugEnabled())
LOG.debug("Received {}", frame);
while (true)
if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.REMOTELY_CLOSED))
{
CloseState current = closed.get();
switch (current)
{
case NOT_CLOSED:
{
if (closed.compareAndSet(current, CloseState.REMOTELY_CLOSED))
{
// We received a GO_AWAY, so try to write
// what's in the queue and then disconnect.
closeFrame = frame;
notifyClose(this, frame, new DisconnectCallback());
return;
}
break;
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("Ignored {}, already closed", frame);
return;
}
}
// We received a GO_AWAY, so try to write
// what's in the queue and then disconnect.
closeFrame = frame;
notifyClose(this, frame, new DisconnectCallback());
return;
}
if (LOG.isDebugEnabled())
LOG.debug("Ignored {}, already closed", frame);
}
@Override
@ -537,7 +526,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
protected void onConnectionFailure(int error, String reason, Callback callback)
{
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason, callback));
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new FailureCallback(error, reason, callback));
}
@Override
@ -672,30 +661,42 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
@Override
public boolean close(int error, String reason, Callback callback)
{
while (true)
if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED))
{
CloseState current = closed.get();
switch (current)
{
case NOT_CLOSED:
{
if (closed.compareAndSet(current, CloseState.LOCALLY_CLOSED))
{
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason);
control(null, callback, closeFrame);
return true;
}
break;
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("Ignoring close {}/{}, already closed", error, reason);
callback.succeeded();
return false;
}
}
if (LOG.isDebugEnabled())
LOG.debug("Closing {}/{}", error, reason);
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason);
control(null, callback, closeFrame);
return true;
}
if (LOG.isDebugEnabled())
LOG.debug("Ignoring close {}/{}, already closed", error, reason);
callback.succeeded();
return false;
}
@Override
public CompletableFuture<Void> shutdown()
{
if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED))
{
if (LOG.isDebugEnabled())
LOG.debug("Shutting down {}", this);
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, ErrorCode.NO_ERROR.code, "shutdown");
shutdownCallback = new Callback.Completable();
// Only send the close frame when we can flip Hi and Lo = 0, see onStreamClosed().
if (streamCount.compareAndSet(0, 1, 0, 0))
control(null, shutdownCallback, closeFrame);
return shutdownCallback;
}
if (LOG.isDebugEnabled())
LOG.debug("Ignoring shutdown, already closed");
Callback.Completable result = shutdownCallback;
// Result may be null if the shutdown is in progress,
// don't wait and return a completed CompletableFuture.
return result != null ? result : CompletableFuture.completedFuture(null);
}
private GoAwayFrame newGoAwayFrame(CloseState closeState, int error, String reason)
@ -1041,10 +1042,28 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
protected void onStreamOpened(IStream stream)
{
streamCount.addAndGetLo(1);
}
protected void onStreamClosed(IStream stream)
{
Callback callback = null;
while (true)
{
long encoded = streamCount.get();
int closed = AtomicBiInteger.getHi(encoded);
int streams = AtomicBiInteger.getLo(encoded) - 1;
if (streams == 0 && closed == 0)
{
callback = shutdownCallback;
closed = 1;
}
if (streamCount.compareAndSet(encoded, closed, streams))
break;
}
// Only send the close frame if we can flip Hi and Lo = 0, see shutdown().
if (callback != null)
control(null, callback, closeFrame);
}
@Override
@ -1321,8 +1340,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
{
case HEADERS:
{
onStreamOpened(stream);
HeadersFrame headersFrame = (HeadersFrame)frame;
if (headersFrame.getMetaData().isRequest())
onStreamOpened(stream);
if (stream.updateClose(headersFrame.isEndStream(), CloseState.Event.AFTER_SEND))
removeStream(stream);
break;
@ -1598,12 +1618,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
}
private class CloseCallback extends Callback.Nested
private class FailureCallback extends Callback.Nested
{
private final int error;
private final String reason;
private CloseCallback(int error, String reason, Callback callback)
private FailureCallback(int error, String reason, Callback callback)
{
super(callback);
this.error = error;

View File

@ -322,7 +322,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
{
// It's a bad client, it does not deserve to be
// treated gently by just resetting the stream.
session.close(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded", Callback.NOOP);
((HTTP2Session)session).onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded");
callback.failed(new IOException("stream_window_exceeded"));
return;
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http2;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
@ -151,4 +152,14 @@ public interface ISession extends Session
* @param callback the callback to notify when the frame has been processed
*/
public void onData(DataFrame frame, Callback callback);
/**
* <p>Gracefully closes the session, returning a {@code CompletableFuture} that
* is completed when all the streams currently being processed are completed.</p>
* <p>Implementation is idempotent, i.e. calling this method a second time
* or concurrently results in a no-operation.</p>
*
* @return a {@code CompletableFuture} that is completed when all the streams are completed
*/
public CompletableFuture<Void> shutdown();
}

View File

@ -24,11 +24,14 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.HTTP2Connection;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.Frame;
@ -46,6 +49,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
@ManagedObject
@ -296,22 +300,25 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
}
@ManagedObject("The container of HTTP/2 sessions")
public static class HTTP2SessionContainer implements Connection.Listener, Dumpable
public static class HTTP2SessionContainer implements Connection.Listener, Graceful, Dumpable
{
private final Set<Session> sessions = ConcurrentHashMap.newKeySet();
private final Set<ISession> sessions = ConcurrentHashMap.newKeySet();
private final AtomicReference<CompletableFuture<Void>> shutdown = new AtomicReference<>();
@Override
public void onOpened(Connection connection)
{
Session session = ((HTTP2Connection)connection).getSession();
ISession session = ((HTTP2Connection)connection).getSession();
sessions.add(session);
LifeCycle.start(session);
if (isShutdown())
shutdown(session);
}
@Override
public void onClosed(Connection connection)
{
Session session = ((HTTP2Connection)connection).getSession();
ISession session = ((HTTP2Connection)connection).getSession();
if (sessions.remove(session))
LifeCycle.stop(session);
}
@ -327,6 +334,39 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
return sessions.size();
}
@Override
public CompletableFuture<Void> shutdown()
{
CompletableFuture<Void> result = new CompletableFuture<>();
if (shutdown.compareAndSet(null, result))
{
CompletableFuture.allOf(sessions.stream().map(this::shutdown).toArray(CompletableFuture[]::new))
.whenComplete((v, x) ->
{
if (x == null)
result.complete(v);
else
result.completeExceptionally(x);
});
return result;
}
else
{
return shutdown.get();
}
}
@Override
public boolean isShutdown()
{
return shutdown.get() != null;
}
private CompletableFuture<Void> shutdown(ISession session)
{
return session.shutdown();
}
@Override
public String dump()
{

View File

@ -104,23 +104,30 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
}
else
{
stream = createRemoteStream(streamId, (MetaData.Request)metaData);
if (stream != null)
if (isClosed())
{
onStreamOpened(stream);
if (metaData instanceof MetaData.ConnectRequest)
reset(new ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
}
else
{
stream = createRemoteStream(streamId, (MetaData.Request)metaData);
if (stream != null)
{
if (!isConnectProtocolEnabled() && ((MetaData.ConnectRequest)metaData).getProtocol() != null)
{
stream.reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
return;
}
}
onStreamOpened(stream);
stream.process(frame, Callback.NOOP);
Stream.Listener listener = notifyNewStream(stream, frame);
stream.setListener(listener);
if (metaData instanceof MetaData.ConnectRequest)
{
if (!isConnectProtocolEnabled() && ((MetaData.ConnectRequest)metaData).getProtocol() != null)
{
stream.reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
return;
}
}
stream.process(frame, Callback.NOOP);
Stream.Listener listener = notifyNewStream(stream, frame);
stream.setListener(listener);
}
}
}
}

View File

@ -126,9 +126,9 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
continue;
Resource bundleRes = oparser.indexBundle(bundle);
if (!context.getMetaData().getWebInfJars().contains(bundleRes))
if (!context.getMetaData().getWebInfResources(false).contains(bundleRes))
{
context.getMetaData().addWebInfJar(bundleRes);
context.getMetaData().addWebInfResource(bundleRes);
}
if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null)

View File

@ -48,6 +48,16 @@
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jndi</artifactId>

View File

@ -748,8 +748,9 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor
injections.add(injection);
//Record which was the first descriptor to declare an injection for this name
if (context.getMetaData().getOriginDescriptor(node.getTag() + "." + jndiName + ".injection") == null)
context.getMetaData().setOrigin(node.getTag() + "." + jndiName + ".injection", descriptor);
String name = node.getTag() + "." + jndiName + ".injection";
if (context.getMetaData().getOriginDescriptor(name) == null)
context.getMetaData().setOrigin(name, descriptor);
}
catch (ClassNotFoundException e)
{

View File

@ -74,20 +74,20 @@ public class PlusDescriptorProcessorTest
URL webXml = Thread.currentThread().getContextClassLoader().getResource("web.xml");
webDescriptor = new WebDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(webXml));
webDescriptor.parse();
webDescriptor.parse(WebDescriptor.getParser(false));
URL frag1Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-1.xml");
fragDescriptor1 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag1Xml));
fragDescriptor1.parse();
fragDescriptor1.parse(WebDescriptor.getParser(false));
URL frag2Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-2.xml");
fragDescriptor2 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag2Xml));
fragDescriptor2.parse();
fragDescriptor2.parse(WebDescriptor.getParser(false));
URL frag3Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-3.xml");
fragDescriptor3 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag3Xml));
fragDescriptor3.parse();
fragDescriptor3.parse(WebDescriptor.getParser(false));
URL frag4Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-4.xml");
fragDescriptor4 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag4Xml));
fragDescriptor4.parse();
fragDescriptor4.parse(WebDescriptor.getParser(false));
}
@AfterEach

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.StandardDescriptorProcessor;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
@ -211,9 +212,9 @@ public class QuickStartConfiguration extends AbstractConfiguration
context.setConfigurations(context.getConfigurations().stream()
.filter(c -> !__replacedConfigurations.contains(c.replaces()) && !__replacedConfigurations.contains(c.getClass()))
.collect(Collectors.toList()).toArray(new Configuration[]{}));
context.getMetaData().setWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML));
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion());
context.getMetaData().setWebDescriptor(new WebDescriptor((Resource)context.getAttribute(QUICKSTART_WEB_XML)));
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebDescriptor().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebDescriptor().getMinorVersion());
}
/**

View File

@ -3,7 +3,7 @@
<!-- =============================================================== -->
<!-- Documentation of this file format can be found at: -->
<!-- https://www.eclipse.org/jetty/documentation/current/ -->
<!-- https://www.eclipse.org/jetty/documentation/current/ -->
<!-- -->
<!-- Additional configuration files are available in $JETTY_HOME/etc -->
<!-- and can be mixed in. See start.ini file for the default -->

View File

@ -60,6 +60,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO, baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, baseRequest.getAttribute(RequestDispatcher.FORWARD_MAPPING));
}
else
{
@ -68,6 +69,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, baseRequest.getServletPath());
baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO, baseRequest.getPathInfo());
baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, baseRequest.getQueryString());
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, baseRequest.getHttpServletMapping());
}
}
}

View File

@ -27,6 +27,7 @@ import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -108,7 +109,7 @@ public class Dispatcher implements RequestDispatcher
attr._servletPath = null; // set by ServletHandler
attr._pathInfo = _pathInContext;
attr._query = _uri.getQuery();
attr._mapping = null; //set by ServletHandler
if (attr._query != null)
baseRequest.mergeQueryParameters(baseRequest.getQueryString(), attr._query, false);
baseRequest.setAttributes(attr);
@ -147,6 +148,7 @@ public class Dispatcher implements RequestDispatcher
final String old_context_path = baseRequest.getContextPath();
final String old_servlet_path = baseRequest.getServletPath();
final String old_path_info = baseRequest.getPathInfo();
final HttpServletMapping old_mapping = baseRequest.getHttpServletMapping();
final MultiMap<String> old_query_params = baseRequest.getQueryParameters();
final Attributes old_attr = baseRequest.getAttributes();
@ -175,6 +177,7 @@ public class Dispatcher implements RequestDispatcher
attr._requestURI = (String)old_attr.getAttribute(FORWARD_REQUEST_URI);
attr._contextPath = (String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
attr._servletPath = (String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
attr._mapping = (HttpServletMapping)old_attr.getAttribute(FORWARD_MAPPING);
}
else
{
@ -183,6 +186,7 @@ public class Dispatcher implements RequestDispatcher
attr._requestURI = old_uri.getPath();
attr._contextPath = old_context_path;
attr._servletPath = old_servlet_path;
attr._mapping = old_mapping;
}
HttpURI uri = new HttpURI(old_uri.getScheme(), old_uri.getHost(), old_uri.getPort(),
@ -261,6 +265,7 @@ public class Dispatcher implements RequestDispatcher
String _servletPath;
String _pathInfo;
String _query;
HttpServletMapping _mapping;
ForwardAttributes(Attributes attributes)
{
@ -282,6 +287,8 @@ public class Dispatcher implements RequestDispatcher
return _contextPath;
if (key.equals(FORWARD_QUERY_STRING))
return _query;
if (key.equals(FORWARD_MAPPING))
return _mapping;
}
if (key.startsWith(__INCLUDE_PREFIX))
@ -312,6 +319,7 @@ public class Dispatcher implements RequestDispatcher
set.add(FORWARD_REQUEST_URI);
set.add(FORWARD_SERVLET_PATH);
set.add(FORWARD_CONTEXT_PATH);
set.add(FORWARD_MAPPING);
if (_query != null)
set.add(FORWARD_QUERY_STRING);
else
@ -336,7 +344,8 @@ public class Dispatcher implements RequestDispatcher
_contextPath = (String)value;
else if (key.equals(FORWARD_QUERY_STRING))
_query = (String)value;
else if (key.equals(FORWARD_MAPPING))
_mapping = (HttpServletMapping)value;
else if (value == null)
_attr.removeAttribute(key);
else
@ -376,6 +385,7 @@ public class Dispatcher implements RequestDispatcher
String _servletPath;
String _pathInfo;
String _query;
HttpServletMapping _mapping;
IncludeAttributes(Attributes attributes)
{
@ -397,6 +407,8 @@ public class Dispatcher implements RequestDispatcher
return _query;
if (key.equals(INCLUDE_REQUEST_URI))
return _requestURI;
if (key.equals(INCLUDE_MAPPING))
return _mapping;
}
else if (key.startsWith(__INCLUDE_PREFIX))
return null;
@ -425,6 +437,7 @@ public class Dispatcher implements RequestDispatcher
set.add(INCLUDE_REQUEST_URI);
set.add(INCLUDE_SERVLET_PATH);
set.add(INCLUDE_CONTEXT_PATH);
set.add(INCLUDE_MAPPING);
if (_query != null)
set.add(INCLUDE_QUERY_STRING);
else
@ -449,6 +462,8 @@ public class Dispatcher implements RequestDispatcher
_contextPath = (String)value;
else if (key.equals(INCLUDE_QUERY_STRING))
_query = (String)value;
else if (key.equals(INCLUDE_MAPPING))
_mapping = (HttpServletMapping)value;
else if (value == null)
_attr.removeAttribute(key);
else

View File

@ -189,6 +189,85 @@ public class Request implements HttpServletRequest
return null;
}
public static HttpServletMapping getServletMapping(PathSpec pathSpec, String servletPath, String servletName)
{
final MappingMatch match;
final String mapping;
if (pathSpec instanceof ServletPathSpec)
{
switch (((ServletPathSpec)pathSpec).getGroup())
{
case ROOT:
match = MappingMatch.CONTEXT_ROOT;
mapping = "";
break;
case DEFAULT:
match = MappingMatch.DEFAULT;
mapping = "/";
break;
case EXACT:
match = MappingMatch.EXACT;
mapping = servletPath.startsWith("/") ? servletPath.substring(1) : servletPath;
break;
case SUFFIX_GLOB:
match = MappingMatch.EXTENSION;
int dot = servletPath.lastIndexOf('.');
mapping = servletPath.substring(0, dot);
break;
case PREFIX_GLOB:
match = MappingMatch.PATH;
mapping = servletPath;
break;
default:
match = null;
mapping = servletPath;
break;
}
}
else
{
match = null;
mapping = servletPath;
}
return new HttpServletMapping()
{
@Override
public String getMatchValue()
{
return (mapping == null ? "" : mapping);
}
@Override
public String getPattern()
{
if (pathSpec != null)
return pathSpec.getDeclaration();
return "";
}
@Override
public String getServletName()
{
return (servletName == null ? "" : servletName);
}
@Override
public MappingMatch getMappingMatch()
{
return match;
}
@Override
public String toString()
{
return "HttpServletMapping{matchValue=" + getMatchValue() +
", pattern=" + getPattern() + ", servletName=" + getServletName() +
", mappingMatch=" + getMappingMatch() + "}";
}
};
}
private final HttpChannel _channel;
private final List<ServletRequestAttributeListener> _requestAttributeListeners = new ArrayList<>();
@ -2354,73 +2433,6 @@ public class Request implements HttpServletRequest
@Override
public HttpServletMapping getHttpServletMapping()
{
final PathSpec pathSpec = _pathSpec;
final MappingMatch match;
final String mapping;
if (pathSpec instanceof ServletPathSpec)
{
switch (((ServletPathSpec)pathSpec).getGroup())
{
case ROOT:
match = MappingMatch.CONTEXT_ROOT;
mapping = "";
break;
case DEFAULT:
match = MappingMatch.DEFAULT;
mapping = "/";
break;
case EXACT:
match = MappingMatch.EXACT;
mapping = _servletPath.startsWith("/") ? _servletPath.substring(1) : _servletPath;
break;
case SUFFIX_GLOB:
match = MappingMatch.EXTENSION;
int dot = _servletPath.lastIndexOf('.');
mapping = _servletPath.substring(0, dot);
break;
case PREFIX_GLOB:
match = MappingMatch.PATH;
mapping = _servletPath;
break;
default:
match = null;
mapping = _servletPath;
break;
}
}
else
{
match = null;
mapping = _servletPath;
}
return new HttpServletMapping()
{
@Override
public String getMatchValue()
{
return mapping;
}
@Override
public String getPattern()
{
if (pathSpec != null)
pathSpec.toString();
return null;
}
@Override
public String getServletName()
{
return Request.this.getServletName();
}
@Override
public MappingMatch getMappingMatch()
{
return match;
}
};
return Request.getServletMapping(_pathSpec, _servletPath, getServletName());
}
}

View File

@ -52,6 +52,7 @@ public class AsyncDelayHandler extends HandlerWrapper
Object asyncQueryString = null;
Object asyncRequestUri = null;
Object asyncServletPath = null;
Object asyncHttpServletMapping = null;
// Is this request a restarted one?
boolean restart = false;
@ -72,6 +73,8 @@ public class AsyncDelayHandler extends HandlerWrapper
baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI, null);
asyncServletPath = baseRequest.getAttribute(AsyncContext.ASYNC_SERVLET_PATH);
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, null);
asyncHttpServletMapping = baseRequest.getAttribute(AsyncContext.ASYNC_MAPPING);
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, null);
}
// Should we handle this request now?
@ -101,6 +104,7 @@ public class AsyncDelayHandler extends HandlerWrapper
baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING, asyncQueryString);
baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI, asyncRequestUri);
baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH, asyncServletPath);
baseRequest.setAttribute(AsyncContext.ASYNC_MAPPING, asyncHttpServletMapping);
}
// signal the request is leaving the handler

View File

@ -112,15 +112,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public static final int SERVLET_MAJOR_VERSION = 4;
public static final int SERVLET_MINOR_VERSION = 0;
public static final Class<?>[] SERVLET_LISTENER_TYPES =
{
ServletContextListener.class,
ServletContextAttributeListener.class,
ServletRequestListener.class,
ServletRequestAttributeListener.class,
HttpSessionIdListener.class,
HttpSessionListener.class,
HttpSessionAttributeListener.class
};
{
ServletContextListener.class,
ServletContextAttributeListener.class,
ServletRequestListener.class,
ServletRequestAttributeListener.class,
HttpSessionIdListener.class,
HttpSessionListener.class,
HttpSessionAttributeListener.class
};
public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
@ -199,7 +199,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private final List<EventListener> _programmaticListeners = new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _servletContextListeners = new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _destroySerletContextListeners = new ArrayList<>();
private final List<ServletContextListener> _destroyServletContextListeners = new ArrayList<>();
private final List<ServletContextAttributeListener> _servletContextAttributeListeners = new CopyOnWriteArrayList<>();
private final List<ServletRequestListener> _servletRequestListeners = new CopyOnWriteArrayList<>();
private final List<ServletRequestAttributeListener> _servletRequestAttributeListeners = new CopyOnWriteArrayList<>();
@ -646,7 +646,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
_contextListeners.remove(listener);
if (listener instanceof ServletContextListener)
{
_servletContextListeners.remove(listener);
_destroyServletContextListeners.remove(listener);
}
if (listener instanceof ServletContextAttributeListener)
_servletContextAttributeListeners.remove(listener);
@ -675,7 +678,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
return _programmaticListeners.contains(listener);
}
public boolean isDurableListener(EventListener listener)
{
// The durable listeners are those set when the context is started
@ -838,14 +841,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
super.doStart();
// Call context listeners
_destroySerletContextListeners.clear();
_destroyServletContextListeners.clear();
if (!_servletContextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener : _servletContextListeners)
{
callContextInitialized(listener, event);
_destroySerletContextListeners.add(listener);
_destroyServletContextListeners.add(listener);
}
}
}
@ -854,9 +857,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
// Call the context listeners
ServletContextEvent event = new ServletContextEvent(_scontext);
Collections.reverse(_destroySerletContextListeners);
Collections.reverse(_destroyServletContextListeners);
MultiException ex = new MultiException();
for (ServletContextListener listener : _destroySerletContextListeners)
for (ServletContextListener listener : _destroyServletContextListeners)
{
try
{
@ -1447,7 +1450,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
setContextPath(contextPath);
_contextPathDefault = true;
}
public void setDefaultRequestCharacterEncoding(String encoding)
{
_defaultRequestCharacterEncoding = encoding;
@ -1457,17 +1460,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
return _defaultRequestCharacterEncoding;
}
public void setDefaultResponseCharacterEncoding(String encoding)
{
_defaultResponseCharacterEncoding = encoding;
}
public String getDefaultResponseCharacterEncoding()
{
return _defaultResponseCharacterEncoding;
}
/**
* @return True if the current contextPath is from default settings
*/

View File

@ -26,16 +26,20 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.InetAddressSet;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import static org.eclipse.jetty.server.handler.InetAccessSet.AccessTuple;
import static org.eclipse.jetty.server.handler.InetAccessSet.PatternTuple;
/**
* InetAddress Access Handler
* <p>
@ -43,18 +47,13 @@ import org.eclipse.jetty.util.log.Logger;
* provided by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This
* handler uses the real internet address of the connection, not one reported in
* the forwarded for headers, as this cannot be as easily forged.
* <p>
* Additionally, there may be times when you want to only apply this handler to
* a subset of your connectors. In this situation you can use
* <b>connectorNames</b> to specify the connector names that you want this IP
* access filter to apply to.
* </p>
*/
public class InetAccessHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(InetAccessHandler.class);
private final IncludeExcludeSet<String, InetAddress> _addrs = new IncludeExcludeSet<>(InetAddressSet.class);
private final IncludeExclude<String> _names = new IncludeExclude<>();
private final IncludeExcludeSet<PatternTuple, AccessTuple> _set = new IncludeExcludeSet<>(InetAccessSet.class);
/**
* Clears all the includes, excludes, included connector names and excluded
@ -62,92 +61,150 @@ public class InetAccessHandler extends HandlerWrapper
*/
public void clear()
{
_addrs.clear();
_names.clear();
_set.clear();
}
/**
* Includes an InetAddress pattern
* Includes an InetAccess pattern with an optional connector name, address and URI mapping.
*
* @param pattern InetAddress pattern to include
* <p>The connector name is separated from the InetAddress pattern with an '@' character,
* and the InetAddress pattern is separated from the URI pattern using the "|" (pipe)
* character. URI patterns follow the servlet specification for simple * prefix and
* suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).</p>
*
* <br>Examples:
* <ul>
* <li>"connector1@127.0.0.1|/foo"</li>
* <li>"127.0.0.1|/foo"</li>
* <li>"connector1@127.0.0.1"</li>
* <li>"127.0.0.1"</li>
* </ul>
*
* @param pattern InetAccess pattern to include
* @see InetAddressSet
*/
public void include(String pattern)
{
_addrs.include(pattern);
_set.include(PatternTuple.from(pattern));
}
/**
* Includes InetAddress patterns
* Includes InetAccess patterns
*
* @param patterns InetAddress patterns to include
* @see InetAddressSet
*/
public void include(String... patterns)
{
_addrs.include(patterns);
for (String pattern : patterns)
include(pattern);
}
/**
* Excludes an InetAddress pattern
* Includes an InetAccess entry.
* @param connectorName optional name of a connector to include.
* @param addressPattern optional InetAddress pattern to include.
* @param pathSpec optional pathSpec to include.
*/
public void include(String connectorName, String addressPattern, PathSpec pathSpec)
{
_set.include(new PatternTuple(connectorName, InetAddressPattern.from(addressPattern), pathSpec));
}
/**
* Excludes an InetAccess entry pattern with an optional connector name, address and URI mapping.
*
* <p>The connector name is separated from the InetAddress pattern with an '@' character,
* and the InetAddress pattern is separated from the URI pattern using the "|" (pipe)
* character. URI patterns follow the servlet specification for simple * prefix and
* suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).</p>
*
* <br>Examples:
* <ul>
* <li>"connector1@127.0.0.1|/foo"</li>
* <li>"127.0.0.1|/foo"</li>
* <li>"connector1@127.0.0.1"</li>
* <li>"127.0.0.1"</li>
* </ul>
*
* @param pattern InetAddress pattern to exclude
* @see InetAddressSet
*/
public void exclude(String pattern)
{
_addrs.exclude(pattern);
_set.exclude(PatternTuple.from(pattern));
}
/**
* Excludes InetAddress patterns
* Excludes InetAccess patterns
*
* @param patterns InetAddress patterns to exclude
* @see InetAddressSet
*/
public void exclude(String... patterns)
{
_addrs.exclude(patterns);
for (String pattern : patterns)
exclude(pattern);
}
/**
* Excludes an InetAccess entry.
* @param connectorName optional name of a connector to exclude.
* @param addressPattern optional InetAddress pattern to exclude.
* @param pathSpec optional pathSpec to exclude.
*/
public void exclude(String connectorName, String addressPattern, PathSpec pathSpec)
{
_set.exclude(new PatternTuple(connectorName, InetAddressPattern.from(addressPattern), pathSpec));
}
/**
* Includes a connector name.
*
* @param name Connector name to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void includeConnector(String name)
{
_names.include(name);
throw new UnsupportedOperationException();
}
/**
* Excludes a connector name.
*
* @param name Connector name to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void excludeConnector(String name)
{
_names.exclude(name);
_set.exclude(new PatternTuple(name, null, null));
}
/**
* Includes connector names.
*
* @param names Connector names to include in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void includeConnectors(String... names)
{
_names.include(names);
throw new UnsupportedOperationException();
}
/**
* Excludes connector names.
*
* @param names Connector names to exclude in this handler.
* @deprecated use {@link InetAccessHandler#include(String)} instead.
*/
@Deprecated
public void excludeConnectors(String... names)
{
_names.exclude(names);
for (String name : names)
excludeConnector(name);
}
/**
@ -187,26 +244,16 @@ public class InetAccessHandler extends HandlerWrapper
*/
protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request)
{
String name = baseRequest.getHttpChannel().getConnector().getName();
boolean filterAppliesToConnector = _names.test(name);
boolean allowedByAddr = _addrs.test(addr);
if (LOG.isDebugEnabled())
{
LOG.debug("name = {}/{} addr={}/{} appliesToConnector={} allowedByAddr={}",
name, _names, addr, _addrs, filterAppliesToConnector, allowedByAddr);
}
if (!filterAppliesToConnector)
return true;
return allowedByAddr;
String connectorName = baseRequest.getHttpChannel().getConnector().getName();
String path = baseRequest.getMetaData().getURI().getDecodedPath();
return _set.test(new AccessTuple(connectorName, addr, path));
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent,
new DumpableCollection("included", _addrs.getIncluded()),
new DumpableCollection("excluded", _addrs.getExcluded()),
new DumpableCollection("includedConnector", _names.getIncluded()),
new DumpableCollection("excludedConnector", _names.getExcluded()));
new DumpableCollection("included", _set.getIncluded()),
new DumpableCollection("excluded", _set.getExcluded()));
}
}

View File

@ -0,0 +1,158 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.server.handler;
import java.net.InetAddress;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Predicate;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.StringUtil;
public class InetAccessSet extends AbstractSet<InetAccessSet.PatternTuple> implements Set<InetAccessSet.PatternTuple>, Predicate<InetAccessSet.AccessTuple>
{
private ArrayList<PatternTuple> tuples = new ArrayList<>();
@Override
public boolean add(PatternTuple storageTuple)
{
return tuples.add(storageTuple);
}
@Override
public boolean remove(Object o)
{
return tuples.remove(o);
}
@Override
public Iterator<PatternTuple> iterator()
{
return tuples.iterator();
}
@Override
public int size()
{
return tuples.size();
}
@Override
public boolean test(AccessTuple entry)
{
if (entry == null)
return false;
for (PatternTuple tuple : tuples)
{
if (tuple.test(entry))
return true;
}
return false;
}
static class PatternTuple implements Predicate<AccessTuple>
{
private final String connector;
private final InetAddressPattern address;
private final PathSpec pathSpec;
public static PatternTuple from(String pattern)
{
String path = null;
int pathIndex = pattern.indexOf('|');
if (pathIndex >= 0)
path = pattern.substring(pathIndex + 1);
String connector = null;
int connectorIndex = pattern.indexOf('@');
if (connectorIndex >= 0)
connector = pattern.substring(0, connectorIndex);
String addr = null;
int addrStart = (connectorIndex < 0) ? 0 : connectorIndex + 1;
int addrEnd = (pathIndex < 0) ? pattern.length() : pathIndex;
if (addrStart != addrEnd)
addr = pattern.substring(addrStart, addrEnd);
return new PatternTuple(connector, InetAddressPattern.from(addr),
StringUtil.isEmpty(path) ? null : new ServletPathSpec(path));
}
public PatternTuple(String connector, InetAddressPattern address, PathSpec pathSpec)
{
this.connector = connector;
this.address = address;
this.pathSpec = pathSpec;
}
@Override
public boolean test(AccessTuple entry)
{
// Match for connector.
if ((connector != null) && !connector.equals(entry.getConnector()))
return false;
// If we have a path we must must be at this path to match for an address.
if ((pathSpec != null) && !pathSpec.matches(entry.getPath()))
return false;
// Match for InetAddress.
if ((address != null) && !address.test(entry.getAddress()))
return false;
return true;
}
}
static class AccessTuple
{
private final String connector;
private final InetAddress address;
private final String path;
public AccessTuple(String connector, InetAddress address, String path)
{
this.connector = connector;
this.address = address;
this.path = path;
}
public String getConnector()
{
return connector;
}
public InetAddress getAddress()
{
return address;
}
public String getPath()
{
return path;
}
}
}

View File

@ -155,8 +155,9 @@ public class HouseKeeper extends AbstractLifeCycle
LOG.info("{} Stopped scavenging", _sessionIdManager.getWorkerName());
}
_task = null;
if (_ownScheduler)
if (_ownScheduler && _scheduler != null)
{
_ownScheduler = false;
_scheduler.stop();
_scheduler = null;
}

View File

@ -33,6 +33,7 @@ import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@ -42,6 +43,7 @@ import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@ -49,6 +51,7 @@ import javax.servlet.http.Part;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.http.tools.HttpTester;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
@ -1410,6 +1413,69 @@ public class RequestTest
assertThat(response, containsString("Hello World"));
}
@Test
public void testHttpServletMapping() throws Exception
{
String request = "GET / HTTP/1.1\n" +
"Host: whatever\n" +
"Connection: close\n" +
"\n";
_server.stop();
PathMappingHandler handler = new PathMappingHandler(null, null, null);
_server.setHandler(handler);
_server.start();
String response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=, pattern=, servletName=, mappingMatch=null}"));
_server.stop();
ServletPathSpec spec = new ServletPathSpec("");
handler = new PathMappingHandler(spec, spec.getPathMatch("foo"), "Something");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=, pattern=, servletName=Something, mappingMatch=CONTEXT_ROOT}"));
_server.stop();
spec = new ServletPathSpec("/");
handler = new PathMappingHandler(spec, "", "Default");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/, pattern=/, servletName=Default, mappingMatch=DEFAULT}"));
_server.stop();
spec = new ServletPathSpec("/foo/*");
handler = new PathMappingHandler(spec, spec.getPathMatch("/foo/bar"), "BarServlet");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/foo, pattern=/foo/*, servletName=BarServlet, mappingMatch=PATH}"));
_server.stop();
spec = new ServletPathSpec("*.jsp");
handler = new PathMappingHandler(spec, spec.getPathMatch("/foo/bar.jsp"), "JspServlet");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=/foo/bar, pattern=*.jsp, servletName=JspServlet, mappingMatch=EXTENSION}"));
_server.stop();
spec = new ServletPathSpec("/catalog");
handler = new PathMappingHandler(spec, spec.getPathMatch("/catalog"), "CatalogServlet");
_server.setHandler(handler);
_server.start();
response = _connector.getResponse(request);
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat("Response body content", response, containsString("HttpServletMapping{matchValue=catalog, pattern=/catalog, servletName=CatalogServlet, mappingMatch=EXACT}"));
_server.stop();
}
@Test
public void testCookies() throws Exception
{
@ -1915,4 +1981,68 @@ public class RequestTest
}
}
}
private class TestUserIdentityScope implements UserIdentity.Scope
{
private ContextHandler _handler;
private String _contextPath;
private String _name;
public TestUserIdentityScope(ContextHandler handler, String contextPath, String name)
{
_handler = handler;
_contextPath = contextPath;
_name = name;
}
@Override
public ContextHandler getContextHandler()
{
return _handler;
}
@Override
public String getContextPath()
{
return _contextPath;
}
@Override
public String getName()
{
return _name;
}
@Override
public Map<String, String> getRoleRefMap()
{
return null;
}
}
private class PathMappingHandler extends AbstractHandler
{
private ServletPathSpec _spec;
private String _servletPath;
private String _servletName;
public PathMappingHandler(ServletPathSpec spec, String servletPath, String servletName)
{
_spec = spec;
_servletPath = servletPath;
_servletName = servletName;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
baseRequest.setServletPath(_servletPath);
baseRequest.setPathSpec(_spec);
if (_servletName != null)
baseRequest.setUserIdentityScope(new TestUserIdentityScope(null, null, _servletName));
HttpServletMapping mapping = baseRequest.getHttpServletMapping();
response.getWriter().println(mapping);
}
}
}

View File

@ -25,7 +25,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -36,6 +35,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
@ -67,7 +67,6 @@ public class InetAccessHandlerTest
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.OK_200);
@ -85,7 +84,7 @@ public class InetAccessHandlerTest
@ParameterizedTest
@MethodSource("data")
public void testHandler(String include, String exclude, String includeConnectors, String excludeConnectors, String code)
public void testHandler(String path, String include, String exclude, String includeConnectors, String excludeConnectors, String code)
throws Exception
{
_handler.clear();
@ -107,14 +106,14 @@ public class InetAccessHandlerTest
{
if (inc.length() > 0)
{
_handler.includeConnector(inc);
_handler.include(inc + "@");
}
}
for (String exc : excludeConnectors.split(";", -1))
{
if (exc.length() > 0)
{
_handler.excludeConnector(exc);
_handler.exclude(exc + "@");
}
}
@ -127,11 +126,11 @@ public class InetAccessHandlerTest
}
}
testConnector(_connector1.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
testConnector(_connector2.getLocalPort(), include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
testConnector(_connector1.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(0));
testConnector(_connector2.getLocalPort(), path, include, exclude, includeConnectors, excludeConnectors, codePerConnector.get(1));
}
private void testConnector(int port, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException
private void testConnector(int port, String path, String include, String exclude, String includeConnectors, String excludeConnectors, String code) throws IOException
{
try (Socket socket = new Socket("127.0.0.1", port);)
{
@ -139,7 +138,7 @@ public class InetAccessHandlerTest
HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET");
request.setURI("/path");
request.setURI(StringUtil.isEmpty(path) ? "/" : path);
request.setHeader("Host", "127.0.0.1");
request.setVersion(HttpVersion.HTTP_1_0);
@ -164,62 +163,83 @@ public class InetAccessHandlerTest
public static Stream<Arguments> data()
{
Object[][] data = new Object[][]
{
// Empty lists 1
{"", "", "", "", "200;200"},
{
// Empty lists 1
{"", "", "", "", "", "200;200"},
// test simple filters
{"127.0.0.1", "", "", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"192.0.0.1", "", "", "", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "", "403;403"},
// test simple filters
{"", "127.0.0.1", "", "", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "", "200;200"},
{"", "192.0.0.1", "", "", "", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "", "403;403"},
// test includeConnector
{"127.0.0.1", "", "http_connector1", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
{"192.0.0.1", "", "http_connector1", "", "403;200"},
{"192.0.0.1-192.0.0.254", "", "http_connector1", "", "403;200"},
{"192.0.0.1", "", "http_connector2", "", "200;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector2", "", "200;403"},
// test includeConnector
{"", "127.0.0.1", "", "http_connector1", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1", "", "200;200"},
{"", "192.0.0.1", "", "http_connector1", "", "200;403"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1", "", "200;403"},
{"", "192.0.0.1", "", "http_connector2", "", "403;200"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector2", "", "403;200"},
// test includeConnector names where none of them match
{"127.0.0.1", "", "nothttp", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"},
{"192.0.0.1", "", "nothttp", "", "200;200"},
{"192.0.0.1-192.0.0.254", "", "nothttp", "", "200;200"},
// test includeConnector names where none of them match
{"", "127.0.0.1", "", "nothttp", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "nothttp", "", "200;200"},
{"", "192.0.0.1", "", "nothttp", "", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "nothttp", "", "403;403"},
// text excludeConnector
{"127.0.0.1", "", "", "http_connector1", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1", "200;200"},
{"192.0.0.1", "", "", "http_connector1", "200;403"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1", "200;403"},
{"192.0.0.1", "", "", "http_connector2", "403;200"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;200"},
// text excludeConnector
{"", "127.0.0.1", "", "", "http_connector1", "403;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "http_connector1", "403;200"},
{"", "192.0.0.1", "", "", "http_connector1", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector1", "403;403"},
{"", "192.0.0.1", "", "", "http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector2", "403;403"},
// test excludeConnector where none of them match.
{"127.0.0.1", "", "", "nothttp", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"},
{"192.0.0.1", "", "", "nothttp", "403;403"},
{"192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"},
// test excludeConnector where none of them match.
{"", "127.0.0.1", "", "", "nothttp", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "", "nothttp", "200;200"},
{"", "192.0.0.1", "", "", "nothttp", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "nothttp", "403;403"},
// both connectors are excluded
{"127.0.0.1", "", "", "http_connector1;http_connector2", "200;200"},
{"127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1", "", "", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "200;200"},
// both connectors are excluded
{"", "127.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"", "127.0.0.1-127.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1", "", "", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "", "http_connector1;http_connector2", "403;403"},
// both connectors are included
{"127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"192.0.0.1", "", "http_connector1;http_connector2", "", "403;403"},
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "403;403"},
// both connectors are included
{"", "127.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"", "192.0.0.1", "", "http_connector1;http_connector2", "", "200;200"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "", "200;200"},
{"", "", "127.0.0.1", "http_connector1;http_connector2", "", "403;403"},
// exclude takes precedence over include
{"127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
{"127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"},
{"192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "200;200"}
};
return Arrays.asList(data).stream().map(Arguments::of);
// exclude takes precedence over include
{"", "127.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"", "127.0.0.1-127.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
{"", "192.0.0.1-192.0.0.254", "", "http_connector1;http_connector2", "http_connector1;http_connector2", "403;403"},
// Test path specific include/exclude.
{"/testPath", "", "", "http_connector1", "", "200;403"},
{"/", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "200;200"},
{"/testPath", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "403;403"},
{"/notTestPath", "127.0.1.11|/testPath", "", "http_connector1", "", "200;403"},
{"/testPath", "127.0.1.11|/testPath", "", "http_connector1", "", "200;403"},
{"/testPath", "127.0.0.13|/testPath;127.0.0.1|/testPath", "", "http_connector1", "", "200;200"},
{"/testPath", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "403;403"},
{"/", "127.0.0.1", "127.0.0.1|/testPath", "http_connector1", "", "200;200"},
{"/a/b", "", "127.0.0.1|/a/*", "", "", "403;403"},
{"/b/a", "", "127.0.0.1|/a/*", "", "", "200;200"},
{"/org/eclipse/jetty/test.html", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "200;200"},
{"/org/eclipse/jetty/test.xml", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "403;403"},
{"/org/eclipse/jetty/test.pdf", "127.0.0.1|*.html", "127.0.0.1|*.xml", "", "", "403;403"},
{"/a/test.html", "", "127.0.0.1|*.html;127.0.0.10|/a/*", "", "", "403;403"},
{"/foo/bar/test.xml", "127.0.0.1|/foo/*", "127.0.0.0-127.0.0.2|*.html", "", "", "200;200"},
{"/foo/bar/test.html", "127.0.0.1|/foo/*", "127.0.0.0-127.0.0.2|*.html", "", "", "403;403"},
{"/foo/bar/test.xml", "127.0.0.1|/foo/bar/*", "127.0.0.1|/foo/*", "", "", "403;403"},
};
return Arrays.stream(data).map(Arguments::of);
}
}

View File

@ -19,10 +19,12 @@
package org.eclipse.jetty.servlet;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.UnavailableException;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
@ -177,6 +179,40 @@ public abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpabl
return _instance;
}
protected synchronized T createInstance() throws Exception
{
ServletContext ctx = getServletContext();
if (ctx == null)
return getHeldClass().getDeclaredConstructor().newInstance();
if (ServletContextHandler.Context.class.isAssignableFrom(ctx.getClass()))
return ((ServletContextHandler.Context)ctx).createInstance(this);
return null;
}
public ServletContext getServletContext()
{
ServletContext scontext = null;
//try the ServletHandler first
if (getServletHandler() != null)
scontext = getServletHandler().getServletContext();
if (scontext != null)
return scontext;
//try the ContextHandler next
Context ctx = ContextHandler.getCurrentContext();
if (ctx != null)
{
ContextHandler contextHandler = ctx.getContextHandler();
if (contextHandler != null)
return contextHandler.getServletContext();
}
return null;
}
/**
* @return True if this holder was created for a specific instance.
*/

View File

@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
@ -113,10 +114,7 @@ public class FilterHolder extends Holder<Filter>
{
try
{
ServletContext context = getServletHandler().getServletContext();
_filter = (context != null)
? context.createFilter(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
_filter = createInstance();
}
catch (ServletException ex)
{
@ -135,6 +133,19 @@ public class FilterHolder extends Holder<Filter>
}
}
@Override
protected synchronized Filter createInstance() throws Exception
{
Filter filter = super.createInstance();
if (filter == null)
{
ServletContext context = getServletContext();
if (context != null)
filter = context.createFilter(getHeldClass());
}
return filter;
}
@Override
public void doStop()
throws Exception

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.servlet;
import java.util.EventListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@ -88,10 +89,7 @@ public class ListenerHolder extends BaseHolder<EventListener>
//create an instance of the listener and decorate it
try
{
ServletContext context = contextHandler.getServletContext();
_listener = (context != null)
? context.createListener(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
_listener = createInstance();
}
catch (ServletException ex)
{
@ -107,6 +105,20 @@ public class ListenerHolder extends BaseHolder<EventListener>
}
}
@Override
protected synchronized EventListener createInstance() throws Exception
{
EventListener listener = super.createInstance();
if (listener == null)
{
ServletContext ctx = getServletContext();
if (ctx != null)
listener = ctx.createListener(getHeldClass());
}
return listener;
}
@Override
public void doStop() throws Exception
{

View File

@ -1260,6 +1260,21 @@ public class ServletContextHandler extends ContextHandler
{
return _objFactory.decorate(super.createInstance(clazz));
}
public <T> T createInstance(BaseHolder<T> holder) throws ServletException
{
try
{
//set a thread local
DecoratedObjectFactory.associateInfo(holder);
return createInstance(holder.getHeldClass());
}
finally
{
//unset the thread local
DecoratedObjectFactory.disassociateInfo();
}
}
public <T extends Filter> void destroyFilter(T f)
{

View File

@ -449,6 +449,7 @@ public class ServletHandler extends ScopedHandler
{
baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, servletPath);
baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, pathInfo);
baseRequest.setAttribute(RequestDispatcher.INCLUDE_MAPPING, Request.getServletMapping(pathSpec, servletPath, servletHolder.getName()));
}
else
{

View File

@ -33,6 +33,7 @@ import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
import javax.servlet.GenericServlet;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
@ -1110,30 +1111,22 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
* @throws NoSuchMethodException if creating new instance resulted in error
* @throws InvocationTargetException If creating new instance throws an exception
*/
protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException,
NoSuchMethodException, InvocationTargetException
protected Servlet newInstance() throws Exception
{
try
{
ServletContext ctx = getServletHandler().getServletContext();
if (ctx != null)
return ctx.createServlet(getHeldClass());
return getHeldClass().getDeclaredConstructor().newInstance();
return createInstance();
}
}
catch (ServletException ex)
@Override
protected synchronized Servlet createInstance() throws Exception
{
Servlet servlet = super.createInstance();
if (servlet == null)
{
Throwable cause = ex.getRootCause();
if (cause instanceof InstantiationException)
throw (InstantiationException)cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException)cause;
if (cause instanceof NoSuchMethodException)
throw (NoSuchMethodException)cause;
if (cause instanceof InvocationTargetException)
throw (InvocationTargetException)cause;
throw ex;
ServletContext ctx = getServletContext();
if (ctx != null)
servlet = ctx.createServlet(getHeldClass());
}
return servlet;
}
@Override

View File

@ -32,8 +32,7 @@ public class Source
{
EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION
}
;
public Origin _origin;
public String _resource;

View File

@ -27,6 +27,7 @@ import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
@ -224,6 +225,10 @@ public class AsyncContextTest
assertThat("query string attr is correct", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("context path attr is correct", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct", responseBody, containsString("async:run:attr:requestURI:/ctx/servletPath"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:servletPath"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/servletPath"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:EXACT"));
}
@Test
@ -257,6 +262,10 @@ public class AsyncContextTest
assertThat("async run attr query string", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("async run context path", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("async run request uri has correct encoding", responseBody, containsString("async:run:attr:requestURI:/ctx/test/hello%2fthere"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:/test"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/test/*"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:PATH"));
}
@Test
@ -296,6 +305,10 @@ public class AsyncContextTest
assertThat("query string attr is correct", responseBody, containsString("async:run:attr:queryString:dispatch=true&queryStringWithEncoding=space%20space"));
assertThat("context path attr is correct", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct", responseBody, containsString("async:run:attr:requestURI:/ctx/path%20with%20spaces/servletPath"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:path with spaces/servletPath"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/path with spaces/servletPath"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:EXACT"));
}
@Test
@ -338,6 +351,10 @@ public class AsyncContextTest
assertThat("query string attr is correct", responseBody, containsString("async:run:attr:queryString:dispatch=true"));
assertThat("context path attr is correct", responseBody, containsString("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct", responseBody, containsString("async:run:attr:requestURI:/ctx/servletPath"));
assertThat("http servlet mapping matchValue is correct", responseBody, containsString("async:run:attr:mapping:matchValue:servletPath"));
assertThat("http servlet mapping pattern is correct", responseBody, containsString("async:run:attr:mapping:pattern:/servletPath"));
assertThat("http servlet mapping servletName is correct", responseBody, containsString("async:run:attr:mapping:servletName:"));
assertThat("http servlet mapping mappingMatch is correct", responseBody, containsString("async:run:attr:mapping:mappingMatch:EXACT"));
}
@Test
@ -687,6 +704,14 @@ public class AsyncContextTest
_context.getResponse().getOutputStream().print("async:run:attr:queryString:" + req.getAttribute(AsyncContext.ASYNC_QUERY_STRING) + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:contextPath:" + req.getAttribute(AsyncContext.ASYNC_CONTEXT_PATH) + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:requestURI:" + req.getAttribute(AsyncContext.ASYNC_REQUEST_URI) + "\n");
HttpServletMapping mapping = (HttpServletMapping)req.getAttribute(AsyncContext.ASYNC_MAPPING);
if (mapping != null)
{
_context.getResponse().getOutputStream().print("async:run:attr:mapping:matchValue:" + mapping.getMatchValue() + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:mapping:pattern:" + mapping.getPattern() + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:mapping:servletName:" + mapping.getServletName() + "\n");
_context.getResponse().getOutputStream().print("async:run:attr:mapping:mappingMatch:" + mapping.getMappingMatch() + "\n");
}
}
catch (IOException e)
{

View File

@ -41,6 +41,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
@ -728,9 +729,12 @@ public class DispatcherTest
assertEquals("/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=assertforward&do=more&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/ForwardServlet", fwdMapping.getMatchValue());
List<String> expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH,
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING);
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING);
List<String> requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -760,9 +764,12 @@ public class DispatcherTest
assertEquals("/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/ForwardServlet", fwdMapping.getMatchValue());
List<String> expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH,
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING);
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING);
List<String> requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -806,9 +813,12 @@ public class DispatcherTest
assertEquals("/AssertIncludeServlet", request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_PATH_INFO));
assertEquals("do=end&do=the", request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING));
HttpServletMapping incMapping = (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING);
assertNotNull(incMapping);
assertEquals("/AssertIncludeServlet", incMapping.getMatchValue());
List expectedAttributeNames = Arrays.asList(Dispatcher.INCLUDE_REQUEST_URI, Dispatcher.INCLUDE_CONTEXT_PATH,
Dispatcher.INCLUDE_SERVLET_PATH, Dispatcher.INCLUDE_QUERY_STRING);
Dispatcher.INCLUDE_SERVLET_PATH, Dispatcher.INCLUDE_QUERY_STRING, Dispatcher.INCLUDE_MAPPING);
List requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -836,17 +846,23 @@ public class DispatcherTest
assertEquals("/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals("/forwardpath", request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=include", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/ForwardServlet", fwdMapping.getMatchValue());
assertEquals("/context/AssertForwardIncludeServlet/assertpath", request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI));
assertEquals("/context", request.getAttribute(Dispatcher.INCLUDE_CONTEXT_PATH));
assertEquals("/AssertForwardIncludeServlet", request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH));
assertEquals("/assertpath", request.getAttribute(Dispatcher.INCLUDE_PATH_INFO));
assertEquals("do=end", request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING));
HttpServletMapping incMapping = (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING);
assertNotNull(incMapping);
assertEquals("/AssertForwardIncludeServlet", incMapping.getMatchValue());
List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH,
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING,
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING,
Dispatcher.INCLUDE_REQUEST_URI, Dispatcher.INCLUDE_CONTEXT_PATH, Dispatcher.INCLUDE_SERVLET_PATH,
Dispatcher.INCLUDE_PATH_INFO, Dispatcher.INCLUDE_QUERY_STRING);
Dispatcher.INCLUDE_PATH_INFO, Dispatcher.INCLUDE_QUERY_STRING, Dispatcher.INCLUDE_MAPPING);
List requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
@ -874,15 +890,19 @@ public class DispatcherTest
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_PATH_INFO));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_QUERY_STRING));
assertEquals(null, request.getAttribute(Dispatcher.INCLUDE_MAPPING));
assertEquals("/context/IncludeServlet/includepath", request.getAttribute(Dispatcher.FORWARD_REQUEST_URI));
assertEquals("/context", request.getAttribute(Dispatcher.FORWARD_CONTEXT_PATH));
assertEquals("/IncludeServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals("/includepath", request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals("do=forward", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING));
HttpServletMapping fwdMapping = (HttpServletMapping)request.getAttribute(Dispatcher.FORWARD_MAPPING);
assertNotNull(fwdMapping);
assertEquals("/IncludeServlet", fwdMapping.getMatchValue());
List expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH, Dispatcher.FORWARD_SERVLET_PATH,
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING);
Dispatcher.FORWARD_PATH_INFO, Dispatcher.FORWARD_QUERY_STRING, Dispatcher.FORWARD_MAPPING);
List requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));

View File

@ -27,10 +27,12 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
/**
@ -38,6 +40,22 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
public class FilterHolderTest
{
public static class DummyFilter implements Filter
{
public DummyFilter()
{
}
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
}
}
@Test
public void testInitialize()
@ -95,4 +113,28 @@ public class FilterHolderTest
fh.initialize();
assertEquals(2, counter.get());
}
@Test
public void testCreateInstance() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(ServletHandler.class, ServletContextHandler.class))
{
//test without a ServletContextHandler or current ContextHandler
FilterHolder holder = new FilterHolder();
holder.setName("foo");
holder.setHeldClass(DummyFilter.class);
Filter filter = holder.createInstance();
assertNotNull(filter);
//test with a ServletContextHandler
Server server = new Server();
ServletContextHandler context = new ServletContextHandler();
server.setHandler(context);
ServletHandler handler = context.getServletHandler();
handler.addFilter(holder);
holder.setServletHandler(handler);
context.start();
assertNotNull(holder.getFilter());
}
}
}

View File

@ -0,0 +1,57 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.util.EventListener;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class ListenerHolderTest
{
public static class DummyListener implements EventListener
{
}
@Test
public void testCreateInstance() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(ServletHandler.class, ServletContextHandler.class))
{
//test without a ServletContextHandler or current ContextHandler
ListenerHolder holder = new ListenerHolder();
holder.setHeldClass(DummyListener.class);
EventListener listener = holder.createInstance();
assertNotNull(listener);
//test with a ServletContextHandler
Server server = new Server();
ServletContextHandler context = new ServletContextHandler();
server.setHandler(context);
ServletHandler handler = context.getServletHandler();
handler.addListener(holder);
holder.setServletHandler(handler);
context.start();
assertNotNull(holder.getListener());
}
}
}

View File

@ -20,10 +20,13 @@ package org.eclipse.jetty.servlet;
import java.util.Collections;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletRegistration;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.log.StacklessLogging;
@ -33,6 +36,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -113,6 +117,30 @@ public class ServletHolderTest
assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("/a/b/c/blah.jsp"));
assertEquals("org.apache.jsp.a.b.c.blah_jsp", h.getClassNameForJsp("a/b/c/blah.jsp"));
}
@Test
public void testCreateInstance() throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(ServletHandler.class, ContextHandler.class, ServletContextHandler.class))
{
//test without a ServletContextHandler or current ContextHandler
ServletHolder holder = new ServletHolder();
holder.setName("foo");
holder.setHeldClass(FakeServlet.class);
Servlet servlet = holder.createInstance();
assertNotNull(servlet);
//test with a ServletContextHandler
Server server = new Server();
ServletContextHandler context = new ServletContextHandler();
server.setHandler(context);
ServletHandler handler = context.getServletHandler();
handler.addServlet(holder);
holder.setServletHandler(handler);
context.start();
assertNotNull(holder.getServlet());
}
}
@Test
public void testNoClassName() throws Exception

View File

@ -154,12 +154,12 @@ public class Module implements Comparable<Module>
private final List<String> _license = new ArrayList<>();
/**
* Dependencies
* Dependencies from {@code [depends]} section
*/
private final List<String> _depends = new ArrayList<>();
/**
* Optional
* Optional dependencies from {@code [optional]} section are structural in nature.
*/
private final Set<String> _optional = new HashSet<>();
@ -196,6 +196,18 @@ public class Module implements Comparable<Module>
process(basehome);
}
public static boolean isRequiredDependency(String depends)
{
return (depends != null) && (depends.charAt(0) != '?');
}
public static String normalizeModuleName(String name)
{
if (!isRequiredDependency(name))
return name.substring(1);
return name;
}
public String getName()
{
return _name;

View File

@ -247,6 +247,7 @@ public class ModuleGraphWriter
{
for (String depends : module.getDepends())
{
depends = Module.normalizeModuleName(depends);
out.printf(" \"%s\" -> \"%s\";%n", module.getName(), depends);
}
for (String optional : module.getOptional())

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.start;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
@ -132,7 +133,12 @@ public class Modules implements Iterable<Module>
label = " Depend: %s";
for (String parent : module.getDepends())
{
parent = Module.normalizeModuleName(parent);
System.out.printf(label, parent);
if (!Module.isRequiredDependency(parent))
{
System.out.print(" [not-required]");
}
label = ", %s";
}
System.out.println();
@ -353,45 +359,59 @@ public class Modules implements Iterable<Module>
// Process module dependencies (always processed as may be dynamic)
StartLog.debug("Enabled module %s depends on %s", module.getName(), module.getDepends());
for (String dependsOn : module.getDepends())
for (String dependsOnRaw : module.getDepends())
{
// Look for modules that provide that dependency
Set<Module> providers = getAvailableProviders(dependsOn);
boolean isRequired = Module.isRequiredDependency(dependsOnRaw);
// Final to allow lambda's below to use name
final String dependentModule = Module.normalizeModuleName(dependsOnRaw);
StartLog.debug("Module %s depends on %s provided by %s", module, dependsOn, providers);
// Look for modules that provide that dependency
Set<Module> providers = getAvailableProviders(dependentModule);
StartLog.debug("Module %s depends on %s provided by %s", module, dependentModule, providers);
// If there are no known providers of the module
if (providers.isEmpty())
{
// look for a dynamic module
if (dependsOn.contains("/"))
if (dependentModule.contains("/"))
{
Path file = _baseHome.getPath("modules/" + dependsOn + ".mod");
registerModule(file).expandDependencies(_args.getProperties());
providers = _provided.get(dependsOn);
if (providers == null || providers.isEmpty())
throw new UsageException("Module %s does not provide %s", _baseHome.toShortForm(file), dependsOn);
Path file = _baseHome.getPath("modules/" + dependentModule + ".mod");
if (isRequired || Files.exists(file))
{
registerModule(file).expandDependencies(_args.getProperties());
providers = _provided.get(dependentModule);
if (providers == null || providers.isEmpty())
throw new UsageException("Module %s does not provide %s", _baseHome.toShortForm(file), dependentModule);
enable(newlyEnabled, providers.stream().findFirst().get(), "dynamic dependency of " + module.getName(), true);
enable(newlyEnabled, providers.stream().findFirst().get(), "dynamic dependency of " + module.getName(), true);
continue;
}
}
// is this a non-required module
if (!isRequired)
{
StartLog.debug("Skipping non-required module [%s]: doesn't exist", dependentModule);
continue;
}
throw new UsageException("No module found to provide %s for %s", dependsOn, module);
// throw an exception (not a dynamic module and a required dependency)
throw new UsageException("No module found to provide %s for %s", dependentModule, module);
}
// If a provider is already enabled, then add a transitive enable
if (providers.stream().filter(Module::isEnabled).count() > 0)
providers.stream().filter(m -> m.isEnabled() && !m.equals(module)).forEach(m -> enable(newlyEnabled, m, "transitive provider of " + dependsOn + " for " + module.getName(), true));
if (providers.stream().anyMatch(Module::isEnabled))
providers.stream().filter(m -> m.isEnabled() && !m.equals(module)).forEach(m -> enable(newlyEnabled, m, "transitive provider of " + dependentModule + " for " + module.getName(), true));
else
{
// Is there an obvious default?
Optional<Module> dftProvider = (providers.size() == 1)
? providers.stream().findFirst()
: providers.stream().filter(m -> m.getName().equals(dependsOn)).findFirst();
: providers.stream().filter(m -> m.getName().equals(dependentModule)).findFirst();
if (dftProvider.isPresent())
enable(newlyEnabled, dftProvider.get(), "transitive provider of " + dependsOn + " for " + module.getName(), true);
enable(newlyEnabled, dftProvider.get(), "transitive provider of " + dependentModule + " for " + module.getName(), true);
else if (StartLog.isDebugEnabled())
StartLog.debug("Module %s requires a %s implementation from one of %s", module, dependsOn, providers);
StartLog.debug("Module %s requires a %s implementation from one of %s", module, dependentModule, providers);
}
}
}

View File

@ -36,6 +36,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
@ExtendWith(WorkDirExtension.class)
public class ModulesTest
@ -202,6 +204,152 @@ public class ModulesTest
assertThat("Resolved XMLs: " + actualXmls, actualXmls, contains(expectedXmls.toArray()));
}
@Test
public void testResolveNotRequiredModuleNotFound() throws IOException
{
// Test Env
File homeDir = MavenTestingUtils.getTestResourceDir("non-required-deps");
File baseDir = testdir.getEmptyPathDir().toFile();
String[] cmdLine = new String[]{"bar.type=cannot-find-me"};
// Configuration
CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
ConfigSources config = new ConfigSources();
config.add(cmdLineSource);
config.add(new JettyHomeConfigSource(homeDir.toPath()));
config.add(new JettyBaseConfigSource(baseDir.toPath()));
// Initialize
BaseHome basehome = new BaseHome(config);
StartArgs args = new StartArgs(basehome);
args.parse(config);
// Test Modules
Modules modules = new Modules(basehome, args);
modules.registerAll();
// Enable module
modules.enable("bar", TEST_SOURCE);
// Collect active module list
List<Module> active = modules.getEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("foo");
expectedNames.add("bar");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
assertThat("Resolved Names: " + actualNames, actualNames, contains(expectedNames.toArray()));
Props props = args.getProperties();
assertThat(props.getString("bar.name"), is(nullValue()));
}
@Test
public void testResolveNotRequiredModuleFound() throws IOException
{
// Test Env
File homeDir = MavenTestingUtils.getTestResourceDir("non-required-deps");
File baseDir = testdir.getEmptyPathDir().toFile();
String[] cmdLine = new String[]{"bar.type=dive"};
// Configuration
CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
ConfigSources config = new ConfigSources();
config.add(cmdLineSource);
config.add(new JettyHomeConfigSource(homeDir.toPath()));
config.add(new JettyBaseConfigSource(baseDir.toPath()));
// Initialize
BaseHome basehome = new BaseHome(config);
StartArgs args = new StartArgs(basehome);
args.parse(config);
// Test Modules
Modules modules = new Modules(basehome, args);
modules.registerAll();
// Enable module
modules.enable("bar", TEST_SOURCE);
// Collect active module list
List<Module> active = modules.getEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("foo");
expectedNames.add("bar");
expectedNames.add("bar-dive");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
assertThat("Resolved Names: " + actualNames, actualNames, contains(expectedNames.toArray()));
Props props = args.getProperties();
assertThat(props.getString("bar.name"), is("dive"));
}
@Test
public void testResolveNotRequiredModuleFoundDynamic() throws IOException
{
// Test Env
File homeDir = MavenTestingUtils.getTestResourceDir("non-required-deps");
File baseDir = testdir.getEmptyPathDir().toFile();
String[] cmdLine = new String[]{"bar.type=dynamic"};
// Configuration
CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine);
ConfigSources config = new ConfigSources();
config.add(cmdLineSource);
config.add(new JettyHomeConfigSource(homeDir.toPath()));
config.add(new JettyBaseConfigSource(baseDir.toPath()));
// Initialize
BaseHome basehome = new BaseHome(config);
StartArgs args = new StartArgs(basehome);
args.parse(config);
// Test Modules
Modules modules = new Modules(basehome, args);
modules.registerAll();
// Enable module
modules.enable("bar", TEST_SOURCE);
// Collect active module list
List<Module> active = modules.getEnabled();
// Assert names are correct, and in the right order
List<String> expectedNames = new ArrayList<>();
expectedNames.add("foo");
expectedNames.add("bar");
expectedNames.add("impls/bar-dynamic");
List<String> actualNames = new ArrayList<>();
for (Module actual : active)
{
actualNames.add(actual.getName());
}
assertThat("Resolved Names: " + actualNames, actualNames, contains(expectedNames.toArray()));
Props props = args.getProperties();
assertThat(props.getString("bar.name"), is("dynamic"));
}
private List<String> normalizeLibs(List<Module> active)
{
List<String> libs = new ArrayList<>();

View File

@ -0,0 +1,2 @@
[ini]
bar.name=dive

View File

@ -0,0 +1,6 @@
# Top level mod
[depends]
foo
?bar-${bar.type}
?impls/bar-${bar.type}

View File

@ -0,0 +1 @@
# nothing here

View File

@ -0,0 +1,2 @@
[ini]
bar.name=dynamic

View File

@ -239,7 +239,7 @@ public class DateCache
// recheck the tick, to save multiple formats
if (tick == null || tick._seconds != seconds)
{
String s = ZonedDateTime.ofInstant(Instant.now(), _zoneId).format(_tzFormat);
String s = ZonedDateTime.ofInstant(Instant.ofEpochMilli(now), _zoneId).format(_tzFormat);
_tick = new Tick(seconds, s);
tick = _tick;
}

View File

@ -45,9 +45,26 @@ public class DecoratedObjectFactory implements Iterable<Decorator>
* ServletContext attribute for the active DecoratedObjectFactory
*/
public static final String ATTR = DecoratedObjectFactory.class.getName();
private static final ThreadLocal<Object> decoratorInfo = new ThreadLocal<>();
private List<Decorator> decorators = new ArrayList<>();
public static void associateInfo(Object info)
{
decoratorInfo.set(info);
}
public static void disassociateInfo()
{
decoratorInfo.set(null);
}
public static Object getAssociatedInfo()
{
return decoratorInfo.get();
}
public void addDecorator(Decorator decorator)
{
LOG.debug("Adding Decorator: {}", decorator);
@ -70,7 +87,7 @@ public class DecoratedObjectFactory implements Iterable<Decorator>
{
if (LOG.isDebugEnabled())
{
LOG.debug("Creating Instance: " + clazz);
LOG.debug("Creating Instance: {}", clazz);
}
T o = clazz.getDeclaredConstructor().newInstance();
return decorate(o);

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@ -145,10 +146,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void include(T... element)
{
for (T e : element)
{
_includes.add(e);
}
_includes.addAll(Arrays.asList(element));
}
public void exclude(T element)
@ -158,10 +156,7 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
public void exclude(T... element)
{
for (T e : element)
{
_excludes.add(e);
}
_excludes.addAll(Arrays.asList(element));
}
@Override
@ -233,34 +228,4 @@ public class IncludeExcludeSet<T, P> implements Predicate<P>
{
return _includes.isEmpty() && _excludes.isEmpty();
}
/**
* Match items in combined IncludeExcludeSets.
* @param item1 The item to match against set1
* @param set1 A IncludeExcludeSet to match item1 against
* @param item2 The item to match against set2
* @param set2 A IncludeExcludeSet to match item2 against
* @param <T1> The type of item1
* @param <T2> The type of item2
* @return True IFF <ul>
* <li>Neither item is excluded from their respective sets</li>
* <li>Both sets have no includes OR at least one of the items is included in its respective set</li>
* </ul>
*/
public static <T1,T2> boolean matchCombined(T1 item1, IncludeExcludeSet<?,T1> set1, T2 item2, IncludeExcludeSet<?,T2> set2)
{
Boolean match1 = set1.isIncludedAndNotExcluded(item1);
Boolean match2 = set2.isIncludedAndNotExcluded(item2);
// if we are excluded from either set, then we do not match
if (match1 == Boolean.FALSE || match2 == Boolean.FALSE)
return false;
// If either set has any includes, then we must be included by one of them
if (set1.hasIncludes() || set2.hasIncludes())
return match1 == Boolean.TRUE || match2 == Boolean.TRUE;
// If not excluded and no includes, then we match
return true;
}
}

View File

@ -0,0 +1,282 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.util;
import java.net.InetAddress;
import java.util.function.Predicate;
/**
* A pattern representing a single or range of {@link InetAddress}. To create a pattern use
* the {@link InetAddressPattern#from(String)} method, which will create a pattern given a
* string conforming to one of the following formats.
*
* <dl>
* <dt>InetAddress</dt>
* <dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt>
* <dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt>
* <dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* <dt>Legacy format</dt>
* <dd>The legacy format used for IPv4 only.
* eg. "10.10.10-14.0-128"</dd>
* </dl>
*/
public abstract class InetAddressPattern implements Predicate<InetAddress>
{
protected final String _pattern;
public static InetAddressPattern from(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetAddressRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetAddressRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetAddressRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetAddressRange(pattern);
}
catch (Exception ex2)
{
e.addSuppressed(ex2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
}
public InetAddressPattern(String pattern)
{
_pattern = pattern;
}
@Override
public String toString()
{
return _pattern;
}
static class SingletonInetAddressRange extends InetAddressPattern
{
final InetAddress _address;
public SingletonInetAddressRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address)
{
return _address.equals(address);
}
}
static class MinMaxInetAddressRange extends InetAddressPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetAddressRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetAddressRange extends InetAddressPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetAddressRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
return _mask == 0 || (raw[_octets] & _mask) == _masked;
}
}
static class LegacyInetAddressRange extends InetAddressPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetAddressRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress address)
{
byte[] raw = address.getAddress();
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
}

View File

@ -30,62 +30,20 @@ import java.util.function.Predicate;
* A set of InetAddress patterns.
* <p>This is a {@link Set} of String patterns that are used to match
* a {@link Predicate} over InetAddress for containment semantics.
* The patterns that may be set are:
* The patterns that may be set are defined in {@link InetAddressPattern}.
* </p>
* <dl>
* <dt>InetAddress</dt><dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt><dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt><dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* </dl>
* <p>This class is designed to work with {@link IncludeExcludeSet}</p>
*
* @see IncludeExcludeSet
*/
public class InetAddressSet extends AbstractSet<String> implements Set<String>, Predicate<InetAddress>
{
private Map<String, InetPattern> _patterns = new HashMap<>();
private Map<String, InetAddressPattern> _patterns = new HashMap<>();
@Override
public boolean add(String pattern)
{
return _patterns.put(pattern, newInetRange(pattern)) == null;
}
private InetPattern newInetRange(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetRange(pattern);
}
catch (Exception e2)
{
e.addSuppressed(e2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
return _patterns.put(pattern, InetAddressPattern.from(pattern)) == null;
}
@Override
@ -111,219 +69,11 @@ public class InetAddressSet extends AbstractSet<String> implements Set<String>,
{
if (address == null)
return false;
byte[] raw = address.getAddress();
for (InetPattern pattern : _patterns.values())
for (InetAddressPattern pattern : _patterns.values())
{
if (pattern.test(address, raw))
if (pattern.test(address))
return true;
}
return false;
}
abstract static class InetPattern
{
final String _pattern;
InetPattern(String pattern)
{
_pattern = pattern;
}
abstract boolean test(InetAddress address, byte[] raw);
@Override
public String toString()
{
return _pattern;
}
}
static class SingletonInetRange extends InetPattern
{
final InetAddress _address;
public SingletonInetRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address, byte[] raw)
{
return _address.equals(address);
}
}
static class MinMaxInetRange extends InetPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetRange extends InetPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
if (_mask != 0 && (raw[_octets] & _mask) != _masked)
return false;
return true;
}
}
static class LegacyInetRange extends InetPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
}

View File

@ -438,6 +438,22 @@ public class StringUtil
}
}
/**
* Generate a string from another string repeated n times.
*
* @param s the string to use
* @param n the number of times this string should be appended
*/
public static String stringFrom(String s, int n)
{
StringBuilder stringBuilder = new StringBuilder(s.length() * n);
for (int i = 0; i < n; i++)
{
stringBuilder.append(s);
}
return stringBuilder.toString();
}
/**
* Return a non null string.
*

View File

@ -124,7 +124,8 @@ public class StdErrLog extends AbstractLogger
private int _level;
// Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
private int _configuredLevel;
private PrintStream _stderr = System.err;
// The alternate stream to print to (if set)
private PrintStream _altStream;
private boolean _source;
// Print the long form names, otherwise use abbreviated
private boolean _printLongNames = LONG_CLASSNAMES;
@ -378,16 +379,14 @@ public class StdErrLog extends AbstractLogger
this._level = level;
}
/**
* The alternate stream to use for STDERR.
*
* @param stream the stream of choice, or {@code null} to use {@link System#err}
*/
public void setStdErrStream(PrintStream stream)
{
if (stream == null)
{
this._stderr = System.err;
}
else
{
this._stderr = stream;
}
this._altStream = stream;
}
@Override
@ -431,7 +430,14 @@ public class StdErrLog extends AbstractLogger
private void println(StringBuilder builder)
{
_stderr.println(builder);
if (_altStream != null)
_altStream.println(builder);
else
{
// We always use the PrintStream stored in System.err,
// just in case someone has replaced it with a call to System.setErr(PrintStream)
System.err.println(builder);
}
}
private void format(StringBuilder builder, String level, String msg, Object... inArgs)
@ -632,7 +638,7 @@ public class StdErrLog extends AbstractLogger
StdErrLog logger = new StdErrLog(fullname);
// Preserve configuration for new loggers configuration
logger.setPrintLongNames(_printLongNames);
logger._stderr = this._stderr;
logger._altStream = this._altStream;
// Force the child to have any programmatic configuration
if (_level != _configuredLevel)

View File

@ -460,10 +460,17 @@ public class PathResource extends Resource
}
@Override
public boolean isContainedIn(Resource r) throws MalformedURLException
public boolean isContainedIn(Resource r)
{
// not applicable for FileSystem / path
return false;
try
{
PathResource pr = PathResource.class.cast(r);
return (path.startsWith(pr.getPath()));
}
catch (ClassCastException e)
{
return false;
}
}
@Override

View File

@ -282,7 +282,7 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
{
LOG.ignore(e);
_size.getAndIncrement();
_stack.addFirst(this);
_stack.offerFirst(this);
return false;
}
}
@ -307,14 +307,13 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
if (task != null)
return task;
// Because threads are held in a stack, excess threads will be
// idle. However, we cannot remove threads from the bottom of
// the stack, so we submit a poison pill job to stop the thread
// on top of the stack (which unfortunately will be the most
// recently used)
if (LOG.isDebugEnabled())
LOG.debug("{} IDLE", this);
tryExecute(STOP);
if (_stack.remove(this))
{
if (LOG.isDebugEnabled())
LOG.debug("{} IDLE", this);
_size.decrementAndGet();
return STOP;
}
}
catch (InterruptedException e)
{

View File

@ -393,7 +393,7 @@ public class FileSystemResourceTest
try (Resource base = newResource(resourceClass, dir.toFile()))
{
Resource res = base.addPath("foo");
assertThat("is contained in", res.isContainedIn(base), is(false));
assertThat("is contained in", res.isContainedIn(base), is(true));
}
}

View File

@ -32,6 +32,8 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -183,6 +185,26 @@ public class ReservedThreadExecutorTest
assertThat(_reservedExecutor.getAvailable(), is(0));
}
@Test
public void testReservedIdleTimeoutWithOneReservedThread() throws Exception
{
long idleTimeout = 500;
_reservedExecutor.stop();
_reservedExecutor.setIdleTimeout(idleTimeout, TimeUnit.MILLISECONDS);
_reservedExecutor.start();
assertThat(_reservedExecutor.tryExecute(NOOP), is(false));
Thread thread = _executor.startThread();
assertNotNull(thread);
waitForAvailable(1);
Thread.sleep(2 * idleTimeout);
waitForAvailable(0);
thread.join(2 * idleTimeout);
assertFalse(thread.isAlive());
}
protected void waitForAvailable(int size) throws InterruptedException
{
long started = System.nanoTime();
@ -211,11 +233,16 @@ public class ReservedThreadExecutorTest
_queue.addLast(task);
}
public void startThread()
public Thread startThread()
{
Runnable task = _queue.pollFirst();
if (task != null)
new Thread(task).start();
{
Thread thread = new Thread(task);
thread.start();
return thread;
}
return null;
}
}

View File

@ -48,7 +48,7 @@ public class AbsoluteOrdering implements Ordering
//1. put everything into the list of named others, and take the named ones out of there,
//assuming we will want to use the <other> clause
Map<String, FragmentDescriptor> others = new HashMap<String, FragmentDescriptor>(_metaData.getNamedFragments());
Map<String, FragmentDescriptor> others = new HashMap<String, FragmentDescriptor>(_metaData.getNamedFragmentDescriptors());
//2. for each name, take out of the list of others, add to tail of list
int index = -1;
@ -59,7 +59,7 @@ public class AbsoluteOrdering implements Ordering
FragmentDescriptor f = others.remove(item);
if (f != null)
{
Resource jar = _metaData.getJarForFragment(item);
Resource jar = _metaData.getJarForFragmentName(item);
orderedList.add(jar); //take from others and put into final list in order, ignoring duplicate names
//remove resource from list for resource matching name of descriptor
tmp.remove(jar);

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.webapp;
import java.util.Objects;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser;
@ -26,30 +28,21 @@ public abstract class Descriptor
protected Resource _xml;
protected XmlParser.Node _root;
protected String _dtd;
protected boolean _validating;
public Descriptor(Resource xml)
{
_xml = xml;
_xml = Objects.requireNonNull(xml);
}
public abstract XmlParser ensureParser()
throws ClassNotFoundException;
public void setValidating(boolean validating)
{
_validating = validating;
}
public void parse()
public void parse(XmlParser parser)
throws Exception
{
if (_root == null)
{
Objects.requireNonNull(parser);
try
{
XmlParser parser = ensureParser();
_root = parser.parse(_xml.getInputStream());
_dtd = parser.getDTD();
}
@ -59,6 +52,11 @@ public abstract class Descriptor
}
}
}
public boolean isParsed()
{
return _root != null;
}
public Resource getResource()
{

View File

@ -51,6 +51,11 @@ public abstract class DiscoveredAnnotation
_className = className;
_resource = resource;
}
public String getClassName()
{
return _className;
}
public Resource getResource()
{
@ -84,4 +89,10 @@ public abstract class DiscoveredAnnotation
LOG.warn(e);
}
}
@Override
public String toString()
{
return getClass().getName() + "[" + getClassName() + "," + getResource() + "]";
}
}

View File

@ -66,11 +66,11 @@ public class FragmentConfiguration extends AbstractConfiguration
{
if (key.isDirectory()) //tolerate the case where the library is a directory, not a jar. useful for OSGi for example
{
metaData.addFragment(key, frags.get(key));
metaData.addFragmentDescriptor(key, new FragmentDescriptor(frags.get(key)));
}
else //the standard case: a jar most likely inside WEB-INF/lib
{
metaData.addFragment(key, frags.get(key));
metaData.addFragmentDescriptor(key, new FragmentDescriptor(frags.get(key)));
}
}
}

View File

@ -60,10 +60,10 @@ public class FragmentDescriptor extends WebDescriptor
}
@Override
public void parse()
public void parse(XmlParser parser)
throws Exception
{
super.parse();
super.parse(parser);
processName();
}

View File

@ -19,17 +19,22 @@
package org.eclipse.jetty.webapp;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.ServletContext;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlParser;
/**
* MetaData
@ -44,36 +49,67 @@ public class MetaData
public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
public static final Resource NON_FRAG_RESOURCE = EmptyResource.INSTANCE;
protected Map<String, OriginInfo> _origins = new HashMap<String, OriginInfo>();
protected Map<String, OriginInfo> _origins = new HashMap<>();
protected WebDescriptor _webDefaultsRoot;
protected WebDescriptor _webXmlRoot;
protected final List<WebDescriptor> _webOverrideRoots = new ArrayList<WebDescriptor>();
protected final List<WebDescriptor> _webOverrideRoots = new ArrayList<>();
protected boolean _metaDataComplete;
protected final List<DescriptorProcessor> _descriptorProcessors = new ArrayList<DescriptorProcessor>();
protected final List<FragmentDescriptor> _webFragmentRoots = new ArrayList<FragmentDescriptor>();
protected final Map<String, FragmentDescriptor> _webFragmentNameMap = new HashMap<String, FragmentDescriptor>();
protected final Map<Resource, FragmentDescriptor> _webFragmentResourceMap = new HashMap<Resource, FragmentDescriptor>();
protected final Map<Resource, List<DiscoveredAnnotation>> _annotations = new HashMap<Resource, List<DiscoveredAnnotation>>();
protected final List<Resource> _webInfClasses = new ArrayList<Resource>();
protected final List<Resource> _webInfJars = new ArrayList<Resource>();
protected final List<Resource> _orderedContainerResources = new ArrayList<Resource>();
protected final List<Resource> _orderedWebInfResources = new ArrayList<Resource>();
protected final List<DescriptorProcessor> _descriptorProcessors = new ArrayList<>();
protected final List<FragmentDescriptor> _webFragmentRoots = new ArrayList<>();
protected final Map<String, FragmentDescriptor> _webFragmentNameMap = new HashMap<>();
protected final Map<Resource, FragmentDescriptor> _webFragmentResourceMap = new HashMap<>();
protected final Map<Resource, List<DiscoveredAnnotation>> _annotations = new HashMap<>();
protected final List<Resource> _webInfClasses = new ArrayList<>();
protected final List<Resource> _webInfJars = new ArrayList<>();
protected final List<Resource> _orderedContainerResources = new ArrayList<>();
protected final List<Resource> _orderedWebInfResources = new ArrayList<>();
protected Ordering _ordering;//can be set to RelativeOrdering by web-default.xml, web.xml, web-override.xml
protected boolean _allowDuplicateFragmentNames = false;
protected boolean _validateXml = false;
public enum Complete
{
NotSet, True, False
}
/**
* Metadata regarding where a deployable element was declared:
* by annotation or by descriptor.
*
*/
public static class OriginInfo
{
/**
* Identifier for the deployable element
*/
private final String name;
/**
* Origin of the deployable element
*/
private final Origin origin;
/**
* Reference to the descriptor, if declared in one
*/
private final Descriptor descriptor;
/**
* Reference to the annotation, if declared by one
*/
private final Annotation annotation;
/**
* The class containing the annotation, if declared by one
*/
private final Class<?> annotated;
public OriginInfo(String n, Annotation a, Class<?> ac)
{
if (Objects.isNull(n))
throw new IllegalArgumentException("No name");
name = n;
origin = Origin.Annotation;
origin = Origin.of(a);
descriptor = null;
annotation = a;
annotated = ac;
@ -81,24 +117,21 @@ public class MetaData
public OriginInfo(String n, Descriptor d)
{
if (Objects.isNull(n))
throw new IllegalArgumentException("No name");
if (Objects.isNull(d))
throw new IllegalArgumentException("No descriptor");
name = n;
origin = Origin.of(d);
descriptor = d;
annotation = null;
annotated = null;
if (d == null)
throw new IllegalArgumentException("No descriptor");
if (d instanceof FragmentDescriptor)
origin = Origin.WebFragment;
else if (d instanceof OverrideDescriptor)
origin = Origin.WebOverride;
else if (d instanceof DefaultsDescriptor)
origin = Origin.WebDefaults;
else
origin = Origin.WebXml;
annotated = null;
}
public OriginInfo(String n)
{
if (Objects.isNull(n))
throw new IllegalArgumentException("No name");
name = n;
origin = Origin.API;
annotation = null;
@ -159,12 +192,17 @@ public class MetaData
_allowDuplicateFragmentNames = false;
}
public void setDefaults(Resource webDefaults)
/**
* Set the web-default.xml.
*
* @param descriptor the web-default.xml
* @throws Exception
*/
public void setDefaultsDescriptor(DefaultsDescriptor descriptor)
throws Exception
{
_webDefaultsRoot = new DefaultsDescriptor(webDefaults);
_webDefaultsRoot.setValidating(isValidateXml());
_webDefaultsRoot.parse();
_webDefaultsRoot = descriptor;
_webDefaultsRoot.parse(WebDescriptor.getParser(isValidateXml()));
if (_webDefaultsRoot.isOrdered())
{
Ordering ordering = getOrdering();
@ -185,13 +223,16 @@ public class MetaData
}
}
public void setWebXml(Resource webXml)
/**
* @param descriptor the web.xml descriptor
* @throws Exception
*/
public void setWebDescriptor(WebDescriptor descriptor)
throws Exception
{
_webXmlRoot = new WebDescriptor(webXml);
_webXmlRoot.setValidating(isValidateXml());
_webXmlRoot.parse();
_metaDataComplete = _webXmlRoot.getMetaDataComplete() == MetaDataComplete.True;
_webXmlRoot = descriptor;
_webXmlRoot.parse(WebDescriptor.getParser(isValidateXml()));
_metaDataComplete = WebDescriptor.isMetaDataComplete(_webXmlRoot);
if (_webXmlRoot.isOrdered())
{
@ -213,14 +254,18 @@ public class MetaData
}
}
public void addOverride(Resource override)
/**
* Add a override-web.xml descriptor.
*
* @param descriptor the override-web.xml
* @throws Exception
*/
public void addOverrideDescriptor(OverrideDescriptor descriptor)
throws Exception
{
OverrideDescriptor webOverrideRoot = new OverrideDescriptor(override);
webOverrideRoot.setValidating(false);
webOverrideRoot.parse();
descriptor.parse(WebDescriptor.getParser(isValidateXml()));
switch (webOverrideRoot.getMetaDataComplete())
switch (descriptor.getMetaDataComplete())
{
case True:
_metaDataComplete = true;
@ -232,14 +277,14 @@ public class MetaData
break;
}
if (webOverrideRoot.isOrdered())
if (descriptor.isOrdered())
{
Ordering ordering = getOrdering();
if (ordering == null)
ordering = new AbsoluteOrdering(this);
List<String> order = webOverrideRoot.getOrdering();
List<String> order = descriptor.getOrdering();
for (String s : order)
{
if (s.equalsIgnoreCase("others"))
@ -251,29 +296,29 @@ public class MetaData
//set or reset the ordering to cause the webinf jar ordering to be recomputed
setOrdering(ordering);
}
_webOverrideRoots.add(webOverrideRoot);
_webOverrideRoots.add(descriptor);
}
/**
* Add a web-fragment.xml
* Add a web-fragment.xml, and the jar it is contained in.
*
* @param jarResource the jar the fragment is contained in
* @param xmlResource the resource representing the xml file
* @param jarResource the jar of the fragment
* @param descriptor web-fragment.xml
* @throws Exception if unable to add fragment
*/
public void addFragment(Resource jarResource, Resource xmlResource)
public void addFragmentDescriptor(Resource jarResource, FragmentDescriptor descriptor)
throws Exception
{
if (_metaDataComplete)
return; //do not process anything else if web.xml/web-override.xml set metadata-complete
Objects.requireNonNull(jarResource);
Objects.requireNonNull(descriptor);
//Metadata-complete is not set, or there is no web.xml
FragmentDescriptor descriptor = new FragmentDescriptor(xmlResource);
_webFragmentResourceMap.put(jarResource, descriptor);
_webFragmentRoots.add(descriptor);
descriptor.setValidating(isValidateXml());
descriptor.parse();
descriptor.parse(WebDescriptor.getParser(isValidateXml()));
if (descriptor.getName() != null)
{
@ -298,8 +343,8 @@ public class MetaData
}
/**
* Annotations not associated with a WEB-INF/lib fragment jar.
* These are from WEB-INF/classes or the ??container path??
* Annotations such as WebServlet, WebFilter, WebListener that
* can be discovered by scanning unloaded classes.
*
* @param annotations the list of discovered annotations to add
*/
@ -315,7 +360,8 @@ public class MetaData
/**
* Add an annotation that has been discovered on a class, method or field within a resource
* eg a jar or dir.
* eg a jar or dir. The annotation may also have no associated resource, or that resource
* may be a system or container resource.
*
* This method is synchronized as it is anticipated that it may be called by many threads
* during the annotation scanning phase.
@ -327,20 +373,68 @@ public class MetaData
if (annotation == null)
return;
//if no resource associated with an annotation or the resource is not one of the WEB-INF/lib jars,
//map it to empty resource
//if no resource associated with an annotation map it to empty resource - these
//annotations will always be processed first
Resource enclosingResource = EmptyResource.INSTANCE;
Resource resource = annotation.getResource();
if (resource == null || !_webInfJars.contains(resource))
resource = EmptyResource.INSTANCE;
if (resource != null)
{
//check if any of the web-inf classes dirs is a parent
enclosingResource = getEnclosingResource(_webInfClasses, resource);
List<DiscoveredAnnotation> list = _annotations.get(resource);
//check if any of the web-inf jars is a parent
if (enclosingResource == null)
enclosingResource = getEnclosingResource(_webInfJars, resource);
//check if any of the container resources is a parent
if (enclosingResource == null)
enclosingResource = getEnclosingResource(_orderedContainerResources, resource);
//Couldn't find a parent resource in any of the known resources, map it to the empty resource
if (enclosingResource == null)
enclosingResource = EmptyResource.INSTANCE;
}
List<DiscoveredAnnotation> list = _annotations.get(enclosingResource);
if (list == null)
{
list = new ArrayList<DiscoveredAnnotation>();
_annotations.put(resource, list);
list = new ArrayList<>();
_annotations.put(enclosingResource, list);
}
list.add(annotation);
}
/**
* Check if the resource is contained within one of the list of resources.
* In other words, check if the given resource is a sub-resource of one
* of the list of resources.
*
* @param resources the list of resources to check against
* @param resource the resource for which to find the parent resource
* @return the resource from the list that contains the given resource.
*/
private Resource getEnclosingResource(List<Resource> resources, Resource resource)
{
Resource enclosingResource = null;
try
{
for (Resource r : resources)
{
if (Resource.isContainedIn(resource, r))
{
enclosingResource = r;
break;
}
}
return enclosingResource;
}
catch (Exception e)
{
LOG.warn(e);
return null;
}
}
public void addDescriptorProcessor(DescriptorProcessor p)
{
@ -375,9 +469,9 @@ public class MetaData
// Set the ordered lib attribute
List<Resource> orderedWebInfJars = null;
if (getOrdering() != null)
if (isOrdered())
{
orderedWebInfJars = getOrderedWebInfJars();
orderedWebInfJars = getWebInfResources(true);
List<String> orderedLibs = new ArrayList<String>();
for (Resource webInfJar : orderedWebInfJars)
{
@ -387,7 +481,7 @@ public class MetaData
int j = fullname.lastIndexOf("/", i);
orderedLibs.add(fullname.substring(j + 1, i + 4));
}
context.setAttribute(ServletContext.ORDERED_LIBS, orderedLibs);
context.setAttribute(ServletContext.ORDERED_LIBS, Collections.unmodifiableList(orderedLibs));
}
// set the webxml version
@ -397,40 +491,29 @@ public class MetaData
context.getServletContext().setEffectiveMinorVersion(_webXmlRoot.getMinorVersion());
}
//process web-defaults.xml, web.xml and override-web.xmls
for (DescriptorProcessor p : _descriptorProcessors)
{
p.process(context, getWebDefault());
p.process(context, getWebXml());
for (WebDescriptor wd : getOverrideWebs())
p.process(context, getDefaultsDescriptor());
p.process(context, getWebDescriptor());
for (WebDescriptor wd : getOverrideDescriptors())
{
LOG.debug("process {} {} {}", context, p, wd);
p.process(context, wd);
}
}
//get an apply the annotations that are not associated with a fragment (and hence for
//which no ordering applies
List<DiscoveredAnnotation> nonFragAnnotations = _annotations.get(NON_FRAG_RESOURCE);
if (nonFragAnnotations != null)
{
for (DiscoveredAnnotation a : nonFragAnnotations)
{
LOG.debug("apply {}", a);
a.apply();
}
}
//apply the annotations that are associated with a fragment, according to the
//established ordering
List<Resource> resources = null;
if (getOrdering() != null)
resources = orderedWebInfJars;
else
resources = getWebInfJars();
List<Resource> resources = new ArrayList<>();
resources.add(EmptyResource.INSTANCE); //always apply annotations with no resource first
resources.addAll(_orderedContainerResources); //next all annotations from container path
resources.addAll(_webInfClasses);//next everything from web-inf classes
resources.addAll(getWebInfResources(isOrdered())); //finally annotations (in order) from webinf path
for (Resource r : resources)
{
//Process the web-fragment.xml before applying annotations from a fragment.
//Note that some fragments, or resources that aren't fragments won't have
//a descriptor.
FragmentDescriptor fd = _webFragmentResourceMap.get(r);
if (fd != null)
{
@ -441,10 +524,13 @@ public class MetaData
}
}
List<DiscoveredAnnotation> fragAnnotations = _annotations.get(r);
if (fragAnnotations != null)
//Then apply the annotations - note that if metadata is complete
//either overall or for a fragment, those annotations won't have
//been discovered.
List<DiscoveredAnnotation> annotations = _annotations.get(r);
if (annotations != null)
{
for (DiscoveredAnnotation a : fragAnnotations)
for (DiscoveredAnnotation a : annotations)
{
LOG.debug("apply {}", a);
a.apply();
@ -453,6 +539,13 @@ public class MetaData
}
}
/**
* A webapp is distributable if web.xml is metadata-complete and
* distributable=true, or if metadata-complete is false, but all
* web-fragments.xml are distributable=true.
*
* @return true if the webapp is distributable, false otherwise
*/
public boolean isDistributable()
{
boolean distributable = (
@ -464,9 +557,9 @@ public class MetaData
distributable &= d.isDistributable();
}
if (getOrdering() != null)
if (isOrdered())
{
List<Resource> orderedResources = getOrderedWebInfJars();
List<Resource> orderedResources = getWebInfResources(true);
for (Resource r : orderedResources)
{
FragmentDescriptor d = _webFragmentResourceMap.get(r);
@ -477,44 +570,24 @@ public class MetaData
return distributable;
}
public WebDescriptor getWebXml()
public WebDescriptor getWebDescriptor()
{
return _webXmlRoot;
}
public List<WebDescriptor> getOverrideWebs()
public List<WebDescriptor> getOverrideDescriptors()
{
return _webOverrideRoots;
}
public WebDescriptor getWebDefault()
public WebDescriptor getDefaultsDescriptor()
{
return _webDefaultsRoot;
}
public List<FragmentDescriptor> getFragments()
public boolean isOrdered()
{
return _webFragmentRoots;
}
public List<Resource> getOrderedWebInfJars()
{
return _orderedWebInfResources;
}
public List<FragmentDescriptor> getOrderedFragments()
{
List<FragmentDescriptor> list = new ArrayList<FragmentDescriptor>();
if (getOrdering() == null)
return list;
for (Resource r : getOrderedWebInfJars())
{
FragmentDescriptor fd = _webFragmentResourceMap.get(r);
if (fd != null)
list.add(fd);
}
return list;
return getOrdering() != null;
}
public Ordering getOrdering()
@ -528,32 +601,60 @@ public class MetaData
orderFragments();
}
public FragmentDescriptor getFragment(Resource jar)
{
return _webFragmentResourceMap.get(jar);
}
public FragmentDescriptor getFragment(String name)
/**
* @param name the name specified in a web-fragment.xml
* @return the web-fragment.xml that defines that name or null
*/
public FragmentDescriptor getFragmentDescriptor(String name)
{
return _webFragmentNameMap.get(name);
}
public Resource getJarForFragment(String name)
/**
* @param descriptorResource the web-fragment.xml location as a Resource
* @return the FrgmentDescriptor for the web-fragment.xml, or null if none exists
*/
public FragmentDescriptor getFragmentDescriptor(Resource descriptorResource)
{
FragmentDescriptor f = getFragment(name);
return _webFragmentRoots.stream().filter(d -> d.getResource().equals(descriptorResource)).findFirst().orElse(null);
}
/**
* @param name the name specified in a web-fragment.xml
* @return the jar that contains the web-fragment.xml with the given name or null
*/
public Resource getJarForFragmentName(String name)
{
Resource jar = null;
FragmentDescriptor f = getFragmentDescriptor(name);
if (f == null)
return null;
Resource jar = null;
for (Resource r : _webFragmentResourceMap.keySet())
for (Map.Entry<Resource,FragmentDescriptor> entry : _webFragmentResourceMap.entrySet())
{
if (_webFragmentResourceMap.get(r).equals(f))
jar = r;
if (entry.getValue().equals(f))
jar = entry.getKey();
}
return jar;
}
/**
* Get the web-fragment.xml related to a jar
*
* @param jar the jar to check for a mapping to web-fragment.xml
* @return the FragmentDescriptor or null if no web-fragment.xml is associated with the jar
*/
public FragmentDescriptor getFragmentDescriptorForJar(Resource jar)
{
return _webFragmentResourceMap.get(jar);
}
public Map<String, FragmentDescriptor> getNamedFragments()
/**
* @return a map of name to FragmentDescriptor, for those FragmentDescriptors that
* define a name element.
*/
public Map<String, FragmentDescriptor> getNamedFragmentDescriptors()
{
return Collections.unmodifiableMap(_webFragmentNameMap);
}
@ -586,6 +687,9 @@ public class MetaData
public void setOrigin(String name, Descriptor d)
{
if (name == null)
return;
OriginInfo x = new OriginInfo(name, d);
_origins.put(name, x);
}
@ -613,19 +717,22 @@ public class MetaData
return _metaDataComplete;
}
public void addWebInfJar(Resource newResource)
public void addWebInfResource(Resource newResource)
{
_webInfJars.add(newResource);
}
public List<Resource> getWebInfJars()
public List<Resource> getWebInfResources(boolean withOrdering)
{
return Collections.unmodifiableList(_webInfJars);
if (!withOrdering)
return Collections.unmodifiableList(_webInfJars);
else
return Collections.unmodifiableList(_orderedWebInfResources);
}
public List<Resource> getContainerResources()
{
return _orderedContainerResources;
return Collections.unmodifiableList(_orderedContainerResources);
}
public void addContainerResource(Resource jar)
@ -633,14 +740,14 @@ public class MetaData
_orderedContainerResources.add(jar);
}
public void setWebInfClassesDirs(List<Resource> dirs)
public void setWebInfClassesResources(List<Resource> dirs)
{
_webInfClasses.addAll(dirs);
}
public List<Resource> getWebInfClassesDirs()
public List<Resource> getWebInfClassesResources()
{
return _webInfClasses;
return Collections.unmodifiableList(_webInfClasses);
}
public boolean isAllowDuplicateFragmentNames()
@ -650,19 +757,19 @@ public class MetaData
public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames)
{
this._allowDuplicateFragmentNames = allowDuplicateFragmentNames;
_allowDuplicateFragmentNames = allowDuplicateFragmentNames;
}
/**
* @return the validateXml
* @return true if the parser validates, false otherwise
*/
public boolean isValidateXml()
{
return _validateXml;
}
/**
* @param validateXml the validateXml to set
* @param validateXml if true xml syntax is validated by the parser, false otherwise
*/
public void setValidateXml(boolean validateXml)
{

View File

@ -144,7 +144,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
@Override
public void matched(URI uri) throws Exception
{
_context.getMetaData().addWebInfJar(Resource.newResource(uri));
_context.getMetaData().addWebInfResource(Resource.newResource(uri));
}
}
@ -169,7 +169,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
findAndFilterWebAppPaths(context);
//No pattern to appy to classes, just add to metadata
context.getMetaData().setWebInfClassesDirs(findClassDirs(context));
context.getMetaData().setWebInfClassesResources(findClassDirs(context));
scanJars(context);
}
@ -358,7 +358,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
List<String> scanTypes = new ArrayList<>(__allScanTypes);
if (context.getMetaData().isMetaDataComplete() || (context.getServletContext().getEffectiveMajorVersion() < 3) && !context.isConfigurationDiscovered())
scanTypes.remove(METAINF_FRAGMENTS);
scanJars(context, context.getMetaData().getWebInfJars(), false, scanTypes);
scanJars(context, context.getMetaData().getWebInfResources(false), false, scanTypes);
}
/**

View File

@ -20,5 +20,23 @@ package org.eclipse.jetty.webapp;
public enum Origin
{
NotSet, WebXml, WebDefaults, WebOverride, WebFragment, Annotation, API
}
NotSet, WebXml, WebDefaults, WebOverride, WebFragment, Annotation, API;
public static Origin of(Object o)
{
if (o == null)
return null;
if (o instanceof java.lang.annotation.Annotation)
return Annotation;
if (o instanceof FragmentDescriptor)
return WebFragment;
else if (o instanceof OverrideDescriptor)
return WebOverride;
else if (o instanceof DefaultsDescriptor)
return WebDefaults;
else if (o instanceof WebDescriptor)
return WebXml;
else
return API;
}
}

View File

@ -52,7 +52,7 @@ public class RelativeOrdering implements Ordering
// Pass 1: split the jars into 'before others', 'others' or 'after others'
for (Resource jar : jars)
{
FragmentDescriptor fragment = _metaData.getFragment(jar);
FragmentDescriptor fragment = _metaData.getFragmentDescriptorForJar(jar);
if (fragment == null)
others.add(jar);
@ -79,7 +79,7 @@ public class RelativeOrdering implements Ordering
Set<Resource> referenced = new HashSet<>();
for (Resource jar : jars)
{
FragmentDescriptor fragment = _metaData.getFragment(jar);
FragmentDescriptor fragment = _metaData.getFragmentDescriptorForJar(jar);
if (fragment != null)
{
@ -87,7 +87,7 @@ public class RelativeOrdering implements Ordering
// and remember that the dependency has been referenced.
for (String name : fragment.getAfters())
{
Resource after = _metaData.getJarForFragment(name);
Resource after = _metaData.getJarForFragmentName(name);
sort.addDependency(jar, after);
referenced.add(after);
}
@ -96,7 +96,7 @@ public class RelativeOrdering implements Ordering
// and remember that the dependency has been referenced.
for (String name : fragment.getBefores())
{
Resource before = _metaData.getJarForFragment(name);
Resource before = _metaData.getJarForFragmentName(name);
sort.addDependency(before, jar);
referenced.add(before);
}

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.webapp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -97,7 +96,6 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
registerVisitor("filter", this.getClass().getMethod("visitFilter", __signature));
registerVisitor("filter-mapping", this.getClass().getMethod("visitFilterMapping", __signature));
registerVisitor("listener", this.getClass().getMethod("visitListener", __signature));
registerVisitor("distributable", this.getClass().getMethod("visitDistributable", __signature));
registerVisitor("deny-uncovered-http-methods", this.getClass().getMethod("visitDenyUncoveredHttpMethods", __signature));
registerVisitor("default-context-path", this.getClass().getMethod("visitDefaultContextPath", __signature));
registerVisitor("request-character-encoding", this.getClass().getMethod("visitRequestCharacterEncoding", __signature));
@ -1894,7 +1892,6 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
public void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
String className = node.getString("listener-class", false, true);
EventListener listener = null;
try
{
if (className != null && className.length() > 0)
@ -1922,14 +1919,6 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
}
}
public void visitDistributable(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
// the element has no content, so its simple presence
// indicates that the webapp is distributable...
//Servlet Spec 3.0 p.74 distributable only if all fragments are distributable
((WebDescriptor)descriptor).setDistributable(true);
}
/**
* Servlet spec 3.1. When present in web.xml, this means that http methods that are
* not covered by security constraints should have access denied.

View File

@ -39,32 +39,50 @@ public class WebDescriptor extends Descriptor
{
private static final Logger LOG = Log.getLogger(WebDescriptor.class);
protected static XmlParser _nonValidatingStaticParser;
protected MetaDataComplete _metaDataComplete;
public static XmlParser __nonValidatingStaticParser = newParser(false);
protected MetaData.Complete _metaDataComplete;
protected int _majorVersion = 4; //default to container version
protected int _minorVersion = 0;
protected ArrayList<String> _classNames = new ArrayList<String>();
protected ArrayList<String> _classNames = new ArrayList<>();
protected boolean _distributable;
protected boolean _isOrdered = false;
protected List<String> _ordering = new ArrayList<String>();
@Override
public XmlParser ensureParser() throws ClassNotFoundException
protected List<String> _ordering = new ArrayList<>();
/**
* Check if the descriptor is metadata-complete.
*
* @param d the descriptor (web.xml, web-fragment.xml,
* web-default.xml, web-override.xml) to check
*
* @return true iff metadata-complete=true is declared in the
* descriptor
*/
public static boolean isMetaDataComplete(WebDescriptor d)
{
synchronized (WebDescriptor.class)
{
if (_nonValidatingStaticParser == null)
_nonValidatingStaticParser = newParser(false);
}
return (d != null && d.getMetaDataComplete() == MetaData.Complete.True);
}
if (!isValidating())
return _nonValidatingStaticParser;
/**
* Get a parser for parsing web descriptor content.
*
* @param validating true if the parser should validate syntax, false otherwise
* @return an XmlParser for web descriptors
*/
public static XmlParser getParser(boolean validating)
{
if (!validating)
return __nonValidatingStaticParser;
else
return newParser(true);
}
public static XmlParser newParser(boolean validating) throws ClassNotFoundException
/**
* Create a new parser for parsing web descriptors.
*
* @param validating if true, the parser will validate syntax
* @return an XmlParser
*/
public static XmlParser newParser(boolean validating)
{
XmlParser xmlParser = new XmlParser(validating)
{
@ -216,17 +234,18 @@ public class WebDescriptor extends Descriptor
{
super(xml);
}
@Override
public void parse()
public void parse(XmlParser parser)
throws Exception
{
super.parse();
super.parse(parser);
processVersion();
processOrdering();
processDistributable();
}
public MetaDataComplete getMetaDataComplete()
public MetaData.Complete getMetaDataComplete()
{
return _metaDataComplete;
}
@ -266,14 +285,14 @@ public class WebDescriptor extends Descriptor
}
if (_majorVersion <= 2 && _minorVersion < 5)
_metaDataComplete = MetaDataComplete.True; // does not apply before 2.5
_metaDataComplete = MetaData.Complete.True; // does not apply before 2.5
else
{
String s = (String)_root.getAttribute("metadata-complete");
if (s == null)
_metaDataComplete = MetaDataComplete.NotSet;
_metaDataComplete = MetaData.Complete.NotSet;
else
_metaDataComplete = Boolean.valueOf(s).booleanValue() ? MetaDataComplete.True : MetaDataComplete.False;
_metaDataComplete = Boolean.valueOf(s).booleanValue() ? MetaData.Complete.True : MetaData.Complete.False;
}
if (LOG.isDebugEnabled())
@ -308,6 +327,14 @@ public class WebDescriptor extends Descriptor
_ordering.add(node.toString(false, true));
}
}
public void processDistributable()
{
XmlParser.Node distributable = _root.get("distributable");
if (distributable == null)
return; //no <distributable> element
_distributable = true;
}
public void addClassName(String className)
{
@ -320,27 +347,11 @@ public class WebDescriptor extends Descriptor
return _classNames;
}
public void setDistributable(boolean distributable)
{
_distributable = distributable;
}
public boolean isDistributable()
{
return _distributable;
}
@Override
public void setValidating(boolean validating)
{
_validating = validating;
}
public boolean isValidating()
{
return _validating;
}
public boolean isOrdered()
{
return _isOrdered;

View File

@ -57,16 +57,16 @@ public class WebXmlConfiguration extends AbstractConfiguration
if (dftResource == null)
dftResource = context.newResource(defaultsDescriptor);
}
context.getMetaData().setDefaults(dftResource);
context.getMetaData().setDefaultsDescriptor(new DefaultsDescriptor(dftResource));
}
//parse, but don't process web.xml
Resource webxml = findWebXml(context);
if (webxml != null)
{
context.getMetaData().setWebXml(webxml);
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion());
context.getMetaData().setWebDescriptor(new WebDescriptor(webxml));
context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebDescriptor().getMajorVersion());
context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebDescriptor().getMinorVersion());
}
//parse but don't process override-web.xml
@ -77,7 +77,7 @@ public class WebXmlConfiguration extends AbstractConfiguration
Resource orideResource = Resource.newSystemResource(overrideDescriptor);
if (orideResource == null)
orideResource = context.newResource(overrideDescriptor);
context.getMetaData().addOverride(orideResource);
context.getMetaData().addOverrideDescriptor(new OverrideDescriptor(orideResource));
}
}
}

View File

@ -16,29 +16,34 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.util.messages;
package org.acme.webapp;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
public class CallbackBuffer
public class TestAnnotation extends DiscoveredAnnotation
{
public ByteBuffer buffer;
public Callback callback;
public CallbackBuffer(Callback callback, ByteBuffer buffer)
private List<TestAnnotation> applications;
public TestAnnotation(WebAppContext context, String className, Resource resource, List<TestAnnotation> applications)
{
Objects.requireNonNull(buffer, "buffer");
this.callback = callback;
this.buffer = buffer;
super(context, className, resource);
this.applications = applications;
}
@Override
public void apply()
{
if (applications != null)
applications.add(this);
}
@Override
public String toString()
{
return String.format("CallbackBuffer[%s,%s]", BufferUtil.toDetailString(buffer), callback.getClass().getSimpleName());
return getClassName();
}
}

View File

@ -88,7 +88,7 @@ public class MetaInfConfigurationTest
MetaInfConfiguration meta25 = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes,
Arrays.asList(MetaInfConfiguration.METAINF_TLDS, MetaInfConfiguration.METAINF_RESOURCES));
WebAppContext context25 = new WebAppContext();
context25.getMetaData().setWebXml(Resource.newResource(web25));
context25.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25.getServletContext().setEffectiveMajorVersion(2);
context25.getServletContext().setEffectiveMinorVersion(5);
meta25.preConfigure(context25);
@ -98,7 +98,7 @@ public class MetaInfConfigurationTest
MetaInfConfiguration.__allScanTypes);
WebAppContext context25b = new WebAppContext();
context25b.setConfigurationDiscovered(true);
context25b.getMetaData().setWebXml(Resource.newResource(web25));
context25b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25b.getServletContext().setEffectiveMajorVersion(2);
context25b.getServletContext().setEffectiveMinorVersion(5);
meta25b.preConfigure(context25b);
@ -107,7 +107,7 @@ public class MetaInfConfigurationTest
MetaInfConfiguration meta31 = new TestableMetaInfConfiguration(MetaInfConfiguration.__allScanTypes,
Arrays.asList(MetaInfConfiguration.METAINF_TLDS, MetaInfConfiguration.METAINF_RESOURCES));
WebAppContext context31 = new WebAppContext();
context31.getMetaData().setWebXml(Resource.newResource(web31));
context31.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31)));
context31.getServletContext().setEffectiveMajorVersion(3);
context31.getServletContext().setEffectiveMinorVersion(1);
meta31.preConfigure(context31);
@ -117,7 +117,7 @@ public class MetaInfConfigurationTest
MetaInfConfiguration.__allScanTypes);
WebAppContext context31false = new WebAppContext();
context31false.setConfigurationDiscovered(true);
context31false.getMetaData().setWebXml(Resource.newResource(web31false));
context31false.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web31false)));
context31false.getServletContext().setEffectiveMajorVersion(3);
context31false.getServletContext().setEffectiveMinorVersion(1);
meta31false.preConfigure(context31false);

View File

@ -478,11 +478,11 @@ public class OrderingTest
final Resource jarResource = new TestResource("A");
metadata.setOrdering(new RelativeOrdering(metadata));
metadata.addWebInfJar(jarResource);
metadata.addWebInfResource(jarResource);
metadata.orderFragments();
assertEquals(1, metadata.getOrderedWebInfJars().size());
assertEquals(1, metadata.getWebInfResources(true).size());
metadata.orderFragments();
assertEquals(1, metadata.getOrderedWebInfJars().size());
assertEquals(1, metadata.getWebInfResources(true).size());
}
@Test
@ -625,7 +625,7 @@ public class OrderingTest
TestResource jar4 = new TestResource("D");
resources.add(jar4);
TestResource r4 = new TestResource("D/web-fragment.xml");
FragmentDescriptor f4 = new FragmentDescriptor((Resource)null);
FragmentDescriptor f4 = new FragmentDescriptor(r4);
f4._name = "D";
metaData._webFragmentNameMap.put(f4._name, f4);
metaData._webFragmentResourceMap.put(jar4, f4);
@ -633,7 +633,7 @@ public class OrderingTest
TestResource jar5 = new TestResource("E");
resources.add(jar5);
TestResource r5 = new TestResource("E/web-fragment.xml");
FragmentDescriptor f5 = new FragmentDescriptor((Resource)null);
FragmentDescriptor f5 = new FragmentDescriptor(r5);
f5._name = "E";
metaData._webFragmentNameMap.put(f5._name, f5);
metaData._webFragmentResourceMap.put(jar5, f5);
@ -641,7 +641,7 @@ public class OrderingTest
TestResource jar6 = new TestResource("plain");
resources.add(jar6);
TestResource r6 = new TestResource("plain/web-fragment.xml");
FragmentDescriptor f6 = new FragmentDescriptor((Resource)null);
FragmentDescriptor f6 = new FragmentDescriptor(r6);
f6._name = FragmentDescriptor.NAMELESS + "1";
metaData._webFragmentNameMap.put(f6._name, f6);
metaData._webFragmentResourceMap.put(jar6, f6);
@ -897,7 +897,7 @@ public class OrderingTest
TestResource jar4 = new TestResource("D");
resources.add(jar4);
TestResource r4 = new TestResource("D/web-fragment.xml");
FragmentDescriptor f4 = new FragmentDescriptor((Resource)null);
FragmentDescriptor f4 = new FragmentDescriptor(r4);
f4._name = "D";
metaData._webFragmentNameMap.put(f4._name, f4);
metaData._webFragmentResourceMap.put(jar4, f4);
@ -905,7 +905,7 @@ public class OrderingTest
TestResource jar5 = new TestResource("E");
resources.add(jar5);
TestResource r5 = new TestResource("E/web-fragment.xml");
FragmentDescriptor f5 = new FragmentDescriptor((Resource)null);
FragmentDescriptor f5 = new FragmentDescriptor(r5);
f5._name = "E";
metaData._webFragmentNameMap.put(f5._name, f5);
metaData._webFragmentResourceMap.put(jar5, f5);
@ -913,7 +913,7 @@ public class OrderingTest
TestResource jar6 = new TestResource("plain");
resources.add(jar6);
TestResource r6 = new TestResource("plain/web-fragment.xml");
FragmentDescriptor f6 = new FragmentDescriptor((Resource)null);
FragmentDescriptor f6 = new FragmentDescriptor(r6);
f6._name = FragmentDescriptor.NAMELESS + "1";
metaData._webFragmentNameMap.put(f6._name, f6);
metaData._webFragmentResourceMap.put(jar6, f6);

View File

@ -0,0 +1,204 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.webapp;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.acme.webapp.TestAnnotation;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestMetaData
{
File fragFile;
File nonFragFile;
Resource fragResource;
Resource nonFragResource;
Resource webfragxml;
Resource containerDir;
Resource webInfClassesDir;
WebAppContext wac;
TestAnnotation annotationA;
TestAnnotation annotationB;
TestAnnotation annotationC;
TestAnnotation annotationD;
TestAnnotation annotationE;
List<TestAnnotation> applications;
@BeforeEach
public void setUp() throws Exception
{
File jarDir = new File(MavenTestingUtils.getTestResourcesDir(), "fragments");
assertTrue(jarDir.exists());
fragFile = new File(jarDir, "zeta.jar");
assertTrue(fragFile.exists());
fragResource = Resource.newResource(fragFile);
nonFragFile = new File(jarDir, "sigma.jar");
nonFragResource = Resource.newResource(nonFragFile);
assertTrue(nonFragFile.exists());
webfragxml = Resource.newResource("jar:" + fragFile.toURI().toString() + "!/META-INF/web-fragment.xml");
containerDir = Resource.newResource(MavenTestingUtils.getTargetTestingDir("container"));
webInfClassesDir = Resource.newResource(MavenTestingUtils.getTargetTestingDir("webinfclasses"));
wac = new WebAppContext();
applications = new ArrayList<>();
annotationA = new TestAnnotation(wac, "com.acme.A", fragResource, applications);
annotationB = new TestAnnotation(wac, "com.acme.B", nonFragResource, applications);
annotationC = new TestAnnotation(wac, "com.acme.C", null, applications);
annotationD = new TestAnnotation(wac, "com.acme.D", containerDir, applications);
annotationE = new TestAnnotation(wac, "com.acme.E", webInfClassesDir, applications);
}
@Test
public void testAddWebInfResource() throws Exception
{
assertTrue(wac.getMetaData().getWebInfResources(false).isEmpty());
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
assertTrue(wac.getMetaData().getWebInfResources(false).contains(fragResource));
assertTrue(wac.getMetaData().getWebInfResources(false).contains(nonFragResource));
}
@Test
public void testGetFragmentForJar() throws Exception
{
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
wac.getMetaData().addFragmentDescriptor(fragResource, new FragmentDescriptor(webfragxml));
assertThrows(NullPointerException.class, () ->
{
wac.getMetaData().addFragmentDescriptor(nonFragResource, null);
});
assertNotNull(wac.getMetaData().getFragmentDescriptorForJar(fragResource));
assertNull(wac.getMetaData().getFragmentDescriptorForJar(nonFragResource));
assertNull(wac.getMetaData().getFragmentDescriptorForJar(null));
}
@Test
public void testGetFragmentDescriptorByName() throws Exception
{
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
FragmentDescriptor fragDescriptor = new FragmentDescriptor(webfragxml);
wac.getMetaData().addFragmentDescriptor(fragResource, fragDescriptor);
assertNotNull(wac.getMetaData().getFragmentDescriptor(fragDescriptor.getName()));
}
@Test
public void testGetFragmentDescriptorByLocation() throws Exception
{
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
FragmentDescriptor fragDescriptor = new FragmentDescriptor(webfragxml);
wac.getMetaData().addFragmentDescriptor(fragResource, fragDescriptor);
assertNotNull(wac.getMetaData().getFragmentDescriptor(webfragxml));
}
@Test
public void testGetJarForFragmentName() throws Exception
{
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
wac.getMetaData().addFragmentDescriptor(fragResource, new FragmentDescriptor(webfragxml));
FragmentDescriptor descriptor = wac.getMetaData().getFragmentDescriptorForJar(fragResource);
assertNotNull(descriptor);
assertNotNull(wac.getMetaData().getJarForFragmentName(descriptor.getName()));
assertNull(wac.getMetaData().getJarForFragmentName(null));
assertNull(wac.getMetaData().getJarForFragmentName(""));
assertNull(wac.getMetaData().getJarForFragmentName("xxx"));
}
@Test
public void testAddDiscoveredAnnotation() throws Exception
{
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
wac.getMetaData().addFragmentDescriptor(fragResource, new FragmentDescriptor(webfragxml));
wac.getMetaData().addContainerResource(containerDir);
wac.getMetaData().setWebInfClassesResources(Collections.singletonList(webInfClassesDir));
wac.getMetaData().addDiscoveredAnnotation(annotationA);
wac.getMetaData().addDiscoveredAnnotation(annotationB);
wac.getMetaData().addDiscoveredAnnotation(annotationC);
wac.getMetaData().addDiscoveredAnnotation(annotationD);
wac.getMetaData().addDiscoveredAnnotation(annotationE);
//test an annotation from a web-inf lib fragment
List<DiscoveredAnnotation> list = wac.getMetaData()._annotations.get(fragResource);
assertThat(list, contains(annotationA));
assertThat(list, hasSize(1));
//test an annotation from a web-inf lib fragment without a descriptor
list = wac.getMetaData()._annotations.get(nonFragResource);
assertThat(list, contains(annotationB));
assertThat(list, hasSize(1));
//test an annotation that didn't have an associated resource
list = wac.getMetaData()._annotations.get(EmptyResource.INSTANCE);
assertThat(list, contains(annotationC));
assertThat(list, hasSize(1));
//test an annotation that came from the container path
list = wac.getMetaData()._annotations.get(containerDir);
assertThat(list, contains(annotationD));
assertThat(list, hasSize(1));
//test an annoation from web-inf classes
list = wac.getMetaData()._annotations.get(webInfClassesDir);
assertThat(list, contains(annotationE));
assertThat(list, hasSize(1));
}
@Test
public void testResolve() throws Exception
{
wac.getMetaData().addWebInfResource(fragResource);
wac.getMetaData().addWebInfResource(nonFragResource);
wac.getMetaData().addFragmentDescriptor(fragResource, new FragmentDescriptor(webfragxml));
wac.getMetaData().addContainerResource(containerDir);
wac.getMetaData().setWebInfClassesResources(Collections.singletonList(webInfClassesDir));
wac.getMetaData().addDiscoveredAnnotation(annotationA);
wac.getMetaData().addDiscoveredAnnotation(annotationB);
wac.getMetaData().addDiscoveredAnnotation(annotationC);
wac.getMetaData().addDiscoveredAnnotation(annotationD);
wac.getMetaData().addDiscoveredAnnotation(annotationE);
wac.getMetaData().resolve(wac);
//test that annotations are applied from resources in order:
//no resource associated, container resources, web-inf classes resources, web-inf lib resources
assertThat(applications, contains(annotationC, annotationD, annotationE, annotationA, annotationB));
}
}

View File

@ -483,6 +483,6 @@ public class WebAppContextTest
context.setServer(new Server());
new MetaInfConfiguration().preConfigure(context);
assertEquals(Arrays.asList("acme.jar", "alpha.jar", "omega.jar"),
context.getMetaData().getWebInfJars().stream().map(r -> r.getURI().toString().replaceFirst(".+/", "")).collect(Collectors.toList()));
context.getMetaData().getWebInfResources(false).stream().map(r -> r.getURI().toString().replaceFirst(".+/", "")).collect(Collectors.toList()));
}
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More