Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.

This commit is contained in:
Simone Bordet 2019-08-10 00:39:54 +02:00
commit 62758122c3
60 changed files with 1691 additions and 429 deletions

View File

@ -33,23 +33,12 @@
</module> </module>
<module name="TreeWalker"> <module name="TreeWalker">
<!--
Eclipse Jetty Specific.
===========================================================================================
-->
<module name="SuppressionCommentFilter"> <module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="@checkstyle-disable-check : ([\w\|]+)"/> <property name="offCommentFormat" value="@checkstyle-disable-check : ([\w\|]+)"/>
<property name="onCommentFormat" value="@checkstyle-enable-check : ([\w\|]+)"/> <property name="onCommentFormat" value="@checkstyle-enable-check : ([\w\|]+)"/>
<property name="checkFormat" value="$1"/> <property name="checkFormat" value="$1"/>
</module> </module>
<!-- Check abbreviations(consecutive capital letters) length in identifier name -->
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="true"/>
<property name="allowedAbbreviations" value="ALPN, ASCII, AWT, CRLDP, CRLF, FCGI, GZIP, HTTP, HTTPS, ID, IP, ISO8859, JAAS, JDBC, JMXRMI, JNDI, JPMS, JSON, JSTL, LDAP, PROXY, RFC, SPNEGO, URI, URL, UTF8, XML"/>
</module>
<!-- Location of Annotations --> <!-- Location of Annotations -->
<module name="AnnotationLocation"> <module name="AnnotationLocation">
<property name="allowSamelineSingleParameterlessAnnotation" value="false"/> <property name="allowSamelineSingleParameterlessAnnotation" value="false"/>
@ -90,7 +79,7 @@
<!-- Indentation Rules --> <!-- Indentation Rules -->
<module name="Indentation"> <module name="Indentation">
<property name="throwsIndent" value="0"/> <property name="arrayInitIndent" value="8"/>
</module> </module>
<!-- Interface Type Parameter Name --> <!-- Interface Type Parameter Name -->
@ -215,7 +204,9 @@
</module> </module>
<!-- all switch statements should have "default" label declared --> <!-- all switch statements should have "default" label declared -->
<!-- Disabled: Is super noisy
<module name="MissingSwitchDefault"/> <module name="MissingSwitchDefault"/>
-->
<!-- prevent line wrapping of import / package statements --> <!-- prevent line wrapping of import / package statements -->
<module name="NoLineWrap"/> <module name="NoLineWrap"/>
@ -226,9 +217,6 @@
<!-- Filename and Classname match --> <!-- Filename and Classname match -->
<module name="OuterTypeFilename"/> <module name="OuterTypeFilename"/>
<!-- Checks that overload methods are grouped together -->
<module name="OverloadMethodsDeclarationOrder"/>
<!-- <!--
Checks based on the Java Language Specification recommendations. Checks based on the Java Language Specification recommendations.
https://docs.oracle.com/javase/specs/jls/se8/html/index.html https://docs.oracle.com/javase/specs/jls/se8/html/index.html
@ -284,8 +272,5 @@
<module name="UpperEll"/> <module name="UpperEll"/>
<!-- TODO: look for float / double version of above --> <!-- TODO: look for float / double version of above -->
<!-- Checks the distance between declaration of variable and its first usage -->
<module name="VariableDeclarationUsageDistance"/>
</module> </module>
</module> </module>

View File

