Links: Table of Contents | Single HTML

Chapter 10. Experimental Features

Table of Contents

10.1. Hypermedia Actions
10.1.1. Introduction
10.1.2. Hypermedia by Example
10.1.3. Server API
10.1.4. Client API
10.1.5. Server Evolution
10.1.6. Configuring Hypermedia in Jersey

This chapter describes experimental features of Jersey that are only available in maven SNAPSHOT releases. Such features are not intended to be utilized for stable development as the APIs may change.

Providing such experimental features allows developers to experiment and provide feedback, please send feedback to users@jersey.java.net.

10.1. Hypermedia Actions

10.1.1. Introduction

It is generally understood that, in order to follow the REST style, URIs should be assigned to anything of interest (resources) and a few, well-defined operations should be used to interact with these resources. For example, the state of a purchase order resource can be updated by POSTing or PATCHing a new value for the its state field. However, 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. For example, given a purchase order resource, the operation of setting its state to REVIEWED may involve a number of different steps such as:

  1. checking the customer's credit status
  2. reserving product inventory
  3. verifying per-customer quantity limits.

Clearly, this workflow cannot be equated to simply updating a field on a resource. Moreover, clients are generally uninterested in the details behind these type of workflows, and in some cases computing the final state of a resource on the client side, as required for a PUT operation, is impractical or impossible.

We call these operations actions and, because they are of interest to us, we turn them into 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.

Action resources as first-class citizens provide the necessary tools for developers to implement HATEOAS. 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.

10.1.2. Hypermedia by Example

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. Finally, it has been argued that using client proxies is simply more natural for developers and less error prone as fewer URIs need to be constructed.

Rather than presenting all these extensions abstractly, we shall illustrate their use via an example. The complete example can found in hypermedia-action-sample. The Purchase Ordering System exemplifies a system in which customers can submit orders and where orders are guided by a workflow that includes states like REVIEWED, PAYED and SHIPPED. The system's model is comprised of 4 entities: Order, Product, Customer and Address. These model entities are controlled by 3 resource classes: OrderResource, CustomerResource and ProductResource. Addresses are sub-resources that are also controlled by CustomerResource. An order instance refers to a single customer, a single address (of that customer) and one or more products. The XML representation (or view) of a sample order is shown below.

    <order>
        <id>1</id>
        <customer>http://.../customers/21</customer>
        <shippingAddress>http://.../customers/21/address/1</shippingAddress>
        <orderItems>
            <product>http://.../products/3345</product>
            <quantity>1</quantity>
        </orderItems>
        <status>RECEIVED</status>
    </order>

Note the use of URIs to refer to each component of an order. This form of serialization by reference is supported in Jersey using JAXB beans and the @XmlJavaTypeAdapter annotation. An XmlAdapter is capable of mapping an object reference in the model to a URI. Refer to hypermedia-action-sample for more information on how this mapping is implemented.

10.1.3. Server API

The server API introduces 3 new annotation types: @Action, @ContextualActionSet and @HypermediaController. The @Action annotation identifies a sub-resource as a named action. The @ContextualActionSet is used to support contextual contracts and must annotate a method that returns a set of action names. Finally, @HypermediaController marks a resource class as a hypermedia controller class: a class with one more methods annotated with @Action and at most one method annotated with @ContextualActionSet.

The following example illustrates the use of all these annotation types to define the OrderResource controller.[2]

  1     @Path("/orders/{id}")
  2     @HypermediaController(
  3         model=Order.class,
  4         linkType=LinkType.LINK_HEADERS)
  5     public class OrderResource {
  6         
  7         private Order order;
  8     
  9         ...
 10         
 11         @GET @Produces("application/xml")
 12         public Order getOrder(@PathParam("id") String id) {      
 13             return order;
 14         }
 15         
 16         @POST @Action("review") @Path("review")
 17         public void review(@HeaderParam("notes") String notes) {
 18             ...
 19             order.setStatus(REVIEWED);
 20         }
 21         
 22         @POST @Action("pay") @Path("pay")
 23         public void pay(@QueryParam("newCardNumber") String newCardNumber) {
 24             ...
 25             order.setStatus(PAYED);
 26         }
 27         
 28         @PUT @Action("ship") @Path("ship")
 29         @Produces("application/xml")
 30         @Consumes("application/xml")
 31         public Order ship(Address newShippingAddress) {
 32             ...
 33             order.setStatus(SHIPPED);
 34             return order;
 35         }
 36         
 37         @POST @Action("cancel") @Path("cancel")
 38         public void cancel(@QueryParam("notes") String notes) {
 39             ...
 40             order.setStatus(CANCELED);
 41         }
 42     }

