Monday, February 11, 2013

A HelloWorld-example of a Jersey REST service hosted at Google AppEngine

In this short tutorial I will outline the project setup and deployment steps needed to get a Jersey REST-application up and running at Google AppEngine. There are a couple of non-intuitive issues that got me struggling for days - hopefully you won't have to do the same.

1. Environment and AppEngine installation

I will be using the following IDE, framework and SDK versions in this example. Installation of these are out of the scope of this post but I will provide a couple of links:

After installing the AppEngine SDK and the plugin for Eclipse you shall have the Google Toolbar available as well. Use this as a verification that you're done with the installation phase.

2. Project setup

Go to File > New > Other... (or Ctrl+N) and select Google > Web Application Project

Give your project a name and a namespace and unselect "Use Google Web Toolkit", as we won't have any UI in our application. Also uncheck "Generate project sample code".

Now go to Project Properties (Alt+Enter while focusing on the project root in the Project Explorer) and go to Google > App Engine. Select v1 of Datanucleus JDO/JPA version. This is because using v2 will generate a dependency conflict on the file version of asm*.jar which Jersey also depends on.

Now copy all of the following jar files from the Jersey home page into your /war/WEB-INF/lib folder:

JAR fileRole
jersey-bundle-1.17.jarCore Jersey Server components
jettison-1.1.jar
activation-1.1.1.jarPOJO-to-JSON object mapping support
jackson-core-asl-1.9.2.jar
jackson-jaxrs-1.9.2.jar
jackson-mapper-asl-1.9.2.jar
jaxb-api-2.2.4.jar
jaxb-impl-2.2.4-1.jar
stax-api-1.0-2.jar
jackson-xc-1.9.11.jarAllows you to use JAXB annotations and rids of an initial exception if you don't

Note that the links from the Jersey.java.net-page always links to the newest version of Jersey. I can only guarantee that this tutorial works with the exact specified versions of the jar's.

Remove asm-4.0.jar from the lib-folder. Note that you have asm-3.3.1.jar there instead.

Finally right click on jersey-bundle-1.17.jar and select Build Path > Add to build path

3. Implementing a Hello world method

Add a new class HelloWorldObject to the namespace "com.example" or whatever namespace you chose for your project. Let the conent of that class be the following:

public class HelloWorldObject {
 public String text = "Hello world";
}

Add a new class HelloWorldApi to the same namespace. Let it contain this:

import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;

 @Path("/hello")
 public class HelloWorldApi {
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public HelloWorldObject getHelloWorld() {
   return new HelloWorldObject();
  }
 }

Edit the file /war/WEB-INF/web.xml. Add a new servlet definition "HelloWorldServlet":

