Lqd's Journal

Icon

It's actually pronounced liquid!

Hacking Jersey/JAX-RS to run RESTful web services on Google AppEngine/Java

Update: this article is about Jersey 1.0.2, i’ll update the modifications i made for the 1.1.0-ea version soon.

Jersey doesn’t run out of the box on GAE, and since i can’t wait for a new release to try RESTful services on AppEngine/Java, let’s modify its servlet container so it will at least run basic samples.

I really find Jersey/JAX-RS compelling, while others think it could be the ultimate framework, and with all the recent buzz about the AppEngine platform, i wanted to try and make the two meet, grab a little dinner, a glass of wine or two, and make sweet web service babies. That didn’t go as easily as I planned, but after a little hacking i was able to run basic examples, which is probably enough for now, until a later release fixes the issues.

In this post i’ll describe what i did to make Jersey’s basic servlet example work (ie. what i broke) and provide you with the code (code + binary here) so you can try it on your own.

AppEngine runs a tight ship with strict security policies, which caused most of the errors you get using Jersey: classes you can’t access inside their sandbox. I’ve done my best bypassing those errors, and in doing so i actually removed and broke features, some of which i know about, the others, well, i don’t (:

The people following my twitter stream know i learned the hard way that the dev server google provides with its eclipse plugin doesn’t enforce the same security policies as their appspot servers (why is that is a question to which i don’t have the answer), and the kicker is that Jersey runs fine in their dev server….

The first major error you encounter is related to javax.naming.InitialContext, the second one to JAXB binding for WADL generation. I removed both the WADL pregeneration and root resources registration in the JNDI context. I don’t care much for WADL so it’s not a big loss anyway, however i’m not totally sure how the JNDI context is used inside Jersey. This might break something more useful.

A couple more classloader errors, and other tiny issues, and we’re good to go.

Here’s how to make the Jersey basic servlet sample (simple-servlet) run on AppEngine (which you’ll be able to see here for a little while, if you want to test it. It comes with a little ajax ui changing Accept headers, choosing resources to test, etc)

Create a GAE using the Google plugin for Eclipse, add the jersey jars into the war/WEB-INF/lib directory (i used the jersey archive found here to get Jersey, its dependencies and the samples), while you’re at it also add the modified jersey servlet container there, jersey-appengine-container_0.1.jar found in the linked zip file.

Add the modified servlet (org.hybird.appengine.jersey.container.ServletContainer) to web.xml:

<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.hybird.appengine.jersey.container.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.sun.jersey.samples.servlet.resources</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>

Now you can take the simple-servlet sample, copy its index.html in the war folder, and java source files inside your src. And if i remember correctly, that should be it ^^ and you’ll get the same app as i have.

Since Jersey has so many features (a lot more than the JAX-RS spec would let you believe), i haven’t had the time to test a lot of them yet, and i think other security exceptions are probably waiting for us.

Category: java

Tagged: , ,

12 Responses

  1. Paul Sandoz says:

    Great! thanks for trying this out.

    The next release (after 1.0.3) will include changes so that Jersey will work gracefully with GAE, but with limited functionality as some packages like that for JAXB are not available for use at runtime, which is the reason why WADL support causes things to fail (JAXB classes are visible to the class loader but JAXBContext.newInstance fails with a java.lang.NoClassDefFoundError).

  2. James says:

    It looks like it’s not populating the ${it} instance, when you return a new Viewable(“index”, this). If I don’t have access to it from my jsp, then it’s pretty much useless for me. Have you run into this problem?

    Thanks!

  3. lqd says:

    No, not yet, i’ll see if i can do anything about that. But most likely it’s because of the classes that are unavailable in AppEngine.

  4. Bruce says:

    Thanks for posting this article. I needed to deploy a simple RESTful web service example for my team to view (http://brucerestfulwsexample.appspot.com/) on Google AppEngine/Java.

    Initially, I had a problem using your jersey-appengine-container_0.1.jar with my project. Apparently, your jar doesn’t work with the 1.1.0-ea version of the Jersey jars that I was using. I had to replace that version with 1.0.2 Jersey jar files and then your jersey-appengine-container_0.1.jar worked fine.

    Bruce

  5. lqd says:

    Thanks for the comment Bruce, that’s nice to hear !

    I’ve updated the article to specifiy it’s a modified Jersey 1.0.2. I’ll look at updating it for 1.1.0-ea as soon as i can.

  6. Lemao says:

    First of all, thanks for the patch.

    Unfortunately I am still getting errors when deploying to my appengine account.

    (I am using your patch with Jersey 1.0.2)

    This is the exception I am seeing. Any ideas?

    thanks,

    com.sun.jersey.core.spi.component.ProviderFactory _getComponentProvider: The provider class, class com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App, could not be instantiated
    java.lang.SecurityException: Unable to get members for class com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App
    at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:357)
    at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:347)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMembers(Class_.java:347)
    at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMethods(Class_.java:170)
    at com.sun.jersey.core.reflection.MethodList.(MethodList.java:57)
    at com.sun.jersey.core.spi.component.ComponentConstructor.getPostConstructMethod(ComponentConstructor.java:124)
    at com.sun.jersey.core.spi.component.ComponentConstructor.(ComponentConstructor.java:118)
    at com.sun.jersey.core.spi.component.ProviderFactory.getInstance(ProviderFactory.java:205)
    at com.sun.jersey.core.spi.component.ProviderFactory._getComponentProvider(ProviderFactory.java:133)
    at com.sun.jersey.core.spi.component.ProviderFactory.getComponentProvider(ProviderFactory.java:126)
    at com.sun.jersey.core.spi.component.ProviderServices.getComponent(ProviderServices.java:168)
    at com.sun.jersey.core.spi.component.ProviderServices.getProvidersAndServices(ProviderServices.java:120)
    at com.sun.jersey.core.spi.factory.MessageBodyFactory.getProviderMap(MessageBodyFactory.java:136)
    at com.sun.jersey.core.spi.factory.MessageBodyFactory.initReaders(MessageBodyFactory.java:110)
    at com.sun.jersey.core.spi.factory.MessageBodyFactory.init(MessageBodyFactory.java:105)
    at org.hybird.appengine.jersey.container.WebApplicationImpl.initiate(WebApplicationImpl.java:463)
    at org.hybird.appengine.jersey.container.WebApplicationImpl.initiate(WebApplicationImpl.java:320)
    at org.hybird.appengine.jersey.container.WebComponent.initiate(WebComponent.java:424)
    at org.hybird.appengine.jersey.container.WebComponent.load(WebComponent.java:435)
    at org.hybird.appengine.jersey.container.WebComponent.init(WebComponent.java:168)
    at org.hybird.appengine.jersey.container.ServletContainer.init(ServletContainer.java:198)

  7. Iqbal Yusuf says:

    Hi,

    Which appengine SDK version your container work with? I’m trying 1.2.1 and having problem.

    Jun 23, 2009 1:07:37 AM org.hybird.appengine.jersey.container.WebApplicationImpl processRootResources
    SEVERE: The ResourceConfig instance does not contain any root resource classes.
    Jun 23, 2009 1:07:37 AM com.google.apphosting.utils.jetty.JettyLogger warn
    WARNING: failed Jersey Web Application
    javax.servlet.ServletException: com.sun.jersey.api.container.ContainerException: The ResourceConfig instance does not contain any root resource classes.

  8. Nicolas says:

    Hi,
    It looks like taking time for the first connection.

    Uncaught exception from servlet
    com.google.apphosting.runtime.HardDeadlineExceededError
    (…)

    But after 2-3 minuts, it’s ok. I have a lot of javadoc files in the webapp and it’s possible that everything is parsed at the first connection to look for @Path classes.

  9. lqd says:

    I was using 1.2.0.

    At first i thought 1.2.1 wouldn’t be so different. But I’ve actually run into an almost unsurmountable amount of weird exceptions with Jersey 1.1.0-ea and sdk 1.2.1. We shouldn’t have to do this, at this point it doesn’t seem GAE’s VM is really usable with real world libs and frameworks, without heavy work on our part.

  10. Andy says:

    Has anyone got Jersey 1.1.4 and GAE 1.2.6 (latest versions as of writing) to work together?

    Ideally I would like a JAX-RS implementation to not load any providers/modules for XML and JSON as I want to drop in providers that get around App Engine’s restrictions.

    Restlet seems to work fine. But it appears to not implement all parts of the JAX-RS specification (eg. all @Context type injections).

  11. lqd says:

    I don’t think so, however Paul Sandoz recently said they would look at it for the next release, so it shouldn’t be too long.

  12. Nicolas says:

    Restlet DOES provide Jax-rs functionnalities with an extension:
    http://wiki.restlet.org/docs_2.0/13-restlet/28-restlet/57-restlet.html