diff --git a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiDecoratingListener.java b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiDecoratingListener.java index 674591b8d80e..dbdafb889275 100644 --- a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiDecoratingListener.java +++ b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiDecoratingListener.java @@ -24,7 +24,7 @@ /** * A DecoratingListener that listens for "org.eclipse.jetty.cdi.decorator" */ -class CdiDecoratingListener extends DecoratingListener +public class CdiDecoratingListener extends DecoratingListener { public static final String MODE = "CdiDecoratingListener"; public static final String ATTRIBUTE = "org.eclipse.jetty.cdi.decorator"; @@ -32,5 +32,6 @@ class CdiDecoratingListener extends DecoratingListener public CdiDecoratingListener(ServletContextHandler contextHandler) { super(contextHandler, ATTRIBUTE); + contextHandler.setAttribute(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, MODE); } } diff --git a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiServletContainerInitializer.java b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiServletContainerInitializer.java index ef99c3aa3587..3a72a740525e 100644 --- a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiServletContainerInitializer.java +++ b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiServletContainerInitializer.java @@ -86,8 +86,6 @@ public void onStartup(Set> c, ServletContext ctx) default: throw new IllegalStateException(mode); } - - context.setAttribute(CDI_INTEGRATION_ATTRIBUTE, mode); LOG.info(mode + " enabled in " + ctx); } catch (UnsupportedOperationException | ClassNotFoundException e) diff --git a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiSpiDecorator.java b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiSpiDecorator.java index 4198a49407d8..ad6983e38602 100644 --- a/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiSpiDecorator.java +++ b/jetty-cdi/src/main/java/org/eclipse/jetty/cdi/CdiSpiDecorator.java @@ -65,7 +65,10 @@ public class CdiSpiDecorator implements Decorator public CdiSpiDecorator(ServletContextHandler context) throws UnsupportedOperationException { _context = context; + context.setAttribute(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, MODE); ClassLoader classLoader = _context.getClassLoader(); + if (classLoader == null) + classLoader = this.getClass().getClassLoader(); try { @@ -92,6 +95,35 @@ public CdiSpiDecorator(ServletContextHandler context) throws UnsupportedOperatio } } + /** + * Test if a class can be decorated. + * @implNote The default implementation calls {@link #isKnownUndecoratable(String) } + * on the class and all it's super classes. + * @param clazz The class to check + * @return True if the class and all it's super classes can be decorated + */ + protected boolean isDecoratable(Class clazz) + { + if (Object.class == clazz) + return true; + if (isKnownUndecoratable(clazz.getName())) + return false; + return isDecoratable(clazz.getSuperclass()); + } + + /** + * Test if a specific class name is known to not be decoratable. + * @implNote default implementation checks for well known classes that are used to + * setup CDI itself, and thus cannot themselves be decorated. + * @see #isDecoratable(Class) + * @param className The name of the class to check + * @return True if the class is known not to be decoratable + */ + protected boolean isKnownUndecoratable(String className) + { + return "org.jboss.weld.environment.servlet.Listener".equals(className); + } + /** * Decorate an object. *

The signature of this method must match what is introspected for by the @@ -108,7 +140,8 @@ public T decorate(T o) if (LOG.isDebugEnabled()) LOG.debug("decorate {} in {}", o, _context); - _decorated.put(o, new Decorated(o)); + if (isDecoratable(o.getClass())) + _decorated.put(o, new Decorated(o)); } catch (Throwable th) { diff --git a/jetty-documentation/src/main/asciidoc/development/frameworks/cdi.adoc b/jetty-documentation/src/main/asciidoc/development/frameworks/cdi.adoc new file mode 100644 index 000000000000..b586db93e5a8 --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/development/frameworks/cdi.adoc @@ -0,0 +1,133 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ======================================================================== +// 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. +// ======================================================================== +// + +[[framework-cdi]] +=== CDI + +Contexts and Dependency Injection for Java EE (http://www.cdi-spec.org/[CDI]) +is a standard implemented by frameworks such as +http://seamframework.org/Weld[Weld] and +https://openwebbeans.apache.org/[Apache OpenWebBeans]. This is a common way to +assemble and configure webapplications by a process often referred to as +'decoration'. + +Jetty integration of CDI frameworks allows CDI to be used to inject the +Filters, Servlets and Listeners created within a Servlet Context. There are +two approaches to integration: + + * CDI implementation can integrate with Jetty. This requires the CDI + implementation to have Jetty specific code. Since Jetty-9.4.20 a loosely + bound mechanism has been available for CDI implementations to extends + the Jetty `DecoratedObjectFactory` without hard API dependencies. + Prior to that, CDI implementations directly called jetty APIs that need + to be explicitly exposed to the webapp. + + * Alternately, Jetty can integrate with CDI implementations by using standard + CDI SPIs. + +==== Jetty CDI Modules + +The Jetty distribution come with several CDI modules. These modules do not provide CDI, +but instead enable one of more integration mechanisms. + +===== Jetty `cdi` Module +The `cdi` module supports either two modes of CDI integration which can be +selected either by the "org.eclipse.jetty.cdi" context init parameter or +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". +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi +------------------------- + + +===== Jetty `cdi-decorate` Module +This module depends on the `cdi` module and sets the default mode to `CdiDecoratingListener`. +This is the preferred mode for Weld integration. +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate +------------------------- + +===== Jetty `cdi-spi` Module +This module depends on the `cdi` module and sets the default mode to `CdiSpiDecorator`. +This is the preferred mode for Open Web Beans integration. +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi-spi +------------------------- + +===== Jetty `cdi2` Module +This module supports the *deprecated* technique of exposing private Jetty decorate APIs to the CDI +implementation in the webapp. + +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi2 +------------------------- + +This module is equivalent to directly modifying the class path configuration with a `jetty-web.xml` like: + +[source.XML, xml] +------------------------------------------------------------- + + + + + -org.eclipse.jetty.util.Decorator + + + -org.eclipse.jetty.util.DecoratedObjectFactory + + + -org.eclipse.jetty.server.handler.ContextHandler. + + + -org.eclipse.jetty.server.handler.ContextHandler + + + -org.eclipse.jetty.servlet.ServletContextHandler + + +------------------------------------------------------------- + +____ +[TIP] +The `cdi2` module or directly modifying the web application classpath will not work for Jetty 10.0.0 and later. +It should only be used for versions prior to Jetty 9.4.20 and/or Weld 3.1.2.Final +____ + + +[[cdi-embedded]] +==== Embedded Jetty with CDI +When starting embedded Jetty programmatically from the `main` method, to use CDI it may be +necessary: + * enable a Jetty CDI integration mode + * and/or enable a CDI frame integration. + +However, depending on the exact configuration of the embedded server, either or both steps +may not be required as Servlet Container Initializers may be discovered. + +The details for embedding CDI will be explain in the Embedded Jetty with Weld section, but +can be adapted to other CDI frameworks. diff --git a/jetty-documentation/src/main/asciidoc/development/frameworks/chapter.adoc b/jetty-documentation/src/main/asciidoc/development/frameworks/chapter.adoc index 0f21a388b3ff..65565050b5f0 100644 --- a/jetty-documentation/src/main/asciidoc/development/frameworks/chapter.adoc +++ b/jetty-documentation/src/main/asciidoc/development/frameworks/chapter.adoc @@ -19,7 +19,8 @@ [[frameworks]] == Frameworks +include::cdi.adoc[] +include::weld.adoc[] include::spring-usage.adoc[] include::osgi.adoc[] -include::weld.adoc[] include::metro.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc b/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc index bd937ab78746..c76e3dff3e67 100644 --- a/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc +++ b/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc @@ -26,124 +26,70 @@ It is easily configured with Jetty 9. ==== Weld Setup The easiest way to configure weld is within the Jetty distribution itself. -This can be accomplished either by enabling one of the startup link:#startup-modules[modules] for Weld, or by creating/editing a `jetty-web.xml` descriptor (see also https://www.eclipse.org/jetty/documentation/current/jetty-web-xml-config.html[Jetty XML Reference]). +This can be accomplished either by enabling one of the +startup link:#startup-modules[modules] described in link:#framework-cdi[CDI Framework]: -===== Jetty Weld Modules + * the `cdi-decorate` module is the preferred Weld integration. The activation +of this module by Weld can be confirmed by the following Weld log: +[source, screen, subs="{sub-order}"] +.... +INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters. +.... + * the `cdi-spi` module works with Weld, but may restrict some non standard features.The activation +of this module by Weld can be confirmed by the following Weld log: +[source, screen, subs="{sub-order}"] +.... +INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. +.... + * the deprecated `cdi2` module works with Weld prior to 3.1.2.Final. The activation +of this module by Weld can be confirmed by the following Weld log: +[source, screen, subs="{sub-order}"] +.... +INFO: WELD-ENV-001201: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported. +.... -====== Jetty `cdi-decorate` Module -Since Jetty 9.4.20 and Weld 3.1.2.Final, the Weld/Jetty integration uses the jetty `cdi-decorate` module. -To activate this module in Jetty the `cdi-decorate` module needs activated on the command line, which can be done as follows: +To activate the preferred `cdi-decorate` module use: ------------------------- cd $JETTY_BASE java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate ------------------------- -====== Jetty `cdi2` Module - -For versions prior to Jetty 9.4.20 and Weld 3.1.2, the Weld/Jetty integration required some internal Jetty APIs to be made visible to the web application. -This can be done using the deprecated `cdi2` module either by activating the `cdi2` module: - -------------------------- -cd $JETTY_BASE -java -jar $JETTY_HOME/start.jar --add-to-start=cdi2 -------------------------- - - -===== Jetty-Web XML - - -[source.XML, xml] -------------------------------------------------------------- - - - - - -org.eclipse.jetty.util.Decorator - - - -org.eclipse.jetty.util.DecoratedObjectFactory - - - -org.eclipse.jetty.server.handler.ContextHandler. - - - -org.eclipse.jetty.server.handler.ContextHandler - - - -org.eclipse.jetty.servlet.ServletContextHandler - - -------------------------------------------------------------- ____ [TIP] -Directly modifying the web application classpath via `jetty-web.xml` will not work for Jetty 10.0.0 and later. +For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet and jetty-cdi artifacts _plugin_ dependencies (__not__ a webapp dependency). ____ -===== Jetty `cdi-spi` Module -Since Jetty 9.4.20 the Jetty `cdi-spi` module has been available that integrates any compliant CDI implementation by directly calling the CDI SPI. -Since Weld support specific Jetty integration, it is not recommended to use this module with Weld. +[[weld-embedded]] +==== Embedded Jetty -When you start up your Jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration): +When starting embedded Jetty programmatically from the `main` method it is necessary to: -[source, screen, subs="{sub-order}"] -.... -2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms -2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-{VERSION} -2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1 -2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty -Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup -INFO: WELD-ENV-001008: Initialize Weld using ServletContainerInitializer -Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup -INFO: WELD-000900: 2.2.9 (Final) -Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.deployment.WebAppBeanArchiveScanner scan -WARN: WELD-ENV-001004: Found both WEB-INF/beans.xml and WEB-INF/classes/META-INF/beans.xml. It's not portable to use both locations at the same time. Weld is going to use file:/tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/WEB-INF/beans.xml. -Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup startContainer -INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously. -Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry -WARN: WELD-001700: Interceptor annotation class javax.ejb.PostActivate not found, interception based on it is not enabled -Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry -WARN: WELD-001700: Interceptor annotation class javax.ejb.PrePassivate not found, interception based on it is not enabled -Jun 18, 2015 12:13:56 PM org.jboss.weld.bootstrap.MissingDependenciesRegistry handleResourceLoadingException -Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.WeldServletLifecycle findContainer -INFO: WELD-ENV-001002: Container detection skipped - custom container class loaded: org.jboss.weld.environment.jetty.JettyContainer. -Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.jetty.JettyContainer initialize -INFO: WELD-ENV-001200: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners should work on Jetty 9.1.1 and newer. -Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.Listener contextInitialized -INFO: WELD-ENV-001006: org.jboss.weld.environment.servlet.EnhancedListener used for ServletContext notifications -Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.EnhancedListener contextInitialized -INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for ServletRequest and HttpSession notifications -2015-06-18 12:13:56.535:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@6574b225{/cdi-webapp,file:///tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/,AVAILABLE}{/cdi-webapp.war} -2015-06-18 12:13:56.554:INFO:oejs.ServerConnector:main: Started ServerConnector@7112f81c{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} -2015-06-18 12:13:56.587:INFO:oejus.SslContextFactory:main: x509={jetty.eclipse.org=jetty} wild={} alias=null for SslContextFactory@3214ee6(file:///tmp/cdi-demo/etc/keystore,file:///tmp/cdi-demo/etc/keystore) -2015-06-18 12:13:56.821:INFO:oejs.ServerConnector:main: Started ServerConnector@69176a9b{SSL,[ssl, http/1.1]}{0.0.0.0:8443} -2015-06-18 12:13:56.822:INFO:oejs.Server:main: Started @2383ms + * enable a jetty CDI integration mode by registering a `Listener` or `ServletContainerInitializer` -.... + * enable Weld by registering either its `Listener` or `ServletContainerInitializer` -For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency). -[[weld-embedded]] -==== Embedded Jetty +===== Using a `ServletContextHandler` -When starting embedded Jetty programmatically from the `main` method it is necessary to register Weld's listener: +Embedded usage often uses a `ServletContextHandler` which is the base class of `WebappContext` and +lacks the features of "web.xml" configuration and must be configured directly. The examples in this section +based on a server and context set up as follows: [source.JAVA, java] ---- public class Main { - public static void main(String[] args) throws Exception { Server jetty = new Server(8080); - WebAppContext context = new WebAppContext(); + ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); - context.setResourceBase("src/main/resources"); - jetty.setHandler(context); + server.setHandler(context); + context.addServlet(HelloWorldServlet.class, "/*"); - context.addEventListener(new DecoratingListener()); # <1> - context.addEventListener(new Listener()); # <2> + /* CDI enabling goes here. See options below */ jetty.start(); jetty.join(); @@ -159,7 +105,107 @@ public class Main { } } } +---- + +====== Initialize Weld with `ServletContainerInitializers` +The best way to initialize both Jetty Weld integration is to use their respective `ServletContainerInitializers`: +[source.JAVA, java] +---- + import org.eclipse.jetty.cdi.CdiServletContainerInitializer; + import org.eclipse.jetty.cdi.CdiDecoratingListener; + import org.jboss.weld.environment.servlet.EnhancedListener; + // ... + context.setInitParameter( + CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, + CdiDecoratingListener.MODE); + context.addBean(new ServletContextHandler.Initializer(context, + new EnhancedListener())); + context.addBean(new ServletContextHandler.Initializer(context, + new CdiServletContainerInitializer())); +---- +This code uses the `ServletContextHandler.Initializer` utility class added in Jetty-9.4.30. Prior +to that the same effect can be achieved with a custom implementation of +`ServletContextHandler.ServletContainerInitializerCaller`. + +====== Initialize Weld with Listeners +Jetty Weld integration can also be initialized by directly adding the listeners +required: +[source.JAVA, java] +---- + import org.eclipse.jetty.cdi.CdiDecoratingListener; + import org.jboss.weld.environment.servlet.Listener; + // ... + context.addEventListener(new CdiDecoratingListener(context)); + context.addEventListener(new Listener()); +---- + +====== Other Weld initializations +When running embedded without a context classloader, it is not actually required +to initialize Jetty at all. If just Weld is initialized then it will disover the +Jetty APIs and use the deprecated integration: +[source.JAVA, java] +---- + import org.jboss.weld.environment.servlet.Listener; + // ... + context.addEventListener(new Listener()); +---- +However, this results in only a partially functional integration and the +following warning: +---- +INFO: WELD-ENV-001201: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported. +---- + +Jetty can also be initialized by adding the `org.eclipse.jetty.webapp.DecoratingListener` listener instead +of the `org.eclipse.jetty.cdi.CdiDecoratingListener`. However, this introduces a needless +dependency on `jetty-webapp` and is not the preferred method. + +====== Initialize Weld with `WebappContext` +Some embedded usage still makes use of the `WebappContext` class for the convention-over-configuration +benefits. The methods described for `ServletContextHandler` will work for `WebappContext`: -<1> Jetty's `org.eclipse.jetty.webapp.DecoratingListener` registered programmatically (since Jetty-9.4.20) -<2> Weld's `org.jboss.weld.environment.servlet.Listener` registered programmatically +[source.JAVA, java] +---- + import org.eclipse.jetty.cdi.CdiServletContainerInitializer; + import org.eclipse.jetty.cdi.CdiDecoratingListener; + import org.jboss.weld.environment.servlet.EnhancedListener; + // ... + Server server = new Server(8080); + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/"); + server.setHandler(webapp); + + webapp.setInitParameter( + CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, + CdiDecoratingListener.MODE); + webapp.addBean(new ServletContextHandler.Initializer(webapp, + new CdiServletContainerInitializer())); + webapp.addBean(new ServletContextHandler.Initializer(webapp, + new EnhancedListener())); + + // ... +---- + +Alternately the webapp can be configured to discover the SCIs: + +[source.JAVA, java] ---- + Server server = new Server(8080); + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/"); + server.setHandler(webapp); + + webapp.setInitParameter( + CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, + CdiDecoratingListener.MODE); + + // Need the AnnotationConfiguration to detect SCIs + Configuration.ClassList.setServerDefault(server).addBefore( + JettyWebXmlConfiguration.class.getName(), + AnnotationConfiguration.class.getName()); + + // Need to expose our SCI class. + webapp.getServerClasspathPattern().add("-" + CdiServletContainerInitializer.class.getName()); + webapp.getSystemClasspathPattern().add(CdiServletContainerInitializer.class.getName()); + + // ... +---- \ No newline at end of file diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java index 75c858eebba3..91acd211eba5 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/annotation/ContainerInitializer.java @@ -124,9 +124,6 @@ public void callStartup(WebAppContext context) { Set> classes = new HashSet>(); - ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(context.getClassLoader()); - try { for (String s : _applicableTypeNames) @@ -147,7 +144,6 @@ public void callStartup(WebAppContext context) finally { context.getServletContext().setExtendedListenerTypes(false); - Thread.currentThread().setContextClassLoader(oldLoader); } } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 8d4ac4c6dd43..2793b5cc7712 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -34,6 +34,7 @@ import javax.servlet.FilterRegistration; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; +import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -68,6 +69,7 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -336,9 +338,15 @@ protected ServletHandler newServletHandler() @Override protected void startContext() throws Exception { - ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class); - if (sciBean != null) - sciBean.start(); + for (ServletContainerInitializerCaller sci : getBeans(ServletContainerInitializerCaller.class)) + { + if (sci.isStopped()) + { + sci.start(); + if (isAuto(sci)) + manage(sci); + } + } if (_servletHandler != null) { @@ -749,13 +757,13 @@ public static ServletContextHandler getServletContextHandler(ServletContext cont public static class JspPropertyGroup implements JspPropertyGroupDescriptor { - private List _urlPatterns = new ArrayList<>(); + private final List _urlPatterns = new ArrayList<>(); private String _elIgnored; private String _pageEncoding; private String _scriptingInvalid; private String _isXml; - private List _includePreludes = new ArrayList<>(); - private List _includeCodas = new ArrayList<>(); + private final List _includePreludes = new ArrayList<>(); + private final List _includeCodas = new ArrayList<>(); private String _deferredSyntaxAllowedAsLiteral; private String _trimDirectiveWhitespaces; private String _defaultContentType; @@ -1001,8 +1009,8 @@ public String toString() public static class JspConfig implements JspConfigDescriptor { - private List _taglibs = new ArrayList<>(); - private List _jspPropertyGroups = new ArrayList<>(); + private final List _taglibs = new ArrayList<>(); + private final List _jspPropertyGroups = new ArrayList<>(); public JspConfig() { @@ -1474,7 +1482,7 @@ public interface Decorator extends org.eclipse.jetty.util.Decorator */ private static class LegacyDecorator implements Decorator { - private org.eclipse.jetty.util.Decorator decorator; + private final org.eclipse.jetty.util.Decorator decorator; public LegacyDecorator(org.eclipse.jetty.util.Decorator decorator) { @@ -1493,4 +1501,43 @@ public void destroy(Object o) decorator.destroy(o); } } + + /** + * A utility class to hold a {@link ServletContainerInitializer} and implement the + * {@link ServletContainerInitializerCaller} interface so that the SCI is correctly + * started if an instance of this class is added as a bean to a {@link ServletContextHandler}. + */ + public static class Initializer extends AbstractLifeCycle implements ServletContainerInitializerCaller + { + private final ServletContextHandler _context; + private final ServletContainerInitializer _sci; + private final Set> _classes; + + public Initializer(ServletContextHandler context, ServletContainerInitializer sci, Set> classes) + { + _context = context; + _sci = sci; + _classes = classes; + } + + public Initializer(ServletContextHandler context, ServletContainerInitializer sci) + { + this(context, sci, Collections.emptySet()); + } + + @Override + protected void doStart() throws Exception + { + boolean oldExtended = _context.getServletContext().isExtendedListenerTypes(); + try + { + _context.getServletContext().setExtendedListenerTypes(true); + _sci.onStartup(_classes, _context.getServletContext()); + } + finally + { + _context.getServletContext().setExtendedListenerTypes(oldExtended); + } + } + } } diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java index fd5ef687a16f..ebdac3c9d836 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/CDITests.java @@ -61,7 +61,7 @@ public static Stream tests() 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 + Arguments.of("weld", "cdi-decorate", null), // Weld >= 3.1.3 // -- Apache OpenWebBeans -- Arguments.of("owb", "cdi-spi", null), diff --git a/tests/test-webapps/test-weld-cdi-webapp/pom.xml b/tests/test-webapps/test-weld-cdi-webapp/pom.xml index 00f6f2ab45df..2ee653ba8be6 100644 --- a/tests/test-webapps/test-weld-cdi-webapp/pom.xml +++ b/tests/test-webapps/test-weld-cdi-webapp/pom.xml @@ -26,7 +26,7 @@ test-cdi-common-webapp ${project.version} war - runtime + test @@ -35,5 +35,25 @@ weld-servlet-core ${weld.version} + + + + org.eclipse.jetty + jetty-servlet + ${project.version} + test + + + org.eclipse.jetty + jetty-cdi + ${project.version} + test + + + org.eclipse.jetty + jetty-annotations + ${project.version} + test + diff --git a/tests/test-webapps/test-weld-cdi-webapp/src/test/java/org/eclipse/jetty/cdi/weld/EmbeddedWeldTest.java b/tests/test-webapps/test-weld-cdi-webapp/src/test/java/org/eclipse/jetty/cdi/weld/EmbeddedWeldTest.java new file mode 100644 index 000000000000..31818b656fbb --- /dev/null +++ b/tests/test-webapps/test-weld-cdi-webapp/src/test/java/org/eclipse/jetty/cdi/weld/EmbeddedWeldTest.java @@ -0,0 +1,241 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// 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.weld; + +import java.io.IOException; +import java.util.EnumSet; +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.cdi.CdiServletContainerInitializer; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ListenerHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.test.GreetingsServlet; +import org.eclipse.jetty.test.MyContextListener; +import org.eclipse.jetty.test.ServerIDFilter; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.JettyWebXmlConfiguration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +public class EmbeddedWeldTest +{ + public static Server createServerWithServletContext(int mode) + { + Server server = new Server(); + server.addConnector(new LocalConnector(server)); + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + // Setup context + context.addServlet(GreetingsServlet.class, "/"); + context.addServlet(BeanServlet.class, "/beans"); + context.addFilter(ServerIDFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + // Setup Jetty weld integration + switch (mode) + { + case 0: // Do nothing, let weld work it out. + // Expect:INFO: WELD-ENV-001201: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported. + context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class)); + break; + + case 1: // Deprecated use of Decorating Listener + // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters. + context.addEventListener(new org.eclipse.jetty.webapp.DecoratingListener(context)); + context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class)); + break; + + case 2: // CDI Decorating Listener + // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters. + context.addEventListener(new org.eclipse.jetty.cdi.CdiDecoratingListener(context)); + context.addEventListener(new org.jboss.weld.environment.servlet.Listener()); + break; + + case 3: // CDI SPI + // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. + context.getObjectFactory().addDecorator(new org.eclipse.jetty.cdi.CdiSpiDecorator(context)); + context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class)); + break; + + case 4: // SCI invocation with no mode selected + // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. + context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer())); + context.addEventListener(new org.jboss.weld.environment.servlet.Listener()); + // context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class)); + break; + + case 5: // SCI invocation with mode selected + // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters + context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE); + context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer())); + context.getServletHandler().addListener(new ListenerHolder(org.jboss.weld.environment.servlet.Listener.class)); + break; + + case 6: // direct SCI invocation of jetty and Weld SCI + // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. + context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); + context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer())); + + // Can decorate MyContextListener in this setup + context.getServletHandler().addListener(new ListenerHolder(MyContextListener.class)); + break; + + case 7: // direct SCI invocation of jetty and Weld SCI with mode selected + // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters + context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE); + context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); + context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer())); + + // Can decorate MyContextListener in this setup + context.getServletHandler().addListener(new ListenerHolder(MyContextListener.class)); + break; + + case 8: // direct SCI invocation of jetty and Weld SCI with mode selected - check order independent + // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters + context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE); + context.addBean(new ServletContextHandler.Initializer(context, new org.eclipse.jetty.cdi.CdiServletContainerInitializer())); + context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); + + // Can decorate MyContextListener in this setup + context.getServletHandler().addListener(new ListenerHolder(MyContextListener.class)); + break; + } + + return server; + } + + @ParameterizedTest() + @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8}) + public void testServletContext(int mode) throws Exception + { + Server server = createServerWithServletContext(mode); + server.start(); + LocalConnector connector = server.getBean(LocalConnector.class); + String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("Hello GreetingsServlet")); + if (mode >= 6) + assertThat(response, containsString(" from CDI-Demo-org.eclipse.jetty.test")); + + response = connector.getResponse("GET /beans HTTP/1.0\r\n\r\n"); + assertThat(response, containsString("Beans from Weld BeanManager for ")); + + server.stop(); + } + + @Test + public void testWebappContext() throws Exception + { + Server server = new Server(8080); + server.addConnector(new LocalConnector(server)); + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/"); + webapp.setResourceBase("src/test/resources"); + server.setHandler(webapp); + + webapp.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE); + webapp.addBean(new ServletContextHandler.Initializer(webapp, new org.eclipse.jetty.cdi.CdiServletContainerInitializer())); + webapp.addBean(new ServletContextHandler.Initializer(webapp, new org.jboss.weld.environment.servlet.EnhancedListener())); + + // This is ugly but needed for maven for testing in a overlaid war pom + webapp.getServerClasspathPattern().add("-org.eclipse.jetty.test."); + webapp.getSystemClasspathPattern().add("org.eclipse.jetty.test."); + + webapp.addServlet(GreetingsServlet.class, "/"); + webapp.addFilter(ServerIDFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class)); + + server.start(); + + LocalConnector connector = server.getBean(LocalConnector.class); + String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n"); + System.err.println(response); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("Hello GreetingsServlet")); + assertThat(response, containsString(" from CDI-Demo-org.eclipse.jetty.test")); + server.stop(); + + } + + @Test + public void testWebappContextDiscovered() throws Exception + { + Server server = new Server(8080); + server.addConnector(new LocalConnector(server)); + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/"); + webapp.setResourceBase("src/test/resources"); + server.setHandler(webapp); + + // Need the AnnotationConfiguration to detect SCIs + Configuration.ClassList.setServerDefault(server).addBefore(JettyWebXmlConfiguration.class.getName(), + AnnotationConfiguration.class.getName()); + + // Need to expose our SCI. This is ugly could be made better in jetty-10 with a CdiConfiguration + webapp.getServerClasspathPattern().add("-" + CdiServletContainerInitializer.class.getName()); + webapp.getSystemClasspathPattern().add(CdiServletContainerInitializer.class.getName()); + + // This is ugly but needed for maven for testing in a overlaid war pom + webapp.getServerClasspathPattern().add("-org.eclipse.jetty.test."); + webapp.getSystemClasspathPattern().add("org.eclipse.jetty.test."); + + webapp.addServlet(GreetingsServlet.class, "/"); + webapp.addFilter(ServerIDFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class)); + + server.start(); + + LocalConnector connector = server.getBean(LocalConnector.class); + String response = connector.getResponse("GET / HTTP/1.0\r\n\r\n"); + System.err.println(response); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("Hello GreetingsServlet")); + assertThat(response, containsString(" from CDI-Demo-org.eclipse.jetty.test")); + server.stop(); + + } + + public static class BeanServlet extends HttpServlet + { + @Inject + BeanManager manager; + + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.setContentType("text/plain"); + resp.getWriter().append("Beans from " + manager); + } + } + +}