HATEOAS - REST and the JAX-RS Standard - RESTful Java with JAX-RS 2.0 (2013)

RESTful Java with JAX-RS 2.0 (2013)

Part I. REST and the JAX-RS Standard

Chapter 10. HATEOAS

The Internet is commonly referred to as “the Web” because information is connected together through a series of hyperlinks embedded within HTML documents. These links create threads between interrelated websites on the Internet. Because of this, humans can “surf” the Web for interesting tidbits of related information by clicking through these links with their browsers. Search engines can crawl these links and create huge indexes of searchable data. Without them, the Internet would never have scaled. There would have been no way to easily index information, and registering websites would have been a painful manual process.

Besides links, another key feature of the Internet is HTML . Sometimes a website wants you to fill out information to buy something or register for some service. The server is telling you, the client, what information it needs to complete an action described on the web page you are viewing. The browser renders the web page into a format that you can easily understand. You read the web page and fill out and submit the form. An HTML form is an interesting data format because it is a self-describing interaction between the client and server.

The architectural principle that describes linking and form submission is called HATEOAS. HATEOAS stands for Hypermedia As The Engine Of Application State. It is a bit of a weird name for a key architecture principle, but we’re stuck with it (my editor actually thought I was making the acronym up). The idea of HATEOAS is that your data format provides extra information on how to change the state of your application. On the Web, HTML links allow you to change the state of your browser. When you’re reading a web page, a link tells you which possible documents (states) you can view next. When you click a link, your browser’s state changes as it visits and renders a new web page. HTML forms, on the other hand, provide a way for you to change the state of a specific resource on your server. When you buy something on the Internet through an HTML form, you are creating two new resources on the server: a credit card transaction and an order entry.

HATEOAS and Web Services

How does HATEOAS relate to web services? When you’re applying HATEOAS to web services, the idea is to embed links within your XML or JSON documents. While this can be as easy as inserting a URL as the value of an element or attribute, most XML-based RESTful applications use syntax from the Atom Syndication Format as a means to implement HATEOAS.[10] From the Atom RFC:

Atom is an XML-based document format that describes lists of related information known as “feeds.” Feeds are composed of a number of items, known as “entries,” each with an extensible set of attached metadata.

Think of Atom as the next evolution of RSS. It is generally used to publish blog feeds on the Internet, but a few data structures within the format are particularly useful for web services, particularly Atom links.

Atom Links

The Atom link XML type is a very simple yet standardized way of embedding links within your XML documents. Let’s look at an example:

<customers>

<link rel="next"

href="http://example.com/customers?start=2&size=2"

type="application/xml"/>

<customer id="123">

<name>Bill Burke</name>

</customer>

<customer id="332">

<name>Roy Fielding</name>

</customer>

</customers>

The Atom link is just a simple XML element with a few specific attributes.

The rel attribute

The rel attribute is used for link relationships. It is the logical, simple name used to reference the link. This attribute gives meaning to the URL you are linking to, much in the same way that text enclosed in an HTML <a> element gives meaning to the URL you can click in your browser.

The href attribute

This is the URL you can traverse in order to get new information or change the state of your application.

The type attribute

This is the exchanged media type of the resource the URL points to.

The hreflang attribute

Although not shown in the example, this attribute represents the language the data format is translated into. Some examples are French, English, German, and Spanish.

When a client receives a document with embedded Atom links, it looks up the relationship it is interested in and invokes the URI embedded within the href link attribute.

Advantages of Using HATEOAS with Web Services

It is pretty obvious why links and forms have done so much to make the Web so prevalent. With one browser, we have a window to a wide world of information and services. Search engines crawl the Internet and index websites, so all that data is at our fingertips. This is all possible because the Web is self-describing. We get a document and we know how to retrieve additional information by following links. We know how to purchase something from Amazon because the HTML form tells us how.

