HATEOAS by Example

January 7th, 2010 by Henri Bezemer

Hypermedia as the Engine of Application State (HATEOAS) is a very useful concept in the RESTful architectural style. The best way to show the benefits of HATEOAS is by giving an example. In this post I’ll be using the Jersey JAX-RS framework to build three RESTFul services. By mixing XForms into the equation, these services form a complete (yet tiny) web application. The example can be deployed on GlassFish.

The RESTFul resources and their representations

There are three resources that make up my example.

The first resource is a list of customers and will be represented as pure XML data. The resource is bound to the method GET, a URI of /customers and it produces “application/xml” content. It has two query parameters: start and size. These parameters are intended to constrain the size of the response produced by this service. Start means: give me a list starting at this index. Size means: give me this many customers. To protect the service from unintentional misuse, the parameters have default values of 0 and 10 respectively. So get /customers is effectively the same as get /customers?start=0&size=10. Besides the customer data, the service produces two links: “next”, providing the full url to get the next chunk of customers, and “previous” to get the previous chunk. As an example, here is the response to get /customers?size=2:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customers xmlns=http://www.zienit.com/customer xmlns:ns2="http://www.w3.org/2005/Atom">
	<customer id="1">
		<first-name>Roland</first-name>
		<last-name>Langston</last-name>
		<street>746 Hill Avenue</street>
		<city>Fabens</city>
		<state>HI</state>
		<zip>87743</zip>
		<ns2:link type="application/xml" href="http://localhost:8080/simplerest/customers/1"
			rel="self" />
	</customer>
	<customer id="2">
		<first-name>Tony</first-name>
		<last-name>Salas</last-name>
		<street>2642 Hill Blvd.</street>
		<city>Jefferson City</city>
		<state>FL</state>
		<zip>66317</zip>
		<ns2:link type="application/xml" href="http://localhost:8080/simplerest/customers/2"
			rel="self" />
	</customer>
	<ns2:link type="application/xml"
		href="http://localhost:8080/simplerest/customers?start=2&size=2"
		rel="next" />
</customers>

In this example, there is a link to the next chunk of customers (at lines 23-25), but there is no link to the previous chunk, because there is none.  Also, the individual customers each have a link (which turns out to be a link to themselves, as will become clear once I present the next resource. The link element is in namespace URI http://www.w3c.org/2005/Atom. Atom has become a very popular to structure RESTFul data, here I’m only using its link element for simplicity.

The second resource is a single customer. Its URI is /customers/{id} where {id} is a specific (numerical) identifier. An example of a URI is /customers/1 which will yield either an XML representation of a customer or a 404 “not-found” code if no customer with this id exists.

The third resource is a XHTML+XForms form through which a list of customers can be edited (for simplicities sake, “editing” here means browsing forwards and backwards through the list. Adding new customers, editing and deleting existing ones is not implemented). This is the actual form:

<?xml-stylesheet href="/simplerest/xsltforms/xsltforms.xsl" type="text/xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
	xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:q="http://www.zienit.com/customer"
	xmlns:atom="http://www.w3.org/2005/Atom"
>
	<head>
		<title>Customers</title>
		<xf:model>
			<xf:instance src="/customers"/>
			<xf:submission ref="false()" id="submit-prev" method="get" replace="instance">
				<xf:resource value="atom:link[@rel='previous']/@href"/>
			</xf:submission>
			<xf:submission ref="false()" id="submit-next" method="get" replace="instance">
				<xf:resource value="atom:link[@rel='next']/@href"/>
			</xf:submission>
			<xf:bind id="link-prev" nodeset="atom:link[@rel='previous']/@href" relevant="."/>
			<xf:bind id="link-next" nodeset="atom:link[@rel='next']/@href" relevant="."/>
		</xf:model>
	</head>
	<body>
		<table>
			<xf:repeat id="rows" nodeset="q:customer">
				<tr>
					<td>
						<xf:output ref="q:first-name" />
					</td>
					<td>
						<xf:output ref="q:last-name" />
					</td>
					<td>
						<xf:output ref="q:street" />
					</td>
					<td>
						<xf:output ref="q:city" />
					</td>
					<td>
						<xf:output ref="q:state" />
					</td>
					<td>
						<xf:output ref="q:zip" />
					</td>
				</tr>
			</xf:repeat>
		</table>
		<xf:submit bind="link-prev" submission="submit-prev">
			<xf:label>Previous</xf:label>
		</xf:submit>
		<xf:submit bind="link-next" submission="submit-next">
			<xf:label>Next</xf:label>
		</xf:submit>
	</body>
</html>

Because browsers currently do not provide XForms processing, I’m using an implementation called XSLTForms in this example. To make XSLTForms work, a special processing instruction is inserted at line 1. Also, it is important that the browser recognizes this resource as XML, otherwise it will not perform an XSLT transformation.

Let me explain what this form does. At line 9, XML data is pulled into the form from a specific url. At lines 10-12 a submission is defined. This submission will invoke a GET on the url as specified by the previous link element. The response to the GET will replace the current instance data. Note that ref is set to XPath expression false() as my means of setting it to an empty node-set. If ref is not set to an empty node-set, the particular node-set will be added to the GET request. At line 13-15, a similar submission is defined based on the next link. At line 16-17, two bindings are defined that are relevant (visible) when the previous resp. next link is present in the instance data. The rest of the form is used to display individual customers in the rows of the table. Two buttons, labeled previous and next are linked their respective submission element and will be shown or hidden based on their respective binding.

JAXB classes

We need a few Java classes to (un)marshal XML. These classes are POJO classes with some JAXB annotations. Let’s start with the simplest, the AtomLink:

package com.zienit.simplerest;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="link", namespace="http://www.w3.org/2005/Atom")
public class AtomLink {
	public @XmlAttribute String rel;
	public @XmlAttribute String href;
	public @XmlAttribute String type;
	public AtomLink() {}
	public AtomLink(String rel, String href, String type) {
		this.rel = rel;
		this.href = href;
		this.type = type;
	}
	public AtomLink(String rel, String href) {
		this(rel,href,"application/xml");
	}
}

