Wednesday 26 August 2015

Jersey: sending links to resources (HATEOAS)

Hurray, finally we come to final stage of application. Till now we developed following REST APIs.

API URL
Method
Description
/employees
GET
Return all employees
/employees
POST
Add new employee
/employees/employeeID
GET
Get employee details for given id
/employees/employeeID
PUT
Update specific employee
/employees/employeeID
DELETE
Delete employee details for given id
/employees?city=Bangalore
GET
Get all employees staying at Bangalore
/employees?start=1&size=2
GET
Get all employees with ids 1, 2 and 3
/employees/1/address/permanentAddress
GET
Get Permanent address of employee 1.
/employees/1/address/temporaryAddress
GET
Get Temporary address of employee 1.

Above URL gives following response.

{
    "firstName": "Hari Krishna",
    "id": 1,
    "lastName": "Gurram",
    "permAddrees": {
        "area": "Marthali",
        "city": "Bangalore",
        "country": "India",
        "state": "Karnataka"
    },
    "tempAddrees": {
        "area": "Electronic City",
        "city": "Bangalore",
        "country": "India",
        "state": "Karnataka"
    }
}

But I don’t want to send address information in response, instead of that I want to send hyper links to get information of temporary and permanent addresses. Sending links to resurces in called HATEOAS.  HATEOAS stands for Hypertext As The Engine Of Application State. It means that hypertext should be used to find your way through the API.

Add ‘XMLTransient’ annotation on top of properties, that you don’t want to send in JSON response.



Step 1: First we need to exclude tempAddress, permAddress from json response. We can exclude instance properties from response by using @XmlTransient annotation.

@XmlTransient
public Address getPermAddrees() {
         return permAddrees;
}


@XmlTransient
public Address getTempAddrees() {
         return tempAddrees;
}

Step 2: I want to send response like below.
{
    "firstName": "Hari Krishna",
    "id": 1,
    "lastName": "Gurram",
    "links": [
        {
            "link": "http://localhost:8080/jersey_tutorial/employees/1/address/temporaryAddress",
            "rel": "Temporary Address"
        },
        {
            "link": "http://localhost:8080/jersey_tutorial/employees/1/address/permanentAddress",
            "rel": "Permanent Address"
        }
    ]
}

First we need to create a Link class, which hold link and rel properties.
public class Link {
         private String link;
         private String rel;
         ....
         ....    
}

Next we need to add List<Link> property to Employee class.
public class Employee {
 private String firstName;
 private String lastName;
 private long id;
 private List<Link> links = new ArrayList<Link> ();

 private Address permAddrees;

 private Address tempAddrees;

 …… 

public void updateLinks(String tempAddressURL, String permAddressURL) {
  Link link1 = new Link(tempAddressURL, "Temporary Address");
  Link link2 = new Link(permAddressURL, "Permanent Address");

  links.add(link1);
  links.add(link2);
 }
}


Update EmployeeResource class like below.
public class EmployeeResource {


 @GET
 @Path("/{employeeId}")
 public Employee getEmployee(@PathParam("employeeId") long empId,
   @Context UriInfo uriInfo) {
  Employee emp = EmployeeService.getEmployee(empId);

  String tempAddressURL = uriInfo.getAbsolutePathBuilder()
    .path("address").path("/temporaryAddress").toString();

  String permAddressURL = uriInfo.getAbsolutePathBuilder()
    .path("address").path("/permanentAddress").toString();

  emp.updateLinks(tempAddressURL, permAddressURL);

  return emp;
 }

 
}

Following is the step-by-step procedure to develop complete application.

Step 1: Create Model classes. Create package com.jersey_tutorial.model.