@ -188,11 +188,12 @@ public class AnnotationConfiguration extends AbstractConfiguration
/** /**
* ServletContainerInitializerOrdering * ServletContainerInitializerOrdering
* * <p>Applies an ordering to the {@link ServletContainerInitializer}s for the context, using
* A list of classnames of ServletContainerInitializers in the order in which * the value of the "org.eclipse.jetty.containerInitializerOrder" context attribute.
* they are to be called back. One name only in the list can be "*", which is a * The attribute value is a list of classnames of ServletContainerInitializers in the order in which
* they are to be called. One name only in the list can be "*", which is a
* wildcard which matches any other ServletContainerInitializer name not already * wildcard which matches any other ServletContainerInitializer name not already
* matched. * matched.</p>
*/ */
public class ServletContainerInitializerOrdering public class ServletContainerInitializerOrdering
{ {

View File

@ -5,14 +5,34 @@
<version>10.0.0-SNAPSHOT</version> <version>10.0.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.cdi</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>cdi-2</artifactId> <artifactId>jetty-cdi</artifactId>
<name>Jetty :: CDI 2</name> <name>Jetty :: CDI</name>
<url>http://www.eclipse.org/jetty</url> <url>http://www.eclipse.org/jetty</url>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<bundle-symbolic-name>${project.groupId}.cdi2</bundle-symbolic-name> <bundle-symbolic-name>${project.groupId}.cdi</bundle-symbolic-name>
</properties> </properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="setAttribute">
<Arg type="String">org.eclipse.jetty.cdi</Arg>
<Arg><Property name="jetty.cdi.mode" default="CdiSpiDecorator"/></Arg>
</Call>
</Configure>

View File

@ -0,0 +1,17 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
<Call name="warn"><Arg>cdi2 module is deprecated!</Arg></Call>
</Get>
<Ref refid="DeploymentManager">
<Call name="addLifeCycleBinding">
<Arg>
<New class="org.eclipse.jetty.deploy.bindings.GlobalWebappConfigBinding">
<Set name="jettyXml"><Property name="jetty.home" default="." />/etc/cdi/jetty-web-cdi2.xml
</Set>
</New>
</Arg>
</Call>
</Ref>
</Configure>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/demo</Set>
<Set name="war"><Property name="jetty.webapps"/>/demo/</Set>
<Get name="serverClassMatcher"> <Get name="serverClassMatcher">
<Call name="add"> <Call name="add">
<Arg>-org.eclipse.jetty.util.Decorator</Arg> <Arg>-org.eclipse.jetty.util.Decorator</Arg>

View File

@ -0,0 +1,18 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Configures Jetty to use the "CdiDecoratingListener" as the default
CDI integration mode that allows a webapp to register it's own CDI
decorator.
[tag]
cdi
[provides]
cdi-mode
[depend]
cdi
[ini]
jetty.cdi.mode=CdiDecoratingListener

View File

@ -0,0 +1,17 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Configures Jetty to use the "CdiSpiDecorator" that calls the CDI SPI
as the default CDI integration mode.
[tag]
cdi
[provides]
cdi-mode
[depend]
cdi
[ini]
jetty.cdi.mode=CdiSpiDecorator

View File

@ -1,7 +1,34 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html # DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description] [description]
Jetty setup to support Weld/CDI2 with WELD inside the webapp Support for CDI inside the webapp.
This module does not provide CDI, but configures jetty to support various
integration modes with a CDI implementation on the webapp classpath.
CDI integration modes can be selected per webapp with the "org.eclipse.jetty.cdi"
init parameter or defaults to the mode set by the "org.eclipse.jetty.cdi" server
attribute (which is initialised from the "jetty.cdi.mode" start property).
Supported modes are:
CdiSpiDecorator - Jetty will call the CDI SPI within the webapp to decorate
objects (default).
CdiDecoratingLister - The webapp may register a decorator on the context attribute
"org.eclipse.jetty.cdi.decorator".
[tag]
cdi
[provides]
cdi
[depend] [depend]
cdi2 deploy
[xml]
etc/cdi/jetty-cdi.xml
[lib]
lib/jetty-cdi-${jetty.version}.jar
lib/apache-jsp/org.mortbay.jasper.apache-el-*.jar
[ini]
jetty.webapp.addSystemClasses+=,org.eclipse.jetty.cdi.CdiServletContainerInitializer
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.cdi.CdiServletContainerInitializer

View File

@ -1,20 +1,23 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html # DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description] [description]
Jetty setup to support Weld/CDI2 with WELD inside the webapp Deprecated support for CDI integrations inside the webapp.
This module does not provide CDI, but configures jetty so that a CDI implementation
can enable itself as a decorator for Filters, Servlets and Listeners.
This modules uses the deprecated technique of exposing private Jetty decorate APIs to the CDI
implementation in the webapp.
[tag]
cdi
[provides]
cdi-mode
[depend] [depend]
annotations deploy
[lib] [lib]
lib/apache-jsp/org.mortbay.jasper.apache-el-*.jar lib/apache-jsp/org.mortbay.jasper.apache-el-*.jar
[ini] [xml]
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.util.Decorator,-org.eclipse.jetty.util.DecoratedObjectFactory etc/cdi/jetty-cdi2.xml
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.server.handler.ContextHandler.,-org.eclipse.jetty.server.handler.ContextHandler,-org.eclipse.jetty.servlet.ServletContextHandler
[license]
Weld is an open source project hosted on Github and released under the Apache 2.0 license.
http://weld.cdi-spec.org/
http://www.apache.org/licenses/LICENSE-2.0.html

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.cdi;
import org.eclipse.jetty.servlet.DecoratingListener;
import org.eclipse.jetty.servlet.ServletContextHandler;
/**
* A DecoratingListener that listens for "org.eclipse.jetty.cdi.decorator"
*/
class CdiDecoratingListener extends DecoratingListener
{
public static final String MODE = "CdiDecoratingListener";
public static final String ATTRIBUTE = "org.eclipse.jetty.cdi.decorator";
public CdiDecoratingListener(ServletContextHandler contextHandler)
{
super(contextHandler, ATTRIBUTE);
}
}

View File

@ -0,0 +1,99 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.cdi;
import java.util.Objects;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* <p>A {@link ServletContainerInitializer} that introspects for a CDI API
* implementation within a web application and applies an integration
* mode if CDI is found. CDI integration modes can be selected per webapp with
* the "org.eclipse.jetty.cdi" init parameter or default to the mode set by the
* "org.eclipse.jetty.cdi" server attribute. Supported modes are:</p>
* <dl>
* <dt>CdiSpiDecorator</dt>
* <dd>Jetty will call the CDI SPI within the webapp to decorate objects (default).</dd>
* <dt>CdiDecoratingLister</dt>
* <dd>The webapp may register a decorator on the context attribute
* "org.eclipse.jetty.cdi.decorator".</dd>
* </dl>
*
* @see AnnotationConfiguration.ServletContainerInitializerOrdering
*/
public class CdiServletContainerInitializer implements ServletContainerInitializer
{
public static final String CDI_INTEGRATION_ATTRIBUTE = "org.eclipse.jetty.cdi";
private static final Logger LOG = Log.getLogger(CdiServletContainerInitializer.class);
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx)
{
try
{
ServletContextHandler context = ServletContextHandler.getServletContextHandler(ctx);
Objects.requireNonNull(context);
// Test if CDI is in the webapp by trying to load the CDI class.
ClassLoader loader = context.getClassLoader();
if (loader == null)
Loader.loadClass("javax.enterprise.inject.spi.CDI");
else
loader.loadClass("javax.enterprise.inject.spi.CDI");
String mode = ctx.getInitParameter(CDI_INTEGRATION_ATTRIBUTE);
if (mode == null)
{
mode = (String)context.getServer().getAttribute(CDI_INTEGRATION_ATTRIBUTE);
if (mode == null)
mode = CdiSpiDecorator.MODE;
}
switch (mode)
{
case CdiSpiDecorator.MODE:
context.getObjectFactory().addDecorator(new CdiSpiDecorator(context));
break;
case CdiDecoratingListener.MODE:
context.addEventListener(new CdiDecoratingListener(context));
break;
default:
throw new IllegalStateException(mode);
}
context.setAttribute(CDI_INTEGRATION_ATTRIBUTE, mode);
LOG.info(mode + " enabled in " + ctx);
}
catch (UnsupportedOperationException | ClassNotFoundException e)
{
if (LOG.isDebugEnabled())
LOG.debug("CDI not found in " + ctx, e);
}
}
}

View File

@ -0,0 +1,166 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.cdi;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.Decorator;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A Decorator that invokes the CDI provider within a webapp to decorate objects created by
* the contexts {@link org.eclipse.jetty.util.DecoratedObjectFactory}
* (typically Listeners, Filters and Servlets).
* The CDI provider is invoked using {@link MethodHandle}s to avoid any CDI instance
* or dependencies within the server scope. The code invoked is equivalent to:
* <pre>
* public &lt;T&gt; T decorate(T o)
* {
* BeanManager manager = CDI.current().getBeanManager();
* manager.createInjectionTarget(manager.createAnnotatedType((Class&lt;T&gt;)o.getClass()))
* .inject(o,manager.createCreationalContext(null));
* return o;
* }
* </pre>
*/
public class CdiSpiDecorator implements Decorator
{
private static final Logger LOG = Log.getLogger(CdiServletContainerInitializer.class);
public static final String MODE = "CdiSpiDecorator";
private final ServletContextHandler _context;
private final Map<Object, Decorated> _decorated = new HashMap<>();
private final MethodHandle _current;
private final MethodHandle _getBeanManager;
private final MethodHandle _createAnnotatedType;
private final MethodHandle _createInjectionTarget;
private final MethodHandle _createCreationalContext;
private final MethodHandle _inject;
private final MethodHandle _dispose;
private final MethodHandle _release;
public CdiSpiDecorator(ServletContextHandler context) throws UnsupportedOperationException
{
_context = context;
ClassLoader classLoader = _context.getClassLoader();
try
{
Class<?> cdiClass = classLoader.loadClass("javax.enterprise.inject.spi.CDI");
Class<?> beanManagerClass = classLoader.loadClass("javax.enterprise.inject.spi.BeanManager");
Class<?> annotatedTypeClass = classLoader.loadClass("javax.enterprise.inject.spi.AnnotatedType");
Class<?> injectionTargetClass = classLoader.loadClass("javax.enterprise.inject.spi.InjectionTarget");
Class<?> creationalContextClass = classLoader.loadClass("javax.enterprise.context.spi.CreationalContext");
Class<?> contextualClass = classLoader.loadClass("javax.enterprise.context.spi.Contextual");
MethodHandles.Lookup lookup = MethodHandles.lookup();
_current = lookup.findStatic(cdiClass, "current", MethodType.methodType(cdiClass));
_getBeanManager = lookup.findVirtual(cdiClass, "getBeanManager", MethodType.methodType(beanManagerClass));
_createAnnotatedType = lookup.findVirtual(beanManagerClass, "createAnnotatedType", MethodType.methodType(annotatedTypeClass, Class.class));
_createInjectionTarget = lookup.findVirtual(beanManagerClass, "createInjectionTarget", MethodType.methodType(injectionTargetClass, annotatedTypeClass));
_createCreationalContext = lookup.findVirtual(beanManagerClass, "createCreationalContext", MethodType.methodType(creationalContextClass, contextualClass));
_inject = lookup.findVirtual(injectionTargetClass, "inject", MethodType.methodType(Void.TYPE, Object.class, creationalContextClass));
_dispose = lookup.findVirtual(injectionTargetClass, "dispose", MethodType.methodType(Void.TYPE, Object.class));
_release = lookup.findVirtual(creationalContextClass, "release", MethodType.methodType(Void.TYPE));
}
catch (Exception e)
{
throw new UnsupportedOperationException(e);
}
}
/**
* Decorate an object.
* <p>The signature of this method must match what is introspected for by the
* Jetty DecoratingListener class. It is invoked dynamically.</p>
*
* @param o The object to be decorated
* @param <T> The type of the object to be decorated
* @return The decorated object
*/
public <T> T decorate(T o)
{
try
{
if (LOG.isDebugEnabled())
LOG.debug("decorate {} in {}", o, _context);
_decorated.put(o, new Decorated(o));
}
catch (Throwable th)
{
LOG.warn("Unable to decorate " + o, th);
}
return o;
}
/**
* Destroy a decorated object.
* <p>The signature of this method must match what is introspected for by the
* Jetty DecoratingListener class. It is invoked dynamically.</p>
*
* @param o The object to be destroyed
*/
public void destroy(Object o)
{
try
{
Decorated decorated = _decorated.remove(o);
if (decorated != null)
decorated.destroy(o);
}
catch (Throwable th)
{
LOG.warn("Unable to destroy " + o, th);
}
}
private class Decorated
{
private final Object _injectionTarget;
private final Object _creationalContext;
Decorated(Object o) throws Throwable
{
// BeanManager manager = CDI.current().getBeanManager();
Object manager = _getBeanManager.invoke(_current.invoke());
// AnnotatedType annotatedType = manager.createAnnotatedType((Class<T>)o.getClass());
Object annotatedType = _createAnnotatedType.invoke(manager, o.getClass());
// CreationalContext creationalContext = manager.createCreationalContext(null);
_creationalContext = _createCreationalContext.invoke(manager, null);
// InjectionTarget injectionTarget = manager.createInjectionTarget();
_injectionTarget = _createInjectionTarget.invoke(manager, annotatedType);
// injectionTarget.inject(o, creationalContext);
_inject.invoke(_injectionTarget, o, _creationalContext);
}
public void destroy(Object o) throws Throwable
{
_dispose.invoke(_injectionTarget, o);
_release.invoke(_creationalContext);
}
}
}

View File

@ -0,0 +1 @@
org.eclipse.jetty.cdi.CdiServletContainerInitializer

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- Bind the jetty-web-decorate.xml to every deployed webapp -->
<Ref refid="DeploymentManager">
<Call name="addLifeCycleBinding">
<Arg>
<New class="org.eclipse.jetty.deploy.bindings.GlobalWebappConfigBinding">
<Set name="jettyXml"><Property name="jetty.home" default="." />/etc/jetty-web-decorate.xml
</Set>
</New>
</Arg>
</Call>
</Ref>
</Configure>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext" id="context">
<Get class="org.eclipse.jetty.webapp.DecoratingListener" name="DECORATOR_ATTRIBUTE" id="decoratorAttribute"/>
<!-- Add the DecoratingListener to the webapp to look for dynamic decorators -->
<Call name="addEventListener">
<Arg>
<New class="org.eclipse.jetty.webapp.DecoratingListener">
<Arg><Ref refid="context" /></Arg>
<Arg type="String"><Ref refid="decoratorAttribute"/></Arg>
</New>
</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,14 @@
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[description]
Jetty setup to support Decoration of Listeners, Filters and Servlets within a deployed webapp.
This module uses the DecoratingListener to register an object set as a context attribute
as a dynamic decorator. This module sets the "org.eclipse.jetty.webapp.DecoratingListener"
context attribute with the name of the context attribute that will be listened to.
By default the attribute is "org.eclipse.jetty.webapp.decorator".
[depend]
deploy
[xml]
etc/jetty-decorate.xml

View File

@ -30,13 +30,13 @@
<table> <table>
<tr> <tr>
<td> <td>
<h2>examples ...</h2> <h2>tests ...</h2>
<ul> <ul>
<li><a href="/test/">Test Jetty Webapp</a></li> <li><a href="/test/">Test Jetty Webapp</a></li>
<li><a href="/async-rest/">Async Rest</a></li> <li><a href="/test-spec/">Servlet 3.1 Test</a></li>
<li><a href="/test-jaas/">JAAS Test</a></li> <li><a href="/test-jaas/">JAAS Test</a></li>
<li><a href="/test-jndi/">JNDI Test</a></li> <li><a href="/test-jndi/">JNDI Test</a></li>
<li><a href="/test-spec/">Servlet 3.1 Test</a></li> <li><a href="/async-rest/">Async Rest</a></li>
<li><a href="/oldContextPath/">Redirected Context</a></li> <li><a href="/oldContextPath/">Redirected Context</a></li>
</ul> </ul>
</td> </td>

View File

@ -689,8 +689,8 @@
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.cdi</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>cdi-2</artifactId> <artifactId>jetty-cdi</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -31,9 +31,6 @@
<version>3.8.1</version> <version>3.8.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet</artifactId>
<version>@weld.version@</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId> <groupId>org.eclipse.jetty.toolchain</groupId>

View File

@ -18,10 +18,6 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId> <groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-servlet-api</artifactId> <artifactId>jetty-servlet-api</artifactId>

View File

@ -58,6 +58,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletMapping; import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.HttpUpgradeHandler;
@ -484,57 +485,30 @@ public class Request implements HttpServletRequest
{ {
try try
{ {
int maxFormContentSize = -1; int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE;
int maxFormKeys = -1; int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS;
if (_context != null) if (_context != null)
{ {
maxFormContentSize = _context.getContextHandler().getMaxFormContentSize(); ContextHandler contextHandler = _context.getContextHandler();
maxFormKeys = _context.getContextHandler().getMaxFormKeys(); maxFormContentSize = contextHandler.getMaxFormContentSize();
maxFormKeys = contextHandler.getMaxFormKeys();
} }
else
if (maxFormContentSize < 0)
{ {
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize);
if (obj == null) maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys);
maxFormContentSize = 200000;
else if (obj instanceof Number)
{
Number size = (Number)obj;
maxFormContentSize = size.intValue();
}
else if (obj instanceof String)
{
maxFormContentSize = Integer.parseInt((String)obj);
}
}
if (maxFormKeys < 0)
{
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
if (obj == null)
maxFormKeys = 1000;
else if (obj instanceof Number)
{
Number keys = (Number)obj;
maxFormKeys = keys.intValue();
}
else if (obj instanceof String)
{
maxFormKeys = Integer.parseInt((String)obj);
}
} }
int contentLength = getContentLength(); int contentLength = getContentLength();
if (contentLength > maxFormContentSize && maxFormContentSize > 0) if (maxFormContentSize >= 0 && contentLength > maxFormContentSize)
{ throw new IllegalStateException("Form is larger than max length " + maxFormContentSize);
throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
}
InputStream in = getInputStream(); InputStream in = getInputStream();
if (_input.isAsync()) if (_input.isAsync())
throw new IllegalStateException("Cannot extract parameters with async IO"); throw new IllegalStateException("Cannot extract parameters with async IO");
UrlEncoded.decodeTo(in, params, getCharacterEncoding(), contentLength < 0 ? maxFormContentSize : -1, maxFormKeys); UrlEncoded.decodeTo(in, params, getCharacterEncoding(), maxFormContentSize, maxFormKeys);
} }
catch (IOException e) catch (IOException e)
{ {
@ -543,6 +517,16 @@ public class Request implements HttpServletRequest
} }
} }
private int lookupServerAttribute(String key, int dftValue)
{
Object attribute = _channel.getServer().getAttribute(key);
if (attribute instanceof Number)
return ((Number)attribute).intValue();
else if (attribute instanceof String)
return Integer.parseInt((String)attribute);
return dftValue;
}
@Override @Override
public AsyncContext getAsyncContext() public AsyncContext getAsyncContext()
{ {
@ -2137,7 +2121,7 @@ public class Request implements HttpServletRequest
AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, servletRequest, servletResponse); AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, servletRequest, servletResponse);
event.setDispatchContext(getServletContext()); event.setDispatchContext(getServletContext());
String uri = ((HttpServletRequest)servletRequest).getRequestURI(); String uri = unwrap(servletRequest).getRequestURI();
if (_contextPath != null && uri.startsWith(_contextPath)) if (_contextPath != null && uri.startsWith(_contextPath))
uri = uri.substring(_contextPath.length()); uri = uri.substring(_contextPath.length());
else else
@ -2149,6 +2133,19 @@ public class Request implements HttpServletRequest
return _async; return _async;
} }
public static HttpServletRequest unwrap(ServletRequest servletRequest)
{
if (servletRequest instanceof HttpServletRequestWrapper)
{
return (HttpServletRequestWrapper)servletRequest;
}
if (servletRequest instanceof ServletRequestWrapper)
{
return unwrap(((ServletRequestWrapper)servletRequest).getRequest());
}
return ((HttpServletRequest)servletRequest);
}
@Override @Override
public String toString() public String toString()
{ {

View File

@ -32,8 +32,11 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import org.eclipse.jetty.http.CookieCompliance; import org.eclipse.jetty.http.CookieCompliance;
@ -1292,6 +1295,19 @@ public class Response implements HttpServletResponse
} }
} }
public static HttpServletResponse unwrap(ServletResponse servletResponse)
{
if (servletResponse instanceof HttpServletResponseWrapper)
{
return (HttpServletResponseWrapper)servletResponse;
}
if (servletResponse instanceof ServletResponseWrapper)
{
return unwrap(((ServletResponseWrapper)servletResponse).getResponse());
}
return (HttpServletResponse)servletResponse;
}
private static class HttpFieldsSupplier implements Supplier<HttpFields> private static class HttpFieldsSupplier implements Supplier<HttpFields>
{ {
private final Supplier<Map<String, String>> _supplier; private final Supplier<Map<String, String>> _supplier;

View File

@ -558,8 +558,8 @@ public class Server extends HandlerWrapper implements Attributes
} }
final String target = baseRequest.getPathInfo(); final String target = baseRequest.getPathInfo();
final HttpServletRequest request = (HttpServletRequest)event.getSuppliedRequest(); final HttpServletRequest request = Request.unwrap(event.getSuppliedRequest());
final HttpServletResponse response = (HttpServletResponse)event.getSuppliedResponse(); final HttpServletResponse response = Response.unwrap(event.getSuppliedResponse());
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("{} {} {} on {}", request.getDispatcherType(), request.getMethod(), target, channel); LOG.debug("{} {} {} on {}", request.getDispatcherType(), request.getMethod(), target, channel);

