Saturday, 29 December 2012

JAX-RS as JSON provider for AngularJS

Introduction

In this blog text, I'll discuss the steps for using JAX-RS restful services as JSON data provider of a AngularJS application.
With JAX-RS it is quit easy to return some data when a certain URL is called.  By annotating a POJO class and his methods, you can write a flexible gateway in no time.
In the specification, there is explicitly mentioned that multiple mediaTypes are supported.  And although JAX-RS implementations use JAXB to produce and consume XML content, they all support JSON.
So my second goal was to create an AngularJS application that uses JAX-RS to retrieve and store his data.

The annotations

As already mentioned, a few annotations on a POJO are enough to create your JAX-RS restful service.
I'll explain the annotations based on the example.

@Path("person")
public class PersonDataController {
    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    @Path("list")
    public List<Person> getList() {
        ...
    }
}

The @Path on the class defines the part of the URL on which this restful resource will respond. The one on the method defines the next part of the URL.  So the above class will respond to calls of the following format <host><rootWebApp><restRoot>/person/list
<host> is quite obvious and <rootWebApp> is the context root of the deployed web application. The <restRoot> will be discussed later in the text.
With the @Get, we specify that the method getList() will be called when the application receives a HTTP GET request on the URL.  Other methods can be called in the case we issue other HTTP command like POST or DELETE.
And the last annotation defines the format of the data the URL will return. Since we are producing data (with a POST you need to Consume data), we use the @Produces annotation.  Here we specified both XML and JSON.  That way we can easily test the outcome of our methods in a browser.

The method to receive the post data when a new Person needs to be added is

    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    @Path("item")
    public Person save(Person person) {
        ...
    }

The annotations are self explaining now that you know the principles behind them.
Also the PUT method is supported.  So for those that want to make a strict distinction between PUT and POST operation, they can make it.

With JAX-RS you can also create a system that responds on more dynamical URL's.  Let's have a look at the delete person method:

    @DELETE
    @Path("item/{id}")
    public void deleteById(@PathParam("id") Long id) {
        personService.removePerson(id);
    }

In the Path annotation, we define here a variable part and the actual value will be used as the parameter of the method due the @PathParam annotation.
Besides the interception of parts of the URL, you can also have access to Query parameters, Header values, Cookies, form parameters and so on. 

Configuration


The only configuration we need to make before all of this works, is the setting of the <restRoot> part of the URL.  There is a default, but I guess that in most real world applications you like to define it yourself.
You can define it in the web.xml with a parameter but I prefer to do it with an annotation on the Application class.

@ApplicationPath("/rest")
public class NamesApplication extends Application {
}

The <restRoot> part of the URL is defined by the ApplicationPath annotation. For our simple use case, we don't need any other configuration, so the class can be empty.

Injections


At the moment, the JAX-RS resource classes, those annotated with @Path, are islands.  So in non hello world situations, they need to retrieve and store the data somewhere.

Since the specification, currently JAX-RS 1.1 is the latest, dates from before the CDI era, the most important injectable resource is the EJB.
Within the specification, there is an explicit mentioning of support for @EJB. One of the other possibilities is using @PersistenceContext to communicate directly with a database.

With the new specification JAX-RS 2.0, JSR-339, targeted for Java EE 7, CDI will be supported of course.  But until then the best option is using EJB's.  CDI is supported for instance in Glassfish (Jersey) and JBoss (RestEasy) but needs some additional jars (RestEasy) or has some issues in certain versions (Glassfish)

By converting our POJO into a stateless EJB (just the @Stateless annotation on the class level) we can inject other EJB's, like services, into the JAX-RS resource class.

@Path("person")
@Stateless
public class PersonDataController {
    @EJB
    private PersonService personService;
...    
}

Servers


Glassfish 3.1.2/wls 12c


Both have the Jersey implementation which is the default implementation for the JAX-RS 1.1 specification.
If you try to show a list of persons, let say a list of 2 persons, it isn't working.
The problem lies in the 'formatting' of the JSON data.

If you inspect the http calls in for instance FireBug, you see the following return value for the call to the restful service.

{"person":[{"firstName":"a","id":"0","lastName":"b"},{"firstName":"x","id":"1","lastName":"y"}]}

And it is not difficult to see that the returned value is not an array of objects, but is an object that contains the list of persons.

Jersey uses the Jettison framework for this JSON support. And looking in the documentation, I found the option to change the ‘formatting’

@Provider
public class JSONContextResolver implements ContextResolver<JAXBContext> {

    private JAXBContext context;

    private Class[] types = {Person.class};

    public JSONContextResolver() throws Exception {
        this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
    }

    public JAXBContext getContext(Class<?> objectType) {
        for (Class type : types) {
            if (type == objectType) {
                return context;
            }
        }
        return null;
    }
}

With this class, see it as an extension to the JAX-RS system; we define our version of the ContextResolver. Classes annotated with @Provider which implement certain classes, look in the specification text for the complete list, are used to customize the way the system works.

Here we define that we use the ‘natural’ convention when the class Person is used.

With this class on the class path, the result of the URL call looks likes this


[{"id":0,"firstName":"a","lastName":"b"},{"id":0,"firstName":"x","lastName":"y"}]
Now we returned an array as top level object which makes AngularJS recognize it correctlyand shows the data.
Another option would be that you use JSONArray (and JSONObject) as the return type of the methods. 

But these classes are jettison specific so that reduces your portability even more. Even this solution is not perfect.  It doesn’t work on all the servers and you need to specify each class

JBoss AS 7.1


This application servers has RestEasy as JAX-RS implementation.  It also supports JSON data and by default, it formats the JSON data in a way which is directly understood by AngularJS

TomEE 1.5.1


The Apache TomEE is based on a Apache Tomcat servlet server which has been enriched with all the required frameworks to make it a Java EE 6 Web profile compliant server.  You can download a version that contains JAX-RS, provided by the Apache CXF framework.

Here we also have support for JSON data but have the same problem when we supply AngularJS with a List of Objects.
However Apache CXF is based on the Spring Framework and I couldn’t find a way to configure the JSON formatting without using Spring.

Since I decided already to go for the universal way, so nesting arrays in a root object, I didn’t try to fix this problem.

Conclusion


Since there are some minor differences in the JAX-RS implementations you should try to keep your code server agnostic.  This means that returning a List of objects as top level should be avoided.

On the other side, with JAX-RS, part of the Java EE 5 and 6 versions, we are able to supply the AngularJS framework with JSON data in a very easy way.

You can find the version for Glassfish and JBoss in the GitHub code repository.

2 comments:

  1. Good day,
    i'm also implementing something similar albeit on different servers and your json formatting is particularly useful to me

    Curious as to how you resolved the Access-Control-allow-Origin issue though?

    ReplyDelete