Container & Control


Introduction

There are basically two parts:

  • The CdiContainer interface allows to boot and shutdown the CDI container in SE applications.
  • The ContextControl interface allows to control the life-cycle of the built-in contexts of the CDI container.

CdiContainer

You can use the CdiContainerLoader as a simple factory to gain access to the underlying CdiContainer implementation. This is of little interest for Java EE applications since the CDI Container already gets properly booted and shut down by the Servlet container integration.

// this will give you a CdiContainer for Weld or OWB, depending on the jar you added
CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();

// now we gonna boot the CDI container. This will trigger the classpath scan, etc
cdiContainer.boot();

// and finally we like to start all built-in contexts
cdiContainer.getContextControl().startContexts();

// now we can use CDI in our SE application.
// And there is not a single line of OWB or Weld specific code in your project!

// finally we gonna stop the container
cdiContainer.shutdown();

ContextControl usage

The ContextControl interface allows you to start and stop built-in standard Contexts like @RequestScoped, @ConversationScoped, @SessionScoped, etc. It is provided as @Dependent bean and can get injected in the classic CDI way. This is not only usable in Java SE projects but also very helpful in Servlets and Java EE containers.

Restarting the RequestContext in unit tests

In unit testing it can be necessary to test with attached and also with detached JPA entities. A very common approach for JPA is the entitymanager-per-request approach and thus have a producer method which creates a @RequestScoped EntityManager. Since a single unit test is usually treated as one ‘request’ a problem arises detaching entities.

Using ContextControl to detach entities:

@Test
public void testMyBusinessLogic()
{
    doSomeJpaStuff()
    MyEntity me = em.find(...);

    ContextControl ctxCtrl = BeanProvider.getContextualReference(ContextControl.class);

    //stopping the request context will dispose the @RequestScoped EntityManager
    ctxCtrl.stopContext(RequestScoped.class);

    // and now immediately restart the context again
    ctxCtrl.startContext(RequestScoped.class);

    // the entity 'em' is now in a detached state!
    doSomeStuffWithTheDetachedEntity(em);
}

Attaching a Request Context to a new thread in EE

Accessing the @RequestScoped bean in a new thread will result in a ContextNotActiveException. The request-context usually gets started for a particular thread via a simple ServletRequestListener. So "no servlet-request" means that there is no Servlet-Context for the current (/new) Thread. You might face such issues, if you would like to reuse business services in e.g. a Quartz Job.

Controlling the request-context for a Quartz-Job:

public class CdiJob implements org.quartz.Job
{
    public void execute(JobExecutionContext context) throws JobExecutionException
    {
        ContextControl ctxCtrl = BeanProvider.getContextualReference(ContextControl.class);

        //this will implicitly bind a new RequestContext to the current thread
        ctxCtrl.startContext(RequestScoped.class);

        try
        {
            doYourWork();
        }
        finally
        {
            //stop the RequestContext to ensure that all request-scoped beans get cleaned up.
            ctxCtrl.stopContext(RequestScoped.class);
        }
    }
}

Embedded Servlet Support

Starting with 1.0.2, you can use DeltaSpike to power embedded Servlet runtimes. This work is done via Servlet Listeners. The configuration is specific to each container, below are some examples.

The two main listeners are CdiServletRequestListener and CdiServletContextListener. CdiServletRequestListener is responsible for starting a RequestContext on each incoming request. In most containers this is all you need. For Tomcat specifically, you need to use CdiServletContextListener which registers the CdiServletRequestListener.

The main usecase for this feature is for lightweight embedded runtimes, microservices. For each of these, it is assumed that you are using the following start up code somewhere:

CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();
cdiContainer.boot();
cdiContainer.getContextControl().startContexts();

Jetty

For Jetty, you need to add an EventListener which will be your CdiServletRequestListener. The object must be instantiated. This must be done before the server is started.

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

context.addEventListener(new CdiServletRequestListener());
context.addServlet(new ServletHolder(new YourServlet()),"/*");

server.start();

Undertow

For Undertow, you register the CdiServletRequestListener via ListenerInfo by passing in the class to their builders. Then you add the ListenerInfo to your deployment before starting.

ServletInfo servletInfo = Servlets.servlet("YourServletName", YourServlet.class).setAsyncSupported(true)
    .setLoadOnStartup(1).addMapping("/*");
ListenerInfo listenerInfo = Servlets.listener(CdiServletRequestListener.class);
DeploymentInfo di = new DeploymentInfo()
        .addListener(listenerInfo)
        .setContextPath("/")
        .addServlet(servletInfo).setDeploymentName("CdiSEServlet")
        .setClassLoader(ClassLoader.getSystemClassLoader());
DeploymentManager deploymentManager = Servlets.defaultContainer().addDeployment(di);
deploymentManager.deploy();
Undertow server = Undertow.builder()
        .addHttpListener(port, "localhost")
        .setHandler(deploymentManager.start())
        .build();
server.start();

Tomcat

For Tomcat, you need to register the CdiServletContextListener instead of the CdiServletRequestListener. It is added as an ApplicationListener by passing in the class name as a String.

Tomcat tomcat = new Tomcat();
tomcat.setPort(port);
File base = new File("...");
Context ctx = tomcat.addContext("/",base.getAbsolutePath());
StandardContext standardContext = (StandardContext)ctx;
standardContext.addApplicationListener(CdiServletContextListener.class.getName());
Wrapper wrapper = Tomcat.addServlet(ctx,"YourServlet",YourServlet.class.getName());
wrapper.addMapping("/*");
tomcat.start();