View File

@ -136,6 +136,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes"; public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
public static final String MAX_FORM_KEYS_KEY = "org.eclipse.jetty.server.Request.maxFormKeys";
public static final String MAX_FORM_CONTENT_SIZE_KEY = "org.eclipse.jetty.server.Request.maxFormContentSize";
public static final int DEFAULT_MAX_FORM_KEYS = 1000;
public static final int DEFAULT_MAX_FORM_CONTENT_SIZE = 200000;
/** /**
* Get the current ServletContext implementation. * Get the current ServletContext implementation.
* *
@ -188,8 +193,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private Logger _logger; private Logger _logger;
private boolean _allowNullPathInfo; private boolean _allowNullPathInfo;
private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys", -1).intValue(); private int _maxFormKeys = Integer.getInteger(MAX_FORM_KEYS_KEY, DEFAULT_MAX_FORM_KEYS);
private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize", -1).intValue(); private int _maxFormContentSize = Integer.getInteger(MAX_FORM_CONTENT_SIZE_KEY, DEFAULT_MAX_FORM_CONTENT_SIZE);
private boolean _compactPath = false; private boolean _compactPath = false;
private boolean _usingSecurityManager = System.getSecurityManager() != null; private boolean _usingSecurityManager = System.getSecurityManager() != null;
@ -784,9 +789,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
throw new IllegalStateException("Null contextPath"); throw new IllegalStateException("Null contextPath");
if (_logger == null) if (_logger == null)
{
_logger = Log.getLogger(ContextHandler.class.getName() + getLogNameSuffix()); _logger = Log.getLogger(ContextHandler.class.getName() + getLogNameSuffix());
}
ClassLoader oldClassloader = null; ClassLoader oldClassloader = null;
Thread currentThread = null; Thread currentThread = null;

View File

@ -0,0 +1,86 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
public class ServletRequestWrapperTest
{
private Server server;
private LocalConnector connector;
private RequestHandler handler;
@BeforeEach
public void init() throws Exception
{
server = new Server();
connector = new LocalConnector(server, new HttpConnectionFactory());
server.addConnector(connector);
handler = new RequestHandler();
server.setHandler(handler);
server.start();
}
@Test
public void testServletRequestWrapper() throws Exception
{
String request = "GET / HTTP/1.1\r\n" +
"Host: whatever\r\n" +
"\n";
String response = connector.getResponse(request);
assertThat("Response", response, containsString("200"));
}
private class RequestWrapper extends ServletRequestWrapper
{
public RequestWrapper(ServletRequest request)
{
super(request);
}
}
private class RequestHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
RequestWrapper requestWrapper = new RequestWrapper(request);
AsyncContext context = request.startAsync(requestWrapper, response);
context.complete();
baseRequest.setHandled(true);
}
}
}

View File

@ -0,0 +1,174 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlet;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import org.eclipse.jetty.util.Decorator;
/**
* A ServletContextAttributeListener that listens for a context
* attribute to obtain a decorator instance. The instance is then either
* coerced to a {@link Decorator} or reflected for decorator compatible methods
* so it can be added to the {@link ServletContextHandler#getObjectFactory()} as a
* {@link Decorator}.
*/
public class DecoratingListener implements ServletContextAttributeListener
{
private static final MethodType DECORATE_TYPE;
private static final MethodType DESTROY_TYPE;
static
{
try
{
DECORATE_TYPE = MethodType.methodType(Object.class, Object.class);
DESTROY_TYPE = MethodType.methodType(void.class, Object.class);
// Check we have the right MethodTypes for the current Decorator signatures
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVirtual(Decorator.class, "decorate", DECORATE_TYPE);
lookup.findVirtual(Decorator.class, "destroy", DESTROY_TYPE);
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
private final ServletContextHandler _context;
private final String _attributeName;
private Decorator _decorator;
public DecoratingListener(ServletContextHandler context, String attributeName)
{
Objects.requireNonNull(context);
Objects.requireNonNull(attributeName);
_context = context;
_attributeName = attributeName;
Object decorator = _context.getAttribute(_attributeName);
if (decorator != null)
_context.getObjectFactory().addDecorator(asDecorator(decorator));
}
public String getAttributeName()
{
return _attributeName;
}
public ServletContext getServletContext()
{
return _context.getServletContext();
}
private Decorator asDecorator(Object object)
{
if (object == null)
return null;
if (object instanceof Decorator)
return (Decorator)object;
try
{
Class<?> clazz = object.getClass();
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle decorate = lookup.findVirtual(clazz, "decorate", DECORATE_TYPE);
MethodHandle destroy = lookup.findVirtual(clazz, "destroy", DESTROY_TYPE);
return new DynamicDecorator(object, decorate, destroy);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public void attributeAdded(ServletContextAttributeEvent event)
{
if (_attributeName.equals(event.getName()))
{
_decorator = asDecorator(event.getValue());
_context.getObjectFactory().addDecorator(_decorator);
}
}
@Override
public void attributeRemoved(ServletContextAttributeEvent event)
{
if (_attributeName.equals(event.getName()) && _decorator != null)
{
_context.getObjectFactory().removeDecorator(_decorator);
_decorator = null;
}
}
@Override
public void attributeReplaced(ServletContextAttributeEvent event)
{
attributeRemoved(event);
attributeAdded(event);
}
private static class DynamicDecorator implements Decorator
{
private final Object _object;
private final MethodHandle _decorate;
private final MethodHandle _destroy;
private DynamicDecorator(Object object, MethodHandle decorate, MethodHandle destroy)
{
_object = object;
_decorate = decorate;
_destroy = destroy;
}
@Override
public <T> T decorate(T o)
{
try
{
return (T)_decorate.invoke(_object, o);
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
@Override
public void destroy(Object o)
{
try
{
_destroy.invoke(_object, o);
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
}
}

View File

@ -0,0 +1,203 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlet;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Fields;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FormTest
{
private static final int MAX_FORM_CONTENT_SIZE = 128;
private static final int MAX_FORM_KEYS = 4;
private Server server;
private ServerConnector connector;
private HttpClient client;
private String contextPath = "/ctx";
private String servletPath = "/test_form";
private void start(Function<ServletContextHandler, HttpServlet> config) throws Exception
{
startServer(config);
startClient();
}
private void startServer(Function<ServletContextHandler, HttpServlet> config) throws Exception
{
server = new Server();
connector = new ServerConnector(server, 1, 1);
server.addConnector(connector);
ServletContextHandler handler = new ServletContextHandler(server, contextPath);
HttpServlet servlet = config.apply(handler);
handler.addServlet(new ServletHolder(servlet), servletPath + "/*");
server.start();
}
private void startClient() throws Exception
{
client = new HttpClient();
client.start();
}
@AfterEach
public void dispose() throws Exception
{
if (client != null)
client.stop();
if (server != null)
server.stop();
}
public static Stream<Arguments> formContentSizeScenarios()
{
return Stream.of(
Arguments.of(null, true),
Arguments.of(null, false),
Arguments.of(-1, true),
Arguments.of(-1, false),
Arguments.of(0, true),
Arguments.of(0, false),
Arguments.of(MAX_FORM_CONTENT_SIZE, true),
Arguments.of(MAX_FORM_CONTENT_SIZE, false)
);
}
@ParameterizedTest
@MethodSource("formContentSizeScenarios")
public void testMaxFormContentSizeExceeded(Integer maxFormContentSize, boolean withContentLength) throws Exception
{
start(handler ->
{
if (maxFormContentSize != null)
handler.setMaxFormContentSize(maxFormContentSize);
return new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
{
request.getParameterMap();
}
};
});
byte[] key = "foo=".getBytes(StandardCharsets.US_ASCII);
int length = (maxFormContentSize == null || maxFormContentSize < 0)
? ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE
: maxFormContentSize;
// Avoid empty value.
length = length + 1;
byte[] value = new byte[length];
Arrays.fill(value, (byte)'x');
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(key), ByteBuffer.wrap(value));
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
.content(content)
.onRequestBegin(request ->
{
if (withContentLength)
content.close();
})
.onRequestCommit(request ->
{
if (!withContentLength)
content.close();
})
.send();
int expected = (maxFormContentSize != null && maxFormContentSize < 0)
? HttpStatus.OK_200
: HttpStatus.BAD_REQUEST_400;
assertEquals(expected, response.getStatus());
}
public static Stream<Integer> formKeysScenarios()
{
return Stream.of(null, -1, 0, MAX_FORM_KEYS);
}
@ParameterizedTest
@MethodSource("formKeysScenarios")
public void testMaxFormKeysExceeded(Integer maxFormKeys) throws Exception
{
start(handler ->
{
if (maxFormKeys != null)
handler.setMaxFormKeys(maxFormKeys);
return new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
{
request.getParameterMap();
}
};
});
int keys = (maxFormKeys == null || maxFormKeys < 0)
? ContextHandler.DEFAULT_MAX_FORM_KEYS
: maxFormKeys;
// Have at least one key.
keys = keys + 1;
Fields formParams = new Fields();
for (int i = 0; i < keys; ++i)
{
formParams.add("key_" + i, "value_" + i);
}
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
.content(new FormContentProvider(formParams))
.send();
int expected = (maxFormKeys != null && maxFormKeys < 0)
? HttpStatus.OK_200
: HttpStatus.BAD_REQUEST_400;
assertEquals(expected, response.getStatus());
}
}

View File

@ -51,7 +51,13 @@ public class DecoratedObjectFactory implements Iterable<Decorator>
public void addDecorator(Decorator decorator) public void addDecorator(Decorator decorator)
{ {
LOG.debug("Adding Decorator: {}", decorator); LOG.debug("Adding Decorator: {}", decorator);
this.decorators.add(decorator); decorators.add(decorator);
}
public boolean removeDecorator(Decorator decorator)
{
LOG.debug("Remove Decorator: {}", decorator);
return decorators.remove(decorator);
} }
public void clear() public void clear()

View File

@ -155,7 +155,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
boolean delim = false; boolean delim = false;
for (Map.Entry<String, List<String>> entry : map.entrySet()) for (Map.Entry<String, List<String>> entry : map.entrySet())
{ {
String key = entry.getKey().toString(); String key = entry.getKey();
List<String> list = entry.getValue(); List<String> list = entry.getValue();
int s = list.size(); int s = list.size();
@ -181,7 +181,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
if (val != null) if (val != null)
{ {
String str = val.toString(); String str = val;
if (str.length() > 0) if (str.length() > 0)
{ {
result.append('='); result.append('=');
@ -232,7 +232,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
synchronized (map) synchronized (map)
{ {
String key = null; String key = null;
String value = null; String value;
int mark = -1; int mark = -1;
boolean encoded = false; boolean encoded = false;
for (int i = 0; i < content.length(); i++) for (int i = 0; i < content.length(); i++)
@ -269,8 +269,6 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
case '%': case '%':
encoded = true; encoded = true;
break; break;
default:
break;
} }
} }
@ -293,146 +291,6 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
} }
} }
/**
* Decoded parameters to Map.
*
* @param in the stream containing the encoded parameters
* @param map the MultiMap to decode into
* @param charset the charset to use for decoding
* @param maxLength the maximum length of the form to decode
* @param maxKeys the maximum number of keys to decode
* @throws IOException if unable to decode input stream
*/
public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
throws IOException
{
if (charset == null)
{
if (ENCODING.equals(StandardCharsets.UTF_8))
decodeUtf8To(in, map, maxLength, maxKeys);
else
decodeTo(in, map, ENCODING, maxLength, maxKeys);
}
else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
decodeUtf8To(in, map, maxLength, maxKeys);
else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
decode88591To(in, map, maxLength, maxKeys);
else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
decodeUtf16To(in, map, maxLength, maxKeys);
else
decodeTo(in, map, Charset.forName(charset), maxLength, maxKeys);
}
/**
* Decoded parameters to Map.
*
* @param in the stream containing the encoded parameters
* @param map the MultiMap to decode into
* @param charset the charset to use for decoding
* @param maxLength the maximum length of the form to decode
* @param maxKeys the maximum number of keys to decode
* @throws IOException if unable to decode input stream
*/
public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
throws IOException
{
//no charset present, use the configured default
if (charset == null)
charset = ENCODING;
if (StandardCharsets.UTF_8.equals(charset))
{
decodeUtf8To(in, map, maxLength, maxKeys);
return;
}
if (StandardCharsets.ISO_8859_1.equals(charset))
{
decode88591To(in, map, maxLength, maxKeys);
return;
}
if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
{
decodeUtf16To(in, map, maxLength, maxKeys);
return;
}
synchronized (map)
{
String key = null;
String value = null;
int c;
int totalLength = 0;
try (ByteArrayOutputStream2 output = new ByteArrayOutputStream2();)
{
int size = 0;
while ((c = in.read()) > 0)
{
switch ((char)c)
{
case '&':
size = output.size();
value = size == 0 ? "" : output.toString(charset);
output.setCount(0);
if (key != null)
{
map.add(key, value);
}
else if (value != null && value.length() > 0)
{
map.add(value, "");
}
key = null;
value = null;
if (maxKeys > 0 && map.size() > maxKeys)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys));
break;
case '=':
if (key != null)
{
output.write(c);
break;
}
size = output.size();
key = size == 0 ? "" : output.toString(charset);
output.setCount(0);
break;
case '+':
output.write(' ');
break;
case '%':
int code0 = in.read();
int code1 = in.read();
output.write(decodeHexChar(code0, code1));
break;
default:
output.write(c);
break;
}
totalLength++;
if (maxLength >= 0 && totalLength > maxLength)
throw new IllegalStateException("Form is too large");
}
size = output.size();
if (key != null)
{
value = size == 0 ? "" : output.toString(charset);
output.setCount(0);
map.add(key, value);
}
else if (size > 0)
map.add(output.toString(charset), "");
}
}
}
public static void decodeUtf8To(String query, MultiMap<String> map) public static void decodeUtf8To(String query, MultiMap<String> map)
{ {
decodeUtf8To(query, 0, query.length(), map); decodeUtf8To(query, 0, query.length(), map);
@ -521,14 +379,96 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
} }
} }
/**
* Decoded parameters to MultiMap, using ISO8859-1 encodings.
*
* @param in InputSteam to read
* @param map MultiMap to add parameters to
* @param maxLength maximum length of form to read or -1 for no limit
* @param maxKeys maximum number of keys to read or -1 for no limit
* @throws IOException if unable to decode the InputStream as ISO8859-1
*/
public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
throws IOException
{
synchronized (map)
{
StringBuilder buffer = new StringBuilder();
String key = null;
String value = null;
int b;
int totalLength = 0;
while ((b = in.read()) >= 0)
{
switch ((char)b)
{
case '&':
value = buffer.length() == 0 ? "" : buffer.toString();
buffer.setLength(0);
if (key != null)
{
map.add(key, value);
}
else if (value.length() > 0)
{
map.add(value, "");
}
key = null;
value = null;
checkMaxKeys(map, maxKeys);
break;
case '=':
if (key != null)
{
buffer.append((char)b);
break;
}
key = buffer.toString();
buffer.setLength(0);
break;
case '+':
buffer.append(' ');
break;
case '%':
int code0 = in.read();
int code1 = in.read();
buffer.append(decodeHexChar(code0, code1));
break;
default:
buffer.append((char)b);
break;
}
checkMaxLength(++totalLength, maxLength);
}
if (key != null)
{
value = buffer.length() == 0 ? "" : buffer.toString();
buffer.setLength(0);
map.add(key, value);
}
else if (buffer.length() > 0)
{
map.add(buffer.toString(), "");
}
checkMaxKeys(map, maxKeys);
}
}
/** /**
* Decoded parameters to Map. * Decoded parameters to Map.
* *
* @param in InputSteam to read * @param in InputSteam to read
* @param map MultiMap to add parameters to * @param map MultiMap to add parameters to
* @param maxLength maximum form length to decode * @param maxLength maximum form length to decode or -1 for no limit
* @param maxKeys the maximum number of keys to read or -1 for no limit * @param maxKeys the maximum number of keys to read or -1 for no limit
* @throws IOException if unable to decode input stream * @throws IOException if unable to decode the input stream
*/ */
public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
throws IOException throws IOException
@ -559,8 +499,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
} }
key = null; key = null;
value = null; value = null;
if (maxKeys > 0 && map.size() > maxKeys) checkMaxKeys(map, maxKeys);
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys));
break; break;
case '=': case '=':
@ -587,8 +526,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
buffer.append((byte)b); buffer.append((byte)b);
break; break;
} }
if (maxLength >= 0 && (++totalLength > maxLength)) checkMaxLength(++totalLength, maxLength);
throw new IllegalStateException("Form is too large");
} }
if (key != null) if (key != null)
@ -601,6 +539,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
{ {
map.add(buffer.toReplacedString(), ""); map.add(buffer.toReplacedString(), "");
} }
checkMaxKeys(map, maxKeys);
} }
} }
@ -615,88 +554,157 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
} }
/** /**
* Decoded parameters to MultiMap, using ISO8859-1 encodings. * Decoded parameters to Map.
* *
* @param in InputSteam to read * @param in the stream containing the encoded parameters
* @param map MultiMap to add parameters to * @param map the MultiMap to decode into
* @param maxLength maximum length of form to read * @param charset the charset to use for decoding
* @param maxKeys maximum number of keys to read or -1 for no limit * @param maxLength the maximum length of the form to decode or -1 for no limit
* @throws IOException if unable to decode inputstream as ISO8859-1 * @param maxKeys the maximum number of keys to decode or -1 for no limit
* @throws IOException if unable to decode the input stream
*/ */
public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
throws IOException throws IOException
{ {
if (charset == null)
{
if (ENCODING.equals(StandardCharsets.UTF_8))
decodeUtf8To(in, map, maxLength, maxKeys);
else
decodeTo(in, map, ENCODING, maxLength, maxKeys);
}
else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
decodeUtf8To(in, map, maxLength, maxKeys);
else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
decode88591To(in, map, maxLength, maxKeys);
else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
decodeUtf16To(in, map, maxLength, maxKeys);
else
decodeTo(in, map, Charset.forName(charset), maxLength, maxKeys);
}
/**
* Decoded parameters to Map.
*
* @param in the stream containing the encoded parameters
* @param map the MultiMap to decode into
* @param charset the charset to use for decoding
* @param maxLength the maximum length of the form to decode
* @param maxKeys the maximum number of keys to decode
* @throws IOException if unable to decode input stream
*/
public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
throws IOException
{
//no charset present, use the configured default
if (charset == null)
charset = ENCODING;
if (StandardCharsets.UTF_8.equals(charset))
{
decodeUtf8To(in, map, maxLength, maxKeys);
return;
}
if (StandardCharsets.ISO_8859_1.equals(charset))
{
decode88591To(in, map, maxLength, maxKeys);
return;
}
if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
{
decodeUtf16To(in, map, maxLength, maxKeys);
return;
}
synchronized (map) synchronized (map)
{ {
StringBuffer buffer = new StringBuffer();
String key = null; String key = null;
String value = null; String value = null;
int b; int c;
int totalLength = 0; int totalLength = 0;
while ((b = in.read()) >= 0)
try (ByteArrayOutputStream2 output = new ByteArrayOutputStream2())
{ {
switch ((char)b) int size = 0;
while ((c = in.read()) > 0)
{ {
case '&': switch ((char)c)
value = buffer.length() == 0 ? "" : buffer.toString(); {
buffer.setLength(0); case '&':
if (key != null) size = output.size();
{ value = size == 0 ? "" : output.toString(charset);
map.add(key, value); output.setCount(0);
} if (key != null)
else if (value != null && value.length() > 0) {
{ map.add(key, value);
map.add(value, ""); }
} else if (value != null && value.length() > 0)
key = null; {
value = null; map.add(value, "");
if (maxKeys > 0 && map.size() > maxKeys) }
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys)); key = null;
break; value = null;
checkMaxKeys(map, maxKeys);
case '=':
if (key != null)
{
buffer.append((char)b);
break; break;
} case '=':
key = buffer.toString(); if (key != null)
buffer.setLength(0); {
break; output.write(c);
break;
case '+': }
buffer.append(' '); size = output.size();
break; key = size == 0 ? "" : output.toString(charset);
output.setCount(0);
case '%': break;
int code0 = in.read(); case '+':
int code1 = in.read(); output.write(' ');
buffer.append(decodeHexChar(code0, code1)); break;
break; case '%':
int code0 = in.read();
default: int code1 = in.read();
buffer.append((char)b); output.write(decodeHexChar(code0, code1));
break; break;
default:
output.write(c);
break;
}
checkMaxLength(++totalLength, maxLength);
} }
if (maxLength >= 0 && (++totalLength > maxLength))
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys));
}
if (key != null) size = output.size();
{ if (key != null)
value = buffer.length() == 0 ? "" : buffer.toString(); {
buffer.setLength(0); value = size == 0 ? "" : output.toString(charset);
map.add(key, value); output.setCount(0);
} map.add(key, value);
else if (buffer.length() > 0) }
{ else if (size > 0)
map.add(buffer.toString(), ""); {
map.add(output.toString(charset), "");
}
checkMaxKeys(map, maxKeys);
} }
} }
} }
private static void checkMaxKeys(MultiMap<String> map, int maxKeys)
{
int size = map.size();
if (maxKeys >= 0 && size > maxKeys)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", size, maxKeys));
}
private static void checkMaxLength(int length, int maxLength)
{
if (maxLength >= 0 && length > maxLength)
throw new IllegalStateException("Form is larger than max length " + maxLength);
}
/** /**
* Decode String with % encoding. * Decode String with % encoding.
* This method makes the assumption that the majority of calls * This method makes the assumption that the majority of calls

View File

@ -0,0 +1,62 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.webapp;
import org.eclipse.jetty.servlet.ServletContextHandler;
/**
* An extended org.eclipse.jetty.servlet.DecoratingListener.
* The context attribute "org.eclipse.jetty.webapp.DecoratingListener" if
* not set, is set to the name of the attribute this listener listens for.
*/
public class DecoratingListener extends org.eclipse.jetty.servlet.DecoratingListener
{
public static final String DECORATOR_ATTRIBUTE = "org.eclipse.jetty.webapp.decorator";
public DecoratingListener()
{
this(DECORATOR_ATTRIBUTE);
}
public DecoratingListener(String attributeName)
{
this(WebAppContext.getCurrentWebAppContext(), attributeName);
}
public DecoratingListener(ServletContextHandler context)
{
this(context, DECORATOR_ATTRIBUTE);
}
public DecoratingListener(ServletContextHandler context, String attributeName)
{
super(context, attributeName);
checkAndSetAttributeName();
}
protected void checkAndSetAttributeName()
{
// If not set (by another DecoratingListener), flag the attribute that are
// listening for. If more than one DecoratingListener is used then this
// attribute reflects only the first.
if (getServletContext().getAttribute(getClass().getName()) != null)
throw new IllegalStateException("Multiple DecoratingListeners detected");
getServletContext().setAttribute(getClass().getName(), getAttributeName());
}
}

