Friday 20 September 2013

JAX-RS 2.0 using JSON and Persistence with JEE7

This post is about building a basic RESTful application with Maven using the JAX-RS 2.0 API (JSR339: JAX-RS 2.0). The data is exchanged using the JSON format. You will also see how JEE7 can simplify persistence for you. As server we will use Glassfish4 which contains the reference implementation of JEE7. At the end we will test our service with curl and with a unit test.

You can checkout this project from Github or download the archive here.

POM

So let's start. The only dependency we need in our pom.xml is the following:

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

Persistence

To be able to persist the data we first create a basic entity class, nothing special about that.

@Entity
public class Customer {

    @Id
    private long id;
    private String name;
    
    // getters, setters and others
    ...
}

Using JEE7 we now have the possibility of using a default datasource for persistence. The persistence.xml file looks a bit shorter though.

<persistence-unit name="mypu" transaction-type="JTA">
    <properties>
        <property name="javax.persistence.schema-generation.database.action" value="drop-and-create">
    </property></properties>
</persistence-unit>

That's all about your persistence configuration. Featured by JEE7 we will use a default datasource under java:comp/DefaultDataSource. This is defined in Glassfish4 and connects to an integrated Derby database instance.

CRUD

To work with the Customer object we first need a service that handles the persistence. The following stateless bean offers the common CRUD functionality.

@Stateless
public class CustomerService {

    @PersistenceContext
    EntityManager em;

    public void create(Customer entity) {
        em.persist(entity);
    }

    public void update(Customer entity) {
        em.merge(entity);
    }

    public void remove(long id) {
        Customer customer = find(id);
        em.remove(customer);
    }

    public Customer find(long id) {
        return em.find(Customer.class, id);
    }
}

To make sure that Glassfish find our bean we also need the beans.xml in the WEB-INF directory.

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xsi:schemalocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" 
bean-discovery-mode="all"></beans>

Make sure you use the bean-discovery-mode attribute because otherwise the container will not find your bean. At this point we are done with persistence so far.

RESTful

So let's step further to the RESTful part of our project. What we need is another stateless session bean with a method for each CRUD operation we want to support. The only thing we have to do for getting RESTful functionality is to use some annotations. On class level we annotate @Path("customers") as path to our service. Then we use @POST, @GET, @PUT and @DELETE to denote the different RESTful operations. As we want to communicate through JSON we use the MediaType.APPLICATION_JSON.

@Stateless
@Path("customers")
public class CustomerResource {

    @Inject
    CustomerService service;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void create(Customer customer) {
        service.create(customer);
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Customer find(@PathParam("id") long id) {
        return service.find(id);
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    public void update(Customer customer) {
        service.update(customer);
    }

    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") long id) {
        service.remove(id);
    }
}

There is one last thing we need to do to complete our REST configuration. @ApplicationPath("api") defines the global path to our application and this class has to extend javax.ws.rs.core.Application.

@ApplicationPath("api")
public class RESTConfig extends Application {
}

That's all the magic. Start the integrated Derby database with asadmin start-database and fire up your Glassfish server to try your new RESTful service.

curl

You can do a quick functionality check using curl. To send a POST request to your server and thereby create and persist the data do something like this:

curl -i -X POST -d '{"id":1,"name":"Annabelle"}' http://localhost:8080/jee7-rest-crud/api/customers -H 'Content-Type: application/json'

Basically that means, send a POST request containing that string to this address using the JSON format. Doing it right, your server will return something like that:

HTTP/1.1 204 No Content
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  4.0  Java/Oracle Corporation/1.7)
Server: GlassFish Server Open Source Edition  4.0
Date: Fri, 20 Sep 2013 05:11:11 GMT

Status code 204 No Content is perfect here because we defined our RESTful service not to send any data back on a POST request. If you want to read the data you saved before you can send a GET request:

curl -i -X GET http://localhost:8080/jee7-rest-crud/api/customers/1 -H 'Content-Type: application/json'

Your server should return a result like this:

HTTP/1.1 200 OK
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  4.0  Java/Oracle Corporation/1.7)
Server: GlassFish Server Open Source Edition  4.0
Content-Type: application/json
Date: Fri, 20 Sep 2013 05:10:16 GMT
Content-Length: 27

{"id":1,"name":"Annabelle"}

The PUT (update) and DELETE (remove) operations can be tested in the same way.

Unit Test

If you want to unit test your RESTful service you can now use the new ClientBuilder from the JAX-RS 2.0 API coming with JEE7. But you will need some extra dependencies for the JSON data binding because Java do not yet provide that (likely to come with Java 8).

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.6.3</version>
    <scope>test</scope>
</dependency>

To do the data binding for JSON we need to implement the MessageBodyReader<T> and MessageBodyWriter<T> from the javax.ws.rs.ext package. These classes need to be annotated with @Provider and @Consumes respectively @Produces. Check the CustomerMessageBodyReader and CustomerMessageBodyWriter classes of the project for implementation details.

Now we can write a unit test to check the CRUD functionality of our RESTful service.

public class CustomerClientTest {

    private static final String SERVER = "http://localhost:8080/jee7-rest-crud/api/customers";

    private WebTarget target;

    @Before
    public void setUp() {
        Client client = ClientBuilder.newClient();
        client.register(CustomerMessageBodyReader.class);
        client.register(CustomerMessageBodyWriter.class);
        this.target = client.target(SERVER);
    }

    @Test
    public void crud() {

        Customer origin = new Customer(1, "Mathilda");
        Entity entity = Entity.entity(origin, MediaType.APPLICATION_JSON);

        // create
        Response response = target.request(MediaType.APPLICATION_JSON).post(entity, Response.class);
        assertThat(response.getStatus(), equalTo(204));

        // read
        Customer result = target.path(String.valueOf(origin.getId())).request(MediaType.APPLICATION_JSON).get(Customer.class);
        assertThat(result, equalTo(origin));

        // update
        entity.getEntity().setName("Annabelle");
        target.request().put(entity);

        // delete
        target.path(String.valueOf(origin.getId())).request(MediaType.APPLICATION_JSON).delete();
    }
}

First we build the client and register the custom reader and writer classes. Then we set the target to the address of our service. The rest is straight forward testing of the various functionality we have built.

See also