Machine-based clients are a little different, though. Other than browsers, there aren’t a lot of generic machine-based clients that know how to interpret self-describing documents. They can’t make decisions on the fly like humans can. They require programmers to tell them how to interpret data received from a service and how to transition to other states in the interaction between client and server. So, does that make HATEOAS useless to machine-based clients? Not at all. Let’s look at some of the advantages.

Location transparency

One feature that HATEOAS provides is location transparency. In a RESTful system that leverages HATEOAS, very few URIs are published to the outside world. Services and information are represented within links embedded in the data formats returned by accessing these top-level URIs. Clients need to know the logical link names to look for, but don’t have to know the actual network locations of the linked services.

For those of you who have written EJBs, this isn’t much different than using the Java Naming and Directory Interface (JNDI). Like a naming service, links provide a level of indirection so that underlying services can change their locations on the network without breaking client logic and code. HATEOAS has an additional advantage in that the top-level web service has control over which links are transferred.

Decoupling interaction details

Consider a request that gives us a list of customers in a customer database: GET /customers. If our database has thousands and thousands of entries, we do not want to return them all with one basic query. What we could do is define a view into our database using URI query parameters:

/customers?start={startIndex}&size={numberReturned}

The start query parameter identifies the starting index for our customer list. The size parameter specifies how many customers we want returned from the query.

This is all well and good, but what we’ve just done is increased the amount of predefined knowledge the client must have to interact with the service beyond a simple URI of /customers. Let’s say in the future, the server wanted to change how view sets are queried. For instance, maybe the customer database changes rather quickly and a start index isn’t enough information anymore to calculate the view. If the service changes the interface, we’ve broken older clients.

Instead of publishing this RESTful interface for viewing our database, what if, instead, we embedded this information within the returned document?

<customers>

<link rel="next"

href="http://example.com/customers?start=2&size=2"

type="application/xml"/>

<customer id="123">

<name>Bill Burke</name>

</customer>

<customer id="332">

<name>Roy Fielding</name>

</customer>

</customers>

By embedding an Atom link within a document, we’ve given a logical name to a state transition. The state transition here is the next set of customers within the database. We are still requiring the client to have predefined knowledge about how to interact with the service, but the knowledge is much simpler. Instead of having to remember which URI query parameters to set, all that’s needed is to follow a specific named link. The client doesn’t have to do any bookkeeping of the interaction. It doesn’t have to remember which section of the database it is currently viewing.

Also, this returned XML is self-contained. What if we were to hand off this document to a third party? We would have to tell the third party that it is only a partial view of the database and specify the start index. Since we now have a link, this information is all a part of the document.

By embedding an Atom link, we’ve decoupled a specific interaction between the client and server. We’ve made our web service a little more transparent and change-resistant because we’ve simplified the predefined knowledge the client must have to interact with the service. Finally, the server has the power to guide the client through interactions by providing links.

Reduced state transition errors

Links are not used only as a mechanism to aggregate and navigate information. They can also be used to change the state of a resource. Consider an order in an ecommerce website obtained by traversing the URI /orders/333:

<order id="333">

<customer id="123">...</customer>

<amount>$99.99</amount>

<order-entries>

...

</order-entries>

</order>

Let’s say a customer called up and wanted to cancel her order. We could simply do an HTTP DELETE on /orders/333. This isn’t always the best approach, as we usually want to retain the order for data warehousing purposes. So, instead, we might PUT a new representation of the order with a cancelled element set to true:

PUT /orders/333 HTTP/1.1

Content-Type: application/xml

<order id="333">

<customer id="123">...</customer>

<amount>$99.99</amount>

<cancelled>true</cancelled>

<order-entries>

...

</order-entries>

</order>

But what happens if the order can’t be cancelled? We may be at a certain state in our order process where such an action is not allowed. For example, if the order has already been shipped, it cannot be cancelled. In this case, there really isn’t a good HTTP status code to send back that represents the problem. A better approach would be to embed a cancel link:

<order id="333">

<customer id="123">...</customer>

