A first look at MVC 1.0

Recently, Twitter brought the renaming of Ozark to Krazo to my attention. It pulled my attention: I had never heard of either projects, and I wondered what they would be about. Ozark (or Krazo) will be the Reference Implementation of the new Model-View-Controller Specification. This MVC specification, also known as JSR 371, was planned for inclusion in Java EE 8, but eventually dropped. Apparently, this didn’t kill the effort. I was curious to see where the specification (and it’s implementation) would be now.

I assume you are familiar with the model-view-controller design pattern. If not, scan through Wikipedia to get the idea.

Status

Currently, there is a draft of the specification available for the public. I was a little afraid of reading this document, since specifications can be dry and academic. But this specification is surprisingly easy to read! Also, the expert committee held a ballot about this draft, which has been successful. Before a JSR can be released, it needs a Reference Implementation (RI) and a Technology Compatibility Kit (TCK). Ozark will be the RI for the MVC specification. Since the ballot was successful, I suppose there won’t be too much changes in the specification. Time to give it a try!

Getting started

To get started, I grabbed the latest OpenLiberty build - 18.0.0.2 at the time of writing. I unzipped the download to a temporary directory. From there, I created a new server, using ./bin/server create mvc. MVC builds upon existing Java EE features such as JAX-RS and JSP, so I enabled those in server.xml:

<featureManager>
  <feature>beanValidation-2.0</feature>
  <feature>cdi-2.0</feature>
  <feature>jaxrs-2.1</feature>
  <feature>jsp-2.3</feature>
  <feature>localConnector-1.0</feature>
</featureManager>

The localConnector-1.0 feature is not required, but convenient in combination with IntelliJ.

Next, I bootstrapped a dead simple Java EE project, using the following POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>it.mulders.junk</groupId>
  <artifactId>openliberty-mvc</artifactId>
  <version>0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </properties>

  <repositories>
    <repository>
      <id>sonatype-oss-snapshots</id>
      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        <releases>
          <enabled>false</enabled>
        </releases>
        <snapshots>
          <enabled>true</enabled>
        </snapshots>
      </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>8.0</version>
      <scope>provided</scope>
    </dependency>


    <dependency>
      <groupId>javax.mvc</groupId>
      <artifactId>javax.mvc-api</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.mvc-spec.ozark</groupId>
      <artifactId>ozark-core</artifactId>
      <version>1.0.0-m04-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

Normally, I wouldn’t add a SNAPSHOT version of a dependency that I do not control. This time, I have made an exception to that rule.

The latest released version of Ozark doesn’t work on Liberty, as Christian Kaltepoth, one of the Specification Leads, pointed out on GitHub. After switching to a snapshot, I found another subtle bug. It turns out that there’s a little unexpected thing in OpenLiberty. The currently available mappings of a Servlet can be null (instead of an empty collection). This time, I think the bug is actually in OpenLiberty. The getMappings() JavaDoc say that it should return an empty Collection rather than a null value. Anyway, Christian quickly created a work-around for that - within a few hours, a new snapshot was available that didn’t crash on Liberty.

With that out of the way, it’s time to get coding!

Show me some code!

Since MVC builds upon on JAX-RS, the first thing we need to do is configure JAX-RS. We do this using a subclass of javax.ws.rs.core.Application:

@ApplicationPath("app")
public class Application extends javax.ws.rs.core.Application {
}

This tells the JAX-RS runtime that we want to enable it, and under which path the resources should be available. I’m not using / here, so JAX-RS will not interfere with static resources or other application components.

Although the pattern is named Model-View-Controller, I’ll walk through them in different order.

Controller

First thing we need is a Controller. Again, since MVC builds upon JAX-RS, we can turn any JAX-RS resource into a controller by annotating it with @javax.mvc.Controller. We also annotate it with @javax.enterprise.context.RequestScoped. As a consequence, every request to this controller will be handled by a new instance of this class. Using @javax.ws.rs.Path we indicate the path under which the controller will be accessible.

Next, we add an instance variable @javax.mvc.Models. We’ll see later what it is.

