Sunday, February 28, 2010

Create and Deploy a JAX-RS REST service on Google App Engine

In this article you will learn how to create a REST service using JAX-RS reference implementation (Jersey) and deploy it on Google AppEngine.

Prerequisites

For this tutorial you will need:
  • a Google AppEngine account : http://code.google.com/appengine/
  • Eclipse Galileo (3.5.x)
  • Google App Engine SDK for Java

    • Install the Google Plugin for Eclipse as documented here (Check that you are using the release 1.3.1 of the GAE Java SDK, if not download it and configure the plugin to use it)
    • it is also useful to have the AppEngine documentation locally, you can download it from here.
  • JAX-RS Reference Implementation, be sure you take the Jersey 1.1.5 release. You can download it from here.

    • Unzip the file in a directory that we will call $JERSEY_HOME
  • JAXB 2.2 Implementation to simplify the marshalling/unmarshalling of the XML, and also facilitate the JSON support. Download it from here 


    • Install it using thejava -jar JAXB2_20091104.jar command.  The installation directory of JAXB will be called $JAXB_HOME

Creating new application

To create a new App Engine project in Eclipse:
  1. Click on the "New Web Application Project" button in the toolbar . It is also possible to do it using the menu File > Web Application Project

  2. The "Create a Web Application Project" wizard opens:
  • Project Name: EmployeeService
  • Package : com.grallandco.employee.service
  • Uncheck "Use Google Web Toolkit"
  • Check that the SDK version your are using is "App Engine 1.3.0"; if not configure the project to use it.
  • The screen should look like the following screen :


  • Click Finish

  • The project should look like the following screen :



Running the application

The App Egine SDK, installed with the Eclipse plugin contains a Web server (based on Jetty), that could be used for testing and debugging. To test that your application has been created correctly select the menu Run > Run As > Web Application. I personnaly most of the time run my server using the debug command Run > DebugAs > Web Application. In debug mode you can change source code and test is without restarting the server.

The web server is starting automatically, you should see the following message in the Eclipse console

The server is running at http://localhost:8080/

You can access the application, and the sample servlet that has been created using the URL: http://localhost:8080/employeeservice

To stop the server, click on the terminate button in the Eclipse console.

Configuring the REST support in the application

To be able to create and run REST services in your application you need to:
  • Add the JAX-RS, JAXB Jars in your project and application
  • Configure the web application (web.xml) to handle REST requests
Add JAX-RS, JAXB to your project
  1. Right click on the project and select menu entry Build Path > Configure Build Path...
  2. Click on the Add External JARs button

  3. Select all the JARs located in $JERSEY_HOME/lib  and $JAXB_HOME/lib folders. You can for better visibility and reuse create a user library with all these JARs
  4. You also need to copy the JARs in the web-inf/lib directory of your application, this step is mandatory to be sure that the JARs are included in the application when deployed to App Engine.
    Note: I do not like this step. I would prefer to do that by configuration of the build path, to automatically add the JARs to the WEB-INF/lib directory when executing/deploying the application. Unfortunately I did not find the way to do it, so if you know it, feel free to post a comment and I will update the article.

Configure the web application