<amount>$99.99</amount>

<cancelled>false</cancelled>

<link rel="cancel"

href="http://example.com/orders/333/cancelled"/>

<order-entries>

...

</order-entries>

</order>

The client would do a GET /orders/333 and get the XML document representing the order. If the document contains the cancel link, the client is allowed to change the order status to “cancelled” by doing an empty POST or PUT to the URI referenced in the link. If the document doesn’t contain the link, the client knows that this operation is not possible. This allows the web service to control how the client is able to interact with it in real time.

W3C standardized relationships

An interesting thing that is happening in the REST community is an effort to define, register, and standardize a common set of link relationship names and their associated behaviors.[11] Some examples are given in Table 10-1.

Table 10-1. W3C standard relationship names

Relationship

Description

previous

A URI that refers to the immediately preceding document in a series of documents.

next

A URI that refers to the immediately following document in a series of documents.

edit

A URI that can be retrieved, updated, and deleted.

payment

A URI where payment is accepted. It is meant as a general way to facilitate acts of payment.

This is not an exhaustive list, but hopefully you get the general idea where this registry is headed. Registered relationships can go a long way to help make data formats even more self-describing and intuitive to work with.

Link Headers Versus Atom Links

While Atom links have become very popular for publishing links in RESTful systems, there is an alternative. Instead of embedding a link directly in your document, you can instead use Link response headers.[12] This is best explained with an example.

Consider the order cancellation example described in the previous section. An Atom link is used to specify whether or not the cancelling of an order is allowed and which URL to use to do a POST that will cancel the order. Instead of using an Atom link embedded within the order XML document, let’s use a Link header. So, if a user submits GET /orders/333, he will get back the following HTTP response:

HTTP/1.1 200 OK

Content-Type: application/xml

Link: <http://example.com/orders/333/cancelled>; rel=cancel

<order id="333">

...

</order>

The Link header has all the same characteristics as an Atom link. The URI is enclosed within <> followed by one or more attributes delimited by semicolons. The rel attribute is required and means the same thing as the corresponding Atom attribute of the same name. This part isn’t shown in the example, but you may also specify a media type via the type attribute.

Personally, I really like Link headers as an alternative to embedding Atom links. Many times, I find that my client isn’t interested in the resource representation and is only interested in the link relations. You shouldn’t have to parse a whole XML or JSON document just to find the URL you’re interested in invoking on. Another nice thing is that instead of doing a GET invocation, you can do a HEAD invocation and avoid getting the XML document entirely. In general, I like to use Atom links for data aggregation and Link headers for everything else.

HATEOAS and JAX-RS

JAX-RS doesn’t have many facilities to help with HATEOAS. HATEOAS is defined by the application, so there’s not much a framework can add. What it does have, though, are helper classes that you can use to build the URIs that you link to in your data formats.

Building URIs with UriBuilder

One such helper class is javax.ws.rs.core.UriBuilder. The UriBuilder class allows you to construct a URI piece by piece and is also sensitive to template parameters:

public abstract class UriBuilder {

public static UriBuilder fromUri(URI uri)

throws IllegalArgumentException

public static UriBuilder fromUri(String uri)

throws IllegalArgumentException

public static UriBuilder fromPath(String path)

throws IllegalArgumentException

public static UriBuilder fromResource(Class<?> resource)

throws IllegalArgumentException

public static UriBuilder fromLink(Link link)

throws IllegalArgumentException

UriBuilder instances can only be instantiated from the static helper methods listed. They can be initialized by a URI, path, or the @Path annotation of a JAX-RS resource class:

public abstract UriBuilder clone();

public abstract UriBuilder uri(URI uri)

throws IllegalArgumentException;

public abstract UriBuilder scheme(String scheme)

throws IllegalArgumentException;

public abstract UriBuilder schemeSpecificPart(String ssp)

throws IllegalArgumentException;

public abstract UriBuilder userInfo(String ui);

public abstract UriBuilder host(String host)

throws IllegalArgumentException;

public abstract UriBuilder port(int port)

throws IllegalArgumentException;

public abstract UriBuilder replacePath(String path);

public abstract UriBuilder path(String path)

throws IllegalArgumentException;

public abstract UriBuilder path(Class resource)

throws IllegalArgumentException;

public abstract UriBuilder path(Class resource, String method)

throws IllegalArgumentException;

public abstract UriBuilder path(Method method)

throws IllegalArgumentException;

public abstract UriBuilder segment(String... segments)

throws IllegalArgumentException;

public abstract UriBuilder replaceMatrix(String matrix)

throws IllegalArgumentException;

public abstract UriBuilder matrixParam(String name, Object... vals)

throws IllegalArgumentException;

public abstract UriBuilder replaceMatrixParam(String name,

Object... values) throws IllegalArgumentException;

public abstract UriBuilder replaceQuery(String query)

throws IllegalArgumentException;

public abstract UriBuilder queryParam(String name, Object... values)

throws IllegalArgumentException;

public abstract UriBuilder replaceQueryParam(String name,

Object... values) throws IllegalArgumentException;

public abstract UriBuilder fragment(String fragment);

These methods are used to piece together various parts of the URI. You can set the values of a specific part of a URI directly or by using the @Path annotation values declared on JAX-RS resource methods. Both string values and @Path expressions are allowed to contain template parameters:

public abstract URI buildFromMap(Map<String, ? extends Object> values)

throws IllegalArgumentException, UriBuilderException;

public abstract URI buildFromEncodedMap(

Map<String, ? extends Object> values)

throws IllegalArgumentException, UriBuilderException;

public abstract URI build(Object... values)

throws IllegalArgumentException, UriBuilderException;

public abstract URI buildFromEncoded(Object... values)

throws IllegalArgumentException, UriBuilderException;

}

The build() methods create the actual URI. Before building the URI, though, any template parameters you have defined must be filled in. The build() methods take either a map of name/value pairs that can match up to named template parameters or you can provide a list of values that will replace template parameters as they appear in the templated URI. These values can either be encoded or decoded values, your choice. Let’s look at a few examples:

UriBuilder builder = UriBuilder.fromPath("/customers/{id}");

builder.scheme("http")

.host("{hostname}")

.queryParam("param={param}");

In this code block, we have defined a URI pattern that looks like this:

http://{hostname}/customers/{id}?param={param}

Since we have template parameters, we need to initialize them with values passed to one of the build arguments to create the final URI. If you want to reuse this builder, you should clone() it before calling a build() method, as the template parameters will be replaced in the internal structure of the object:

UriBuilder clone = builder.clone();

URI uri = clone.build("example.com", "333", "value");

This code would create a URI that looks like this:

http://example.com/customers/333?param=value

We can also define a map that contains the template values:

Map<String, Object> map = new HashMap<String, Object>();

map.put("hostname", "example.com");

map.put("id", 333);

map.put("param", "value");

UriBuilder clone = builder.clone();

URI uri = clone.buildFromMap(map);

Another interesting example is to create a URI from the @Path expressions defined in a JAX-RS annotated class. Here’s an example of a JAX-RS resource class:

@Path("/customers")

public class CustomerService {

@Path("{id}")

public Customer getCustomer(@PathParam("id") int id) {...}

}

We can then reference this class and the getCustomer() method within our UriBuilder initialization to define a new template:

UriBuilder builder = UriBuilder.fromResource(CustomerService.class);

builder.host("{hostname}")

builder.path(CustomerService.class, "getCustomer");

This builder code defines a URI template with a variable hostname and the patterns defined in the @Path expressions of the CustomerService class and the getCustomer() method. The pattern would look like this in the end:

http://{hostname}/customers/{id}

You can then build a URI from this template using one of the build() methods discussed earlier.

There’s also a few peculiarities with this interface. The build(Object..) and build(Map<String, ?>) methods automatically encode / characters. Take this, for example:

