October 08, 2012
JAX-RS 2.0 MVC
It is not unusual for services that expose a technical REST API to also need human-targeted UI for configuration, status checks or reporting.
What I have seen a couple of times is that developers naturally use some form of REST framework (for example JAX-RS) for the technical API but then make use of yet another API technology (for example Spring or JSF) for the human-targeted UI.
This not only increases the technology mix (something I personally strive to avoid as much as possible), but it also introduces a distinction between technical and human API that should not be made in my opinion. From an API point of view an HTTP interface that serves HTML and AJAX-targeted JSON is as much a REST-API as is one that serves XML or ‘technical’ JSON.
Instead, you should treat both as aspects of one and the same API. For one you will likely find resources in both API-’parts’ that serve representations of the same concepts (for example calendar events, shopping carts or contract information). Such repesentations of should be produced by one and the same resource. This reduces the amount of code and will help you, for example, to apply corresponding cacheability properties to both representations.
In addition, it will encourage you to treat the human-targeted API-parts with the same care as the technical ones. For example, you should allow introspection into them using some form of home document. Above all, you’ll never know in which ways clients re-use those HTML pages or snippets you serve.
Having said that, what might – besides just habits – be the reason that developers do not think ‘REST-API’ when they think ‘GUI’? Is it because many REST frameworks out there have very poor support for serving document-oriented representations such as HTML pages?
While some JAX-RS implementations have proprietary support for templating, the new JAX-RS 2.0 filter API lets you roll your own with a couple of lines of code.
Instead of the ‘Web-container-only’-setup I used in the last blog I would like full Java EE6 support this time. Fortunately, Arun Gupta has provided an example of how get JAX-RS 2.0 to work in a Java EE 6 container.
First I created a project based on the archetype Arun uses:
mvn archetype:generate \
-DarchetypeGroupId=org.codehaus.mojo.archetypes \
-DarchetypeArtifactId=webapp-javaee6 \
-DgroupId=net.jalg \
-DartifactId=mvc \
-DinteractiveMode=false
and then added the neccessary repository and dependencies the way he describes. Because JAX-RS 2.0 and Jersey currently undergo some sort of last minute changes I have changed the Jersey dependencies to be on 2.0-SNAPSHOT to get the latest developments.
The templating support I have in mind would let me return any object from a JAX-RS resource method. In addition it would let me annotate the resource method to specify a template to use for constructing the representations. Like so:
@Path("user/{userId}/account")
public class Facade {
@PathParam("userId") String userId;
@GET
@Produces("text/html")
@Path("contract-details")
@Template("templates/contract.vm")
public Contract getContractDetails() {
return new Contract("123456", userId, "new");
}
}
The problem here is that the JAX-RS runtime will pick the MessageBodyWriter implementation based on the returned type. It will not, magically, invoke some sort of templating engine. So, how can we trick the runtime into choosing a different MessageBodyWriter? And how can we pass to that not only the returned object but also the path of the desired template?
Here the filter API comes in. We can use the @Template annotation to also act as a binding for a particular filter. This is done with the @NameBinding annotation from the new filter API:
@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Template {
String value() default "";
}
Now the runtime will invoke all filters annotated with @Template when our getContractDetails() method is called and we can work the magic in a filter.
What happens in the filter below is that we replace the response entity with a wrapper object that holds the template path and the original response entity. The template path we can extract from the annotation, the original response entity we extract from the response context.
@Provider
@Template
public class EntityToModelAndViewWrapper implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
for (Annotation a : responseContext.getEntityAnnotations()) {
if (a.annotationType() == Template.class) {
String templatePath = ((Template) a).value();
ModelAndView mav = new ModelAndView(responseContext.getEntity(),templatePath);
responseContext.setEntity(mav,
responseContext.getEntityAnnotations(),
responseContext.getMediaType());
break;
}
}
}
}
By changing the type of the response entity, we gain control over the MessageBodyWriter selection. And the runtime will now invoke the MessageBodyWriter we have provided for ModelAndView instances:
@Provider
...
public class ModelToViewMBR implements MessageBodyWriter<ModelAndView> {
...
@Override
void writeTo(ModelAndView mav, Class<?> arg1, Type arg2,
Annotation[] arg3, MediaType arg4,
MultivaluedMap<String, Object> arg5, OutputStream output)
throws IOException, WebApplicationException {
Map<String, Object> map = new HashMap<String,Object>();
map.put("entity", mav.getModel());
engine.merge(mav.getView(),output,map);
}
...
}
In the writeTo() method we make the original response entity available to a templating engine under the name ‘entity’ and invoke the merge of template and entity.
The engine member is an instance of a template engine wrapper. You can see the details in the TemplateEngine class in the example source code.
In addition to the response entity it should not be too hard to make available to the template engine all the managed beans of the container runtime’s current session (the request). That way one would have the full power of EJB 3.1 and CDI at one’s disposal.