Finally, we add one method, hello(), to the controller. We annotate that with @javax.ws.rs.GET to tell JAX-RS it should invoke this method when an GET-request comes in at the specified path. We also add @javax.mvc.View to specify the view that MVC should render after the method hello() has executed. The method will put a simple value in the model under the key greeting.

The class now looks like this:

@javax.enterprise.context.RequestScoped
@javax.mvc.Controller
@javax.ws.rs.Path("hello")
public class HelloController {
  @javax.inject.Inject
  Models models;

  @javax.ws.rs.GET
  @View("hello.jsp")
  public void hello() {
    models.put("greeting", "Hello, world");
  }
}

Model

The Model class that we used comes straight from MVC. Every request will have its own instance of it. This implementation comes from Ozark and provides a key-value mapping. It provides a simple yet very effective way for our controller and views to interact with each other.

View

Now it’s time to create a view for our application. By default, Ozark comes with a two implementations of javax.mvc.engine.ViewEngine. This interface describes how MVC processes views. One implementation supports rendering of a JavaServer Page (JSP) file, the other supports rendering of a JavaServer Faces (JSF) file. In this example, I’m going to use JSP.

The view is actually a pretty simple JSP-file, stored in /WEB-INF/views/. This is the default location where MVC will look for views, but you can configure another location if needed.

<!doctype html>
<html>
<head>
  <title>Hello MVC</title>
</head>
<body>
<div>${greeting}</div>
</body>
</html>

Deploying

From IntelliJ, you can easily run this application in an OpenLiberty container. Alternatively, package the application into a WAR file (using mvn clean package) and drop the WAR in an application server that is supports Java EE 8. To name a few other options:

Time for something more serious

This is all nice and fine, but the application doesn’t really do anything special. Let’s make it a little more useful by adding two features:

  1. Customise the name of the greeted person.
  2. Show the current date and time in a language of the users choice.

Customise the name

For customising the name of the greeted person, we could read a query parameter from the requested resource. So, when someone requests /app/hello?name=Maarten, the greeting will be Hello, Maarten instead of Hello, world. Feels a lot more welcoming, doesn’t it?

We need to tell JAX-RS that our hello() method is interested in this query parameter. To do that, we add a method parameter String name and annotate it with @javax.ws.rs.QueryParam("name").

Since our controller shouldn’t contain business logic, we decide to refactor the greeting logic to a separate class. Let’s call it GreetingService, and annotate it with @javax.enterprise.context.ApplicationScoped. This annotation tells CDI that we can create one instance of this class for our application. Since it is not going to keep any state, it doesn’t need to be instantiated for every session or every request. We add one “business” method and one helper method to it:

private String defaultName(final String input) {
  if (input == null || input.length() == 0) {
    return "world";
  } else {
    return input;
  }
}

public String generateGreeting(final String name) {
  return String.format("Hello, %s", defaultName(name));
}

Now, our controller needs to change as well:

@javax.inject.Inject
GreetingService greetingService;

@GET
@View("hello.jsp")
public void hello(@QueryParam("name") final String name) {
  models.put("greeting", greetingService.generateGreeting(name));
}

The good news is: we can keep our view as it was. The updated business logic is completely implemented in the controller and the classes it delegates to!

Show current date and time in language of choice

This one is a little more advanced. We want the user to first select a language of choice, and then keep that preference persisted during the session.

To do that, we create a welcome page where the user can choose a language. After the page submits, we keep the language in the users’ session so it doesn’t need to be re-entered every time.

This time, let’s start with the view. It’s not that complicated:

<!doctype html>
<html>
<head>
  <title>Hello MVC</title>
</head>
<body>
<div>
  <form action="${pageContext.request.contextPath}/app/hello/configure" method="post">
    <label for="locale">Select the language of your choice</label>
    <select id="locale" name="locale">
      <option value="nl-NL">Dutch</option>
      <option value="de-DE">German</option>
      <option value="fr-FR">French</option>
      <option value="en-UK">English (UK)</option>
      <option value="en-US">English (US)</option>
    </select>
    <button type="submit">Submit</button>
  </form>