Address.java
package com.jersey_tutorial.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Address {
 private String street;
 private String city;
 private String state;
 private String country;

 public Address() {
  this("No Data", "No Data", "No Data", "No Data");
 }

 public Address(String area, String city, String state, String country) {
  super();
  this.street = area;
  this.city = city;
  this.state = state;
  this.country = country;
 }

 public String getArea() {
  return street;
 }

 public String getCity() {
  return city;
 }

 public String getCountry() {
  return country;
 }

 public String getState() {
  return state;
 }

 public void setArea(String area) {
  this.street = area;
 }

 public void setCity(String city) {
  this.city = city;
 }

 public void setCountry(String country) {
  this.country = country;
 }

 public void setState(String state) {
  this.state = state;
 }

}


Employee.java
package com.jersey_tutorial.model;

import java.util.ArrayList;
import java.util.List;

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

@XmlRootElement
public class Employee {
 private String firstName;
 private String lastName;
 private long id;
 private List<Link> links = new ArrayList<Link> ();

 private Address permAddrees;

 private Address tempAddrees;

 public Employee() {
  this("No Data", "No Data", -1, new Address(), new Address());
 }

 public Employee(String firstName, String lastName, long id,
   Address permAddrees, Address tempAddrees) {
  super();
  this.firstName = firstName;
  this.lastName = lastName;
  this.id = id;
  this.permAddrees = permAddrees;
  this.tempAddrees = tempAddrees;
 }

 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 long getId() {
  return id;
 }

 public void setId(long id) {
  this.id = id;
 }

 @XmlTransient
 public Address getPermAddrees() {
  return permAddrees;
 }

 public void setPermAddrees(Address permAddrees) {
  this.permAddrees = permAddrees;
 }

 @XmlTransient
 public Address getTempAddrees() {
  return tempAddrees;
 }

 public void setTempAddrees(Address tempAddrees) {
  this.tempAddrees = tempAddrees;
 }

 public List<Link> getLinks() {
  return links;
 }

 public void setLinks(List<Link> links) {
  this.links = links;
 }

 public void updateLinks(String tempAddressURL, String permAddressURL) {
  links = new ArrayList<>();
  Link link1 = new Link(tempAddressURL, "Temporary Address");
  Link link2 = new Link(permAddressURL, "Permanent Address");

  links.add(link1);
  links.add(link2);
 }

}


Link.java
package com.jersey_tutorial.model;

public class Link {
 private String link;
 private String rel;

 public String getLink() {
  return link;
 }

 public void setLink(String link) {
  this.link = link;
 }

 public String getRel() {
  return rel;
 }

 public void setRel(String rel) {
  this.rel = rel;
 }

 public Link(String link, String rel) {
  this.link = link;
  this.rel = rel;
 }
 
 public Link(){
  
 }

}


ErrorMessage.java
package com.jersey_tutorial.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ErrorMessage {
 private int errorCode;
 private String errorMessage;

 public ErrorMessage(int errorCode, String errorMessage) {
  super();
  this.errorCode = errorCode;
  this.errorMessage = errorMessage;
 }

 public ErrorMessage() {
  super();
 }

 public int getErrorCode() {
  return errorCode;
 }

 public void setErrorCode(int errorCode) {
  this.errorCode = errorCode;
 }

 public String getErrorMessage() {
  return errorMessage;
 }

 public void setErrorMessage(String errorMessage) {
  this.errorMessage = errorMessage;
 }

}

Step 2: Create package ‘com.jersey_tutorial.exception” and add exception classes.


DataNotFoundException.java
package com.jersey_tutorial.exception;

public class DataNotFoundException extends RuntimeException {
 private int errorCode;
 private String message;

 public DataNotFoundException() {
  super();
 }

 public DataNotFoundException(int errorCode, String message) {
  this.errorCode = errorCode;
  this.message = message;
 }

 public int getErrorCode() {
  return errorCode;
 }

 public void setErrorCode(int errorCode) {
  this.errorCode = errorCode;
 }

 public String getMessage() {
  return message;
 }

 public void setMessage(String message) {
  this.message = message;
 }

}


