By kswaughs | Friday, April 1, 2016

Rest service with Jersey-Filters-Interceptors-Exceptions

This tutorial explains how to develop a rest service with Jersey API and will go through the below components involved in developing with Jersey framework.

1. Maven Dependencies

2. Define Rest service with Jax-RS annotations

3. Jersey servlet configuration

4. Exception Handling

5. Filters Configuration

6. Interceptors Configuration

1. Maven Dependencies

pom.xml
<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.bundles</groupId>
        <artifactId>jaxrs-ri</artifactId>
        <version>2.22.2</version>
    </dependency>
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-json</artifactId>
        <version>1.12</version>
    </dependency>
</dependencies>

2. Define Rest service with Jax-RS annotations

Create a Rest service with GET method.

BookService
package com.kswaughs.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.kswaughs.rest.beans.Book;

@Path("/book")
@Produces(MediaType.APPLICATION_JSON)
public class BookService {
    
    @GET
    @Path("/{authorID}")
    public Response getBooks(@PathParam("authorID") String authorID) {
        
        Book book = new Book();
        
        book.setId(authorID);
        book.setName("A Monk who sold his Ferrari");
        book.setAuthor("Robin Sharma");
        
        return Response.status(200).
                entity(book).build();
    }

}

Below is the Book entity

Book
package com.kswaughs.rest.beans;

public class Book {
    
    private String id;
    
    private String name;
    
    private String author;

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
    
}

3. Jersey servlet configuration

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>jaxrs-book-service</display-name>

    <servlet>
        <servlet-name>jersey-serlvet</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.kswaughs.rest, org.codehaus.jackson.jaxrs</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>jersey-serlvet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    
</web-app>

Testing :

method : GET
URL : http://localhost:8080/simple_rest/book/123

Response
{
   "id": "123",
   "name": "A Monk who sold his Ferrari",
   "author": "Robin Sharma"
}

4. Exception Handling

Let us create below exception classes one for 'Invalid Author' and other for 'Books not found'.

AuthorException
package com.kswaughs.rest.beans;

public class AuthorException extends Exception {
    
    public AuthorException(String message) {
        super(message);
    }
}

BookException
package com.kswaughs.rest.beans;

public class BookException extends Exception{
    
    public BookException(String message) {
        super(message);
    }
}

We will modify our BookService GET method to validate the above errors.

BookService
package com.kswaughs.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.kswaughs.rest.beans.AuthorException;
import com.kswaughs.rest.beans.Book;
import com.kswaughs.rest.beans.BookException;

@Path("/book")
@Produces(MediaType.APPLICATION_JSON)
public class BookService {
    
    @GET
    @Path("/{authorID}")
    public Response getBooks(@PathParam("authorID") String authorID) throws Exception {
        
        if("0".equals(authorID)) {
            throw new AuthorException("invalid author");
        }
        
        if("1".equalsIgnoreCase(authorID)) {
            throw new BookException("Books Not found");
        }
        
        Book book = new Book();
        
        book.setId("122");
        book.setName("A Monk who sold his Ferrari");
        book.setAuthor("Robin Sharma");
        
        return Response.status(200).
                entity(book).build();
    }

}

Write custom Exception handlers that implements ExceptionMapper class for each kind of exception and annotate each class with @Provider.

AuthorExceptionHandler
package com.kswaughs.rest.handlers;

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.kswaughs.rest.beans.AuthorException;
import com.kswaughs.rest.beans.Book;

@Provider
public class AuthorExceptionHandler implements
    ExceptionMapper<AuthorException> {

    @Override
    public Response toResponse(AuthorException  err) {
        
        System.out.println("Exception occured:" + err);
        
        Book resp = new Book();
        resp.setId("404");
        resp.setName("Author not found");
        
        return Response.status(Status.NOT_FOUND).entity(resp).build();
    }
}

BookExceptionHandler
package com.kswaughs.rest.handlers;

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.kswaughs.rest.beans.Book;
import com.kswaughs.rest.beans.BookException;

@Provider
public class BookExceptionHandler implements
    ExceptionMapper<BookException> {

    @Override
    public Response toResponse(BookException  err) {
        
        System.out.println("Exception occured:" + err);
        
        Book resp = new Book();
        resp.setId("404");
        resp.setName("Books not found");
        
        return Response.status(Status.NOT_FOUND).entity(resp).build();
    }
}

Test : 1

method : GET
URL : http://localhost:8080/simple_rest/book/0

Response
{
   "id": "404",
   "name": "Author not found",
   "author": null
}

Console Logs:
Exception occured:com.kswaughs.rest.beans.AuthorException: invalid author

Test : 2

method : GET
URL : http://localhost:8080/simple_rest/book/1

Response
{
   "id": "404",
   "name": "Books not found",
   "author": null
}

Console Logs:
Exception occured:com.kswaughs.rest.beans.BookException: Books Not found

5. Filters Configuration

Filters are mainly used to manipulate request and response parameters like HTTP headers, URIs & HTTP methods.

Create a custom class that implements ContainerRequestFilter, ContainerResponseFilter to update request and response headers. Here, we are calculating the request processing time and sending this value in response header. Start time is calculated and stored in request headers before processing the request and processing time is calculated and sending in response headers after processing the request.

BookFilter
package com.kswaughs.rest.filters;

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;

import com.kswaughs.rest.bindings.UpdateHeader;

