Skip to main content
Last updated September 02, 2011 19:50, by spericas
Feedicon  

Hypermedia / HATEOAS


Discussion

Discussions about hyperlinking and HATEOAS in REST systems seem to center around two type of links: structural links and transitional links. Structural links replace aggregate data and are used to avoid bloated representations. For example, a purchase order's representation includes a link to the customer, the customer's shipping address and the product(s) in it. Transitional links convey information about the transitions (operations) that are available on a given resource. For example, a purchase order may include links that describe available transitions such as pay, ship or cancel.

Structural links are part of the representation and are embedded in the same location in which the data they replace. They may be Atom links or just content links in XML elements (or JSON fields). Transitional links typically appear in some pre-defined location as part of the representation in the form of Atom links or as part of the HTTP headers (as link headers).

The following example shows a representation in which both structural and transitional links are used. In this representation, structural links are just content links and transitional links are link headers:


Link: <http://.../orders/1/ship>; rel=ship,
      <http://.../orders/1/cancel>; rel=cancel
...
<order id="1">
  <customer>http://.../customers/11</customer>
  <address>http://.../customers/11/address/1</customer>
  <items>
    <item>
      <product>http://.../products/111</products>
      <quantity>2</quantity>
  </item>
  ...
</order>

JAX-RS 1.X does not prevent the generation of representations such as the one above. Link generation is possible using UriBuilder and UriInfo however, several steps are required to generate the links and place them in the right location.

Transitional Links


Transitional links conveyed as link headers can be easily supported by introducing a Link class, a LinkBuilder class and some additional methods to ResponseBuilder. The following example illustrates the use of all these classes:

Link link = LinkBuilder.fromUri("http://foo.bar/employee/john")
       .rel("manager").title("employee").build();
Response r = Response.ok().linkHeader(link).build();

Link headers consist of a URI and some link parameters such as "rel" or "title". For more information see RFC5988: Web Linking. A LinkBuilder does not provide the same functionally of a UriBuilder: an instance of the latter can be used to build a more complex URI first,

URI uri = UriBuilder.fromUri("http://foo.bar/employee/{name}")
        .path("john").build();
Link link = LinkBuilder.fromUri(uri).rel("emp").title("employee").build();
return Response.ok().linkHeader(link).build();

Alternatively, LinkBuilder can extend UriBuilder and define a single builder that can create the URI as well as the enclosing link. The proposal above provides better SoC and, consequently, simpler classes.

Client API Support for Transitional Links


The Client API accepts instances of Link for the construction of targets. The following example retrieves a response and follows the "next" link to access another resource:

HttpResponse current = client.target("http://examples.jax-rs-spec.com/current")
        .request(MediaType.APPLICATION_XML)
        .get(); 
HttpResponse next = client.target(current.getLink("next"))
    .request(MediaType.APPLICATION_XML).get();       

Structural Links

Unlike transitional links, structural links are more difficult to support because their generation crosses multiple boundaries. In a nutshell, the problem boils down to the serialization of a model in which certain object references are converted into links. For example, an order in a model may include references to a customer, a customer's address and one or more products; if all these references are converted into links, the order would be serialized as follows:


<order id="1">
  <customer>http://.../customers/11</customer>
  <address>http://.../customers/11/address/1</customer>
  <items>
    <item>
      <product>http://.../products/111</products>
      <quantity>2</quantity>
  </item>
  ...
</order>

Jersey supports declarative hyperlinking in which a model's representation (or view, essentially an isomorphic model in which the type of certain fields has been changed to URI) is annotated with meta-data that provides the necessary information to generate these links. This information is provided in the form of annotations and EL expressions that can cross boundaries and reference both the representation instance as well as the resource class instance.

The use of an external, untyped language like EL has been pointed out as a disadvantage of this approach, as has the complexity of writing annotations for certain use cases.

Naturally, Java instead of EL can be used to provide the necessary logic to generate the structural links that are needed during serialization. The resource (or controller) layer should really be responsible for generating links, not the model.

Understanding the Problem

The problem of supporting structural links can be explained a bit more formally as follows. Let us start with some definitions,

  • M: an application's model
  • C: a class in a model M
  • c: an instance of C
  • L: a set of links
  • l: a link in a set L
  • C_V: A view of a class C in which some object references have been replaced by links
  • M_V: an application's model of C_V's

