Revisiting REST with Java (JAX-RS) using Jersey

RESTful web services are built to work best on the Web. Representational State Transfer (REST) is an architectural style that specifies constraints, such as the uniform interface, that if applied to a web service induce desirable properties, such as performance, scalability, and modifiability, that enable services to work best on the Web. In the REST architectural style, data and functionality are considered resources and are accessed using Uniform Resource Identifiers (URIs), typically links on the Web.

In simple words, REST stands for Representational State Transfer and it’s an architecture style for creating network based applications. JAX-RS is the Java API specification and Jersey is its reference implementation.

Our goal is to create a RESTful service that can retrieve product and price details by ID. Our app (myRetail RESTful service) should be able to do the following:-

  • Responds to an HTTP GET request at /products/{id} and delivers product data as JSON (where {id} will be a number.
  • Reads pricing information from a NoSQL data store and combines it with the product id and name from the HTTP request into a single response.
  • Accepts an HTTP PUT request at the same path (/products/{id} ), containing a JSON request body similar to the GET response, and updates the product’s price in the data store.
  • Delete a product price entry from db.

Example response: {"id":13860428,"name":"The Big Lebowski (Blu-ray) (Widescreen)","current_price":{"value": 13.49,"currency_code":"USD"}}

Okay, lets start with this simple CRUD Restful apis.

Step 1: We will create a dynamic web project in Eclipse.

Step 2: Add web.xml through Generate Deployment Descriptor Stub in Java EE tools.

Step 3: Convert it to a Maven Project.

Step 4: In pom.xml add the following dependencies:-

<!-- https://mvnrepository.com/artifact/asm/asm -->
 <dependency>
    <groupId>asm</groupId>
    <artifactId>asm</artifactId>
    <version>3.3.1</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-bundle -->
 <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-bundle</artifactId>
    <version>1.19</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.json/json -->
 <dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20160810</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-server -->
 <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-server</artifactId>
    <version>1.19.3</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-core -->
 <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.19.3</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
 <dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.4.2</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/com.sun.jersey/jersey-json -->
 <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>1.19</version>
 </dependency>

Step 5: Update web.xml with the following mapping:-

<servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Jersey Web Application</servlet-name>
    <url-pattern>/myRetail/*</url-pattern>
</servlet-mapping>

Step 6: Creating our POJO classes with @XmlRootElement to define the root element for an XML tree.

@XmlRootElement
public class Product{
    private int id;
    private String name;
    private Price current_price;
...
}

Step 7: We will create a class named MyRetailService with /products as its @Path as per the requirements. Read this for guidelines on resource URI naming conventions. This class will have all our api calls.

a) To create pricing information in Product, we will do a POST request consuming a json/xml of Price type.

@POST @Path("/create")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response createPriceData(Price price){
    String success = dao.createPriceData(price);
    return Response.status(201).entity(success).build(); 
}

b) To retrieve all the details of products in myRetail app, we will go for a GET call.

@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response findAll() {
    List<Product> product = dao.createDummyProducts();
    GenericEntity<List<Product>> entity = new GenericEntity<List<Product>>(product) {};
    return Response.ok(entity).build(); 
}

This is the case when we need to return parameterized types from a JAXRS resource method in the Response . Due to type erasure, it requires special handling in Jersey runtime to determine the generic type that is required to select a suitable MessageBodyWriter. GenericEntity can be used to represent a response entity of a generic type.

c) To update a price value, we will go for PUT

@PUT @Path("{id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response updatePrice(@PathParam("id") String id,Product product) {
    Product updatedProduct = dao.update(Integer.parseInt(id),product);
    return Response.status(200).entity(updatedProduct).build(); 
}

d) Delete a price entry from product.

@DELETE @Path("{id}")
public Response delete(@PathParam("id") String id) {
    String response = dao.delete(Integer.parseInt(id));
    return Response.status(202).entity(response).build();
}

We have handled the exceptions by throwing GenericException. However, we still need to create a Handler object to convert this exception into an actual JSON response so we get a nice friendly error message, which is implemented through GenericExceptionMapper.

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable>{
public Response toResponse(Throwable ex){
    if(ex instanceof ProductSearchException){
        return Response.status(Status.NOT_FOUND)
        .entity(new ErrorProps("404", ex.getMessage()))
        .build();
    }else{
        return Response.status(Status.INTERNAL_SERVER_ERROR)
        .entity(new ErrorProps("Some error code, 500 or somthing", ex.getMessage()))
        .build();
    }
  }
}

Step 8: To test, we can use postman or jersey.api.client

private static final String webServiceURI = "http://localhost:8080/myRetail-0.0.1-SNAPSHOT/myRetail/products/";
public static void main(String[] args) {
    Client client = Client.create();
    WebResource webResource = client.resource(webServiceURI);
    createDummyPriceTest(webResource);
}
private static void createDummyPriceTest(WebResource webResource){
    Price price = new Price();
    price.set_id(16483589);
    price.setCurrency_code("USD");
    price.setValue(13.49);
    //or String input = "{\"currency_code\":\"USD\",\"_id\":\"16483589\",\"value\":\"13.49\"}";
    ClientResponse response = webResource.path("create").type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class,price);
    if (response.getStatus() != 201) {
        throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
    }
    String output = response.getEntity(String.class);
    System.out.println(output);
}

Summary of JAX-RS Annotations used

AnnotationsUsage
PathIt identifies the URI path
PathParamRepresents the parameter of the URI path.
GETSpecifies method responds to GET request.
POSTSpecifies method responds to POST request.
PUTSpecifies method responds to PUT request.
DELETESpecifies method responds to DELETE request.
ProducesDefines media type for the response such as XML, PLAIN, JSON etc. It defines the media type that the methods of a resource class or MessageBodyWriter can produce.
ConsumesIt defines the media type that the methods of a resource class or MessageBodyReader can produce.
ProviderThe @Provider annotation is used for anything that is of interest to the JAX-RS runtime, such as MessageBodyReader and MessageBodyWriter. For HTTP requests, the MessageBodyReader is used to map an HTTP request entity body to method parameters. On the response side, a return value is mapped to an HTTP response entity body by using a MessageBodyWriter. If the application needs to supply additional metadata, such as HTTP headers or a different status code, a method can return a Response that wraps the entity and that can be built using Response.ResponseBuilder.

This is a very simple example of a RESTful service that uses JAX-RS annotations. You can check the repo for the DAO layer implementation with the usage of MongoClient. Find the same repo in Kotlin.

No Comments Yet

Add a comment