DataNotFoundExceptionMapper.java
package com.jersey_tutorial.exception;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import com.jersey_tutorial.model.ErrorMessage;

@Provider
public class DataNotFoundExceptionMapper implements
  ExceptionMapper<DataNotFoundException> {

 @Override
 public Response toResponse(DataNotFoundException ex) {
  ErrorMessage model = new ErrorMessage(ex.getErrorCode(),
    ex.getMessage());
  return Response.status(Status.NOT_FOUND).entity(model).build();
 }

}


Step 3: Create EmployeeService.java
package com.jersey_tutorial.services;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.jersey_tutorial.exception.DataNotFoundException;
import com.jersey_tutorial.model.Address;
import com.jersey_tutorial.model.Employee;

public class EmployeeService {
 private static TreeMap<Long, Employee> employees = new TreeMap<>();

 static {
  initializeEmployees();
 }

 private static void initializeEmployees() {
  Address tempAddr1 = new Address("Electronic City", "Bangalore",
    "Karnataka", "India");
  Address tempAddr2 = new Address("BTM Layout", "Bangalore", "Karnataka",
    "India");
  Address tempAddr3 = new Address("SR Nagar", "Hyderabad",
    "Andhra Pradesh", "India");

  Address permAddr1 = new Address("Marthali", "Bangalore", "Karnataka",
    "India");
  Address permAddr2 = new Address("Bharath Nagar", "Hyderabad",
    "Andhra Pradesh", "India");
  Address permAddr3 = new Address("Bharath Nagar", "Hyderabad",
    "Andhra Pradesh", "India");

  Employee emp1 = new Employee("Hari Krishna", "Gurram", 1, permAddr1,
    tempAddr1);
  Employee emp2 = new Employee("PTR", "PTR", 2, permAddr2, tempAddr2);
  Employee emp3 = new Employee("Rama Krishna", "Gurram", 3, permAddr3,
    tempAddr3);

  employees.put(1l, emp1);
  employees.put(2l, emp2);
  employees.put(3l, emp3);
 }

 public static Map<Long, Employee> getAllEmployees() {
  return employees;
 }

 public static List<Employee> getEmployeesForCity(String city) {
  List<Employee> employeesForCity = new ArrayList<>();

  for (Employee emp : employees.values()) {
   if (emp.getPermAddrees().getCity().equals(city)
     || emp.getTempAddrees().getCity().equals(city)) {
    employeesForCity.add(emp);
   }
  }
  return employeesForCity;
 }

 public static List<Employee> getEmployeesPaginated(long start, long size) {
  return new ArrayList<>(employees
    .subMap(start, true, start + size, true).values());
 }

 public static Employee getEmployee(long id) {
  Employee emp = employees.get(id);
  if (emp == null)
   throw new DataNotFoundException(1, "Employee with id " + id
     + " Not exist");
  return emp;
 }

 public static Employee addEmployee(Employee emp) {
  long newId = employees.size() + 1;
  emp.setId(newId);
  employees.put(newId, emp);
  return emp;
 }

 public static Employee updateEmployee(long id, Employee emp) {
  emp.setId(id);
  employees.put(id, emp);
  return emp;
 }

 public static Employee deleteEmployee(long empId) {
  Employee emp = employees.get(empId);
  employees.remove(empId);
  return emp;
 }
}

Step 4: Create AddressResource, EmployeeResource.

AddressResource.java
package com.jersey_tutorial.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

import com.jersey_tutorial.model.Address;
import com.jersey_tutorial.services.EmployeeService;

public class AddressResource {

 @GET
 @Path("/temporaryAddress")
 public Address getTempAddress(@PathParam("employeeId") long empId) {
  return EmployeeService.getEmployee(empId).getTempAddrees();
 }

 @GET
 @Path("/permanentAddress")
 public Address getPermAddress(@PathParam("employeeId") long empId) {
  return EmployeeService.getEmployee(empId).getPermAddrees();
 }
}


