Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x
This commit is contained in:
commit
f649bcbe4c
|
@ -39,6 +39,7 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.annotation.HandlesTypes;
|
||||
|
||||
|
@ -58,7 +59,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;
|
||||
|
@ -335,11 +335,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));
|
||||
|
@ -408,7 +410,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
|
||||
|
@ -431,6 +434,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
|
||||
|
@ -438,6 +442,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();
|
||||
|
@ -691,14 +696,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())
|
||||
|
@ -710,17 +715,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -785,7 +790,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))
|
||||
{
|
||||
|
@ -831,7 +836,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
catch (Error e)
|
||||
{
|
||||
// Probably a SCI discovered on the system classpath that is hidden by the context classloader
|
||||
LOG.info("Error: " + e.getMessage() + " for " + context);
|
||||
LOG.info("Error: {} for {}", e.getMessage(), context);
|
||||
LOG.debug(e);
|
||||
continue;
|
||||
}
|
||||
|
@ -867,7 +872,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
|
||||
|
@ -894,7 +899,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
|
||||
{
|
||||
|
@ -923,7 +928,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())
|
||||
{
|
||||
|
@ -977,7 +982,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);
|
||||
|
@ -1002,9 +1007,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);
|
||||
|
@ -1019,7 +1025,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
|
||||
|
@ -1027,20 +1036,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())
|
||||
{
|
||||
|
@ -1053,13 +1055,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)
|
||||
|
@ -1069,7 +1071,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)
|
||||
|
@ -1087,7 +1089,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
|
||||
|
@ -1105,7 +1107,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)
|
||||
{
|
||||
|
@ -1120,39 +1122,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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -245,7 +245,8 @@ public class InetAccessHandler extends HandlerWrapper
|
|||
protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request)
|
||||
{
|
||||
String connectorName = baseRequest.getHttpChannel().getConnector().getName();
|
||||
return _set.test(new AccessTuple(connectorName, addr, baseRequest.getPathInfo()));
|
||||
String path = baseRequest.getMetaData().getURI().getDecodedPath();
|
||||
return _set.test(new AccessTuple(connectorName, addr, path));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,8 +32,7 @@ public class Source
|
|||
{
|
||||
EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
public Origin _origin;
|
||||
public String _resource;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[ini]
|
||||
bar.name=dive
|
|
@ -0,0 +1,6 @@
|
|||
# Top level mod
|
||||
|
||||
[depends]
|
||||
foo
|
||||
?bar-${bar.type}
|
||||
?impls/bar-${bar.type}
|
|
@ -0,0 +1 @@
|
|||
# nothing here
|
|
@ -0,0 +1,2 @@
|
|||
[ini]
|
||||
bar.name=dynamic
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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.
Binary file not shown.
|
@ -221,6 +221,12 @@ public class Frame
|
|||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payload of the frame as a UTF-8 string.
|
||||
* <p>Should only be used in testing, does not validate the
|
||||
* UTF-8 and a non fin frame can contain partial UTF-8 characters.</p>
|
||||
* @return the payload as a UTF-8 string.
|
||||
*/
|
||||
public String getPayloadAsUTF8()
|
||||
{
|
||||
if (payload == null)
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.javax.tests.client;
|
|||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
@ -37,15 +36,13 @@ import javax.websocket.Session;
|
|||
import javax.websocket.WebSocketContainer;
|
||||
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.MessageHandler;
|
||||
import org.eclipse.jetty.websocket.core.server.Negotiation;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||
import org.eclipse.jetty.websocket.javax.tests.CoreServer;
|
||||
import org.eclipse.jetty.websocket.javax.tests.WSEventTracker;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -58,21 +55,14 @@ public class DecoderReaderManySmallTest
|
|||
@BeforeEach
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
server = new CoreServer(new CoreServer.BaseNegotiator()
|
||||
server = new CoreServer(WebSocketNegotiator.from((negotiation) ->
|
||||
{
|
||||
@Override
|
||||
public FrameHandler negotiate(Negotiation negotiation) throws IOException
|
||||
{
|
||||
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols();
|
||||
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols();
|
||||
if (!offeredSubProtocols.isEmpty())
|
||||
negotiation.setSubprotocol(offeredSubProtocols.get(0));
|
||||
|
||||
if (!offeredSubProtocols.isEmpty())
|
||||
{
|
||||
negotiation.setSubprotocol(offeredSubProtocols.get(0));
|
||||
}
|
||||
|
||||
return new EventIdFrameHandler();
|
||||
}
|
||||
});
|
||||
return new EventIdFrameHandler();
|
||||
}));
|
||||
server.start();
|
||||
|
||||
client = ContainerProvider.getWebSocketContainer();
|
||||
|
@ -86,15 +76,13 @@ public class DecoderReaderManySmallTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testManyIds(TestInfo testInfo) throws Exception
|
||||
public void testManyIds() throws Exception
|
||||
{
|
||||
URI wsUri = server.getWsUri().resolve("/eventids");
|
||||
EventIdSocket clientSocket = new EventIdSocket(testInfo.getTestMethod().toString());
|
||||
|
||||
final int from = 1000;
|
||||
final int to = 2000;
|
||||
|
||||
try (Session clientSession = client.connectToServer(clientSocket, wsUri))
|
||||
EventIdSocket clientSocket = new EventIdSocket();
|
||||
try (Session clientSession = client.connectToServer(clientSocket, server.getWsUri()))
|
||||
{
|
||||
clientSession.getAsyncRemote().sendText("seq|" + from + "|" + to);
|
||||
}
|
||||
|
@ -154,12 +142,6 @@ public class DecoderReaderManySmallTest
|
|||
{
|
||||
public BlockingQueue<EventId> messageQueue = new LinkedBlockingDeque<>();
|
||||
|
||||
public EventIdSocket(String id)
|
||||
{
|
||||
super(id);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@OnMessage
|
||||
public void onMessage(EventId msg)
|
||||
{
|
||||
|
|
|
@ -477,7 +477,7 @@ public class MessageReceivingTest
|
|||
@Override
|
||||
public void onMessage(ByteBuffer message)
|
||||
{
|
||||
final String stringResult = new String(message.array());
|
||||
final String stringResult = BufferUtil.toString(message);
|
||||
messageQueue.offer(stringResult);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,47 +20,76 @@ package org.eclipse.jetty.websocket.javax.tests.server;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSession;
|
||||
import org.eclipse.jetty.websocket.javax.tests.DataUtils;
|
||||
import org.eclipse.jetty.websocket.javax.tests.Fuzzer;
|
||||
import org.eclipse.jetty.websocket.javax.tests.LocalServer;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.eclipse.jetty.websocket.javax.tests.WSEndpointTracker;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TextStreamTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(TextStreamTest.class);
|
||||
private static final BlockingArrayQueue<QueuedTextStreamer> serverEndpoints = new BlockingArrayQueue<>();
|
||||
|
||||
private static LocalServer server;
|
||||
private static ServerContainer container;
|
||||
private final ClientEndpointConfig clientConfig = ClientEndpointConfig.Builder.create().build();
|
||||
private LocalServer server;
|
||||
private ServerContainer container;
|
||||
private WebSocketContainer wsClient;
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
@BeforeEach
|
||||
public void startServer() throws Exception
|
||||
{
|
||||
server = new LocalServer();
|
||||
server.start();
|
||||
container = server.getServerContainer();
|
||||
container.addEndpoint(ServerTextStreamer.class);
|
||||
container.addEndpoint(ServerEndpointConfig.Builder.create(QueuedTextStreamer.class, "/test").build());
|
||||
container.addEndpoint(ServerEndpointConfig.Builder.create(QueuedPartialTextStreamer.class, "/partial").build());
|
||||
|
||||
wsClient = ContainerProvider.getWebSocketContainer();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer() throws Exception
|
||||
@AfterEach
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
@ -145,6 +174,121 @@ public class TextStreamTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageOrdering() throws Exception
|
||||
{
|
||||
ClientTextStreamer client = new ClientTextStreamer();
|
||||
Session session = wsClient.connectToServer(client, clientConfig, server.getWsUri().resolve("/test"));
|
||||
|
||||
final int numLoops = 20;
|
||||
for (int i = 0; i < numLoops; i++)
|
||||
{
|
||||
session.getBasicRemote().sendText(Integer.toString(i));
|
||||
}
|
||||
session.close();
|
||||
|
||||
QueuedTextStreamer queuedTextStreamer = serverEndpoints.poll(5, TimeUnit.SECONDS);
|
||||
assertNotNull(queuedTextStreamer);
|
||||
for (int i = 0; i < numLoops; i++)
|
||||
{
|
||||
String msg = queuedTextStreamer.messages.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(msg, Matchers.is(Integer.toString(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFragmentedMessageOrdering() throws Exception
|
||||
{
|
||||
ClientTextStreamer client = new ClientTextStreamer();
|
||||
Session session = wsClient.connectToServer(client, clientConfig, server.getWsUri().resolve("/test"));
|
||||
|
||||
final int numLoops = 20;
|
||||
for (int i = 0; i < numLoops; i++)
|
||||
{
|
||||
session.getBasicRemote().sendText("firstFrame" + i, false);
|
||||
session.getBasicRemote().sendText("|secondFrame" + i, false);
|
||||
session.getBasicRemote().sendText("|finalFrame" + i, true);
|
||||
}
|
||||
session.close();
|
||||
|
||||
QueuedTextStreamer queuedTextStreamer = serverEndpoints.poll(5, TimeUnit.SECONDS);
|
||||
assertNotNull(queuedTextStreamer);
|
||||
for (int i = 0; i < numLoops; i++)
|
||||
{
|
||||
String msg = queuedTextStreamer.messages.poll(5, TimeUnit.SECONDS);
|
||||
String expected = "firstFrame" + i + "|secondFrame" + i + "|finalFrame" + i;
|
||||
assertThat(msg, Matchers.is(expected));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageOrderingDoNotReadToEOF() throws Exception
|
||||
{
|
||||
ClientTextStreamer clientEndpoint = new ClientTextStreamer();
|
||||
Session session = wsClient.connectToServer(clientEndpoint, clientConfig, server.getWsUri().resolve("/partial"));
|
||||
QueuedTextStreamer serverEndpoint = Objects.requireNonNull(serverEndpoints.poll(5, TimeUnit.SECONDS));
|
||||
|
||||
int serverInputBufferSize = 1024;
|
||||
JavaxWebSocketSession serverSession = (JavaxWebSocketSession)serverEndpoint.session;
|
||||
serverSession.getCoreSession().setInputBufferSize(serverInputBufferSize);
|
||||
|
||||
// Write some initial data.
|
||||
Writer writer = session.getBasicRemote().getSendWriter();
|
||||
writer.write("first frame");
|
||||
writer.flush();
|
||||
|
||||
// Signal to stop reading.
|
||||
writer.write("|");
|
||||
writer.flush();
|
||||
|
||||
// Lots of data after we have stopped reading and onMessage exits.
|
||||
final String largePayload = StringUtil.stringFrom("x", serverInputBufferSize * 2);
|
||||
writer.write(largePayload);
|
||||
writer.close();
|
||||
|
||||
session.close();
|
||||
assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
|
||||
assertNull(clientEndpoint.error.get());
|
||||
assertNull(serverEndpoint.error.get());
|
||||
|
||||
String msg = serverEndpoint.messages.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(msg, Matchers.is("first frame"));
|
||||
}
|
||||
|
||||
public static class ClientTextStreamer extends WSEndpointTracker implements MessageHandler.Whole<Reader>
|
||||
{
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private final StringBuilder output = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
session.addMessageHandler(this);
|
||||
super.onOpen(session, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Reader input)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
if (read < 0)
|
||||
break;
|
||||
output.append((char)read);
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ServerEndpoint("/echo")
|
||||
public static class ServerTextStreamer
|
||||
{
|
||||
|
@ -166,4 +310,59 @@ public class TextStreamTest
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class QueuedTextStreamer extends WSEndpointTracker implements MessageHandler.Whole<Reader>
|
||||
{
|
||||
protected BlockingArrayQueue<String> messages = new BlockingArrayQueue<>();
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
session.addMessageHandler(this);
|
||||
super.onOpen(session, config);
|
||||
serverEndpoints.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(Reader input)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(Math.abs(new Random().nextLong() % 200));
|
||||
messages.add(IO.toString(input));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class QueuedPartialTextStreamer extends QueuedTextStreamer
|
||||
{
|
||||
@Override
|
||||
public void onMessage(Reader input)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(Math.abs(new Random().nextLong() % 200));
|
||||
|
||||
// Do not read to EOF but just the first '|'.
|
||||
StringWriter writer = new StringWriter();
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
if (read < 0 || read == '|')
|
||||
break;
|
||||
writer.write(read);
|
||||
}
|
||||
|
||||
messages.add(writer.toString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
|
@ -36,6 +38,7 @@ 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.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTimeout;
|
||||
|
||||
public class MessageInputStreamTest
|
||||
|
@ -166,7 +169,7 @@ public class MessageInputStreamTest
|
|||
{
|
||||
// wait for a little bit before sending input closed
|
||||
TimeUnit.MILLISECONDS.sleep(400);
|
||||
stream.close();
|
||||
stream.accept(new Frame(OpCode.TEXT, true, BufferUtil.EMPTY_BUFFER), Callback.NOOP);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
|
@ -177,11 +180,22 @@ public class MessageInputStreamTest
|
|||
|
||||
// Read byte from stream.
|
||||
int b = stream.read();
|
||||
// Should be a -1, indicating the end of the stream.
|
||||
|
||||
// Test it
|
||||
// Should be a -1, indicating the end of the stream.
|
||||
assertThat("Error when closing", hadError.get(), is(false));
|
||||
assertThat("Initial byte (Should be EOF)", b, is(-1));
|
||||
|
||||
// Close the stream.
|
||||
stream.close();
|
||||
|
||||
// Any frame content after stream is closed should be discarded, and the callback succeeded.
|
||||
FutureCallback callback = new FutureCallback();
|
||||
stream.accept(new Frame(OpCode.TEXT, true, BufferUtil.toBuffer("hello world")), callback);
|
||||
callback.block(5, TimeUnit.SECONDS);
|
||||
|
||||
// Any read after the stream is closed leads to an IOException.
|
||||
IOException error = assertThrows(IOException.class, stream::read);
|
||||
assertThat(error.getMessage(), is("Closed"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.websocket.common;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageWriter;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class MessageWriterTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(MessageWriterTest.class);
|
||||
private static final int OUTPUT_BUFFER_SIZE = 4096;
|
||||
|
||||
public TestableLeakTrackingBufferPool bufferPool = new TestableLeakTrackingBufferPool("Test");
|
||||
|
||||
@AfterEach
|
||||
public void afterEach()
|
||||
{
|
||||
bufferPool.assertNoLeaks();
|
||||
}
|
||||
|
||||
private OutgoingMessageCapture remoteSocket;
|
||||
|
||||
@BeforeEach
|
||||
public void setupSession()
|
||||
{
|
||||
remoteSocket = new OutgoingMessageCapture();
|
||||
remoteSocket.setOutputBufferSize(OUTPUT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleWrites() throws Exception
|
||||
{
|
||||
try (MessageWriter stream = new MessageWriter(remoteSocket, bufferPool))
|
||||
{
|
||||
stream.write("Hello");
|
||||
stream.write(" ");
|
||||
stream.write("World");
|
||||
}
|
||||
|
||||
assertThat("Socket.messageQueue.size", remoteSocket.textMessages.size(), is(1));
|
||||
String msg = remoteSocket.textMessages.poll();
|
||||
assertThat("Message", msg, is("Hello World"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleWrite() throws Exception
|
||||
{
|
||||
try (MessageWriter stream = new MessageWriter(remoteSocket, bufferPool))
|
||||
{
|
||||
stream.append("Hello World");
|
||||
}
|
||||
|
||||
assertThat("Socket.messageQueue.size", remoteSocket.textMessages.size(), is(1));
|
||||
String msg = remoteSocket.textMessages.poll();
|
||||
assertThat("Message", msg, is("Hello World"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteLargeRequiringMultipleBuffers() throws Exception
|
||||
{
|
||||
int size = (int)(OUTPUT_BUFFER_SIZE * 2.5);
|
||||
char[] buf = new char[size];
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Buffer size: {}", size);
|
||||
Arrays.fill(buf, 'x');
|
||||
buf[size - 1] = 'o'; // mark last entry for debugging
|
||||
|
||||
try (MessageWriter stream = new MessageWriter(remoteSocket, bufferPool))
|
||||
{
|
||||
stream.write(buf);
|
||||
}
|
||||
|
||||
assertThat("Socket.messageQueue.size", remoteSocket.textMessages.size(), is(1));
|
||||
String msg = remoteSocket.textMessages.poll();
|
||||
String expected = new String(buf);
|
||||
assertThat("Message", msg, is(expected));
|
||||
}
|
||||
}
|
|
@ -121,7 +121,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes
|
|||
|
||||
if (OpCode.isDataFrame(frame.getOpCode()))
|
||||
{
|
||||
messageSink.accept(frame, callback);
|
||||
messageSink.accept(Frame.copy(frame), callback);
|
||||
if (frame.isFin())
|
||||
{
|
||||
messageSink = null;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.websocket.util.messages;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -49,40 +50,40 @@ public class ByteArrayMessageSink extends AbstractMessageSink
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (frame.hasPayload())
|
||||
size += frame.getPayloadLength();
|
||||
long maxBinaryMessageSize = session.getMaxBinaryMessageSize();
|
||||
if (maxBinaryMessageSize > 0 && size > maxBinaryMessageSize)
|
||||
{
|
||||
ByteBuffer payload = frame.getPayload();
|
||||
size += payload.remaining();
|
||||
long maxBinaryMessageSize = session.getMaxBinaryMessageSize();
|
||||
if (maxBinaryMessageSize > 0 && size > maxBinaryMessageSize)
|
||||
{
|
||||
throw new MessageTooLargeException(String.format("Binary message too large: (actual) %,d > (configured max binary buffer size) %,d",
|
||||
size, maxBinaryMessageSize));
|
||||
}
|
||||
|
||||
if (out == null)
|
||||
out = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||
|
||||
BufferUtil.writeTo(payload, out);
|
||||
throw new MessageTooLargeException(String.format("Binary message too large: (actual) %,d > (configured max binary message size) %,d",
|
||||
size, maxBinaryMessageSize));
|
||||
}
|
||||
|
||||
if (frame.isFin())
|
||||
// If we are fin and no OutputStream has been created we don't need to aggregate.
|
||||
if (frame.isFin() && (out == null))
|
||||
{
|
||||
if (out != null)
|
||||
if (frame.hasPayload())
|
||||
{
|
||||
byte[] buf = out.toByteArray();
|
||||
byte[] buf = BufferUtil.toArray(frame.getPayload());
|
||||
methodHandle.invoke(buf, 0, buf.length);
|
||||
}
|
||||
else
|
||||
methodHandle.invoke(EMPTY_BUFFER, 0, 0);
|
||||
|
||||
callback.succeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
aggregatePayload(frame);
|
||||
if (frame.isFin())
|
||||
{
|
||||
byte[] buf = out.toByteArray();
|
||||
methodHandle.invoke(buf, 0, buf.length);
|
||||
}
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
@ -99,4 +100,15 @@ public class ByteArrayMessageSink extends AbstractMessageSink
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void aggregatePayload(Frame frame) throws IOException
|
||||
{
|
||||
if (frame.hasPayload())
|
||||
{
|
||||
ByteBuffer payload = frame.getPayload();
|
||||
if (out == null)
|
||||
out = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||
BufferUtil.writeTo(payload, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.websocket.util.messages;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -50,38 +51,35 @@ public class ByteBufferMessageSink extends AbstractMessageSink
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (frame.hasPayload())
|
||||
size += frame.getPayloadLength();
|
||||
long maxBinaryMessageSize = session.getMaxBinaryMessageSize();
|
||||
if (maxBinaryMessageSize > 0 && size > maxBinaryMessageSize)
|
||||
{
|
||||
ByteBuffer payload = frame.getPayload();
|
||||
size += payload.remaining();
|
||||
long maxBinaryMessageSize = session.getMaxBinaryMessageSize();
|
||||
if (maxBinaryMessageSize > 0 && size > maxBinaryMessageSize)
|
||||
{
|
||||
throw new MessageTooLargeException(String.format("Binary message too large: (actual) %,d > (configured max binary message size) %,d",
|
||||
size, maxBinaryMessageSize));
|
||||
}
|
||||
|
||||
if (out == null)
|
||||
out = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||
|
||||
BufferUtil.writeTo(payload, out);
|
||||
payload.position(payload.limit()); // consume buffer
|
||||
throw new MessageTooLargeException(String.format("Binary message too large: (actual) %,d > (configured max binary message size) %,d",
|
||||
size, maxBinaryMessageSize));
|
||||
}
|
||||
|
||||
if (frame.isFin())
|
||||
// If we are fin and no OutputStream has been created we don't need to aggregate.
|
||||
if (frame.isFin() && (out == null))
|
||||
{
|
||||
if (out != null)
|
||||
methodHandle.invoke(ByteBuffer.wrap(out.toByteArray()));
|
||||
if (frame.hasPayload())
|
||||
methodHandle.invoke(frame.getPayload());
|
||||
else
|
||||
methodHandle.invoke(BufferUtil.EMPTY_BUFFER);
|
||||
|
||||
callback.succeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
aggregatePayload(frame);
|
||||
if (frame.isFin())
|
||||
methodHandle.invoke(ByteBuffer.wrap(out.toByteArray()));
|
||||
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
@ -98,4 +96,18 @@ public class ByteBufferMessageSink extends AbstractMessageSink
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void aggregatePayload(Frame frame) throws IOException
|
||||
{
|
||||
if (frame.hasPayload())
|
||||
{
|
||||
ByteBuffer payload = frame.getPayload();
|
||||
|
||||
if (out == null)
|
||||
out = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||
|
||||
BufferUtil.writeTo(payload, out);
|
||||
payload.position(payload.limit()); // consume buffer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.util.messages;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
|
||||
|
@ -93,11 +95,8 @@ import org.eclipse.jetty.websocket.core.Frame;
|
|||
* EOF stream.read EOF
|
||||
* RESUME(NEXT MSG)
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the type of object to give to user function
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public abstract class DispatchedMessageSink<T> extends AbstractMessageSink
|
||||
public abstract class DispatchedMessageSink extends AbstractMessageSink
|
||||
{
|
||||
private CompletableFuture<Void> dispatchComplete;
|
||||
private MessageSink typeSink;
|
||||
|
@ -114,57 +113,45 @@ public abstract class DispatchedMessageSink<T> extends AbstractMessageSink
|
|||
if (typeSink == null)
|
||||
{
|
||||
typeSink = newSink(frame);
|
||||
// Dispatch to end user function (will likely start with blocking for data/accept)
|
||||
dispatchComplete = new CompletableFuture<>();
|
||||
|
||||
// Dispatch to end user function (will likely start with blocking for data/accept).
|
||||
// If the MessageSink can be closed do this after invoking and before completing the CompletableFuture.
|
||||
new Thread(() ->
|
||||
{
|
||||
final T dispatchedType = (T)typeSink;
|
||||
try
|
||||
{
|
||||
methodHandle.invoke(dispatchedType);
|
||||
methodHandle.invoke(typeSink);
|
||||
if (typeSink instanceof Closeable)
|
||||
IO.close((Closeable)typeSink);
|
||||
|
||||
dispatchComplete.complete(null);
|
||||
}
|
||||
catch (Throwable throwable)
|
||||
{
|
||||
if (typeSink instanceof Closeable)
|
||||
IO.close((Closeable)typeSink);
|
||||
|
||||
dispatchComplete.completeExceptionally(throwable);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
final Callback frameCallback;
|
||||
|
||||
Callback frameCallback = callback;
|
||||
if (frame.isFin())
|
||||
{
|
||||
CompletableFuture<Void> finComplete = new CompletableFuture<>();
|
||||
frameCallback = new Callback()
|
||||
// This is the final frame we should wait for the frame callback and the dispatched thread.
|
||||
Callback.Completable completableCallback = new Callback.Completable();
|
||||
frameCallback = completableCallback;
|
||||
CompletableFuture.allOf(dispatchComplete, completableCallback).whenComplete((aVoid, throwable) ->
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable cause)
|
||||
{
|
||||
finComplete.completeExceptionally(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
finComplete.complete(null);
|
||||
}
|
||||
};
|
||||
CompletableFuture.allOf(dispatchComplete, finComplete).whenComplete(
|
||||
(aVoid, throwable) ->
|
||||
{
|
||||
typeSink = null;
|
||||
dispatchComplete = null;
|
||||
if (throwable != null)
|
||||
callback.failed(throwable);
|
||||
else
|
||||
callback.succeeded();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-fin-frame
|
||||
frameCallback = callback;
|
||||
typeSink = null;
|
||||
dispatchComplete = null;
|
||||
if (throwable != null)
|
||||
callback.failed(throwable);
|
||||
else
|
||||
callback.succeeded();
|
||||
});
|
||||
}
|
||||
|
||||
typeSink.accept(frame, frameCallback);
|
||||
|
|
|
@ -18,13 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.util.messages;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
|
||||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
|
||||
public class InputStreamMessageSink extends DispatchedMessageSink<InputStream>
|
||||
public class InputStreamMessageSink extends DispatchedMessageSink
|
||||
{
|
||||
public InputStreamMessageSink(CoreSession session, MethodHandle methodHandle)
|
||||
{
|
||||
|
|
|
@ -21,10 +21,12 @@ package org.eclipse.jetty.websocket.util.messages;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -40,10 +42,12 @@ import org.eclipse.jetty.websocket.core.Frame;
|
|||
public class MessageInputStream extends InputStream implements MessageSink
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(MessageInputStream.class);
|
||||
private static final CallbackBuffer EOF = new CallbackBuffer(Callback.NOOP, BufferUtil.EMPTY_BUFFER);
|
||||
private final Deque<CallbackBuffer> buffers = new ArrayDeque<>(2);
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
private CallbackBuffer activeFrame;
|
||||
private static final Entry EOF = new Entry(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
|
||||
private static final Entry CLOSED = new Entry(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
|
||||
private final BlockingArrayQueue<Entry> buffers = new BlockingArrayQueue<>();
|
||||
private boolean closed = false;
|
||||
private Entry currentEntry;
|
||||
private long timeoutMs = -1;
|
||||
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
|
@ -51,119 +55,28 @@ public class MessageInputStream extends InputStream implements MessageSink
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("accepting {}", frame);
|
||||
|
||||
// If closed, we should just toss incoming payloads into the bit bucket.
|
||||
if (closed.get())
|
||||
boolean succeed = false;
|
||||
synchronized (this)
|
||||
{
|
||||
callback.failed(new IOException("Already Closed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame.hasPayload() && !frame.isFin())
|
||||
{
|
||||
callback.succeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (buffers)
|
||||
{
|
||||
boolean notify = false;
|
||||
if (frame.hasPayload())
|
||||
// If closed or we have no payload, request the next frame.
|
||||
if (closed || (!frame.hasPayload() && !frame.isFin()))
|
||||
{
|
||||
buffers.offer(new CallbackBuffer(callback, frame.getPayload()));
|
||||
notify = true;
|
||||
succeed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We cannot wake up blocking read for a zero length frame.
|
||||
callback.succeeded();
|
||||
}
|
||||
if (frame.hasPayload())
|
||||
buffers.add(new Entry(frame.getPayload(), callback));
|
||||
else
|
||||
succeed = true;
|
||||
|
||||
if (frame.isFin())
|
||||
{
|
||||
buffers.offer(EOF);
|
||||
notify = true;
|
||||
}
|
||||
|
||||
if (notify)
|
||||
{
|
||||
// notify other thread
|
||||
buffers.notify();
|
||||
if (frame.isFin())
|
||||
buffers.add(EOF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("close()");
|
||||
|
||||
if (closed.compareAndSet(false, true))
|
||||
{
|
||||
synchronized (buffers)
|
||||
{
|
||||
buffers.offer(EOF);
|
||||
buffers.notify();
|
||||
}
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
public CallbackBuffer getActiveFrame() throws InterruptedIOException
|
||||
{
|
||||
if (activeFrame == null)
|
||||
{
|
||||
// sync and poll queue
|
||||
CallbackBuffer result;
|
||||
synchronized (buffers)
|
||||
{
|
||||
try
|
||||
{
|
||||
while ((result = buffers.poll()) == null)
|
||||
{
|
||||
// TODO: handle read timeout here?
|
||||
buffers.wait();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
shutdown();
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
}
|
||||
activeFrame = result;
|
||||
}
|
||||
|
||||
return activeFrame;
|
||||
}
|
||||
|
||||
private void shutdown()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("shutdown()");
|
||||
synchronized (buffers)
|
||||
{
|
||||
closed.set(true);
|
||||
Throwable cause = new IOException("Shutdown");
|
||||
for (CallbackBuffer buffer : buffers)
|
||||
{
|
||||
buffer.callback.failed(cause);
|
||||
}
|
||||
// Removed buffers that may have remained in the queue.
|
||||
buffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int readlimit)
|
||||
{
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported()
|
||||
{
|
||||
return false;
|
||||
if (succeed)
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -184,43 +97,142 @@ public class MessageInputStream extends InputStream implements MessageSink
|
|||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException
|
||||
{
|
||||
if (closed.get())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Stream closed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
CallbackBuffer result = getActiveFrame();
|
||||
return read(ByteBuffer.wrap(b, off, len).flip());
|
||||
}
|
||||
|
||||
public int read(ByteBuffer buffer) throws IOException
|
||||
{
|
||||
Entry currentEntry = getCurrentEntry();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("result = {}", result);
|
||||
LOG.debug("currentEntry = {}", currentEntry);
|
||||
|
||||
if (result == EOF)
|
||||
if (currentEntry == CLOSED)
|
||||
throw new IOException("Closed");
|
||||
|
||||
if (currentEntry == EOF)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Read EOF");
|
||||
shutdown();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We have content
|
||||
int fillLen = Math.min(result.buffer.remaining(), len);
|
||||
result.buffer.get(b, off, fillLen);
|
||||
// We have content.
|
||||
int fillLen = BufferUtil.append(buffer, currentEntry.buffer);
|
||||
if (!currentEntry.buffer.hasRemaining())
|
||||
succeedCurrentEntry();
|
||||
|
||||
if (!result.buffer.hasRemaining())
|
||||
{
|
||||
activeFrame = null;
|
||||
result.callback.succeeded();
|
||||
}
|
||||
|
||||
// return number of bytes actually copied into buffer
|
||||
// Return number of bytes actually copied into buffer.
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("filled {} bytes from {}", fillLen, currentEntry);
|
||||
return fillLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException
|
||||
public void close() throws IOException
|
||||
{
|
||||
throw new IOException("reset() not supported");
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("close()");
|
||||
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
synchronized (this)
|
||||
{
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
|
||||
if (currentEntry != null)
|
||||
{
|
||||
entries.add(currentEntry);
|
||||
currentEntry = null;
|
||||
}
|
||||
|
||||
// Clear queue and fail all entries.
|
||||
entries.addAll(buffers);
|
||||
buffers.clear();
|
||||
buffers.offer(CLOSED);
|
||||
}
|
||||
|
||||
// Succeed all entries as we don't need them anymore (failing would close the connection).
|
||||
for (Entry e : entries)
|
||||
{
|
||||
e.callback.succeeded();
|
||||
}
|
||||
|
||||
super.close();
|
||||
}
|
||||
|
||||
public void setTimeout(long timeoutMs)
|
||||
{
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
private void succeedCurrentEntry()
|
||||
{
|
||||
Entry current;
|
||||
synchronized (this)
|
||||
{
|
||||
current = currentEntry;
|
||||
currentEntry = null;
|
||||
}
|
||||
if (current != null)
|
||||
current.callback.succeeded();
|
||||
}
|
||||
|
||||
private Entry getCurrentEntry() throws IOException
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (currentEntry != null)
|
||||
return currentEntry;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Waiting {} ms to read", timeoutMs);
|
||||
|
||||
Entry result;
|
||||
if (timeoutMs < 0)
|
||||
{
|
||||
// Wait forever until a buffer is available.
|
||||
result = buffers.take();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wait at most for the given timeout.
|
||||
result = buffers.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
if (result == null)
|
||||
throw new IOException(String.format("Read timeout: %,dms expired", timeoutMs));
|
||||
}
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
currentEntry = result;
|
||||
return currentEntry;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
close();
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Entry
|
||||
{
|
||||
public ByteBuffer buffer;
|
||||
public Callback callback;
|
||||
|
||||
public Entry(ByteBuffer buffer, Callback callback)
|
||||
{
|
||||
this.buffer = Objects.requireNonNull(buffer);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Entry[%s,%s]", BufferUtil.toDetailString(buffer), callback.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ public class MessageOutputStream extends OutputStream
|
|||
this.bufferPool = bufferPool;
|
||||
this.bufferSize = coreSession.getOutputBufferSize();
|
||||
this.buffer = bufferPool.acquire(bufferSize, true);
|
||||
BufferUtil.clear(buffer);
|
||||
}
|
||||
|
||||
void setMessageType(byte opcode)
|
||||
|
@ -93,6 +92,20 @@ public class MessageOutputStream extends OutputStream
|
|||
}
|
||||
}
|
||||
|
||||
public void write(ByteBuffer buffer) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
send(buffer);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
// Notify without holding locks.
|
||||
notifyFailure(x);
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException
|
||||
{
|
||||
|
|
|
@ -18,30 +18,83 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.util.messages;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketConstants;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Support class for reading a (single) WebSocket TEXT message via a Reader.
|
||||
* <p>
|
||||
* In compliance to the WebSocket spec, this reader always uses the {@link StandardCharsets#UTF_8}.
|
||||
*/
|
||||
public class MessageReader extends InputStreamReader implements MessageSink
|
||||
public class MessageReader extends Reader implements MessageSink
|
||||
{
|
||||
private final MessageInputStream stream;
|
||||
private static final int BUFFER_SIZE = WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE;
|
||||
|
||||
public MessageReader(MessageInputStream stream)
|
||||
private final ByteBuffer buffer;
|
||||
private final MessageInputStream stream;
|
||||
private final CharsetDecoder utf8Decoder = UTF_8.newDecoder()
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.onMalformedInput(CodingErrorAction.REPORT);
|
||||
|
||||
public MessageReader()
|
||||
{
|
||||
super(stream, StandardCharsets.UTF_8);
|
||||
this.stream = stream;
|
||||
this(BUFFER_SIZE);
|
||||
}
|
||||
|
||||
public MessageReader(int bufferSize)
|
||||
{
|
||||
this.stream = new MessageInputStream();
|
||||
this.buffer = BufferUtil.allocate(bufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException
|
||||
{
|
||||
CharBuffer charBuffer = CharBuffer.wrap(cbuf, off, len);
|
||||
boolean endOfInput = false;
|
||||
while (true)
|
||||
{
|
||||
int read = stream.read(buffer);
|
||||
if (read == 0)
|
||||
break;
|
||||
if (read < 0)
|
||||
{
|
||||
endOfInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CoderResult result = utf8Decoder.decode(buffer, charBuffer, endOfInput);
|
||||
if (result.isError())
|
||||
result.throwException();
|
||||
|
||||
if (endOfInput && (charBuffer.position() == 0))
|
||||
return -1;
|
||||
return charBuffer.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
stream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
{
|
||||
this.stream.accept(frame, callback);
|
||||
stream.accept(frame, callback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,19 +20,13 @@ package org.eclipse.jetty.websocket.util.messages;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
@ -44,180 +38,38 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||
*/
|
||||
public class MessageWriter extends Writer
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(MessageWriter.class);
|
||||
|
||||
private final MessageOutputStream outputStream;
|
||||
private final CharsetEncoder utf8Encoder = UTF_8.newEncoder()
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.onMalformedInput(CodingErrorAction.REPORT);
|
||||
|
||||
private final CoreSession coreSession;
|
||||
private long frameCount;
|
||||
private Frame frame;
|
||||
private CharBuffer buffer;
|
||||
private Callback callback;
|
||||
private boolean closed;
|
||||
|
||||
public MessageWriter(CoreSession coreSession, ByteBufferPool bufferPool)
|
||||
{
|
||||
this.coreSession = coreSession;
|
||||
this.buffer = CharBuffer.allocate(coreSession.getOutputBufferSize());
|
||||
this.frame = new Frame(OpCode.TEXT);
|
||||
this.outputStream = new MessageOutputStream(coreSession, bufferPool);
|
||||
this.outputStream.setMessageType(OpCode.TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char[] chars, int off, int len) throws IOException
|
||||
public void write(char[] cbuf, int off, int len) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
send(chars, off, len);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
// Notify without holding locks.
|
||||
notifyFailure(x);
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
send(new char[]{(char)c}, 0, 1);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
// Notify without holding locks.
|
||||
notifyFailure(x);
|
||||
throw x;
|
||||
}
|
||||
CharBuffer charBuffer = CharBuffer.wrap(cbuf, off, len);
|
||||
outputStream.write(utf8Encoder.encode(charBuffer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
flush(false);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
// Notify without holding locks.
|
||||
notifyFailure(x);
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
private void flush(boolean fin) throws IOException
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (closed)
|
||||
throw new IOException("Stream is closed");
|
||||
|
||||
closed = fin;
|
||||
|
||||
buffer.flip();
|
||||
ByteBuffer payload = utf8Encoder.encode(buffer);
|
||||
buffer.flip();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("flush({}): {}", fin, BufferUtil.toDetailString(payload));
|
||||
frame.setPayload(payload);
|
||||
frame.setFin(fin);
|
||||
|
||||
FutureCallback b = new FutureCallback();
|
||||
coreSession.sendFrame(frame, b, false);
|
||||
b.block();
|
||||
|
||||
++frameCount;
|
||||
// Any flush after the first will be a CONTINUATION frame.
|
||||
frame = new Frame(OpCode.CONTINUATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void send(char[] chars, int offset, int length) throws IOException
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (closed)
|
||||
throw new IOException("Stream is closed");
|
||||
|
||||
CharBuffer source = CharBuffer.wrap(chars, offset, length);
|
||||
|
||||
int remaining = length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int read = source.read(buffer);
|
||||
if (read == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
remaining -= read;
|
||||
|
||||
if (remaining > 0)
|
||||
{
|
||||
// If we could not write everything, it means
|
||||
// that the buffer was full, so flush it.
|
||||
flush(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
flush(true);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Stream closed, {} frames sent", frameCount);
|
||||
// Notify without holding locks.
|
||||
notifySuccess();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
// Notify without holding locks.
|
||||
notifyFailure(x);
|
||||
throw x;
|
||||
}
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifySuccess()
|
||||
{
|
||||
Callback callback;
|
||||
synchronized (this)
|
||||
{
|
||||
callback = this.callback;
|
||||
}
|
||||
if (callback != null)
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyFailure(Throwable failure)
|
||||
{
|
||||
Callback callback;
|
||||
synchronized (this)
|
||||
{
|
||||
callback = this.callback;
|
||||
}
|
||||
if (callback != null)
|
||||
{
|
||||
callback.failed(failure);
|
||||
}
|
||||
outputStream.setCallback(callback);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.util.messages;
|
|||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
@ -30,6 +29,8 @@ import org.eclipse.jetty.websocket.util.InvalidSignatureException;
|
|||
|
||||
public class PartialByteArrayMessageSink extends AbstractMessageSink
|
||||
{
|
||||
private static byte[] EMPTY_BUFFER = new byte[0];
|
||||
|
||||
public PartialByteArrayMessageSink(CoreSession session, MethodHandle methodHandle)
|
||||
{
|
||||
super(session, methodHandle);
|
||||
|
@ -42,28 +43,17 @@ public class PartialByteArrayMessageSink extends AbstractMessageSink
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] buffer;
|
||||
int offset = 0;
|
||||
int length = 0;
|
||||
|
||||
if (frame.hasPayload())
|
||||
if (frame.hasPayload() || frame.isFin())
|
||||
{
|
||||
ByteBuffer payload = frame.getPayload();
|
||||
length = payload.remaining();
|
||||
buffer = BufferUtil.toArray(payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = new byte[0];
|
||||
byte[] buffer = frame.hasPayload() ? BufferUtil.toArray(frame.getPayload()) : EMPTY_BUFFER;
|
||||
methodHandle.invoke(buffer, 0, buffer.length, frame.isFin());
|
||||
}
|
||||
|
||||
methodHandle.invoke(buffer, offset, length, frame.isFin());
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue