Skip to content

DecoratingListener raises a NullPointerException #5162

Closed
@oliviercailloux

Description

@oliviercailloux

Jetty version
9.4.31.v20200723 (Maven)

Java version
OpenJDK 11

OS type/version
Debian stable

Description
The sample code from the Weld embedded section in the Jetty Reference raises a NullPointerException.

Here is the stack trace.

Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:221)
	at org.eclipse.jetty.servlet.DecoratingListener.<init>(DecoratingListener.java:67)
	at org.eclipse.jetty.webapp.DecoratingListener.<init>(DecoratingListener.java:49)
	at org.eclipse.jetty.webapp.DecoratingListener.<init>(DecoratingListener.java:39)
	at org.eclipse.jetty.webapp.DecoratingListener.<init>(DecoratingListener.java:34)
	at io.github.oliviercailloux.jetty.App.main(App.java:64)

This could be a usage problem. It is unclear to me, reading the documentation, if I have to apply anything from the section “Weld Setup” (in the same page), when running in embedded mode (and if so, what), or if the Weld embedded section is to be applied to the exclusion of the other part. If I have to include the cdi-decorate module, how can I do this with Maven? In any case, the problem also happens with org.eclipse.jetty:jetty-cdi included in the project dependencies.

The Weld documentation gives almost the same instructions, but not exactly: one line of code, context.addEventListener(new Listener()); has been added. Which version is correct? And, similarly, I ignore whether I am supposed to apply section 18.3.2.4. Binding BeanManager to Jetty JNDI (or others) in supplement to 18.3.2.5. Embedded Jetty or if 18.3.2.5. Embedded Jetty is sufficient.

(I also want to use Jersey, which further complicates the matter, but I suppose that this issue is unrelated as the bug happens with a sample code that does not involve Jersey.)

Possibly related
#4064 #3804 #4121

Activity

self-assigned this
on Aug 17, 2020
gregw

gregw commented on Aug 17, 2020

@gregw
Contributor

Sorry for the confusion. We are always a bit confused with CDI as well.... the problem being that there is no standard nor agreement between the CDI implementations as to how they should integrate with a servlet container, further complicated by the historic usage of jetty APIs for integration that were not meant to be public and have now changed.

We've tried to push the implementations to a single obvious way to integrate, but failed in that approach. Hence the hodge podge of different mechanisms that are very version dependent.

I'll check our examples and see if we can update our documentation and/or fix anything for the weld integration.

Jersey should not be anywhere as near as complex, as it does not need to plug into the containers decoration mechanism.

gregw

gregw commented on Aug 17, 2020

@gregw
Contributor

So there is definitely at least something wrong with both sets of embedded documentation as DecoratingListener will not work with a default constructor unless called from the scope of a context listener or similar. Thus in the embedded examples, at the very least the constructor that takes a context needs to be used. It may also be the case that CdiDecoratingListener should be used.

This may take me a little while to page all this back into my brain and work out exactly the best way to fix the documentation, but feel free to try either/both of the above suggestions. Standby ...

oliviercailloux

oliviercailloux commented on Aug 18, 2020

@oliviercailloux
Author

Thanks for investigating!

Jersey should not be anywhere as near as complex, as it does not need to plug into the containers decoration mechanism.

I do not understand this sentence. I thought I’d better first make sure Jetty and Weld work together, before trying to tackle the (or what I thought to be the) additional difficulty of making Jersey also work with Jetty and Weld (meaning, make my Jersey-operated Jax-RS servlet handle dependency injection).

Does your sentence suggest that it is in fact easier to set up Jersey for working with Weld, and that I’d better not insist in making Weld behave well with Jetty (or conversely)?

Specifically, one of the things I am interested in is to have the container react to @Transactional annotations (using JPA and a JTA transactional manager).

joakime

joakime commented on Aug 18, 2020

@joakime
Contributor

"have the container react to @transactional"