The next JAXB class is intended for (un)marshalling individual customers:

package com.zienit.simplerest;

import javax.xml.bind.annotation.*;

@XmlRootElement(name="customer")
public class Customer {
	@XmlAttribute public long id;
	@XmlElement(name="first-name") public String firstName;
	@XmlElement(name="last-name") public String lastName;
	@XmlElement public String street;
	@XmlElement public String city;
	@XmlElement public String state;
	@XmlElement public String zip;
	@XmlElementRef public AtomLink self;
}

Note that for the field called self I’m using an XmlElementRef annotation. This ensures that we’ll honor the XmlRootElement annotation as specified on the AtomLink class. If XmlElement was used, we would have produced an element called self instead of link.

The next class is used to wrap a list of customers:

package com.zienit.simplerest;

import java.io.*;
import java.util.List;

import javax.xml.bind.*;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="customers")
public class Customers {
	@XmlElementRef public List<Customer> list;
	@XmlElementRef public AtomLink prev;
	@XmlElementRef public AtomLink next;
	public static Customers load() {
		try {
			JAXBContext context = JAXBContext.newInstance(Customers.class);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			return (Customers)unmarshaller.unmarshal(new File("c:/data/customers.xml"));
		} catch (JAXBException e) {
			e.printStackTrace();
			return null;
		}
	}
}

This class also contains a factory method called load. This factory unmarshals a XML file that contains a list of customers into a Java Customers object. The namespace for the customers and customer classes is conveniently defined at the package level in a class file called package-info.java:

@javax.xml.bind.annotation.XmlSchema(namespace="http://www.zienit.com/customer",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.zienit.simplerest;

Test data

In order to get a large test set of customers, I used a nifty free MS Excel plugin to create person-oriented sample data and exported the data from Excel to XML format. A zipped file containing a hundred customers is found here.

JAX-RS resource class

Most of the RESTful action takes place in the following class:

package com.zienit.simplerest;

import java.util.ArrayList;

import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;

import com.sun.jersey.api.view.Viewable;

@Path("/customers")
public class CustomerResource {

	@GET
	@Produces("application/xml")
	public Customers getCustomers(
			@QueryParam("start") @DefaultValue("0") long start,
			@QueryParam("size") @DefaultValue("10") int size,
			@Context UriInfo uriInfo) {
		Customers all = Customers.load();
		Customers result = new Customers();
		result.list = new ArrayList<Customer>();
		int i = 0;
		for (Customer c : all.list) {
			if ((i >= start) && (i < start + size)) {
				UriBuilder builder = uriInfo.getBaseUriBuilder();
				builder.path(CustomerResource.class);
				builder.path(CustomerResource.class,"getCustomer");
				c.self = new AtomLink("self",builder.build(c.id).toString());
				result.list.add(c);
			}
			i++;
		}
		if (start > 0) {
			UriBuilder builder = uriInfo.getAbsolutePathBuilder();
			builder.queryParam("start", Math.max(start - size,0));
			builder.queryParam("size", size);
			result.prev = new AtomLink("previous",builder.build().toString());
		}
		if (i > start + size) {
			UriBuilder builder = uriInfo.getAbsolutePathBuilder();
			builder.queryParam("start", start + size);
			builder.queryParam("size", size);
			result.next = new AtomLink("next",builder.build().toString());
		}
		return result;
	}

	@GET
	@Path("edit")
	public Viewable getCustomersEdit(@Context UriInfo uriInfo) {
		UriBuilder builder = uriInfo.getBaseUriBuilder();
		builder.path(CustomerResource.class);
		return new Viewable("/customers-edit.jsp",builder.build().toString());
	}

	@GET
	@Path("{id: \\d+}")
	@Produces("application/xml")
	public Response getCustomer(@PathParam("id") long id, @Context UriInfo uriInfo) {
		ResponseBuilder builder = Response.status(Status.NOT_FOUND);
		Customers all = Customers.load();
		for (Customer c : all.list) {
			if (c.id == id) {
				c.self = new AtomLink("self",uriInfo.getAbsolutePathBuilder().build().toString());
				builder.status(Status.OK).entity(c);
				break;
			}
		}
		return builder.build();
	}
}

 The method getCustomers at line 17 will be invoked by GET /customers (well, the real life URI will be {url of server}/{context root of web-application}/customers), taking two parameters: start and size. Also, a parameter uriInfo is injected at runtime at line 20. This parameter is needed to get some information that is known only after deployment (such as {url of server}). In Lines 21-34 a selection of customers is copied from the list of all customers. Also, the links to each individual customer is created (lines 27-30). Note that uriInfo is used to get the base url {url of server}/{context root of web-application}. Then the path of the class (\customers) is appended, and last path of the method getCustomer ({id}). To substitute {id}, method build is called with customer’s id as the argument. Line 34-46 are concerned with the logic to set the link elements to the appropriate values (if any).

Method getCustomersEdit at line 52 is invoked by GET /customers/edit. This method returns an object of the Jersey specific class Viewable. This class is very useful if you want to return a result that is typically constructed from a template. In this case, I want to return the XForm shown earlier and my template of choice is a JSP page. Here is the JSP page:

<%@ page language="java" contentType="application/xml; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<?xml-stylesheet href="/simplerest/xsltforms/xsltforms.xsl" type="text/xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
	xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:q="http://www.zienit.com/customer"
	xmlns:atom="http://www.w3.org/2005/Atom"
>
	<head>
		<title>Customers</title>
		<xf:model>
			<xf:instance src="${it}"/>
			<xf:submission ref="false()" id="submit-prev" method="get" replace="instance">
				<xf:resource value="atom:link[@rel='previous']/@href"/>
			</xf:submission>
			<xf:submission ref="false()" id="submit-next" method="get" replace="instance">
				<xf:resource value="atom:link[@rel='next']/@href"/>
			</xf:submission>
			<xf:bind id="link-prev" nodeset="atom:link[@rel='previous']/@href" relevant="."/>
			<xf:bind id="link-next" nodeset="atom:link[@rel='next']/@href" relevant="."/>
		</xf:model>
	</head>
	<body>
		<table>
			<xf:repeat id="rows" nodeset="q:customer">
				<tr>
					<td>
						<xf:output ref="q:first-name" />
					</td>
					<td>
						<xf:output ref="q:last-name" />
					</td>
					<td>
						<xf:output ref="q:street" />
					</td>
					<td>
						<xf:output ref="q:city" />
					</td>
					<td>
						<xf:output ref="q:state" />
					</td>
					<td>
						<xf:output ref="q:zip" />
					</td>
					<td>
						<xf:trigger appearance="full">
							<xf:label>Edit</xf:label>
						</xf:trigger>
					</td>
				</tr>
			</xf:repeat>
		</table>
		<xf:submit bind="link-prev" submission="submit-prev">
			<xf:label>Previous</xf:label>
		</xf:submit>
		<xf:submit bind="link-next" submission="submit-next">
			<xf:label>Next</xf:label>
		</xf:submit>
	</body>
</html>

 The second parameter to Viewable’s constructor is an object that you can refer to as “it” in the JSP. In this example (at line 10) I’m using a simple String object which contains the URI of the service to get the list of customers.

The third and final method of CustomerResource is getCustomer at line 61. It uses a regular expression to specify valid values for its {id} path parameter. This helps the JAX-RS runtime to route requests to the proper method (to distinguish /customer/edit from /customer/1 for instance). This method explicitly responds with 404 if the specified customer is not found.

Configuring Jersey

Jersey is completely configured through the web application’s web.xml:

?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
>
	<filter>
		<filter-name>jersey</filter-name>
		<filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
		<init-param>
			<param-name>com.sun.jersey.config.property.WebPageContentRegex</param-name>
			<param-value>/(xsltforms|(WEB-INF/jsp))/.*</param-value>
		</init-param>
		<init-param>
			<param-name>com.sun.jersey.config.property.JSPTemplatesBasePath</param-name>
			<param-value>/WEB-INF/jsp</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>jersey</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

Normally the Jersey servlet is configured as a servlet (what’s in a name?). However, here it is configured as a filter! Jersey intercepts all url’s under the context root (pattern /* at line 20). However, if Jersey is configured as a filter, it is possible exclude specific urls from being handled by Jersey. In effect, those url’s are regular web application url’s. In this example, I’ve specified /xsltforms and /WEB-INF/jsp to be excluded from Jersey’s handling (lines 9-12). Also, the path /WEB-INF/jsp is specified as the root of the jsp templates (lines 13-16). This means that this statement:

return new Viewable("/customers-edit.jsp",null);

will instruct Jersey to look for a template at WEB-INF/jsp/customers-edit.jsp.

Documentation about Jersey configuration is not wide spread, but this blog post helped me greatly.

Building the example

 As always I’ll build my example using Maven2. Without further comments, here is the POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.zienit.simple</groupId>
	<artifactId>simplerest</artifactId>
	<packaging>war</packaging>
	<name>simplerest</name>
	<version>0.0.1-SNAPSHOT</version>
	<repositories>
		<repository>
			<id>maven-repository.dev.java.net</id>
			<name>Java.net Repository for Maven 1</name>
			<url>http://download.java.net/maven/1/</url>
			<layout>legacy</layout>
		</repository>
		<repository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Repository for Maven 2</name>
			<url>http://download.java.net/maven/2/</url>
		</repository>
	</repositories>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-server</artifactId>
			<version>1.1.4</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.xml.bind</groupId>
			<artifactId>jaxb-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<!-- needed for ServletContext -->
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>simplerest</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

HATEOAS conclusion

The use of links in the representations explains the HATEOAS principle. Note that HATEOAS reduces the knowledge that a client must have about the services it uses. As shown in this example, the client does not need to understand what the parameters for cursoring mechanism are and what they mean. It only needs to know that it should use the “next” and “previous” links. This information hiding is very powerful. It allows you to change the cursoring parameters without any change to the clients. Specific types of Atom links (as denoted by the rel attribute) are being standardized. The “previous” and “next” semantics as used in this example have already been standardized.

3 Comments

January 7, 2010 at 9:33 pm Arul

1

Very informative entry. It shows how simple it is to develop HATEOAS based application using Jersey. Thanks for posting it!

-Arul

January 8, 2010 at 7:48 am Arul

2

Hi Henri,

I was trying to run your example on GFv3. I am missing the piece on how to use the XForms with the JSP. I copied the xsltforms.xsl to my webapp/xsltforms dir. I placed the customers-edit.jsp in WEB-INF/jsp dir. But, when I access the customers/edit, I just see the Next and Previous text, and I do not see the form with the stylesheet.

Am I missing anything?

-Arul

January 9, 2010 at 1:16 am Henri

3

I’ve updated the post to fix the problems that Arul has reported. Arul tested the example succesfully after the fix on GlassFish V3 with IE8 and Chrome. Firefox did not work though. Thanks for the feedback, Arul!