Conceptually, supporting structural links is equivalent to the production (or generation) and consumption of a partial mapping P = { c_1 -> l_1, …, c_n -> l_n } for model instances c_i and links l_i. The resource layer should be responsible for producing P. The problem is now reduced to the production and consumption of P.

There a few different way in which P can produced: (a) it can be returned by the resource method together with the root model instance (b) the resource class instance can implement the mapping and provide a callback that can be used by the JAX-RS framework to obtain l_i's from c_i's (c) a new kind of provider classes can be introduced that implement the mapping. Instances of this provider classes will likely need to be in request scope, rather than application scope, as the may need access to request information to generate the appropriate links.

There are two ways in which P can be consumed: (i) by the serialization framework to directly replace the c_i's by the l_i's at the time of serialization and (ii) by a pre-processor framework that injects the l_i's into C_V's before serialization takes place.

Jersey's declarative hyperlinking (DeclarativeHyperlinkingInJersey) uses option (ii) for the consumption of P and a combination of (b) and EL expressions for the generation of the mapping. In general, option (i) is difficult to implement given that serialization frameworks (including JAXB) do not easily support a mapping P at serialization time.

JAX-RS Readers and Writers

JAX-RS message body writers (MBW) and readers (MBR) provide a way to map Java types into representations and vice versa. Let us assume that the mapping P defined in the previous section is represented by the type Map<Object, URI>. The writeTo method in a MessageBodyWriter can be extended to accept an additional parameter:

MessageBodyWrite.writeTo(..., Map<Object, URI> p);

which will be used by the MBW to map model instances into URIs. However, MBW will often delegate to external serialization frameworks (JAXB, JSON frameworks, etc.) that have no support for such a an object to URI mapping. This is the main reason why option (i), as defined in the previous section, is difficult to implement in JAX-RS.

JAXB Adapters

JAXB supports the notion of an XmlAdapter to map bound types to value types during marshaling and value types to bound types during unmarshaling. It is possible to use an XmlAdapter to customize JAXB's serialization to map certain objects into URIs and vice versa. This would require writing an XmlAdapter and specifying its class name as part of an annotation as shown next:

class Order {
    @XmlAdapter(CustomerToURIAdapter.class)
    Customer customer;
    ...
}

The way in which these XmlAdapter's are discovered and used is completely under JAXB's control; a JAX-RS implementation that has knowledge of a mapping P (see previous section) cannot use it directly to customize the serialization process. Thus, even though this is a possible solution for JAX-RS applications that use JAXB, it does not translate into a general solution that could be standardized by JAX-RS.

Summary: Support for Structural Links

Producing mapping P:

  • Option (a): A resource method can return an instance of Response extended with the mapping P. This requires minimal changes to the existing API.
  • Option (b): A resource class can implement a special callback method that the JAX-RS framework can use to map model instances to URIs.
  • Option (c): A new type of provider (with a new non-standard scope) can be defined for the JAX-RS framework to learn about the mapping.

Consuming mapping P:

  • Option (i):
    • Although MBRs and MBWs could be extended to consume this mapping, the use of external serialization frameworks makes it very difficult to properly use the mapping.
    • JAXB specific solutions that use XmlAdapters can be implemented. These solutions require developers to implement these adapters and there isn't enough that a JAX-RS framework can do to ease the process.
  • Option (ii):
    • Writing a pre-processor requires the developer to create a representation/view of the model in which URIs can be stored. There are a few ways in which this can be done, but the end result is a near duplication of the model. Even though the JAX-RS framework can update the representation/view of the model on behalf of the application, the definition of such mapping is non-trivial as shown by some examples in Jersey. It may be easier for the developer to manually update the representation/view than to specify all the declarations that are needed to the JAX-RS framework to complete the task.

Conclusion: Structural Links

An analysis of the development effort required to implement a JAX-RS application that uses structural links with and without support from the JAX-RS framework, coupled with the effort required from the part of the JAX-RS implementor, suggests that this is an area of research that is not matured enough for standardization.

The recommendation is to provide better support for building links and defining link headers and leave the generation of structural links to the application.

Related Work



References


 
 
Close
loading
Please Confirm
Close