<?xml version="1.0" (...)>  
 <servlet>
  <description>Hello World Service</description>
  <servlet-name>HelloWorldServlet</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
   <param-name>com.sun.jersey.config.property.packages</param-name>
   <param-value>nilsnett.chinese.backend</param-value>
  </init-param>
  <init-param>
   <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
   <param-value>true</param-value>
  </init-param>
  <init-param>
   <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
   <param-value>true</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>HelloWorldServlet</servlet-name>
  <url-pattern>/api/*</url-pattern>
 </servlet-mapping>

 <servlet>
  <servlet-name>SystemServiceServlet</servlet-name>
  <servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
  <init-param>
   <param-name>services</param-name>
   <param-value />
  </init-param>
 </servlet>
 <servlet-mapping>
  <servlet-name>SystemServiceServlet</servlet-name>
  <url-pattern>/_ah/spi/*</url-pattern>
 </servlet-mapping>
</web-app>
A couple of notes:
  1. The POJOMappingFeature-param is the one enabling support for returning standard Java objects directly from an API method which then gets serialized to JSON behind the scenes using Jackson. See the Jackson documentation on how to tweak the output generated here.
  2. The DisableWADL-parameter disables use of reflection, which is banned at Google App Engine.
  3. I'm leaving the SystemServiceServlet as it is. You might not need this though.

4. Deployment

The deployment part couldn't be easier. Right click on the project root in the Project Explorer, click Google > Deploy to App Engine. If you haven't already, you'll be guided into the App Engine project settings in order to set your Application ID. When that is done and if everything goes well, you should have the service available in about 30 seconds.

5. Testing and further reading

Finally it's time to test the service. The url will be http://yourAppEngineDomain.appspot.com/api/hello. The output should be like this in Chrome:

From this point on, the fun part starts. If you're new to Jersey I a good starting point is to read . A good starting point is the REST with Java (JAX-RS) using Jersey Tutorial by Lars Vogel. That post contains a lot of setup information that is irrelevant for Google AppEngine though, but chapter 2.2 JAX-RS annotations, and the example in chapter 7.3 Rest Service are still very relevant.

9 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hello, thanks for the comment, this is the first tutorial that worked for me (GAE 1.8.0 with Jersey 1.17).
    I only have one question: can i use the JPA somehow?

    Thanks

    ReplyDelete
  3. As I wrote in the blog you can use JPA v1.0, but not v2.0.

    ReplyDelete
  4. I'm trying to make it work with GAE 1.8.0, Jersey 1.17 and jDK 1.7. GAE documentation states that SDK 1.8.0 supports java 7 and that java 6 will be remove in the future.

    Is it possible to make it work with Java 7?

    Thanks

    ReplyDelete
  5. I tried making it work with GAE 1.8.1 and all the library versions you have listed on the site and this is what I get when I run it:

    java.lang.NoClassDefFoundError: org/objectweb/asm/ClassVisitor
    at com.sun.jersey.api.core.ScanningResourceConfig.init(ScanningResourceConfig.java:79)
    at com.sun.jersey.api.core.PackagesResourceConfig.init(PackagesResourceConfig.java:104)
    at com.sun.jersey.api.core.PackagesResourceConfig.(PackagesResourceConfig.java:78)
    at com.sun.jersey.api.core.PackagesResourceConfig.(PackagesResourceConfig.java:89)
    at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:696)
    at com.sun.jersey.spi.container.servlet.WebComponent.createResourceConfig(WebComponent.java:674)
    at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:203)
    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:374)
    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:557)
    at javax.servlet.GenericServlet.init(GenericServlet.java:212)
    at org.mortbay.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:440)
    at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:263)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685)
    at org.mortbay.jetty.servlet.Context.startContext(Context.java:140)
    at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250)
    at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517)
    at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.jetty.Server.doStart(Server.java:224)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:249)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:307)
    at com.google.appengine.tools.development.AutomaticServerInstanceHolder.startUp(AutomaticServerInstanceHolder.java:26)
    at com.google.appengine.tools.development.AbstractServer.startup(AbstractServer.java:80)
    at com.google.appengine.tools.development.Servers.startup(Servers.java:82)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:237)
    at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:339)
    at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:48)
    at com.google.appengine.tools.development.DevAppServerMain.(DevAppServerMain.java:274)
    at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:250)
    Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.ClassVisitor
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at com.google.appengine.tools.development.IsolatedAppClassLoader.loadClass(IsolatedAppClassLoader.java:215)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)


    Any ideas?

    ReplyDelete
    Replies
    1. Googling the ClassVisitor-class, I see it's a part of the asm-jar. Please check that asm-3.3.1.jar is in your war/libs-folder and NOT asm-4.0.jar (4.0 is correct if you use JPA 2.0, which you can't with Jersey)

      Delete
  6. Felipe: I don't know. I guess one just have to try. I don't have time to test it right now, but I'd be very interesting to hear if you get it working!

    ReplyDelete
  7. Note the difference param value in web.xml and the actually package name com.example
    This causes a runtime error

    ReplyDelete
  8. I am a newbie. This was a very helpful tutorial for me to understand. I tried almost 3-4 tutorials but all of them didn't work because of some differences in versions.

    ReplyDelete