Hypermedia Support in JAX-RS 2.0
Working with JAX-RS? Hypermedia, a Hypertext extension, now supports JAX-RS. Check out more about using these two together, complete with a simple example.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
Hypermedia is an extension to Hypertext concept. Hypermedia is used to represent broader types of resources such as audio, video, image and links. Using hypermedia in RESTful systems is referred to as one of the REST architecture style constraints mentioned by Roy Fielding in his thesis as ‘hypermedia as the engine of application state’ or HATEOAS. In HATEOAS compliant servers, clients are instructed on how to further interact with the server or how to locate further resources. In each stage in client-server interactions, the addresses (hyperlinks) to other resources which the client can consume are provided by the server accordingly. Clients only need to have the address to the main resource.
To take a quick look at hypermedia contents, consider the following example. A client request information about a USER resource identified as “SAM” by calling http://www.mydomain.com/resources/user/sam. The server fetches information about user SAM and returns a response as bellow (Considering the XML data type as the preferred content type):
<user>
<name>SAM</name>
<age>32</age>
<status>active</status>
<link rel="self" href="http://www.mydomain.com/resources/user/sam" />
<link rel="delete" href="http://www.mydomain.com/resources/user/sam/delete" />
<link rel="deactivate" href="http://www.mydomain.com/resources/user/sam/deactivate " />
</user>
Information about user SAM such as name, age and status is provided in the response. In this response hypermedia contents are represented by <link> elements. These hypermedia links are indicating what other options the client have to further interact with the server. In such HATEOAS compliant server, we would expect that the server eliminates delete and deactivate hyperlinks if the client doesn’t have the appropriate security roles to request such operations from the server.
Hypermedia and JAX-RS 2.0 – Building blocks
Server Side
As it is obvious, URIs are the building blocks of hypermedia contents. JAX-RS provides UriBuilder class to facilitate the process of URI creation. With the help of UriBuilder and other helper classes, both relative and absolute URIs can be built quickly and easily. For instance to construct one of the hyperlinks from the example above, UriBuilder can be used:
UriBuilder.fromUri("http://www.mydomain.com/")
.path("resources/user/sam")
.build();
UriBuilder can also be used to construct other URI variations. For instance to construct “http://www.mydomain.com/resources/user?name=sam” using UriBuilder:
UriBuilder.fromUri("http://www.mydomain.com/")
.path("resources/{a}")
.queryParam("name", "{name}")
.build("user", "sam");
Another useful class that is provided by JAS-RS is UriInfo. This class provides access to both application’s and request’s URI information. UriInfo is an injectable interface that is obtained by @Context annotation at runtime.
@Context
UriInfo uriInfo;
In the above example, instead of providing the URI explicitly, it can be obtained by use of the UriInfo. Consider the following example:
UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
uriBuilder.build();
The classes that has been mentioned so far, are the helper classes that are used to construct any kind of URI. It’s advised to refer to API documentation for more information about additional methods that each of these classes provides.
JAX-RS 2.0 introduces another class which is the main point of interest in this article. Link class is used as the representation of resources relationship on the web as defined by RFC 5899. Link class provides methods that are used to construct link elements (consider the example at the beginning of this article). Each link contains URI and other meta-data that can be put into http message header or in the response payload. Considering the previous example, the following code is used to create a link to user resource:
Link user = Link.fromUri(uriInfo.getAbsolutePath()).rel("self").type("GET").build();
If the above code is called inside the method with @path(“/user/{ID}”) and “”, the following link will be constructed:
"<http://www.mydomain.com/resources/user/sam>; rel="self"; type="GET""
To comply with RFC 5899 this link can be put into http message header using Responseclass:
Response.ok(userEntity).links(user).build();
Client Side
JAX-RS 2.0 also provides client side API so clients can read links from http headers, transform them to resource links and use them to invoke any resource based on the link’s meta-data. Consider the following client side sample code. The http message header contains a link to disable the user resource that is contained in the message payload.
"<http://www.mydomain.com/resources/user/sam/disable>; rel="disable"; type="GET""
The client automatically checks the links in the message header for the link with the rel=”disable”, then proceeds to disable the user “sam” by calling the URI that is contained in the link. Obviously the method is GET as the type="GET" meta-data indicates:
Client restClient = ClientBuilder.newClient();
WebTarget target = restClient.target("http://www.mydomain.com/resources/");
WebTarget resourceTarget = target.path("user/sam ");
Response response = resourceTarget.request("*/*").get();
Set<Link> links = response.getLinks();
for (Link link : links) {
if(link.getRel().equals("disable ")){
restClient.target(link).request().get();
}
}
In addition to having LINKs in http header, LINKS can also be contained in application data model. JAX-RS supports serialization and deserialization mechanisms by providing Link.JaxbAdapter and Link.JaxbLink classes. These two classes are nested inside javax.ws.rs.core.Link class.
For instance the link to the user resource “sam” with relationship “self” (that is referring to the resources itself)will be serialized as follows:
<link href=" http://www.mydomain.com/resources/user/sam" rel="self" type="GET"/>
A Simple Case Scenario
In the following section a simple case scenario will be demonstrated to show theses API’s in action. In the first step, the scenario will present a non-hypermedia system. In the next step, the system will be modified to support hypermedia contents using the hypermedia features we’ve discussed so far.
In this example a simple book store scenario will be considered. The book store has many customers and each customer owns many books (one-to-many). The book store system keeps track of each customer and the books that each customer has bought. The relationship between these two entities is as follows:
Considerations:
- In this scenario the preferred data format is application/XML.
- The book store scenario in this article is not the best case scenario but, it is considered to be an easy one.
- The sole purpose of this example is to demonstrate the hypermedia API that is supported by JAX-RS 2.
- The mechanism to persist/retrieve of data in this system will not be discussed. It is out of scope of this document.
Book store system has two main entities classes representing customer and book. The following class represents the BookstoreCustomer entity:
@XmlRootElement
public class BookstoreCustomer {
private String FirstName, LastName, ID;
private final ArrayList<Book> ownage = new ArrayList<>();
@XmlElementWrapper(name="shelf")
@XmlElement(name = "book")
public ArrayList<Book> getOwnage() {
return ownage;
}
public void addBook(Book book) {
ownage.add(book);
}
public String getFirstName() {
return FirstName;
}
public void setFirstName(String FirstName) {
this.FirstName = FirstName;
}
public String getLastName() {
return LastName;
}
public void setLastName(String LastName) {
this.LastName = LastName;
}
public String getID() {
return ID;
}
public void setID(String ID) {
this.ID = ID;
}
}
A book is represented by the Book entity class:
public class Book {
private String TITLE, AUTHOR, YEAR, ISBN;
public String getTITLE() {
return TITLE;
}
public void setTITLE(String TITLE) {
this.TITLE = TITLE;
}
public String getAUTHOR() {
return AUTHOR;
}
public void setAUTHOR(String AUTHOR) {
this.AUTHOR = AUTHOR;
}
public String getYEAR() {
return YEAR;
}
public void setYEAR(String YEAR) {
this.YEAR = YEAR;
}
public String getISBN() {
return ISBN;
}
public void setISBN(String ISBN) {
this.ISBN = ISBN;
}
}
Application clients can request information about any registered customer using “bookstore/customer/{id}” resource address:
@Stateless
@Path("/bookstore")
public class bookStoreResource {
@GET
@Path("/customer/{id}")
@Produces("application/xml")
public Response queryCustomer(@PathParam("id") String id) {
if (id.equalsIgnoreCase("XYZ")) {
BookstoreCustomer customer = new BookstoreCustomer();
customer.setID("XYZ");
customer.setFirstName("Sam");
customer.setLastName("Sepassi");
Book book1 = new Book();
book1.setAUTHOR("F. Scott Fitzgerald");
book1.setISBN("9781597226769");
book1.setTITLE("The Great Gatsby");
Book book2 = new Book();
book2.setAUTHOR("Ken Kesey");
book2.setISBN("9780143105022");
book2.setTITLE("One Flew Over the Cuckoo's Nest");
customer.addBook(book1);
customer.addBook(book2);
return Response.accepted(customer).build();
} else {
return Response.status(300).build();
}
}
}
In the code above, the method accepts an ID parameter and constructs a sample customer entity “SAM”. The sample customer “SAM” owns two books in his shelf. The method serializes the customer and book entities and returns the content as xml formatted data in the payload.
<bookstoreCustomer>
<ID>XYZ</ID>
<firstName>Sam</firstName>
<lastName>Sepassi</lastName>
<shelf>
<book>
<AUTHOR>F. Scott Fitzgerald</AUTHOR>
<ISBN>9781597226769</ISBN>
<TITLE>The Great Gatsby</TITLE>
</book>
<book>
<AUTHOR>Ken Kesey</AUTHOR>
<ISBN>9780143105022</ISBN>
<TITLE>One Flew Over the Cuckoo's Nest</TITLE>
</book>
</shelf>
</bookstoreCustomer>
If the client doesn’t enter a valid ID the server returns status code 300.
In this non-hypermedia server system, the payload contains all the information and entities that the application data model defines. In the previous response, the payload contains both customer and book entities. In this case the client is forced to receive the information about all the books each customer owns (whether the client wants to receive data about books or not).
One solution to such situation is resource separation. One resource to get the CUSTOMER entity and another to get the BOOKs for that customer. In this case the client first calls the customer resource to get information about a customer and then calls the book resource to query about books that are owned by that customer. The client must have the URI to both CUSTOMER and BOOK resources. Normally client needs an API to interact with a multi resource interface.
To change the current system model to a hypermedia supported one, some modifications are required. In a hypermedia supported system, resources are connected using hypermedia links. After the modifications, customer resource will provide links to BOOK entities in both data model and http message header. Technically clients only need the URI to the main resource (CUSTOMER resource). Further navigations (addresses to other resources i.e. book) will be provided by the server.
Moving Toward a Hypermedia Supported System
To begin the transformation, a LINK field is put into each entity classes.This link contains an address to the entity itself. Now each entity owns a direct address (URI) to be located independently in the hypermedia environment.
public class BookstoreCustomer {
…
private Link self;
public Link getSelf() {
return self;
}
public void setSelf(Link self) {
this.self = self;
}
…
}
public class Book {
…
private Link self;
public Link getSelf() {
return self;
}
public void setSelf(Link self) {
this.self = self;
}
…
}
Next a new resource @Path("/book/{isbn}") is created, so customer and book resources can be addressed independently. It’s expected that the method retrieveBook() has a mechanism to retrieve information about books based on the book’s ISBN number. In this example the process is done manually. As mentioned above each book entity contains a link to itself in the response.
@GET
@Path("/book/{isbn}")
@Produces("application/xml")
public Response retrieveBook(@PathParam("isbn") String isbn) {
if (isbn.equalsIgnoreCase("9781597226769")) {
Book book = new Book();
book.setAUTHOR("F. Scott Fitzgerald");
book.setISBN("9781597226769");
book.setTITLE("The Great Gatsby");
book.setSelf(
Link.fromUri(uriInfo.getAbsolutePath().resolve(book.getISBN()))
.rel("self").type("GET").build());
return Response.accepted(book).links(book.getSelf()).build();
} else if (isbn.equalsIgnoreCase("9780143105022")) {
Book book = new Book();
book.setAUTHOR("Ken Kesey");
book.setISBN("9780143105022");
book.setTITLE("One Flew Over the Cuckoo's Nest");
book.setSelf(
Link.fromUri(uriInfo.getAbsolutePath().resolve(book.getISBN()))
.rel("self").type("GET").build());
return Response.accepted(book).links(book.getSelf()).build();
} else {
return Response.status(300).build();
}
}
Now customer information that is returned from queryCustomer() method must contain links to the books that were registered for it. This way the response is more lightweight and the client doesn’t need to consult any API to locate books resources. The following code is the new queryCustomer() method after the modifications.
@Context
UriInfo uriInfo;
@GET
@Path("/customer/{id}")
@Produces("application/xml")
public Response queryCustomer(@PathParam("id") String id) {
if (id.equalsIgnoreCase("XYZ")) {
BookstoreCustomer customer = new BookstoreCustomer();
customer.setID("XYZ");
customer.setFirstName("Sam");
customer.setLastName("Sepassi");
customer.setSelf(Link.fromUri(uriInfo.getAbsolutePath())
.rel("self").type("GET").build());
Book book1 = new Book();
book1.setAUTHOR("F. Scott Fitzgerald");
book1.setISBN("9781597226769");
book1.setTITLE("The Great Gatsby");
book1.setSelf(
Link.fromUri(uriInfo.getBaseUriBuilder()
.path(getClass()).path(getClass(), "retrieveBook")
.build(book1.getISBN())).rel("book1").type("GET").build());
Book book2 = new Book();
book2.setAUTHOR("Ken Kesey");
book2.setISBN("9780143105022");
book2.setTITLE("One Flew Over the Cuckoo's Nest");
book2.setSelf(
Link.fromUri(uriInfo.getBaseUriBuilder()
.path(getClass()).path(getClass(), "retrieveBook")
.build(book2.getISBN())).rel("book2").type("GET").build());
customer.addBook(book1);
customer.addBook(book2);
return Response.accepted(customer).
links(customer.getSelf()).
links(book1.getSelf()).
links(book2.getSelf()).
build();
} else {
return Response.status(300).build();
}
}
Until now, the links that have been added to the Response will be added to http message header. To have these links in the data model too, the Link.JaxbAdapter.class will do the job.
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
private Link self;
Finally by eliminating the JAXB annotations on the getOwnage() method in the CUSTOMER entity class and using @XmlTransient annotation on the ownage property, the payload will only have the information about the CUSTOMER.
After the final modifications to the data model, CUSTOMER and BOOK entity classes become as follows:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class BookstoreCustomer {
private String FirstName, LastName, ID;
@XmlTransient
private final ArrayList<Book> ownage = new ArrayList<>();
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
private Link self;
…
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {
private String TITLE, AUTHOR, YEAR, ISBN;
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
private Link self;
…
}
The Response from the CUSTOMER resource will be:
HTTP HEADER “LINK”:
<http://localhost:8080/HelloWorld/resources/bookstore/customer/XYZ>; rel="self"; type="GET",
<http://localhost:8080/HelloWorld/resources/bookstore/book/9781597226769>; rel="book1"; type="GET",
<http://localhost:8080/HelloWorld/resources/bookstore/book/9780143105022>; rel="book2"; type="GET"
RESPONSE PAYLOAD:
<bookstoreCustomer>
<ID>XYZ</ID>
<firstName>Sam</firstName>
<lastName>Sepassi</lastName>
<self href="http://localhost:8080/HelloWorld/resources/bookstore/customer/XYZ" rel="self" type="GET"/>
</bookstoreCustomer>
The same thing goes for the BOOK resource. For instance by calling the “book1” link from the response header above, the following response will be returned. A “Link” to the resource “book1” is present in both the http header and the payload.
HTTP HEADER “LINK”:
"<http://localhost:8080/HelloWorld/resources/bookstore/book/9781597226769>; rel="self"; type="GET""
HTTP PAYLOAD:
<book>
<TITLE>The Great Gatsby</TITLE>
<AUTHOR>F. Scott Fitzgerald</AUTHOR>
<ISBN>9781597226769</ISBN>
<self href="http://localhost:8080/HelloWorld/resources/bookstore/book/9781597226769" rel="self" type="GET"/>
</book>
Hypermedia Supporting Clients
Clients can read the Link http header seeking the availability of any book record for that customer. Clients can fetch further information about any book by using the URI and “type” metadata. The client code is the same as the example that has been mentioned above.
Client restClient = ClientBuilder.newClient();
WebTarget target = restClient.target("http://localhost:8080/HelloWorld/resources/");
WebTarget resourceTarget = target.path("bookstore/customer/XYZ");
Response response = resourceTarget.request("application/xml").get();
BookstoreCustomer bookstoreCustomer = response.readEntity(BookstoreCustomer.class);
System.out.println("ID : XYZ");
System.out.println("NAME : "+bookstoreCustomer.getFirstName());
System.out.println("LAST NAME : "+bookstoreCustomer.getLastName());
System.out.println("--------------------------------------------");
Set<Link> links = response.getLinks();
System.out.println("The following books are owned by this customer :");
for (Link link : links) {
if (!link.getRel().equals("self")) {
Book book = restClient.target(link).request().get(Book.class);
System.out.println(book.getTITLE());
System.out.println(book.getAUTHOR());
System.out.println(book.getISBN());
System.out.println("This book is located at :");
System.out.println(link.getUri());
System.out.println("--------------------------------------------");
}
}
Real World Hypermedia Supporting Clients
In a real world case scenario, client codes could be inserted inside a servlet class. Unlike the above example which simply prints textual information, the client could attach html tags to the response and represent it as a web page.
Experimental Hypermedia API by Jersey
Jersey provides some experimental API to support hypermedia linking called the declarative hyperlinking API. The API is experimental as is mentioned in the Jersey documentation “This API is currently under development and experimental so it is subject to change at any time”. Although declarative hyperlinking API is experimental at this point, it’s a good practice to consider this API as one of the available facilities to integrate HATEOAS functionality into server systems. You are always welcome to develop your own methods to do the same job.
To use declarative hyperlinking API using Jersey, it’s advised to download the latest distribution jersey from jersey.java.net. It is also required to download or include jersey-declarative-linking library from Jersey/maven repository in order to use the API in any project.
A very simple RESTfull entity control scenario is used to illustrate the use of experimental declarative hyperlinking API in code. The principles regarding the HATEOAS and hypermedia concepts are the same as it’s been discussed earlier.
Consider the following entity class SimpleEntity.java :
@XmlRootElement(name = "SimpleEntity")
public class SimpleEntity implements Serializable {
private String id;
private String name;
private String family;
private int age;
private boolean active;
…
}
SimpleEntity entity class is used to fetch data about a person from a persistence medium. Some boilerplate codes such as setters and getters were eliminated.
In this example a logical persistence system ServerPersistenceService is considered to handle simple persistence operations and is managed by server environment.
The following class represents the application’s main resource. This main resource contains methods to perform simple control operations on SimpleEntity class. Consider the following code:
@Path("control")
public class ControlResource {
@Resource
ServerPersistenceService PersistenceService;
@GET
@Path("/{id}")
public SimpleEntity get(@PathParam("id") String id) {
SimpleEntity simpleEntity = PersistenceService.find(id);
return simpleEntity;
}
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") String id) {
PersistenceService.delete(id);
return Response.status(Response.Status.OK).build();
}
@POST
@Path("state/{id}")
public Response changeState(@PathParam("id") String id) {
SimpleEntity simpleEntity = PersistenceService.find(id);
If(simpleEntity. isActive()){
simpleEntity. setActive(false);
else{
simpleEntity. setActive(true);
}
return Response.status(Response.Status.OK).build();
}
}
By calling http://localhost:8080/ control /12 the following result will be given:
<SimpleEntity>
<id>12</id>
<name>Sam</name>
<family>Sepassi</family>
<age>31</age>
<active>true</active>
</SimpleEntity>
To represent a hypermedia link in SimpleEntity class, the javax.ws.rs.core.Link class have to be used as before. The @InjectLink annotation is used to inject URI and other metadata values to the Link attribute. The Jersey runtime is responsible to construct the appropriate URL and metadata and inject it to the Link attribute inside the SimpleEntity.
@InjectLink(
resource = ControlResource.class,
style = Style.ABSOLUTE,
rel = "self",
bindings = @Binding(name = "id", value = "${instance.id}"),
method = "get"
)
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
@XmlElement(name = "link")
Link self;
By updating the SimpleEntity class, the @InjectLink annotation’s elements instruct the Jersey runtime to build the URI pointing to the current SimpleEnity resource (A link to itself). This link will be available along with the SimpleEntity data in the response:
<SimpleEntity>
<id>12</id>
<name>Sam</name>
<family>Sepassi</family>
<age>31</age>
<active>true</active>
<link href="http://localhost:8080/control/12" rel="self"/>
</SimpleEntity>
If the client has the appropriate privileges to perform other control operations such as changing the status or deletion, other links will be available in the response as well.
@InjectLink(
resource = ControlResource.class,
style = Style.ABSOLUTE,
rel = "updatestat",
bindings = @Binding(name = "id", value = "${instance.id}"),
method = "changeState"
)
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
@XmlElement(name = "link")
Link status;
@InjectLink(
resource = ControlResource.class,
style = Style.ABSOLUTE,
rel = "delete",
bindings = @Binding(name = "id", value = "${instance.id}"),
method = "delete"
)
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
@XmlElement(name = "link")
Link deletion;
And the response will be:
<SimpleEntity>
<id>12</id>
<name>Sam</name>
<family>Sepassi</family>
<age>31</age>
<active>true</active>
<link href="http://localhost:8080/control/12" rel="self"/>
<link href="http://localhost:8080/control/state/12" rel="updatestat"/>
<link href="http://localhost:8080/control/12" rel="delete"/>
</SimpleEntity>
Another useful annotation is @InjectLinks. Instead of defining each Links separately in SimpleEntity class, @InjectLinks annotation could be used to hold all links in one place in a collection. This time the @InjectLinks must be injected on List<Link> links field. The previous code can be modified as follows:
@InjectLinks({
@InjectLink(
resource = ControlResource.class,
style = Style.ABSOLUTE,
rel = "updatestat",
bindings = @Binding(name = "id", value = "${instance.id}"),
method = "changeState"
),
@InjectLink(
resource = ControlResource.class,
style = Style.ABSOLUTE,
rel = "delete",
bindings = @Binding(name = "id", value = "${instance.id}"),
method = "delete"
)
})
@XmlElement(name = "link")
@XmlElementWrapper(name = "links")
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
List<Link> links;
And the response will be:
<SimpleEntity>
<id>12</id>
<name>Sam</name>
<family>Sepassi</family>
<age>31</age>
<active>true</active>
<links>
<link href="http://localhost:8080/control/12" rel="self"/>
<link href="http://localhost:8080/control/state/12" rel="updatestat"/>
<link href="http://localhost:8080/control/12" rel="delete"/>
</links>
</SimpleEntity>
Finally to comply with the RFC 5899, the @InjectLinks annotation can be injected on the SimpleEntity class and the links will appear in the http header too.
@InjectLinks(
@InjectLink(
resource = ControlResource.class,
style = Style.ABSOLUTE,
rel = "self",
bindings = @Binding(name = "id", value = "${instance.id}"),
method = "get"
)
)
@XmlRootElement(name = "SimpleEntity")
public class SimpleEntity implements Serializable {
…
}
Discussions:
To experiment hypermedia features, two methods were discussed above. Former method uses the standard API that is supported by the JAX-RS and the latter is an introduction to experimental features that is supported by Jersey.
Using the standard API offers more flexibility to developers but adds complexity to the code and affecting the code readability. These complexities become more obvious when the scale of project increases. In my opinion using annotations, that are offered by Jersey or other frameworks and implementations hides the complexity of using the standard API and maintains the code readability.
Another good feature which can be offered by third party frameworks or even the JAX-RS, could be a Link Descriptor file. Like the faces-config.xml that is used in JSF framework, such descriptor files would define the general link between resources, entities and even the conditions to be considered in generating links. Having such feature in hand, graphical editing features could be offered by IDE’s to create and modify link descriptor file in the project.
Summery
HATEOS idea has its own advocate and opponents. By evaluating the positive and negative points of this idea (which is not the purpose of this article), it is soon to totally accept or reject the HATEOAS concept. HATEOAS offers new design patterns for development of a new generation of network based client-server applications.
In this article the idea behind HATEOAS and the use of hypermedia contents were discussed. APIs that are provided by JAX-RS standard and Jersey declarative linking API were introduced by use of various simple examples. The main purpose of this document is an introduction to hyperlinking support which is offered by JAX-RS.
At last I have to thank Mr. Reza Rahman for his valuable comments and guidance which helped me a lot in writing this article.
Opinions expressed by DZone contributors are their own.
Comments