URI uri = UriBuilder.fromUri("/{id}").build("a/b");

This expression would result in:

/a%2Fb

Oftentimes, you may not want to encode the / character. So, two new build() methods were introduced in JAX-RS 2.0:

public abstract URI build(Object[] values, boolean encodeSlashInPath)

throws IllegalArgumentException, UriBuilderException

public abstract URI buildFromMap(Map<String, ?> values, boolean encodeSlashInPath)

throws IllegalArgumentException, UriBuilderException

If you set the encodeSlashInPath to false, then the / character will not be encoded.

Finally, you may also want to use UriBuilder to create template strings. These are often embedded within Atom links. A bunch of new resolveTemplate() methods were added to UriBuilder in JAX-RS 2.0:

public abstract UriBuilder resolveTemplate(String name, Object value);

public abstract UriBuilder resolveTemplate(String name, Object value,

boolean encodeSlashInPath);

public abstract UriBuilder resolveTemplateFromEncoded(String name,

Object value);

public abstract UriBuilder resolveTemplates(Map<String, Object>

templateValues);

public abstract UriBuilder resolveTemplates(Map<String, Object>

templateValues, boolean

encodeSlashInPath)

throws IllegalArgumentException;

public abstract UriBuilder resolveTemplatesFromEncoded(Map<String, Object>

templateValues);

These work similarly to their build() counterparts and are used to partially resolve URI templates. Each of them returns a new UriBuilder instance that resolves any of the supplied URI template parameters. You can then use the toTemplate() method to obtain the template as aString. Here’s an example:

String original = "http://{host}/{id}";

String newTemplate = UriBuilder.fromUri(original)

.resolveTemplate("host", "localhost")

.toTemplate();

Relative URIs with UriInfo

When you’re writing services that distribute links, there’s certain information that you cannot know at the time you write your code. Specifically, you will probably not know the hostnames of the links. Also, if you are linking to other JAX-RS services, you may not know the base paths of the URIs, as you may be deployed within a servlet container.

While there are ways to write your applications to get this base URI information from configuration data, JAX-RS provides a cleaner, simpler way through the use of the javax.ws.rs.core.UriInfo interface. You were introduced to a few features of this interface in Chapter 5. Besides basic path information, you can also obtain UriBuilder instances preinitialized with the base URI used to define all JAX-RS services or the URI used to invoke the current HTTP request:

public interface UriInfo {

public URI getRequestUri();

public UriBuilder getRequestUriBuilder();

public URI getAbsolutePath();

public UriBuilder getAbsolutePathBuilder();

public URI getBaseUri();

public UriBuilder getBaseUriBuilder();

For example, let’s say you have a JAX-RS service that exposes the customers in a customer database. Instead of having a base URI that returns all customers in a document, you want to embed previous and next links so that you can navigate through subsections of the database (I described an example of this earlier in this chapter). You will want to create these link relations using the URI to invoke the request:

@Path("/customers")

public class CustomerService {

@GET

@Produces("application/xml")

public String getCustomers(@Context UriInfo uriInfo) {

UriBuilder nextLinkBuilder = uriInfo.getAbsolutePathBuilder();

nextLinkBuilder.queryParam("start", 5);

nextLinkBuilder.queryParam("size", 10);

URI next = nextLinkBuilder.build();

... set up the rest of the document ...

}

To get access to a UriInfo instance that represents the request, we use the @javax.ws.rs.core.Context annotation to inject it as a parameter to the JAX-RS resource method getCustomers(). Within getCustomers(), we call uriInfo.getAbsolutePathBuilder() to obtain a preinitialized UriBuilder. Depending on how this service was deployed, the URI created might look like this:

http://example.com/jaxrs/customers?start=5&size=10

UriInfo also allows you to relativize a URI based on the current request URI.

public URI relativize(URI uri);

So, for example, if the current request was http://localhost/root/a/b/c and you passed a/d/e as a parameter to the relativize() method, then the returned URI would be ../../d/e. The root segment is the context root of your JAX-RS deployment. Relativization is based off of this root.

You can also resolve URIs with respect to the base URI of your JAX-RS deployment using the resolve() method:

public URI resolve(URI uri);

Invoking this method is the same as calling uriInfo.getBaseURI().resolve(uri).

There are other interesting tidbits available for building your URIs. In Chapter 4, I talked about the concept of subresource locators and subresources. Code running within a subresource can obtain partial URIs for each JAX-RS class and method that matches the incoming requests. It can get this information from the following methods on UriInfo:

public interface UriInfo {

...

public List<String> getMatchedURIs();

public List<String> getMatchedURIs(boolean decode);

}

So, for example, let’s reprint the subresource locator example in Chapter 4:

@Path("/customers")

public class CustomerDatabaseResource {

@Path("{database}-db")

public CustomerResource getDatabase(@PathParam("database") String db) {

Map map = ...; // find the database based on the db parameter

return new CustomerResource(map);

}

}

CustomerDatabaseResource is the subresource locator. Let’s also reprint the subresource example from Chapter 4 with a minor change using these getMatchedURIs() methods:

public class CustomerResource {

private Map customerDB;

public CustomerResource(Map db) {

this.customerDB = db;

}

@GET

@Path("{id}")

@Produces("application/xml")

public StreamingOutput getCustomer(@PathParam("id") int id,

@Context UriInfo uriInfo) {

for(String uri : uriInfo.getMatchedURIs()) {

System.out.println(uri);

}

...

}

}

If the request is GET http://example.com/customers/usa-db/333, the output of the for loop in the getCustomer() method would print out the following:

http://example.com/customers

http://example.com/customers/usa-db

http://example.com/customers/usa-db/333

The matched URIs correspond to the @Path expressions on the following:

§ CustomerDatabaseResource

§ CustomerDatabaseResource.getDatabase()

§ CustomerResource.getCustomer()

Honestly, I had a very hard time coming up with a use case for the getMatchedURIs() methods, so I can’t really tell you why you might want to use them.

The final method of this category in UriInfo is the getMatchedResources() method:

public interface UriInfo {

...

public List<Object> getMatchedResources();

}

This method returns a list of JAX-RS resource objects that have serviced the request. Let’s modify our CustomerResource.getCustomer() method again to illustrate how this method works:

public class CustomerResource {

private Map customerDB;

public CustomerResource(Map db) {

this.customerDB = db;

}

@GET

@Path("{id}")

@Produces("application/xml")

public StreamingOutput getCustomer(@PathParam("id") int id,

@Context UriInfo uriInfo) {

for(Object match : uriInfo.getMatchedResources()) {

System.out.println(match.getClass().getName());

}

...

}

}

The for loop in getCustomer() prints out the class names of the JAX-RS resource objects that were used to process the request. If the request is GET http://example.com/customers/usa-db/333, the output of the for loop would be:

com.acme.CustomerDatabaseResource

com.acme.CustomerResource

Again, I’m hard-pressed to find a use case for this method, but it’s in the specification and you should be aware of it.

Building Links and Link Headers

JAX-RS 2.0 added some support to help you build Link headers and to embed links in your XML documents through the Link and Link.Builder classes:

package javax.ws.rs.core;

public abstract class Link {

public abstract URI getUri();

public abstract UriBuilder getUriBuilder();

public abstract String getRel();

public abstract List<String> getRels();

public abstract String getTitle();

public abstract String getType();

public abstract Map<String, String> getParams();

public abstract String toString();

}

Link is an abstract class that represents all the metadata contained in either a Link header or Atom link. The getUri() method pertains to the href attribute of your Atom link. getRel() pertains to the rel attribute, and so on. You can also reference any of these attributes as well as any proprietary extension attributes through the getParams() method. The toString() method will convert the Link instance into a Link header.

Link instances are built through a Link.Builder, which is created by one of these methods:

public abstract class Link {

public static Builder fromUri(URI uri)

public static Builder fromUri(String uri)

public static Builder fromUriBuilder(UriBuilder uriBuilder)

public static Builder fromLink(Link link)

public static Builder fromPath(String path)

public static Builder fromResource(Class<?> resource)

public static Builder fromMethod(Class<?> resource, String method)

All these fromXXX() methods work similarly to the UriBuilder.fromXXX() methods. They initialize an underlying UriBuilder that is used to build the href of the link.

The link(), uri(), and uriBuilder() methods allow you to override the underlying URI of the link you are creating:

public abstract class Link {

interface Builder {

public Builder link(Link link);

public Builder link(String link);

public Builder uri(URI uri);

public Builder uri(String uri);

public Builder uriBuilder(UriBuilder uriBuilder);

...

As you can probably guess, the following methods allow you to set various attributes on the link you are building:

public Builder rel(String rel);

public Builder title(String title);

public Builder type(String type);

public Builder param(String name, String value);

Finally, there’s the build() method that will create the link:

public Link build(Object... values);

The Link.Builder has an underlying UriBuilder. The values passed into the build() method are passed along to this UriBuilder to create the URI for the Link. Let’s look at an example:

Link link = Link.fromUri("http://{host}/root/customers/{id}")

.rel("update").type("text/plain")

.build("localhost", "1234");

Calling toString() on the link instance will result in:

<http://localhost/root/customers/1234>; rel="update"; type="text/plain"

You can also build relativized links using the buildRelativized() method:

public Link buildRelativized(URI uri, Object... values);

This method will build the link instance with a relativized URI based on the underlying URI of the Link.Builder and the passed-in uri parameter. For example:

Link link = Link.fromUri("a/d/e")

.rel("update").type("text/plain")

.buildRelativized(new URI("a"));

The URI is calculated internally like this:

URI base = new URI("a");

URI supplied = new URI("a/d/e");

URI result = base.relativize(supplied);

So, the String representation of the link variable from the example would be:

<d/e>; rel="update"; type="text/plain"

You can also use the baseUri() methods to specific a base URI to prefix your link’s URI. Take this, for example:

Link link = Link.fromUri("a/d/e")

.rel("update").type("text/plain")

.baseUri("http://localhost/")

.buildRelativized(new URI("http://localhost/a"));

This example code would also output:

<d/e>; rel="update"; type="text/plain"

Writing Link Headers

Built Link instances can be used to create Link headers. Here’s an example:

@Path

@GET

Response get() {

Link link = Link.fromUri("a/b/c").build();

Response response = Response.noContent()

.links(link)

.build();

return response;

}

Just build your Link and add it as a header to your Response.

Embedding Links in XML

The Link class also contains a JAXB XmlAdapter so that you can embed links within a JAXB class. For example, let’s take our familiar Customer domain class and enable it to add one or more embedded links:

import javax.ws.rs.core.Link;

@XmlRootElement

public class Customer {

private String name;

private List<Link> links = new ArrayList<Link>();

@XmlElement

public String getName()

{

return name;

}

public void setName(String name)

{

this.name = name;

}

@XmlElement(name = "link")

XmlJavaTypeAdapter(Link.JaxbAdapter.class)

public List<Link> getLinks()

{

return links;

}

}

You can now build any links you want and add them to the Customer domain class. They will be converted into XML elements.

Wrapping Up

In this chapter, we discussed how links and forms have allowed the Web to scale. You learned the advantages of applying HATEOAS to RESTful web service design. Finally, you saw some JAX-RS utilities that can help make enabling HATEOAS in your JAX-RS services easier. Chapter 24contains some code you can use to test-drive many of the concepts in this chapter.


[10] For more information, see the w3 website.

[11] For more information, see http://www.iana.org/assignments/link-relations/link-relations.xhtml.

[12] For more information, see 9 Method Definitions.