</div>
</body>
</html>

Note that the order of languges is completely arbitrary…

This is a very trivial HTML page, except that it’s stored as input.jsp. The only thing to note is <form> tag, where we use an EL expression to configure the action where the form should be submitted.

In itself, this JSP is not accessible by clients, because it lives in /WEB-INF/views/. To enable that, we add a controller method:

@GET
@Path("/start")
@View("input.jsp")
public void start() {
}

This will make the input.jsp file accessible under /app/hello/start.

Now, this form obviously needs a controller method to handle form submits. We add another controller method to process the inputs:

@Inject
UserPreferences preferences;

@POST
@Path("/configure")
@View("redirect:/hello")
public void configure(@FormParam("locale") final String locale) {
  final Locale result = detectLocale(locale);
  preferences.setLocale(result);
}

First, it tries to create an instance of Locale matching the form parameter named locale. It then stores that in an instance of class UserPreferences. This is also a model, but it’s different than the one we saw before, for two reasons. First, it is a Plain Old Java Object (POJO), so no key/value pairs floating around. Second, it is annotated with @javax.enterprise.context.SessionScoped. This tells the CDI implementation to keep an instance during an HTTP session. If all this succeeds, the @javax.mvc.View annotation tells MVC to redirect the browser to /hello. The redirect path is relative to the path that we configured for JAX-RS.

The good stuff is that we can use these preferences later during the session of this user. For example, in the hello() method that we created earlier:

final Locale locale = preferences.getLocale();

Et voilà, our stored Locale is back. To prove this, let’s simulate another call to the application using just the JSESSIONID cookie:

curl http://localhost:9080/app/hello?name=Maarten \
  -H 'Cookie: JSESSIONID=0000Rqe-zdDaydVir5A_V4VDPee:48e9b293-4418-4831-88b3-e60ed05836c8' 

And we see this in the output:

<body>
<h1>Hello, Maarten</h1>
<div>Current date and time is: donderdag 6 september 2018 20:23:29 uur CEST</div>
</body>

Cool stuff, right?

Extra: adding CSRF protection

We would like to prevent our application against CSRF attacks. In other words: our application should only execute HelloController#configure(...) when the form submit comes from input.jsp. How to check that?

It’s not that difficult: we give input.jsp a magic value, which we call the CSRF protection token. This token is part of the HTML form, so it will be submitted to the server. We also store that in the users session at the server. Now when the configure method is invoked, we verify whether the submitted token is valid.

Of course, this is something we could code ourselves. But the good news is: the MVC spec has got us covered. We annotate the configure method with @javax.mvc.security.CsrfProtected, and add the following snippet to input.jsp:

<input type="hidden" name="${mvc.csrf.name}" value="${mvc.csrf.token}"/>

This creates a hidden form input, whose name is configured by the MVC implementation, and so is the value. And that’s all: when the form submission lacks the CSRF protection token, or it is different from what the server remembered, the HTTP response will be 403 (Forbidden). Formally: the server understood the request, but will not fulfil it; also, the request should not be repeated.

Disclaimer: the specification says that this is how it should work. In my setup - with OpenLiberty as the Java EE container - it did not work that way. I’ve filed a bug with Ozark. They’ve asked the JAX-RS expert group for clarification on the specification, regarding the invocation of ReaderInterceptor. It turns out that Jersey (the JAX-RS Reference Implementation) interprets it differently than Apache CXF (the implementation that OpenLiberty uses).

Wrapping up

So, that’s it, our first acquaintance with the MVC specification and its implementation called Ozark. I hope you liked it - I sure did!

Packaging our application results in a WAR-file of 142250 bytes - that’s right, 140 kB. Building upon the standards that come with Java EE, and that ship with our container, keeps the application artifacts relatively small. When MVC will be part of a future Jakarta EE release, our application can become even smaller - leaving out the JAR-files for Ozark and MVC API brings it down to 8679 bytes. In a continuous delivery world, I think the size of an artifact does indeed matter. The smaller they are, the faster I can transfer my application to the servers where I want to deploy it.

The complete source code of this example application is on GitHub.