Skip to main content

Exploring Hypermedia Support in Jersey

Posted by spericas on February 9, 2010 at 8:20 AM PST

During the last few weeks, Marc H., Paul S. and myself have been exploring some ideas to support Hypermedia in Jersey. The outcome of this investigation is an experimental implementation that is available in Jersey's trunk (module version 1.2-SNAPSHOT). Exactly what it means to support hypermedia is still an area of research, and some other implementations of JAX-RS (notably RESTfulie) have also proposed APIs for it.

The REST architectural style, as defined by Roy Fielding in his thesis, is characterized by four constraints: (i) identification of resources (ii) manipulation of resources through representations (iii) self-descriptive messages and (iv) hypermedia as the engine of application state. It is constraint (iv), hypermedia as the engine of application state or HATEOAS for short, that is the least understood and the focus of our investigation. 

It has been identified by other authors that there are actions that cannot be easily mapped to read or write operations on resources. These operations are inherently more complex and their details are rarely of interest to clients. In our work, we introduce the concept of action resources. An action resource is a sub-resource defined for the purpose of exposing workflow-related operations on parent resources. As sub-resources, action resources are identified by URIs that are relative to their parent. For instance, the following are examples of action resources:

 http://.../orders/1/review
http://.../orders/1/pay
http://.../orders/1/ship

 for purchase order “1” identified by http://.../orders/1.

A set of action resources defines—via their link relationships—a contract with clients that has the potential to evolve over time depending on the application’s state. For instance, assuming purchase orders are only reviewed once, the review action will become unavailable and the pay action will become available after an order is reviewed.

The notion of action resources naturally leads to discussions about improved client APIs to support them. Given that action resources are identified by URIs, no additional API is really necessary, but the use of client-side proxies and method invocations to trigger these actions seems quite natural. Additionally, the use of client proxies introduces a level of indirection that enables better support for server evolution, i.e. the ability of a server’s contract to support certain changes without breaking existing clients.

The Jersey extensions that we implemented were influenced by the following (inter-related) requirements:

  • HATEOAS: Support for actions and contextual action sets as first-class citizens.
  • Ease of use: Annotation-driven model for both client APIs and server APIs.
  • Server Evolution: Various degrees of client and server coupling, ranging from static contracts to contextual contracts.

Please refer to the Hypermedia Chapter in the User's Guide for more information. Note that the proposed API is still experimental and will possibly evolve in unforeseen (and incompatible) ways. Your feedback will be extremely valuable in shaping future versions of this API.

Related Topics >>

Comments

Clarification in "Exploring Hypermedia Support in Jersey" code

Hello Santiago,

I came across your article presented in WS-REST 2010. I wanted to present same to a group of Java users in my organization. In this process I'm trying to understand the article and the code. I've downloaded the code from http://tinyurl.com/jersey-hypermedia and was able to successfully compile with all supporting libraries (I'm not using Maven to pull the libraries and compile instead I'm using Eclipse 3.5 to compile and run).

While running the com.sun.jersey.samples.hypermedia.Main, I'm getting following error:

Exception in thread "main" java.lang.IllegalArgumentException: A view proxy is not available for the class 'com.sun.jersey.samples.hypermedia.client.controller.OrderController' at com.sun.jersey.api.client.Client.getViewProxy(Client.java:471) at com.sun.jersey.api.client.ViewResource.handle(ViewResource.java:489) at com.sun.jersey.api.client.ViewResource.get(ViewResource.java:160) at com.sun.jersey.api.client.Client.view(Client.java:418) at com.sun.jersey.samples.hypermedia.Main.main(Main.java:50)

I looked at Jersey API documentation, but the documentation is not helpful. Hence could you please suggest how do I fix it? Thanks!

I've pulled following code from FishEye & using Jersey 1.2 release libraries and support files:

