Search code examples
javatomcatservletscdiweld

Included CDI-enabled Servlet Fails with ContextNotActiveException: WELD-001303:


I am writing a CDI application that runs on Tomcat. I am using Tomcat 7.0.62 with Weld 2.2.12.Final as the CDI implementation. I am using CDI without JSF.

The application consists of a dispatcher servlet that is not CDI enabled. The dispatcher includes the output of a CDI enabled servlet to create the page.

When the dispatcher servlet and the CDI servlet are both in the same web app, it works fine. However, I need the CDI servlet to be in a different web app, so I do a cross-context include. When I do the cross-context include, the CDI servlet produces output until it attempts to access an @RequestScoped bean. Bean access fails with the following exception:

org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
    at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:708)
    at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:90)
    at org.jboss.weld.bean.ContextualInstanceStrategy$CachingContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:165)
    at org.jboss.weld.bean.ContextualInstance.getIfExists(ContextualInstance.java:63)
    at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83)
...

I have tried activating CDI for the dispatcher servlet as well, but it doesn't seem to make any difference.

It looks to me like the request context for the CDI servlet is not being set up properly when the CDI servlet is included as opposed to receiving the request directly.

I have searched this site and also through Google, but have not found a matching problem / solution. I found a tomcat context setting 'fireRequestListenersOnForwards="true"' that I applied to the dispatcher servlet, but that did not make a difference.

Is this a configuration problem? Could anyone provide a clue about how to solve this?

I would be very grateful!


Background information:

The actual application having the problem is large, so I condensed it down to get to the essence of brokenness. As a result, I have two war files. The first war file contains the CDI servlet and the dispatcher (in code I called it the includer) servlet. The second war file contains only the dispatcher servlet.

The CDI Servlet

The CDI Servlet has a context.xml file in its META_INF directory with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="BeanManager" 
        auth="Container"
        type="javax.enterprise.inject.spi.BeanManager"
        factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>

The CDI servlet web.xml file contains the lines:

<listener>
  <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>

<resource-env-ref>
    <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>

The CDI Servlet WEB-INF directory contains a beans.xml file.

The CDI servlet bootstraps bean execution through use of a BeanManager obtained through JNDI lookup (this is working):

BeanManager bm = null;
try {
    InitialContext context = new InitialContext();

    try {
       // "regular" naming
       bm = (BeanManager) context.lookup("java:comp/BeanManager");
    } catch(NameNotFoundException e) {
       // try again with Tomcat naming
       bm = (BeanManager) context.lookup("java:comp/env/BeanManager");
    }
} catch (Exception e) {}

if (bm == null) {
   writer.write("Couldn't look up the bean manager");
} else {
   Set<Bean<?>> beans = bm.getBeans(EnclosingBean.class);
   @SuppressWarnings("unchecked")
   Bean<EnclosingBean> bean = (Bean<EnclosingBean>) bm.resolve(beans);
   if (bean == null) {
      writer.write("Couldn't get the bean");
   } else {
      EnclosingBean eb = (EnclosingBean) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
      writer.write("finally here we are. Name is: ");
      writer.write(eb.getName());
   }
}

The Includer (Dispatcher) Servlet

The includer servlet has a context.xml file in its META-INF directory:

<?xml version="1.0" encoding="UTF-8"?>
<Context 
   path="/ExternalIncluderServlet" 
   docBase="ExternalIncluderServlet.war" 
   crossContext="true" 
   fireRequestListenersOnForwards="true">

    <Resource name="BeanManager" 
        auth="Container"
        type="javax.enterprise.inject.spi.BeanManager"
        factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>

The includer servlet looks up the context of the CDI servlet and obtains a RequestDispatcher as follows (this works):

ServletContext sc = request.getServletContext();
ServletContext extsc = sc.getContext("/SimpleCDIServlet");
if (extsc == null) {
   writer.println("<p>Couldn't get the external context.</p>");
} else {

   RequestDispatcher rd = extsc.getRequestDispatcher("/CDIServlet");
   if (rd == null) {
        writer.println("<p>RequestDispatcher is null.</p>");
   } else {
       writer.println("<p>Got the RequestDispatcher.</p>");
       rd.include(req, resp);
   }
}

Results:

When I use the browser to access the CDI servlet directly via the URI: localhost:8080/SimpleCDIServlet/CDIServlet I get the expected output:

Simple CDI Servlet
finally here we are. Name is: InjectedBean

If I access the CDI servlet through a dispatcher servlet located in the same web app as the CDI servlet, it works as well. URI: /SimpleCDIServlet/IncluderServlet, output:

Simple CDI Servlet Including Servlet
Will now include the CDI servlet ...
Got the RequestDispatcher.
Simple CDI Servlet
finally here we are. Name is: InjectedBean

But if I include the CDI servlet from a different context, I don't get the injected bean name in the output and the exception noted above is in the log. URI: /ExternalIncluderServlet/IncluderServlet, Output:

CDI Servlet Includer

This servlet includes a CDI servlet in a different web app. It is not CDI enabled.

Got the RequestDispatcher.
Simple CDI Servlet
finally here we are. Name is: 

Note that I can't use a request dispatcher forward instead of include as the original application includes output from several other servlets, not just one. And opening a new HTTP request for each include would be inefficient, since the number of requests would be multiplied by the number of included servlets, and it would be fairly ugly on top of that.


Update: I tried this out on Tomee 1.7.2 and also on WebSphere Application Server v8.5. The results are summarized below.

                           WAS 8.5           Tomcat 7.0.62           Tomee 1.7.2
                           =======           =============           ===========
CDI servlet direct access   works               works                   works

Included by servlet         works               works                   works          
   in same web app

Included by servlet         works               broken                  broken 
   in different web app

The more I think about this, the more I feel that it really should work. You should be able to successfully use a request dispatcher to include output from a CDI-enabled servlet on Tomcat. I hope that someone here can help me figure out how to make it work.


Solution

  • WOW! with the help of a good friend, I got it!

    To make this work, you need to configure a special Weld cross-context filter. You configure the filter by adding the following lines to the web.xml file.

    <filter>
       <filter-name>WeldCrossContextFilter</filter-name>
       <filter-class>org.jboss.weld.servlet.WeldCrossContextFilter</filter-class>
    </filter>
    <filter-mapping>
       <filter-name>WeldCrossContextFilter</filter-name>
       <url-pattern>/*</url-pattern>
       <dispatcher>INCLUDE</dispatcher>
       <dispatcher>FORWARD</dispatcher>
       <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    

    When I configure this, a cross-context include of a CDI servlet works as expected. I tried it out on Tomcat 7.0.42 and 8.0.23 and so far, so good.

    See also:

    http://javadox.com/org.jboss.weld/weld-core-impl/2.2.4.Final/org/jboss/weld/servlet/WeldCrossContextFilter.html

    I looked for a reference to the filter in the official Weld documentation, but was unable to locate anything. Anyway, I hope this might help someone.