View File

@ -946,7 +946,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
new DumpableCollection("Serverclasses " + name, serverClasses), new DumpableCollection("Serverclasses " + name, serverClasses),
new DumpableCollection("Configurations " + name, _configurations), new DumpableCollection("Configurations " + name, _configurations),
new DumpableCollection("Handler attributes " + name, ((AttributesMap)getAttributes()).getAttributeEntrySet()), new DumpableCollection("Handler attributes " + name, ((AttributesMap)getAttributes()).getAttributeEntrySet()),
new DumpableCollection("Context attributes " + name, ((Context)getServletContext()).getAttributeEntrySet()), new DumpableCollection("Context attributes " + name, getServletContext().getAttributeEntrySet()),
new DumpableCollection("EventListeners " + this, Arrays.asList(getEventListeners())),
new DumpableCollection("Initparams " + name, getInitParams().entrySet()) new DumpableCollection("Initparams " + name, getInitParams().entrySet())
); );
} }

15
pom.xml
View File

@ -37,7 +37,7 @@
<junit.version>5.5.1</junit.version> <junit.version>5.5.1</junit.version>
<maven.version>3.6.0</maven.version> <maven.version>3.6.0</maven.version>
<maven.resolver.version>1.3.1</maven.resolver.version> <maven.resolver.version>1.3.1</maven.resolver.version>
<weld.version>2.4.5.Final</weld.version> <weld.version>3.1.2.Final</weld.version>
<jetty.perf-helper.version>1.0.5</jetty.perf-helper.version> <jetty.perf-helper.version>1.0.5</jetty.perf-helper.version>
<unix.socket.tmp></unix.socket.tmp> <unix.socket.tmp></unix.socket.tmp>
<!-- enable or not TestTracker junit5 extension i.e log message when test method is starting --> <!-- enable or not TestTracker junit5 extension i.e log message when test method is starting -->
@ -63,7 +63,7 @@
otherwise depending on Spring Boot might be chicken and egg issue :) --> otherwise depending on Spring Boot might be chicken and egg issue :) -->
<springboot.version>2.1.1.RELEASE</springboot.version> <springboot.version>2.1.1.RELEASE</springboot.version>
<annotation-api.version>1.3.4</annotation-api.version> <annotation-api.version>1.3.4</annotation-api.version>
<jackson-databind.version>2.9.7</jackson-databind.version> <jackson-databind.version>2.9.9</jackson-databind.version>
<localRepoPath>${project.build.directory}/local-repo</localRepoPath> <localRepoPath>${project.build.directory}/local-repo</localRepoPath>
<settingsPath>src/it/settings.xml</settingsPath> <settingsPath>src/it/settings.xml</settingsPath>
@ -304,7 +304,7 @@
<id>attach-sources</id> <id>attach-sources</id>
<phase>process-classes</phase> <phase>process-classes</phase>
<goals> <goals>
<goal>jar</goal> <goal>jar-no-fork</goal>
</goals> </goals>
<configuration> <configuration>
<archive> <archive>
@ -449,7 +449,7 @@
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>build-resources</artifactId> <artifactId>build-resources</artifactId>
<version>10.0.0-alpha0</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.puppycrawl.tools</groupId> <groupId>com.puppycrawl.tools</groupId>
@ -547,6 +547,11 @@
</archive> </archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
@ -1261,6 +1266,7 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<!-- already part of the release-jetty.sh script
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId> <artifactId>maven-gpg-plugin</artifactId>
@ -1275,6 +1281,7 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
-->
</plugins> </plugins>
</build> </build>
</profile> </profile>