com.sun.jersey.client.hypermedia.ControllerInvocationHandler com.sun.jersey.client.hypermedia.HypermediaViewProxyProvider com.sun.jersey.core.hypermedia.Action com.sun.jersey.core.hypermedia.ContextualActionSet com.sun.jersey.core.hypermedia.HypermediaController com.sun.jersey.core.hypermedia.Name com.sun.jersey.samples.hypermedia.Main com.sun.jersey.samples.hypermedia.client.controller.CustomerController com.sun.jersey.samples.hypermedia.client.controller.OrderController com.sun.jersey.samples.hypermedia.client.model.Address com.sun.jersey.samples.hypermedia.client.model.Customer com.sun.jersey.samples.hypermedia.client.model.Customers com.sun.jersey.samples.hypermedia.client.model.Order com.sun.jersey.samples.hypermedia.client.model.Orders com.sun.jersey.samples.hypermedia.client.model.Product com.sun.jersey.samples.hypermedia.client.model.Products com.sun.jersey.samples.hypermedia.server.controller.CustomerResource com.sun.jersey.samples.hypermedia.server.controller.CustomersResource com.sun.jersey.samples.hypermedia.server.controller.OrderResource com.sun.jersey.samples.hypermedia.server.controller.OrdersResource com.sun.jersey.samples.hypermedia.server.controller.ProductResource com.sun.jersey.samples.hypermedia.server.controller.ProductsResource com.sun.jersey.samples.hypermedia.server.db.DB com.sun.jersey.samples.hypermedia.server.model.Address com.sun.jersey.samples.hypermedia.server.model.Customer com.sun.jersey.samples.hypermedia.server.model.Customers com.sun.jersey.samples.hypermedia.server.model.Order com.sun.jersey.samples.hypermedia.server.model.Orders com.sun.jersey.samples.hypermedia.server.model.Product com.sun.jersey.samples.hypermedia.server.model.Products com.sun.jersey.samples.hypermedia.server.model.adapters.AddressAdapter com.sun.jersey.samples.hypermedia.server.model.adapters.CustomerAdapter com.sun.jersey.samples.hypermedia.server.model.adapters.OrderAdapter com.sun.jersey.samples.hypermedia.server.model.adapters.ProductAdapter com.sun.jersey.server.hypermedia.filter.HypermediaFilterFactory

Expecting your early suggestion on this. Thanks!

Best regards, Saravan.

Now in Jersey 1.2/1.3

 Hello Saravan,

 Since this blog was written, the code has been integrated into Jersey. If you have Jersey 1.2 or 1.3, you can download the jars from here:

http://download.java.net/maven/2/com/sun/jersey/experimental/hypermedia-action/

 The docs have also been updated here:

https://jersey.dev.java.net/nonav/documentation/latest/user-guide.html#d4e1034

  Hope this helps.

-- Santiago 

This is not REST but HTTP-RPC

As I pointed out in the Jersey forum users@jersey.dev.java.net already, the idea is not REST anymore, since you actually do not transfer state in many of that action invocations. In fact, supporting this style or programming in JAX-RS will soon lead to purely RESTless applications only consisting of lots of method wrappers: You try to do provide a HTTP-RPC API, but a RESTful API anymore. In fact, I do neither see the need for such an API nor that it would match what Fielding had in mind with HATEOAS. Actually the proposal foils REST.

This is real-life

I have come across many cases where it's impossible to be completely stateless. If you happen to have an application other than a document centric one (yeah, those do exist nowadays), chances are, that you have to manage (complex) state.

Claiming REST should be stateless and the client has to deal with any state, just doesn't work in real life. Convincing each client (yes, it's not always you, your company, or a tech-savvy community actually using your services) that he has to implement all state management (so basically understanding your complex business flows) is not practical at all. Besides this organizational issue you might also face technical constraints. You may be forced to limit the amount of web service calls (great firewall, anyone?), so making use of a lot of links (which I really like to do) again might only work in theory.

That being said I really really see a need for controllers / actions. True, this is not the original REST idea anymore, but it's also not HTTP-RPC either, which aims to make remote calls look like local method invocations (which of course can only fail). The idea (my idea anyway) is to use REST where possible, but extend it by allowing to execute actions if e.g. an update causes multiple resources to change. Ideally this should be very explicit (any http verbs we could use?), so that client knows, that he potentially changes the state of the overall applications and not just one resource.

And seeing first approaches in Jersey totally rocks!

State transfer is not limited

State transfer is not limited to entity bodies, headers are also part of the state and are present in all action interactions. You may have not found a use for this, but many people designing client API for REST services have. You have a point in saying that this could be wrongfully overused, though.