In this step you will register a new URI to handle REST requests. To do that you need to register a new servlet that is using the Jersey API and configure it to a specific URI (eg: /ressources  and/or /rest) and configure what are the Java packages that contain the REST implementation classes. So you need to modify the web.xml of your application with the following entries:

  <servlet>
    <servlet-name>Jersey Web Application</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>com.grallandco.employee.service.rest.impl</param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Jersey Web Application</servlet-name>
    <url-pattern>/resources/*</url-pattern>
  </servlet-mapping>
   <servlet-mapping>
    <servlet-name>Jersey Web Application</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>


This servlet that will answer to the /resources/ and /rest/ URL. The configuration parameter com.sun.jersey.config.property.packages is used by Jersey to list the packages where REST services implementation are located.Note that you can put as many package as you need to, you just need to separate the package names by a ; .


Creating a simple REST Service to test the environment

The project is now ready to contain REST service. It is time to create one.Create for example the class com.grallandco.employee.service.rest.impl.HelloWorldResource, be sure to use the package name that you have configured in the web.xml for the Jersey servlet, based on the configuration we have made in previous step the package is com.grallandco.employee.service.rest.impl

Here a sample class with the JAX-RS annotations:
package com.grallandco.employee.service.rest.impl;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
@Path("/hr/")
public class EmployeeResource {
 
 @GET
 @Produces("text/plain")
 @Path("/employee") 
 public String getEmployee() {
        return "Hello World!";
    }
}
You should be able to test it, stop the server and run it again, enter the following URL in your browser:
http://localhost:8080/resources/hr/employee
or
http://localhost:8080/rest/hr/employee


Deploying the application to Google App Engine

Before deploying the application you need to register a new application in Google App Engine using the Administartion Console, see the documentation here. In my example I have used "tugdual" as Application ID.
You can easily now deploy the application to Google App Engine by clicking on the  "Deploy App Engine Project" button Deploy App Engine Project Button available in the Eclipse toolbar.
To be able to deploy your application to Google App Engine, you need to check that your application can be registered, the application ID is stored in the WEB-INF/lib/appengine-web.xml.
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
 <application>[your-application-id]</application>   
 <version>1</version>    
 <!-- Configure java.util.logging -->
  <system-properties>
   <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
 </system-properties>    
</appengine-web-app>

The App Engine deploy button prompts you for multiple informations: username (your Google account) and password.

When the deployment is complete you can access your application using the following URL:
http://[your-application-id].appspot.com/resources/hr/employee
or
http://[your-application-id].appspot.com/rest/hr/employee

Ading XML and JSON support to the service

Let's now add new method to manipulate an "Employee" object using the service, and the data format should be based on JSON and XML. This is where JAXB is useful, since it allows easily to transform marshall/unmarshall Java objects in XML -obviously- and JSON (cool isn't!)

Creating an Employee Class

Start with the creation of a new class to manipulate Employee data, this is a very simple Java class that could look like the following code:

package com.grallandco.employee.service.model;
import java.util.Date;

public class Employee {
    private String firstName;
    private String lastName;
    private Date hireDate;
    private String email;   
    public Employee(String firstName, String lastName, Date hireDate, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.hireDate = hireDate;
        this.email = email;
    }
    public Employee() {}
    public String getFirstName() {
 return firstName;
 }
    public void setFirstName(String firstName) {
 this.firstName = firstName;
 }
    public String getLastName() {
 return lastName;
 }
    public void setLastName(String lastName) {
     this.lastName = lastName;
 }
    public Date getHireDate() {
 return hireDate;
 }
    public void setHireDate(Date hireDate) {
 this.hireDate = hireDate;
 }
    public String getEmail() {
        return email;
 }
    public void setEmail(String email) {
        this.email = email;
   }
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("First: ").append(getFirstName());
        sb.append(" - Last: ").append(getLastName());
        sb.append(" - Date: ").append(getHireDate());
        sb.append(" - Email: ").append(getEmail());
        return sb.toString();
    }
}
When implementing your "real" application with some persistence layer this POJO is the one as JDO/JPA entity.

Create a Converter class for your entity

I usually encapsulate all the transformation in some converter class, like that I do not directly couple my business class to the serialisation mechanism. (So I do that for classes and lists of classes). So instead of adding the JAXB annotations to the Employee class itself, let's create an EmployeeConverter class that will be responsible of the transformation and used by your REST service.

package com.grallandco.employee.service.converter;

import java.util.Date;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.grallandco.employee.service.model.Employee;

@XmlRootElement(name = "employee")
public class EmployeeConverter {
 private Employee entity = null;
 public EmployeeConverter() {
 entity = new Employee();
 }

 public EmployeeConverter(Employee entity) {
 this.entity = entity;
 }

 @XmlElement
 public String getFirstName() {
 return entity.getFirstName();
 }

 @XmlElement
 public String getLastName() {
 return entity.getLastName();
 }

 @XmlElement
 public Date getHireDate() {
 return entity.getHireDate();
 }

 @XmlElement
 public String getEmail() {
 return entity.getEmail();
 }

 public Employee getEmployee() {
 return entity;
 }

 public void setFirstName(String firstName) {
 entity.setFirstName(firstName);
 }

 public void setHireDate(Date hireDate) {
 entity.setHireDate(hireDate);
 }

 public void setLastName(String email) {
 entity.setEmail(email);
 }

 public void setEmail(String lastName) {
 entity.setLastName(lastName);
 }
}
You can now update your service to use this utility/converter class to return XML or JSON ojbect based on the content type of the request.

Add support to JSON and XML to your REST service

You need to change the EmployeeRessource class, to change the signature and add new annotations of the getEmployee() method.
The annotation you are adding:
  • @Produces({"application/xml", "application/json"}) : indicates which type of content will be produced by the service. Based on the type of the request.
  • @Path("/employee/{employeeEmail}/") :    change the Path to indicate a Path parameter, here for example the URL can accept an email in the URI - not the best example, but you get the point...
  • public EmployeeConverter getEmployee( @PathParam ("employeeEmail") String email)  : change the type returned by the method and take a parameter as String that match the Path param defined in the @Path annotation
Here the complete class code:
package com.grallandco.employee.service.rest.impl;

import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import com.grallandco.employee.service.converter.EmployeeConverter;
import com.grallandco.employee.service.model.Employee;

@Path("/hr/")
public class EmployeeRessource {

 
 @GET
 @Produces({"application/xml", "application/json"})
 @Path("/employee/{employeeEmail}/") 
 public EmployeeConverter getEmployee( @PathParam ("employeeEmail") String email) {
 //dummy code
 Employee emp = new Employee();
 emp.setEmail(email);
 emp.setFirstName("John");
 emp.setLastName("Doe");
 EmployeeConverter converter = new EmployeeConverter(emp);
 return converter;
 } 
}


Test the service

You can now run the server locally and test the service
http://localhost:8080/resources/hr/employee/tug@grallandco.com
This will return an XML document.
If you want to test the JSON call you have multiple choice:
  • Using following command
tgrall$ curl -H "Accept: application/json" http://localhost:8080/resources/hr/employee/tug@grallandco.com
{"email":"tug@grallandco.com","firstName":"John","lastName":"Doe"}
  • Using an HTTP client that allows your to configure/set the HTTP request completely, I am using the Poster Firefox Plugin
  • Using some Javascript code in an application
You can repeat the test on your deployed application on Google App Engine.

Conclusion

In this article you have learned how to create and deploy a new REST Service on Google App Engine. This service has been created with the JAX-RS Reference Implementation the Jersey project. In the next article you will learn how to add persistence and create a CRUD Rest service on Google App Engine.

32 comments:

  1. For the question about adding the JARs to the WEB-INF/lib directory, you can take advantage of the Eclipse Builders:

    1. Create an ANT file to do the copy like:
    <?xml version="1.0" encoding="UTF-8"?>
    <project name="JerseyLibraryDeploy" default="copylibs">
      <target name="copylibs" description="description">
        <copy todir="war/WEB-INF/lib/">
          <fileset dir="jersey-1.3/lib">
            <include name="**/*.jar" />
          </fileset>
        </copy>
      </target>
    </project>

    2. Right click on project, Properties

    3. Select Builders

    4. Add new build step

    5. Select "Ant"

    6. Name it "Copy Jersey libraries" for exemple

    7. Buildfile: Use "Browse workspace" to set value "${workspace_loc:/myProject/build.xml}" (assuming your eclipse project is named "myProject")

    8. Base directory: Use "Browse workspace" to set value "${workspace_loc:/myProject}"

    9. Do not forget to visit the tab Refresh and select "Refresh resource on completion", choose "The project containing the selected resource" and tick "Recursive (...)"

    Eclipse will now copy your jars from the original location to the WEB-INF dir every time the project is built.
    You should get the trace on console:

    Buildfile: /home/eric.lemerdy/wrkspc/myProject/build.xml

    copylibs:
      [copy] Copying 8 files to /home/eric.lemerdy/wrkspc/myProject/war/WEB-INF/lib
    BUILD SUCCESSFUL
    Total time: 422 milliseconds

    Regards,

    ReplyDelete
  2. Thanks Tug, I used your tutorial and it worked well. The one issue I'd like to mention is that I had to use JAXB version 2.1.12 or else I got an ExceptionInInitializerError when running deployed to Google App Engine.

    2.1.13 or higher seems to cause a problem. You can find 2.1.12 here...

    https://jaxb.dev.java.net/2.1.12/

    ReplyDelete
  3. Wow this was a really great tutorial. It was exactly what I needed to get going. Thanks for putting it together.

    I think you have a typo in your second version of EmployeeResource, you have the class name as "EmployeeRessource"(extra s) which could be confusing to someone cutting and pasting the code into Eclipse.


    But again, thanks for the tutorial.

    ReplyDelete
  4. Also, you say to copy all .jar files from JAXB and Jersey into your WEB-INF/lib. This was causing all sorts of exceptions for me when I deployed to GAE (not locally). The only .jars you need to put into the WEB-INF/lib are: asm-3.1.jar, jersey-core-1.1.5.jar, jersey-server-1.1.5.jar, jsr311-api-1.1.1.jar

    and then you're good.

    ReplyDelete
  5. Nice little article.

    Any chance of a next article for adding CRUD as mentioned at the end ?

    ReplyDelete
  6. Nice little article.

    Any chance of a next article as you mentioned at the end ?

    Cheers,
    Niklas

    ReplyDelete
  7. Hey,

    Thanks for sharing this link - but unfortunately it seems to be down? Does anybody here at tugdualgrall.blogspot.com have a mirror or another source?


    Thanks,
    Thomas

    ReplyDelete
  8. Thanks so much for your post! It put me in the right direction when I've been having a frustrating time putting this together!

    ReplyDelete
  9. With JAXB 2.2.3,
    On GAE, we have to disable WADL generation on Jersey


    com.sun.jersey.config.feature.DisableWADL
    true
    07


    http://java.net/jira/browse/JERSEY-630

    ReplyDelete
  10. What about security ? Is there a way to secure the WS using Google Accounts ?

    ReplyDelete
  11. Kudos to the author for a great REST on GAE tutorial. It's just what I needed! And what of the CRUD Rest service on Google App Engine? Is this still to be forthcoming?

    ReplyDelete
  12. Really excellent tutorial! Thank you.
    Will the next article on adding persistence and create a CRUD Rest service on Google App Engine be forthcoming? That looked very interesting indeed.

    ReplyDelete
  13. Tug,

    I managed to get this working on my desktop without any JSON or XML support, I deployed it to appengine, which reported success, but when I try to run it I get the following error in the logs:

    java.lang.NoClassDefFoundError: Could not initialize class com.sun.xml.bind.v2.runtime.reflect.opt.Injector

    After a bit of trawling round the web I tried adding a copy of jaxb-impl-2.1.5.jar.

    This got things a bit further but then I got:

    Caused by: java.lang.SecurityException: java.lang.IllegalAccessException: Reflection is not allowed on protected final java.lang.Class java.lang.ClassLoader.findLoadedClass(java.lang.String).

    Not sure where to go from here, any ideas?

    ReplyDelete
  14. Tug,

    I managed to get this working on my desktop without any JSON or XML support, I deployed it to appengine, which reported success, but when I try to run it I get the following error in the logs:

    java.lang.NoClassDefFoundError: Could not initialize class com.sun.xml.bind.v2.runtime.reflect.opt.Injector

    After a bit of trawling round the web I tried adding a copy of jaxb-impl-2.1.5.jar.

    This got things a bit further but then I got:

    Caused by: java.lang.SecurityException: java.lang.IllegalAccessException: Reflection is not allowed on protected final java.lang.Class java.lang.ClassLoader.findLoadedClass(java.lang.String).

    Not sure where to go from here, any ideas?

    ReplyDelete
  15. Hello 8002E,

    Are you sure that the JAXB jar is packaged in your WAR file?

    For me this is just a packaging issue.

    Tug

    ReplyDelete
  16. Hi,

    any idea how i can learn the CRUD operations for rest service on Google app engine??

    thanks a lot!
    Sidhant

    ReplyDelete
  17. Thanks for the post. It was really helpful to get me started with Jerseyon GAE. I hope I will be able to build it out from here into a fully functional REST API

    ReplyDelete
  18. Thanks for the post. After getting me started with Jersey on GAE, hopefully this will enable me to build out a fully functional API.

    ReplyDelete
  19. can I develop rest web service with out Google app engine

    ReplyDelete
  20. hi, all works fine locally, but when I try to run it on GAE, when I press the link EmployeeService I get "Error: Server Error
    The server encountered an error and could not complete your request.
    If the problem persists, please report your problem and mention this error message and the query that caused it."

    and looking in the GAE Administrator Admin Logs it says "API serving not allowed for this application"

    I've searched for possible reasons for the error, and find suggestions that I need to sign up for Google's trusted tester programme!

    Is that really the case - not mentioned in this tutorial :-(

    Tim

    ReplyDelete
  21. Hello Tim,

    Which error do you have in the log (on the GAE console?).

    Which version on JAX-RS are you using? this post is quite old now and I have upgraded my application to Jersey 1.14 to avoid error related to some package not being in the GAE white list

    ReplyDelete
  22. Hi Tug, I have the exact same problem as Tim. I am using Jersey-1.16, Appengine 1.7 and JRE 1.6.

    ReplyDelete
  23. Great article .. Thank you very much Tug.

    ReplyDelete
  24. I am getting following error though it works fine locally.

    Uncaught exception from servlet
    java.lang.IncompatibleClassChangeError: Implementing class
    at com.google.appengine.runtime.Request.process-cc7b6106c16d6f89(Request.java)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:794)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:359)
    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 com.google.tracing.TraceContext$TraceContextRunnable.runInContext(TraceContext.java:435)
    at com.google.tracing.TraceContext$TraceContextRunnable$1.run(TraceContext.java:442)
    at com.google.tracing.CurrentContext.runInContext(CurrentContext.java:186)
    at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContextNoUnref(TraceContext.java:306)
    at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:298)
    at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:439)
    at java.lang.Thread.run(Thread.java:722)

    I 2013-08-14 08:15:34.356

    This request caused a new process to be started for your application, and thus caused your application code to be loaded for the first time. This request may thus take longer and use more CPU than a typical request for your application.

    ReplyDelete
  25. CM, which version of the SDK are you using?

    Note that when I moved to SDK 1.8.x I completely remove JAX-RS and use my own REST services based on simple servlets. (too much dependencies issue with JDO, JAX-RS and ASM)

    It was faster for me to create a simple REST API with Servlet + Jackson

    t

    ReplyDelete
  26. Hi Tug,

    I tried the way you explained it. But I did not get the right dependent jars and because of that its not running. DO you have the code and jars with you? If yes, would you please share that

    Regards
    Vishal

    ReplyDelete
  27. Hi I tried implementing the following service.

    Btw, I had to add "Jersy jar" files also to make it work. because it was giving me compilation errors in

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

    Now the service runs on localhost perfectly.

    But when i deploy the application to google app engine, It doesn't work. I did tried the given url and several others too, to execute the rest service.

    Please help.

    ReplyDelete
  28. Will you be posting the second part to this article any time soon? First part is very informative thanks

    ReplyDelete
  29. Hi,
    I just tried to implement JAX-RS rest service in app engine.
    This is working fine, if I am running in local environment, but when I am deploying this service in Google App Engine
    http///rest/hr/employee
    I am getting 500 Internal Server Error,
    In the logs , I am not able to see the error.

    App Engine SDK: 1.19.17
    JAX-RS jars-1.1.5
    JAXB jars- 2.2.11

    ReplyDelete
  30. maybe worth a try to add the following libs in the WEB-INF/lib folder:
    - jackson-annotations-2.3.2
    - jackson-module-jaxb-annotations-2.1.3
    - jersey-media-json-jackson-2.13

    ReplyDelete
  31. Hi,

    I tried to implement JAX-RS rest service in the google app engine as documented above. Only difference is the Google App Engine version 1.9.21, JDK 1.7, jersey-bundle 1.15 jersey core 1.17.

    There are 2 asm jar 3.3.1 (jersey RS dependency) and 4.0 (from google app engine), may be this could be the reason for failure on the google server.

    Locally the service works using eclipse google plugin.

    ReplyDelete