View File

@ -167,7 +167,7 @@ if proceedyn "Are you sure you want to release using above? (y/N)" n; then
# This is equivalent to 'mvn release:perform' # This is equivalent to 'mvn release:perform'
if proceedyn "Build/Deploy from tag $TAG_NAME? (Y/n)" y; then if proceedyn "Build/Deploy from tag $TAG_NAME? (Y/n)" y; then
git checkout $TAG_NAME git checkout $TAG_NAME
mvn clean package source:jar javadoc:jar gpg:sign deploy \ mvn clean package gpg:sign javadoc:aggregate-jar deploy \
-Peclipse-release $DEPLOY_OPTS -Peclipse-release $DEPLOY_OPTS
reportMavenTestFailures reportMavenTestFailures
git checkout $GIT_BRANCH_ID git checkout $GIT_BRANCH_ID

View File

@ -76,7 +76,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.tests</groupId> <groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-cdi2-webapp</artifactId> <artifactId>test-weld-cdi-webapp</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<type>war</type> <type>war</type>
<scope>test</scope> <scope>test</scope>

View File

@ -127,6 +127,16 @@ public class DistributionTester
return start(Arrays.asList(args)); return start(Arrays.asList(args));
} }
public Path getJettyBase()
{
return config.jettyBase;
}
public Path getJettyHome()
{
return config.jettyHome;
}
/** /**
* Start the distribution with the arguments * Start the distribution with the arguments
* *

View File

@ -19,85 +19,64 @@
package org.eclipse.jetty.tests.distribution; package org.eclipse.jetty.tests.distribution;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@Disabled("Until issue is fixed at weld -> https://github.com/eclipse/jetty.project/issues/3803")
public class CDITests extends AbstractDistributionTest public class CDITests extends AbstractDistributionTest
{ {
/** // Tests from here use these parameters
* Tests a WAR file that is CDI complete as it includes the weld public static Stream<Arguments> tests()
* library in its WEB-INF/lib directory.
*/
@Test
public void testCDI2_IncludedInWebapp() throws Exception
{ {
String jettyVersion = System.getProperty("jettyVersion"); Consumer<DistributionTester> removeJettyWebXml = d ->
DistributionTester distribution = DistributionTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String[] args1 = {
"--create-startd",
"--approve-all-licenses",
"--add-to-start=http,deploy,annotations,jsp"
};
try (DistributionTester.Run run1 = distribution.start(args1))
{ {
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); try
assertEquals(0, run1.getExitValue());
File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-cdi2-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "demo");
distribution.installBaseResource("cdi/demo_context.xml", "webapps/demo.xml");
int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
{ {
assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); Path jettyWebXml = d.getJettyBase().resolve("webapps/demo/WEB-INF/jetty-web.xml");
Files.deleteIfExists(jettyWebXml);
startHttpClient();
ContentResponse response = client.GET("http://localhost:" + port + "/demo/greetings");
assertEquals(HttpStatus.OK_200, response.getStatus());
// Confirm Servlet based CDI
assertThat(response.getContentAsString(), containsString("Hello GreetingsServlet"));
// Confirm Listener based CDI (this has been a problem in the past, keep this for regression testing!)
assertThat(response.getHeaders().get("Server"), containsString("CDI-Demo-org.eclipse.jetty.test"));
run2.stop();
assertTrue(run2.awaitFor(5, TimeUnit.SECONDS));
} }
} catch(IOException e)
{
throw new RuntimeException(e);
}
};
return Stream.of(
// -- Weld --
Arguments.of("weld", "cdi2", null),
Arguments.of("weld", "cdi-spi", null), // Weld >= 3.1.2
Arguments.of("weld", "decorate", null), // Weld >= 3.1.2
// TODO Arguments.of("weld", "cdi-decorate", null), // Weld >= 3.1.3
// -- Apache OpenWebBeans --
Arguments.of("owb", "cdi-spi", removeJettyWebXml),
Arguments.of("owb", "cdi2", null)
// Arguments.of("owb", "decorate", null), // Not supported
// Arguments.of("owb", "cdi-decorate", null) // Not supported
);
} }
/** /**
* Tests a WAR file that is expects CDI to be configured by the server. * Tests a WAR file that includes the CDI
* * library in its WEB-INF/lib directory.
* <p>
* This means the WAR has the weld libs in its
* WEB-INF/lib directory.
* </p>
*
* <p>
* The expectation is that a context xml deployable file is not
* required when using this `cdi2` module, and the appropriate
* server side libs are made available to allow weld to function.
* (the required server side javax.el support comes from the cdi2 module)
* </p>
*/ */
@Test @ParameterizedTest
public void testCDI2_ConfiguredByServer() throws Exception @MethodSource("tests")
public void testCDIIncludedInWebapp(String implementation, String integration, Consumer<DistributionTester> configure) throws Exception
{ {
String jettyVersion = System.getProperty("jettyVersion"); String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance() DistributionTester distribution = DistributionTester.Builder.newInstance()
@ -108,18 +87,17 @@ public class CDITests extends AbstractDistributionTest
String[] args1 = { String[] args1 = {
"--create-startd", "--create-startd",
"--approve-all-licenses", "--approve-all-licenses",
// standard entries "--add-to-start=http,deploy,annotations,jsp,"+integration
"--add-to-start=http,deploy,annotations",
// cdi2 specific entry (should transitively pull in what it needs, the user should not be expected to know the transitive entries)
"--add-to-start=cdi2"
}; };
try (DistributionTester.Run run1 = distribution.start(args1)) try (DistributionTester.Run run1 = distribution.start(args1))
{ {
assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue()); assertEquals(0, run1.getExitValue());
File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-cdi2-webapp:war:" + jettyVersion); File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-" + implementation + "-cdi-webapp:war:" + jettyVersion);
distribution.installWarFile(war, "demo"); distribution.installWarFile(war, "demo");
if (configure != null)
configure.accept(distribution);
int port = distribution.freePort(); int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))

