Table of Contents
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.
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:
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.
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.
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.
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
.
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.
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.