The @HypermediaController annotation above indicates that this resource class is a hypermedia controller for the Order class. Each method annotated with @Action defines a link relationship and associated action resource. These methods are also annotated with @Path to make a sub-resource. There does not appear to be a need to use @Action and @Path simultaneously, but without the latter some resource methods may become ambiguous. In the future, we hope to eliminate the use of @Path when @Action is present. The element linkType selects the way in which URIs corresponding to action resources are serialized: in this case, using link headers. These link headers become part of the an order's representation. For instance, an order in the RECEIVED state, i.e. an order that can only be reviewed or canceled, will be represented as follows.

    Link: <http://.../orders/1/review>;rel=review;op=POST
    Link: <http://.../orders/1/cancel>;rel=cancel;op=POST
    <order>
        <id>1</id>
        <customer>http://.../customers/21</customer>
        <shippingAddress>http://.../customers/21/address/1</shippingAddress>
        <orderItems>
            <product>http://.../products/3345</product>
            <quantity>1</quantity>
        </orderItems>
        <status>RECEIVED</status>
    </order>

Without a method annotated with @ContextualActionSet, all actions are available at all times regardless of the state of an order. The following method can be provided to define a contextual contract for this resource.

  1     @ContextualActionSet
  2     public Set<String> getContextualActionSet() {
  3         Set<String> result = new HashSet<String>();
  4         switch (order.getStatus()) {
  5             case RECEIVED:
  6                 result.add("review");
  7                 result.add("cancel");
  8                 break;
  9             case REVIEWED:
 10                 result.add("pay");
 11                 result.add("cancel");
 12                 break;
 13             case PAYED:
 14                 result.add("ship");
 15                 break;
 16             case CANCELED:
 17             case SHIPPED:
 18                 break;
 19         }
 20         return result;
 21     }

This method returns a set of action names based on the order's internal state; the values returned in this set correspond to the @Action annotations in the controller. For example, this contextual contract prevents shipping an order that has not been payed by only including the ship action in the PAYED state.

10.1.4. Client API

Although action resources can be accessed using traditional APIs for REST, including Jersey's client API, the use of client-side proxies and method invocations to trigger these actions seems quite natural. As we shall see, the use of client proxies also introduces a level of indirection that enables better support for server evolution, permitting the definition of contracts with various degrees of coupling.

Client proxies are created based on hypermedia controller interfaces. Hypermedia controller interfaces are Java interfaces annotated by @HypermediaController that, akin to the server side API, specify the name of a model class and the type of serialization to use for action resource URIs. The client-side model class should be based on the representation returned by the server; in particular, in our example the client-side model for an Order uses instances of URI to link an order to a customer, an address and a list of products.

  1     @HypermediaController(
  2         model=Order.class,
  3         linkType=LinkType.LINK_HEADERS)
  4     public interface OrderController {
  5         
  6         public Order getModel();
  7         
  8         @Action("review")
  9         public void review(@Name("notes") String notes);
 10         
 11         @POST @Action("pay")
 12         public void pay(@QueryParam("newCardNumber") String newCardNumber);
 13         
 14         @Action("ship")
 15         public Order ship(Address newShippingAddress);
 16         
 17         @Action("cancel")
 18         public void cancel(@Name("notes") String notes);
 19     }

The @Action annotation associates an interface method with a link relation and hence an action resource on the server. Thus, invoking a method on the generated proxy results in an interaction with the corresponding action resource. The way in which the method invocation is mapped to an HTTP request depends on the additional annotations specified in the interface. For instance, the pay action in the example above indicates that it must use a POST and that the String parameter newCardNumber must be passed as a query parameter. This is an example of a static contract in which the client has built-in knowledge of the way in which an action is defined by the server.