View File

@ -41,6 +41,8 @@
<module>test-http2-webapp</module> <module>test-http2-webapp</module>
<module>test-simple-webapp</module> <module>test-simple-webapp</module>
<module>test-felix-webapp</module> <module>test-felix-webapp</module>
<module>test-cdi2-webapp</module> <module>test-cdi-common-webapp</module>
<module>test-weld-cdi-webapp</module>
<module>test-owb-cdi-webapp</module>
</modules> </modules>
</project> </project>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-cdi-common-webapp</artifactId>
<name>Test :: CDI :: Common Demo Webapp</name>
<packaging>war</packaging>
<properties>
<bundle-symbolic-name>${project.groupId}.cdi.common</bundle-symbolic-name>
</properties>
<dependencies>
<!-- provided by container -->
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -37,6 +37,8 @@ public class GreetingsServlet extends HttpServlet
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{ {
resp.setContentType("text/plain"); resp.setContentType("text/plain");
resp.getWriter().println("[" + greetings.getGreeting() + "]"); resp.getWriter().print(greetings.getGreeting());
resp.getWriter().print(" from ");
resp.getWriter().println(getServletContext().getAttribute("ServerID"));
} }
} }

View File

@ -21,8 +21,11 @@ package org.eclipse.jetty.test;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Set; import java.util.Set;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject; import javax.inject.Inject;
import javax.servlet.annotation.WebServlet; import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
@ -43,10 +46,12 @@ public class InfoServlet extends HttpServlet
PrintWriter out = resp.getWriter(); PrintWriter out = resp.getWriter();
out.println("Bean Manager: " + beanManager); out.println("Bean Manager: " + beanManager);
Set<Bean<?>> beans = beanManager.getBeans(""); Set<Bean<?>> beans = beanManager.getBeans(Object.class, new AnnotationLiteral<Any>() {});
for (Bean<?> bean : beans) for (Bean<?> bean : beans)
{ {
out.println(" " + bean); out.printf("%16s => %s%n", bean.getName(), bean);
for (InjectionPoint ij : bean.getInjectionPoints())
out.printf("%16s -> %s%n", "", ij);
} }
} }
} }