Can you explain this in a bit more detail?
Which container?
Jetty (which isn't involved in DataSource behaviors)?
Jersey (which is also a container)?
Weld (which is also a container)?
Or something else?

oliviercailloux

oliviercailloux commented on Aug 19, 2020

@oliviercailloux
Author

I should have written: “one of the things I am interested in is to have a container react to @Transactional annotations (using JPA and a JTA transactional manager).” (I also would like to use the other CDI basic stuff such as @Inject, of course.)

AFAIU, this is a job for CDI together with some TransactionManager (which registers CDI interceptors to act upon @Transactional). See this post for example. Hence, my desire to make Jetty interact with Weld properly. Tell me if I am mistaken.

gregw

gregw commented on Aug 19, 2020

@gregw
Contributor

@oliviercailloux I've now found a few niggling problems with the various ways that jetty can integrate with Weld when used in an embedded style, specially following our examples. So I will be doing a PR to fix them up.... mostly the issue is that all the goodness we've written for CDI integration is within a ServletContainerInitializer that may not be triggered by embedded usage.

For now you should be able to write just:

        context.addEventListener(new org.jboss.weld.environment.servlet.Listener());

which should result in the message:

INFO: WELD-ENV-001201: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported.

which shows that Welds built in integration is working for Filters and Servlets. If you want Listeners you need to use:

        context.addEventListener(new org.eclipse.jetty.webapp.DecoratingListener(context));
        context.addEventListener(new org.jboss.weld.environment.servlet.Listener());

which when run will produce the message:

WARN: WELD-ENV-001211: Deprecated Jetty DecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters.

that at least confirms the integration is done.

Actually you can do a little hack to avoid the deprecated warning, by instead invoking the SCI directly

        new CdiServletContainerInitializer().onStartup(Collections.emptySet(), context.getServletContext());        
        context.addEventListener(new org.jboss.weld.environment.servlet.Listener());

which then reports:

INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters.

We will clean this up in the next release. @janbartel what is the status of calling SCIs from embedded server usage? Should that have been working anyway? Would it just be a mater of making sure jetty-cdi is on the classpath? That doesn't appear to work for me?

gregw

gregw commented on Aug 19, 2020

@gregw
Contributor

Looking for better ways to start CDI embedded.... ultimately it is best to just use the ServletContainerInitializers.

If you are not using a real webapp, ie using ServletContextHandler instead of a WebppContext, then you can achieve this by clearing a little class like:

    private static class SciStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
    {
        private final ServletContext _context;
        private final List<ServletContainerInitializer> _initializers = new ArrayList<>();

        private SciStarter(ServletContext context)
        {
            _context = context;
        }

        public void add(ServletContainerInitializer initializer)
        {
            _initializers.add(initializer);
        }

        @Override
        protected void doStart() throws Exception
        {
            for (ServletContainerInitializer sci : _initializers)
                sci.onStartup(Collections.emptySet(), _context);
        }
    }

Your startup code then can look like:

        Server server = new Server(8080);
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        server.setHandler(context);

        SciStarter sciStarter = new SciStarter(context.getServletContext());
        context.addBean(sciStarter);
        context.setInitParameter(org.eclipse.jetty.cdi.CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, org.eclipse.jetty.cdi.CdiDecoratingListener.MODE);
        sciStarter.add(new org.eclipse.jetty.cdi.CdiServletContainerInitializer());
        sciStarter.add(new org.jboss.weld.environment.servlet.EnhancedListener());

So that could be simplified a little (eg we should provide a utility SciStarter) , but it's not too ugly.

Now the strange thing is that because you are using a WebappContext (or at least the documentation suggest that you should), then the SCI's should have already been found for you? Hmm maybe we need to tell it to search for them??? stand by...

joakime

joakime commented on Aug 19, 2020

@joakime
Contributor

I was able to not use jetty-cdi at all, and rely solely on the weld-servlet-core artifact.

This works for ServletContextHandler just fine.
And isn't appropriate when using WebAppContext.

package org.eclipse.jetty.demo.embedded;

import org.eclipse.jetty.demo.logging.Logging;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.JavaUtilLog;
import org.eclipse.jetty.util.log.Log;
import org.jboss.weld.environment.servlet.EnhancedListener;

public class EmbeddedDemo
{
    public static void main(String[] args) throws Exception
    {
        Logging.config();
        Log.setLog(new JavaUtilLog());
        Server server = new Server(8099);

        final ServletContextHandler contextHandler = new ServletContextHandler();
        contextHandler.setContextPath("/");
        contextHandler.addServlet(HelloServlet.class, "/hi/*");
        contextHandler.addServlet(GreetingsServlet.class, "/greetings/*");
        contextHandler.addBean(new WeldInitializer(contextHandler));
        server.setHandler(contextHandler);
        try
        {
            server.start();
            HttpClient.GET(server.getURI().resolve("/hi/"));
            HttpClient.GET(server.getURI().resolve("/greetings/"));
        }
        finally
        {
            LifeCycle.stop(server);
        }
    }

    public static class WeldInitializer extends AbstractLifeCycle
    {
        private final ServletContextHandler contextHandler;

        public WeldInitializer(ServletContextHandler contextHandler)
        {
            this.contextHandler = contextHandler;
        }

        @Override
        protected void doStart() throws Exception
        {
            EnhancedListener enhancedListener = new EnhancedListener();
            enhancedListener.onStartup(null, contextHandler.getServletContext());
            super.doStart();
        }
    }
}
joakime

joakime commented on Aug 19, 2020

@joakime
Contributor

This works too.
For both ServletContextHandler and WebAppContext usages.

package org.eclipse.jetty.demo.embedded;

import org.eclipse.jetty.demo.logging.Logging;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.JavaUtilLog;
import org.eclipse.jetty.util.log.Log;

public class EmbeddedListenerDemo
{
    public static void main(String[] args) throws Exception
    {
        Logging.config();
        Log.setLog(new JavaUtilLog());
        Server server = new Server(8099);

        final ServletContextHandler contextHandler = new ServletContextHandler();
        contextHandler.setContextPath("/");
        contextHandler.addServlet(HelloServlet.class, "/hi/*");
        contextHandler.addServlet(GreetingsServlet.class, "/greetings/*");
        contextHandler.addEventListener(new org.jboss.weld.environment.servlet.Listener());
        server.setHandler(contextHandler);
        try
        {
            server.start();
            HttpClient.GET(server.getURI().resolve("/hi/"));
            HttpClient.GET(server.getURI().resolve("/greetings/"));
        }
        finally
        {
            LifeCycle.stop(server);
        }
    }
}
gregw

gregw commented on Aug 19, 2020

@gregw
Contributor

@joakime that method only partially works. It uses the Weld integration which will give you the log message:

WELD-ENV-001201: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported

ie Listener injection is not supported.

This way will also not work with jetty-10.

added a commit that references this issue on Aug 20, 2020

Issue #5162 CDI embedded integration improvements

added 2 commits that reference this issue on Aug 24, 2020

Issue #5162 CDI embedded integration improvements

Issue #5162 CDI embedded integration improvements

12 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Development

    Participants

    @gregw@joakime@oliviercailloux

    Issue actions

      DecoratingListener raises a NullPointerException · Issue #5162 · jetty/jetty.project