March 27, 2016

How to run standalone Java EE MVC application

This project demonstrates how to create standalone Java EE MVC application.

MVC is newly developed Java EE JSR 371: Model-View-Controller (MVC 1.0) Specification.
The MVC API defines an action-oriented Web framework as an alternative to the component-oriented JSF. In an action-oriented framework, developers are responsible for all the controller logic and are given full control of the URI space for their application. 
The MVC API is layered on top of JAX-RS over Servlets and integrates with existing EE technologies like CDI and Bean Validation.
MVC spec is going to be part of next Java EE 8. MVC reference implementation named Ozark is hosted on java.net. MVC depends on Servlets, JAX-RS and CDI. I've chosen to use UndertowJersey and Weld. And DeltaSpike as CDI extension library. Finally, Gradle to build and run the project.

ext {
    undertowVersion = '1.3.18.Final'
    weldVersion = '2.3.3.Final'
    deltaspikeVersion = '1.5.4'
    jerseyVersion = '2.22.2'
    ozarkVersion = '1.0.0-m02'
}
task wrapper(type: Wrapper) {
    gradleVersion = '2.12'
}

Standalone CDI container

Weld contains module to support Java SE applications. Together with Apache DeltaSpike Java SE & Weld module:

dependencies {
    compile "org.jboss.weld.se:weld-se:$weldVersion"
    compile "org.apache.deltaspike.cdictrl:deltaspike-cdictrl-weld:$deltaspikeVersion"

it is easy to bootstrap and shutdown CDI container:

import javax.enterprise.context.ApplicationScoped;
import org.apache.deltaspike.cdise.api.CdiContainer;
import org.apache.deltaspike.cdise.api.CdiContainerLoader;
import org.jboss.weld.servlet.WeldInitialListener;
import org.jboss.weld.servlet.WeldTerminalListener;

public class Main {

    static CdiContainer init() throws ServletException, NoSuchMethodException {
        CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer();

        cdiContainer.boot();
        cdiContainer.getContextControl().startContext(ApplicationScoped.class);

        initServlet();

        return cdiContainer;
    }

    static void destroy(CdiContainer cdiContainer) {
        cdiContainer.getContextControl().stopContext(ApplicationScoped.class);
        cdiContainer.shutdown();
    }

...

You can see we also start and stop CDI ApplicationScoped scope.

Initialize Servlet

Next we need to register Jersey ServletContainer as Undertow servlet:

dependencies {
    compile "io.undertow:undertow-core:$undertowVersion"
    compile "io.undertow:undertow-servlet:$undertowVersion"

    compile "org.glassfish.jersey.core:jersey-server:$jerseyVersion"
    compile "org.glassfish.jersey.containers:jersey-container-servlet-core:$jerseyVersion"
    compile "org.glassfish.jersey.ext.cdi:jersey-cdi1x-servlet:$jerseyVersion"

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import io.undertow.Undertow;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ServletContainer;
import io.undertow.servlet.api.ServletInfo;

import org.jboss.weld.servlet.WeldInitialListener;
import org.jboss.weld.servlet.WeldTerminalListener;

public class Main {

    ...

    private static void initServlet() throws ServletException, NoSuchMethodException {
        DeploymentInfo deploymentInfo = new DeploymentInfo()
                .addListener(Servlets.listener(WeldInitialListener.class))
                .addListener(Servlets.listener(WeldTerminalListener.class))
                .setContextPath("/")
                .setDeploymentName("standalone-javax-mvc-app")
                .addServlets(
                        createServletInfo("/resources/*", "JAX-RS Resources", org.glassfish.jersey.servlet.ServletContainer.class)
                )
                .setClassIntrospecter(CdiClassIntrospecter.INSTANCE)
                .setAllowNonStandardWrappers(true)
                .setClassLoader(ClassLoader.getSystemClassLoader());

        ServletContainer servletContainer = Servlets.defaultContainer();
        DeploymentManager deploymentManager = servletContainer.addDeployment(deploymentInfo);
        deploymentManager.deploy();

        Undertow server = Undertow.builder()
                .addHttpListener(8080, "0.0.0.0")
                .setHandler(deploymentManager.start())
                .build();
        server.start();
    }

    private static ServletInfo createServletInfo(String mapping, String servletName, Class<? extends Servlet> servlet)
            throws NoSuchMethodException {
        ServletInfo servletInfo = Servlets
                .servlet(servletName, servlet)
                .setAsyncSupported(true)
                .setLoadOnStartup(1)
                .addMapping(mapping);
        servletInfo.setInstanceFactory(CdiClassIntrospecter.INSTANCE.createInstanceFactory(servlet));

        return servletInfo;
    }
}

Important part is registering of Weld Servlet listeners: WeldInitialListener and WeldTerminalListener.

The instance of Jersey ServletContainer is provided as ApplicationScoped bean by custom CdiProducer using CdiClassIntrospecter.

import javax.enterprise.inject.Produces;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;

@ApplicationScoped
public class CdiProducer {

    private long helloCounter;

    @Produces
    @ApplicationScoped
    public ServletContainer createServletContainer() {
        final ResourceConfig resourceConfig = new ResourceConfig();
        resourceConfig.register(HelloController.class);
        resourceConfig.property("javax.mvc.engine.ViewEngine.viewFolder", "WEB-INF/views/");

        return new ServletContainer(resourceConfig);
    }

    ...

You can see we need to customize "javax.mvc.engine.ViewEngine.viewFolder" property to support standalone application class-path behaviour.

CdiClassIntrospecter implements Undertow ClassIntrospecter interface and just delegates to CDI.

MVC

Now you can provide MVC @Controller methods:

dependencies {
    compile "org.glassfish.ozark:ozark:$ozarkVersion"
    compile "org.glassfish.ozark.ext:ozark-mustache:$ozarkVersion"

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.mvc.Models;
import javax.mvc.annotation.Controller;
import javax.mvc.annotation.View;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

@Path("hello")
@ApplicationScoped
public class HelloController {

    @Inject
    private Models models;

    @GET
    @Controller
    @Produces("text/html")
    @View("hello.mustache")
    public void hello(@QueryParam("user") @DefaultValue("World") String user) {
        models.put("user", user);
    }

}

You can see I've chosen to use Mustache template engine, see hello.mustache:

<html>
    <head>
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello {{user}}!</h1>
    </body>
</html>

Sources

Complete project sources are available on GitHub, https://github.com/shamoh/standalone-javax-mvc.

The easiest way how to run the project is:


gradlew run

The application is then available on: http://localhost:8080/resources/hello?user=Libor.


No comments:

Post a Comment