View File

@ -32,6 +32,8 @@ public class MyContextListener implements ServletContextListener
@Override @Override
public void contextInitialized(ServletContextEvent sce) public void contextInitialized(ServletContextEvent sce)
{ {
if (serverId == null)
throw new IllegalStateException("CDI did not inject!");
sce.getServletContext().setAttribute("ServerID", serverId.get()); sce.getServletContext().setAttribute("ServerID", serverId.get());
} }

View File

@ -0,0 +1,7 @@
<H1>OWB CDI Test Webapp</H1>
<H2>CDI Info</H2>
<iframe src="info" width="100%" height="60%"></iframe>
<H2>Greetings test</H2>
<iframe src="greetings" width="100%"></iframe>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-webapps-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-owb-cdi-webapp</artifactId>
<name>Test :: CDI On Jetty :: OWB Demo Webapp</name>
<packaging>war</packaging>
<properties>
<bundle-symbolic-name>${project.groupId}.cdi.owb</bundle-symbolic-name>
</properties>
<build>
<finalName>weld-owb-demo</finalName>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-cdi-common-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<!-- included in webapp -->
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-annotation_1.3_spec</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-interceptor_1.2_spec</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-web</artifactId>
<!-- TODO remove OwbServletContainerInitializer when updated to a version with https://issues.apache.org/jira/browse/OWB-1296 -->
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-jetty9</artifactId>
<version>2.0.11</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,55 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.cdi.owb;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import org.apache.webbeans.servlet.WebBeansConfigurationListener;
/**
* @deprecated This class will not be required once https://issues.apache.org/jira/browse/OWB-1296 is available
*/
@Deprecated
public class OwbServletContainerInitializer implements ServletContainerInitializer
{
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
{
Listener listener = new Listener();
listener.preInitialize(new ServletContextEvent(ctx));
ctx.addListener(listener);
}
public static class Listener extends WebBeansConfigurationListener
{
void preInitialize(ServletContextEvent event)
{
super.contextInitialized(event);
}
@Override
public void contextInitialized(ServletContextEvent event)
{
}
}
}

