Marc Schwieterman

That depends...

Roo-based Jersey Apps on GAE

This past weekend I put together an example of a Spring Roo-based Jersey application that can run on Google App Engine. All code in this post is derived from the example project at https://github.com/marcisme/jersey-on-gae-example. You should be able to get a project up and running by copy and pasting the snippets in this entry, or you can clone the project and follow along. This example uses Spring Roo 1.1.1, Jersey 1.5 and App Engine SDK 1.4.0. A basic understanding of the involved technologies is assumed.

Project

The first step is to create your project. Create a directory for the project, fire up the Roo shell and then enter the commands below. This will both create a new project and configure it to use App Engine for persistence. Make sure to substitute an actual app id for your-appid if you plan to deploy this example.

1
2
project --topLevelPackage com.example.todo
persistence setup --provider DATANUCLEUS --database GOOGLE_APP_ENGINE --applicationId your-appid

Dependencies

Once you’ve set up your project for GAE, you’ll need to add the dependencies for Jersey and JAXB. If adding them manually, you can refer to the dependencies section of the Jersey documentation. In either case, you’ll need to add the Jersey repository to your pom.xml.

1
2
3
4
5
6
<repository>
    <id>maven2-repository.dev.java.net</id>
    <name>Java.net Repository for Maven</name>
    <url>http://download.java.net/maven/2/</url>
    <layout>default</layout>
</repository>

Jersey

Once you’ve added the Jersey repository, you can use these command to add the dependencies to your project.

1
2
3
4
dependency add --groupId com.sun.jersey --artifactId jersey-server --version 1.5
dependency add --groupId com.sun.jersey --artifactId jersey-json --version 1.5
dependency add --groupId com.sun.jersey --artifactId jersey-client --version 1.5
dependency add --groupId com.sun.jersey.contribs --artifactId jersey-spring --version 1.5

Unfortunately the jersey-spring artifact depends on Spring 2.5.x. Because Roo is based on Spring 3.0.x, you need to add some exclusions to prevent pulling in incompatible versions of Spring artifacts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-spring</artifactId>
    <version>1.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </exclusion>
    </exclusions>
</dependency>

JAXB

App Engine has issues with some versions of JAXB. I found 2.1.12 to work, while 2.1.13 and 2.2.x versions did not. This will hopefully change in the future. You can add a dependency on JAXB with the following command.

1
dependency add --groupId com.sun.xml.bind --artifactId jaxb-impl --version 2.1.12

Spring

If you’re adding jersey to a project that uses Roo’s web tier, you’ll already have the spring-web dependency in your project. If not, you’ll need to add that too.

1
dependency add --groupId org.springframework --artifactId spring-web --version ${spring.version}

You can specify an explicit version, but if you created your project with Roo, the spring.version build property should be set. Either way you’ll want to exclude commons-logging.

1
2
3
4
5
6
7
8
9
10
11
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Maven GAE Plugin

For some reason Roo uses a rather old version of the Maven GAE Plugin. The latest version at the time of this writing is 0.8.1. In addition to what Roo will have created for you, you’ll want to bind the start and stop goals if you plan on running integration tests. See the pom.xml in the example project for an example of how to do that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin>
    <groupId>net.kindleit</groupId>
    <artifactId>maven-gae-plugin</artifactId>
    <version>0.8.1</version>
    <configuration>
        <unpackVersion>${gae.version}</unpackVersion>
    </configuration>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>unpack</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Entity

Next you’ll want to create an entity, which you can do with the commands below.

1
2
3
4
5
6
7
enum type --class ~.Status
enum constant --name CREATED
enum constant --name DONE

entity --class ~.Todo --testAutomatically
field string --fieldName description
field enum --fieldName status --type ~.Status

In order to leverage Jersey’s JAXB serialization features, you’ll need to annotate your entity with @XmlRootElement. Set a default value for status while you’re here.

1
2
3
4
5
6
7
8
9
10
11
12
import javax.xml.bind.annotation.XmlRootElement;

@RooJavaBean
@RooToString
@RooEntity
@XmlRootElement
public class Todo {

    @Enumerated
    private Status status = Status.CREATED;

...

Resource

Here’s a very simple resource for the entity we created earlier. In addition to the JAX-RS annotations, you also need to annotate the class with @Service, which makes the class eligible for dependency injection and other Spring services. This resource will support both XML and JSON.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.todo;

import org.springframework.stereotype.Service;

import javax.ws.rs.*;
import java.util.List;

@Service
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
@Path("todo")
public class TodoResource {

    @GET
    public List<Todo> list() {
        return Todo.findAllTodoes();
    }

    @GET
    @Path("{id}")
    public Todo show(@PathParam("id") Long id) {
        return Todo.findTodo(id);
    }

    @POST
    public Todo create(Todo todo) {
        todo.persist();
        return todo;
    }

    @PUT
    public Todo update(Todo todo) {
        return todo.merge();
    }

    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") Long id) {
        Todo.findTodo(id).remove();
    }

}

Final Configuration

At a minumum, you’ll need to have Spring’s OpenSessionInView and Jersey’s SpringServlet filters set up in your web.xml. As with the spring-web module dependency, you will have to create a web.xml in src/main/webapp/WEB-INF if you don’t already have one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<web-app>

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
    </context-param>

    <!-- Ensure a Hibernate Session is available to avoid lazy init issues -->
    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Handles Jersey requests -->
    <servlet>
        <servlet-name>Jersey Spring Web Application</servlet-name>
        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Jersey Spring Web Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

You may also need to change the project’s packaging type to war.

1
2
3
4
<groupId>com.example.todo</groupId>
<artifactId>todo</artifactId>
<packaging>war</packaging>
<version>0.1.0.BUILD-SNAPSHOT</version>

Testing

You can now compile and run the application locally with mvn clean gae:run.

1
2
3
> mvn clean gae:run
...
INFO: The server is running at http://localhost:8080/

The following curl commands can be used to interact with the running application.

list
1
curl -i -HAccept:application/json http://localhost:8080/todo
show
1
curl -i -HAccept:application/json http://localhost:8080/todo/1
create
1
2
curl -i -HAccept:application/json -HContent-Type:application/json \
  http://localhost:8080/todo -d '{"description":"walk the dog"}'
update
1
2
3
4
curl -i -HAccept:application/json -HContent-Type:application/json \
  http://localhost:8080/todo \
  -d '{"description":"walk the dog","id":"1","status":"DONE","version":"1"}' \
  -X PUT
delete
1
curl -i http://localhost:8080/todo/1 -X DELETE

Once you’re happy with your application, you can upload it to App Engine. If you download the example project I created, you can build locally with mvn clean install -Dtodo-appid=<your-appid>. The local App Engine server will be used to run some basic integration tests during the build. After the app is built, you can deploy it with mvn gae:deploy. You will be prompted for your login information if you have not set up your your credentials in your settings.xml. Once deployed, you can run the included integration test against the live server with mvn failsafe:integration-test -Dgae.host=<your-appid>.appspot.com -Dgae.port=80. There is also a simple client you can use to write your own tests or experiment with.

Having some level of integration tests for any GAE apps you write is very important, as there are things that do not work consistently between the local development server and the real App Engine servers. Be aware that you can access the local development server console at http://localhost:8080/_ah/admin, which will let you browse the datastore amongst a few other things. Once deployed to the real App Engine, you’re best source of information is the application log. Be sure you’re looking at the correct version; there’s a drop-down menu in the upper left area of the screen that will let you choose the version of the logs you want to examine.