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:
- Java Development Kit (JDK) 1.6 SE JDK 1.7 is not fully supported by AppEngine
- AppEngine SDK v1.7.4
- Eclipse Juno EE
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 ProjectGive 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 file | Role |
---|---|
jersey-bundle-1.17.jar | Core Jersey Server components |
jettison-1.1.jar | |
activation-1.1.1.jar | POJO-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.jar | Allows 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:
- 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.
- The DisableWADL-parameter disables use of reflection, which is banned at Google App Engine.
- 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.
This comment has been removed by the author.
ReplyDeleteHello, thanks for the comment, this is the first tutorial that worked for me (GAE 1.8.0 with Jersey 1.17).
ReplyDeleteI only have one question: can i use the JPA somehow?
Thanks
As I wrote in the blog you can use JPA v1.0, but not v2.0.
ReplyDeleteI'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.
ReplyDeleteIs it possible to make it work with Java 7?
Thanks
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:
ReplyDeletejava.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?
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)
DeleteFelipe: 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!
ReplyDeleteNote the difference param value in web.xml and the actually package name com.example
ReplyDeleteThis causes a runtime error
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