View File

@ -0,0 +1 @@
org.eclipse.jetty.cdi.owb.OwbServletContainerInitializer

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="webAppCtx" class="org.eclipse.jetty.webapp.WebAppContext">
<New id="BeanManager" class="org.eclipse.jetty.plus.jndi.Resource">
<Arg>
<Ref refid="webAppCtx"/>
</Arg>
<Arg>BeanManager</Arg>
<Arg>
<New class="javax.naming.Reference">
<Arg>javax.enterprise.inject.spi.BeanManager</Arg>
<Arg>org.apache.webbeans.container.ManagerObjectFactory</Arg>
<Arg/>
</New>
</Arg>
</New>
</Configure>

View File

@ -0,0 +1,15 @@
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="wac" class="org.eclipse.jetty.webapp.WebAppContext">
<!-- This file is only needed for cdi2 integration and should be removed if using the cdi module -->
<Get id="wal" name="classLoader"/>
<Get id="objf" name="objectFactory">
<Call name="addDecorator">
<Arg>
<New class="org.apache.webbeans.web.jetty9.JettyDecorator">
<Arg><Ref refid="wal"/></Arg>
</New>
</Arg>
</Call>
</Get>
</Configure>

View File

@ -3,11 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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-app_3_1.xsd" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"> version="3.1">
<display-name>CDI Integration Test WebApp</display-name> <display-name>OWB CDI Integration Test WebApp</display-name>
<listener> <!-- OWB Listener is added by OwbServletContainerInitializer -->
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
<resource-env-ref> <resource-env-ref>
<description>Object factory for the CDI Bean Manager</description> <description>Object factory for the CDI Bean Manager</description>

View File

@ -19,8 +19,6 @@
package com.acme.test; package com.acme.test;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection; import java.util.Collection;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -63,17 +61,13 @@ public class MultiPartTest extends HttpServlet
out.println("<p>"); out.println("<p>");
Collection<Part> parts = request.getParts(); Collection<Part> parts = request.getParts();
out.println("<b>Parts:</b>&nbsp;" + parts.size()); out.println("<b>Parts:</b>&nbsp;" + parts.size() + "<br>");
for (Part p : parts) for (Part p : parts)
{ {
out.println("<h3>" + p.getName() + "</h3>"); out.println("<br><b>PartName:</b>&nbsp;" + sanitizeXmlString(p.getName()));
out.println("<b>Size:</b>&nbsp;" + p.getSize()); out.println("<br><b>Size:</b>&nbsp;" + p.getSize());
if (p.getContentType() == null || p.getContentType().startsWith("text/plain")) String contentType = p.getContentType();
{ out.println("<br><b>ContentType:</b>&nbsp;" + contentType);
out.println("<p>");
copy(p.getInputStream(), out);
out.println("</p>");
}
} }
out.println("</body>"); out.println("</body>");
out.println("</html>"); out.println("</html>");
@ -109,20 +103,67 @@ public class MultiPartTest extends HttpServlet
} }
} }
// TODO remove inline once 9.4.19 is released with a fix for #3726 public static String sanitizeXmlString(String html)
public static void copy(InputStream in,
OutputStream out)
throws IOException
{ {
int bufferSize = 8192; if (html == null)
byte[] buffer = new byte[bufferSize]; return null;
while (true) int i = 0;
// Are there any characters that need sanitizing?
loop:
for (; i < html.length(); i++)
{ {
int len = in.read(buffer, 0, bufferSize); char c = html.charAt(i);
if (len < 0) switch (c)
break; {
out.write(buffer, 0, len); case '&':
case '<':
case '>':
case '\'':
case '"':
break loop;
default:
if (Character.isISOControl(c) && !Character.isWhitespace(c))
break loop;
}
} }
// No characters need sanitizing, so return original string
if (i == html.length())
return html;
// Create builder with OK content so far
StringBuilder out = new StringBuilder(html.length() * 4 / 3);
out.append(html, 0, i);
// sanitize remaining content
for (; i < html.length(); i++)
{
char c = html.charAt(i);
switch (c)
{
case '&':
out.append("&amp;");
break;
case '<':
out.append("&lt;");
break;
case '>':
out.append("&gt;");
break;
case '\'':
out.append("&apos;");
break;
case '"':
out.append("&quot;");
break;
default:
if (Character.isISOControl(c) && !Character.isWhitespace(c))
out.append('?');
else
out.append(c);
}
}
return out.toString();
} }
} }

View File

@ -7,30 +7,32 @@
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>test-cdi2-webapp</artifactId> <artifactId>test-weld-cdi-webapp</artifactId>
<name>Test :: CDI2 On Jetty :: Included in WebApp</name> <name>Test :: CDI On Jetty :: Weld Demo Webapp</name>
<packaging>war</packaging> <packaging>war</packaging>
<properties> <properties>
<bundle-symbolic-name>${project.groupId}.cdi2.webapp</bundle-symbolic-name> <bundle-symbolic-name>${project.groupId}.cdi.weld</bundle-symbolic-name>
</properties> </properties>
<build> <build>
<finalName>cdi2-demo</finalName> <finalName>weld-cdi-demo</finalName>
</build> </build>
<dependencies> <dependencies>
<!-- provided by container -->
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>org.eclipse.jetty.tests</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>test-cdi-common-webapp</artifactId>
<version>3.1.0</version> <version>${project.version}</version>
<scope>provided</scope> <type>war</type>
<scope>runtime</scope>
</dependency> </dependency>
<!-- included in webapp --> <!-- included in webapp -->
<dependency> <dependency>
<groupId>org.jboss.weld.servlet</groupId> <groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet</artifactId> <artifactId>weld-servlet-core</artifactId>
<version>${weld.version}</version> <version>${weld.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<web-app 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-app_3_1.xsd"
version="3.1">
<display-name>Weld CDI Integration Test WebApp</display-name>
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
<resource-env-ref>
<description>Object factory for the CDI Bean Manager</description>
<resource-env-ref-name>BeanManager</resource-env-ref-name>
<resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
</web-app>