@UpdateHeader
@Provider
public class BookFilter implements ContainerRequestFilter, ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext reqCtx) throws IOException {
        
        System.out.println("Adding start time in request headers");
        
        reqCtx.getHeaders().add("startTime", String.valueOf(System.currentTimeMillis()));
        
    }

    @Override
    public void filter(ContainerRequestContext reqCtx,
            ContainerResponseContext respCtx) throws IOException {

        System.out.println("Adding ProcessingTime in response headers");
        
        long startTime = Long.parseLong( reqCtx.getHeaderString("startTime") );
        
        respCtx.getHeaders().add("ProcessingTime", 
            String.valueOf(System.currentTimeMillis()-startTime ) + " millisecs" );
    }

}

When we configure the package name of this filter in init-param of jersey servlet in web.xml, This filter will be executed for all methods of all rest services. We use Name binding so that a specific filter or interceptor will be executed only for a specific resource method.

To configure a Name binding, create a new annotation interface and use the @NameBinding annotation on this interface.

UpdateHeader
package com.kswaughs.rest.bindings;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.ws.rs.NameBinding;

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface UpdateHeader {

}

In the above, BookFilter class is already annotated with @UpdateHeader.

Now adding a new POST method and we will apply this filter to this method only.

BookService
package com.kswaughs.rest;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.kswaughs.rest.beans.AuthorException;
import com.kswaughs.rest.beans.Book;
import com.kswaughs.rest.beans.BookException;
import com.kswaughs.rest.beans.BookResponse;
import com.kswaughs.rest.bindings.UpdateHeader;

@Path("/book")
@Produces(MediaType.APPLICATION_JSON)
public class BookService {
    
    @GET
    @Path("/{authorID}")
    public Response getBooks(@PathParam("authorID") String authorID) throws Exception {
        
        if("0".equals(authorID)) {
            throw new AuthorException("invalid author");
        }
        
        if("1".equalsIgnoreCase(authorID)) {
            throw new BookException("Books Not found");
        }
        
        Book book = new Book();
        
        book.setId("122");
        book.setName("A Monk who sold his Ferrari");
        book.setAuthor("Robin Sharma");
        
        return Response.status(200).
                entity(book).build();
    }
    
    @POST
    @Path("/add")
    @UpdateHeader
    public Response addBook(@Context HttpHeaders headers,  Book book) {
        
        System.out.println("Req Headers :" + headers.getRequestHeaders());
                
        BookResponse resp = new BookResponse();
        resp.setId(book.getId());
        resp.setStatus("Book added successfully.");
        
        return Response.status(200).
                entity(resp).build();
    }

}

Below is the response entity bean of this new method.

BookResponse
package com.kswaughs.rest.beans;

public class BookResponse {

    private String id;

    private String status;

    public String getId() {
        return id;
    }

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

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}

Test with Filters

method : POST
operation : add
URL : http://localhost:8080/simple_rest/book/add

Request
{ "id" : "2222", 
  "name" : "P.S I Love you",
  "author" : "Cecelia Ahern"
}

Response
{
   "id": "2222",
   "status": "Book added successfully."
}

Console Logs:
Adding start time in request headers
Req Headers :{accept-encoding=[gzip,deflate], content-type=[application/json], content-length=[78], host=[localhost:8080], connection=[Keep-Alive], user-agent=[Apache-HttpClient/4.1.1 (java 1.5)], startTime=[1459520672878]}
Adding ProcessingTime in response headers

Response Headers
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
ProcessingTime: 3 millisecs
Content-Type: application/json
Content-Length: 49
Date: Fri, 01 Apr 2016 14:24:32 GMT

6. Interceptors Configuration

Interceptors are used to manipulate entity input/output streams. In another sense, request and response body.

In this example, We are creating a new class that implements ReaderInterceptor, WriterInterceptor and override the required methods with sysout statements.

BookInterceptor
package com.kswaughs.rest.interceptors;

import java.io.IOException;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

import com.kswaughs.rest.bindings.UpdateHeader;

@Provider
@UpdateHeader
public class BookInterceptor implements  ReaderInterceptor, WriterInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext ctx)
            throws IOException, WebApplicationException {
        
          System.out.println("Intercepted request");
        
        return ctx.proceed();
    }

    @Override
    public void aroundWriteTo(WriterInterceptorContext ctx)
            throws IOException, WebApplicationException {
        
        System.out.println("Intercepted response");
        
        ctx.proceed();
        
    }
}

Test with Interceptors

method : POST
operation : add
URL : http://localhost:8080/simple_rest/book/add

Request
{ "id" : "2222", 
  "name" : "P.S I Love you",
  "author" : "Cecelia Ahern"
}

Response
{
   "id": "2222",
   "status": "Book added successfully."
}

Console Logs:
Adding start time in request headers
Intercepted request
Req Headers :{accept-encoding=[gzip,deflate], content-type=[application/json], content-length=[78], host=[localhost:8080], connection=[Keep-Alive], user-agent=[Apache-HttpClient/4.1.1 (java 1.5)], startTime=[1459521292860]}
Adding ProcessingTime in response headers
Intercepted response

Response Headers
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
ProcessingTime: 7 millisecs
Content-Type: application/json
Content-Length: 49
Date: Fri, 01 Apr 2016 14:34:52 GMT

Recommend this on


2 comments:

  1. This example is helpful to me. I had difficulty in finding examples of filter configuration with jersey. This made my work easy.

    ReplyDelete