In contrast, the review action only provides a name for its parameter using the @Name annotation. This is an example of a dynamic contract in which the client is only coupled to the review link relation and the knowledge that this relation requires notes to be supplied. The exact interaction with the review action must therefore be discovered dynamically and the notes parameter mapped accordingly. The Jersey client runtime uses WADL fragments that describe action resources to map these method calls into HTTP requests.

The following sample shows how to use OrderController to generate a proxy to review, pay and ship an order. For the sake of the example, we assume the customer that submitted the order has been suspended and needs to be activated before the order is reviewed. For that purpose, the client code retrieves the customer URI from the order's model and obtains an instance of CustomerController.[3]

  1     // Instantiate Jersey's Client class
  2     Client client = new Client();
  3         
  4     // Create proxy for order and retrieve model
  5     OrderController orderCtrl = client.view(
  6         "http://.../orders/1", OrderController.class);
  7         
  8     // Create proxy for customer in order 1
  9     CustomerController customerCtrl = client.view(
 10         orderCtrl.getModel().getCustomer(),
 11         CustomerController.class);
 12         
 13     // Activate customer in order 1 
 14     customerCtrl.activate();
 15         
 16     // Review and pay order 
 17     orderCtrl.review("approve");
 18     orderCtrl.pay("123456789");
 19         
 20     // Ship order 
 21     Address newAddress = getNewAddress();
 22     orderCtrl.ship(newAddress);

The client runtime will automatically update the action set throughout a conversation: for example, even though the review action does not produce a result, the HTTP response to that action still includes a list of link headers defining the contextual action set, which in this case will consist of the pay and cancel actions but not the ship action. An attempt to interact with any action not in the contextual action set will result in a client-side exception if using proxies (as shown above), or a server-side exception if using some other API.

The following annotations are allowed in hypermedia controller interfaces: @Name, @Consumes, @Produces, @CookieParam, @FormParam, @HeaderParam, @QueryParam, as well as any HTTP method annotation like @POST, @PUT, etc. All other annotations not listed here are unsupported at this time (of note, @MatrixParam is not supported). The @Name annotation used in Jersey is defined in com.sun.jersey.core.hypermedia.

10.1.5. Server Evolution

In the last section, we have seen how the use of client proxies based on partially annotated interfaces facilitates server evolution. An interface method annotated with @Action and @Name represents a loosely-coupled contract with a server. Changes to action resource URIs, HTTP methods and parameter types on the server will not require a client re-spin. Naturally, as in all client-server architectures, it is always possible to break backward compatibility, but the ability to support more dynamic contracts --usually at the cost of additional processing time-- is still an area of investigation.

We see this form of hypermedia support in Jersey as a small step in this direction, showing the potential of using dynamic meta-data for the definition of these type of contracts. The use of dynamic meta-data requires the server to return WADL fragments when using OPTIONS on an action resource URI. When dynamic meta-data is not available (for example if using an older version of Jersey on the server) client interfaces should be fully annotated and a tighly-coupled contract should be used instead.

10.1.6. Configuring Hypermedia in Jersey

There are a few configuration options in hypermedia-action-sample that are worth highlighting. First, a special filter factory HypermediaFilterFactory must be enabled. This can be done programmatically by adding

com.sun.jersey.server.hypermedia.filter.HypermediaFilterFactory

to the list defined by

com.sun.jersey.spi.container.ResourceFilters

or declaratively in the application's web.xml file. Refer to the Java class com.sun.jersey.samples.hypermedia.Main in the hypermedia sample for an example of how to do this using Grizzly (which can be executed with mvn install exec:java). Second, hypermedia applications must included the following Maven runtime dependencies:

        <dependency>
            <groupId>com.sun.jersey.experimental.hypermedia-action</groupId>
            <artifactId>hypermedia-action-server</artifactId>
            <version>${jersey-version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey.experimental.hypermedia-action</groupId>
            <artifactId>hypermedia-action-client</artifactId>
            <version>${jersey-version}</version>
        </dependency>

Last, even though the annotation @HypermediaController has a linkType element, only the LinkType.LINK_HEADERS option is currently supported and must be used by both clients and servers.



[2] Several details about this class are omitted for clarity. The reader is referred to the hypermedia sample for more details.

[3] CustomerController is a hypermedia controller interface akin to OrderController which is omitted since it does not highlight any additional feature.