EmployeeResource.java
package com.jersey_tutorial.resources;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import com.jersey_tutorial.model.Employee;
import com.jersey_tutorial.services.EmployeeService;

@Path("employees")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class EmployeeResource {

 @GET
 public List<Employee> getAllEmployees(@QueryParam("city") String city,
   @QueryParam("start") long start, @QueryParam("size") long size) {
  if (city != null)
   return EmployeeService.getEmployeesForCity(city);

  if (start > 0 && size > -1)
   return EmployeeService.getEmployeesPaginated(start, size);

  return new ArrayList<>(EmployeeService.getAllEmployees().values());
 }

 @GET
 @Path("/{employeeId}")
 public Employee getEmployee(@PathParam("employeeId") long empId,
   @Context UriInfo uriInfo) {
  Employee emp = EmployeeService.getEmployee(empId);

  String tempAddressURL = uriInfo.getAbsolutePathBuilder()
    .path("address").path("/temporaryAddress").toString();

  String permAddressURL = uriInfo.getAbsolutePathBuilder()
    .path("address").path("/permanentAddress").toString();

  emp.updateLinks(tempAddressURL, permAddressURL);

  return emp;
 }

 @POST
 public Response addEmployee(Employee emp, @Context UriInfo uriInfo) {
  Employee employee = EmployeeService.addEmployee(emp);
  String empId = String.valueOf(employee.getId());
  URI uri = uriInfo.getAbsolutePathBuilder().path(empId).build();

  return Response.status(Status.CREATED).entity(employee)
    .header("new_url", uri.toString()).build();
 }

 @PUT
 @Path("/{employeeId}")
 public Employee updateEmployee(@PathParam("employeeId") long empId,
   Employee emp) {
  return EmployeeService.updateEmployee(empId, emp);
 }

 @DELETE
 @Path("/{employeeId}")
 public Employee removeEmployee(@PathParam("employeeId") long empId) {
  return EmployeeService.deleteEmployee(empId);
 }

 @Path("/{employeeId}/address")
 public AddressResource getAddress() {
  AddressResource resource = new AddressResource();
  System.out.println("Hello");
  return resource;
 }
}


Following is the web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 id="WebApp_ID" version="3.0">
 <display-name>jersey_tutorial</display-name>
 <welcome-file-list>
  <welcome-file>index.html</welcome-file>
  <welcome-file>index.htm</welcome-file>
  <welcome-file>index.jsp</welcome-file>
  <welcome-file>default.html</welcome-file>
  <welcome-file>default.htm</welcome-file>
  <welcome-file>default.jsp</welcome-file>
 </welcome-file-list>

 <servlet>
  <servlet-name>Jersey REST Service</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  <init-param>
   <param-name>jersey.config.server.provider.packages</param-name>
   <param-value>com.jersey_tutorial.resources, com.jersey_tutorial.exception</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>Jersey REST Service</servlet-name>
  <url-pattern>/*</url-pattern>
 </servlet-mapping>
</web-app>


Following are the dependencies I used.
<dependencies>
 <dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-servlet-core</artifactId>
  <version>2.21</version>
 </dependency>

 <dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-server</artifactId>
  <version>2.21</version>
 </dependency>

 <dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-moxy</artifactId>
  <version>2.21</version>
 </dependency>

</dependencies>

Deploy application on server and hit following url.


You will get following response.

{
    "firstName": "Hari Krishna",
    "id": 1,
    "lastName": "Gurram",
    "links": [
        {
            "link": "http://localhost:8080/jersey_tutorial/employees/1/address/temporaryAddress",
            "rel": "Temporary Address"
        },
        {
            "link": "http://localhost:8080/jersey_tutorial/employees/1/address/permanentAddress",
            "rel": "Permanent Address"
        }
    ]
}

Complete project structure like below.



Prevoius                                                 Next                                                 Home

No comments:

Post a Comment