RESTEasy is a great JAX-RS 1.1 implementation that comes with JBoss AS 7. However, JAX-RS 1.1 lacks some important features such as Interceptors. As you may have concluded, interceptors will allow you to perform some action around the JAX-RS method execution. It let you do a few interesting things:

  • Logging;
  • Security;
  • Validation;
  • Complement the HTTP Request;
  • Complement the HTTP Response;
  • Add some additional business logic for a set of existing services.

Luckily RESTeasy brings a rich set of features which you can use in your application, one of these feature is a rich interceptor model. In this article we will show you how to create and register interceptors with a very simple example.

A service for Trigonometry basic calcs

Most of the readers should know about basic trigonometry, if not, it should not be  a problem. Our idea is to have working a simple REST Web Service which will allow you to calculate all basic trigonometric functions and keep a history of the recent calculations performed. So our application requirements are simple calculate, keep history and allow the history clean.

For this we created a simple model object:

package org.mastertheboss.resteasy.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class RightTriangle {
    private double adjacent;
    private double opposite;
    private double hypotenusa;

    private double sine;
    private double cosine;
    private double tangent;

    // gets and sets omitted
}


And then a JAX-RS resource to allow us to expose these requirements as a REST Web Service:
package org.mastertheboss.resteasy.resources;

import java.util.List;

import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.mastertheboss.resteasy.controller.TrigonometryController;
import org.mastertheboss.resteasy.model.RightTriangle;

@Path("trigonometry")
public class TrigonometryService {

    /**
     * A RPC function to calculate the basic trigonometric functions for the
     * given parameters
     * 
     * @param hypotenusa
     * @param adjacent
     * @param opposite
     * @return
     */
    @Path("calculate")
    @POST
    public RightTriangle calculateAllBasicTrigonometricFunctions(
            @FormParam("hypotenusa") double hypotenusa,
            @FormParam("adjacent") double adjacent,
            @FormParam("opposite") double opposite) {
        return TrigonometryController.calculate(hypotenusa, adjacent, opposite);
    }

    @Path("history")
    @GET
    public List<RightTriangle> history() {
        return TrigonometryController.getHistory();
    }

    @Path("history")
    // @DELETE it could be delete, but we are using post for ease the WEB page
    @POST
    public String clearAll() {
        TrigonometryController.clearHistory();
        // will return 200
        return "History cleaned with success";
    }
}


Our Resource is doing what it was supposed to do and it is working great. The problem is that after a few days “in production” the requirements for the application increased, we now have to add the following features to our existing service:

  • Logging of all requests to make easy the error troubleshooting if we have some trouble in production;
  • Validation to avoid misuse of the service
  • Security check to avoid unauthorized users to delete the history

As the service is already working and it presents no problems, it was decided to use something more appropriate than change the REST services code. The solution fits very well for interceptors because the goal of these new requirement are not related with the main goal of our service, and we want to maintain these new features in an independent way, without having to change the code of the service all the time.


The Interceptors offered by RESTEasy 2.x

RESTEasy 2.3 offers 3 types of interceptors:

  • MessageBodyReader and MessageBodyWriter Interceptors: Invoked around the HTTP Request body marshalling/unmarshalling;
  • Method execution interceptors: Invoked before(PreProcessInterceptor) and after (PostProcessInterceptor) the JAX-RS method execution;
  • Client side interceptors: Invoked before and after the client performed the request(if you call the ClientExecutionContext proceed() method it will perform the request). For more information about RESTEasy Client API, please see this article.

By default these interceptor will be invoked to all requests that comes to your JAX-RS service, but you can implement the interface AcceptedByMethod and read the service information(HTTP Method, method to be invoked, etc) to chose if your interceptor should be executed, or not.
That’s enough, that’s what we need to know for going on this article. If you want to know more about this, please check the RESTEasy documentation..

Adding Logging

To gather information from the client and information of the method which will be executed, we created a simple Server Interceptor which implements PreProcessInterceptor interface. This means that it will be called before the JAX-RS method execution.

package org.mastertheboss.resteasy.resources.interceptors;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class LoggingInterceptor implements PreProcessInterceptor {
    Logger logger = Logger.getLogger(LoggingInterceptor.class);

    @Context
    HttpServletRequest servletRequest;

    public ServerResponse preProcess(HttpRequest request,
            ResourceMethod resourceMethod) throws Failure,
            WebApplicationException {

        String methodName = resourceMethod.getMethod().getName();
        logger.info("Receiving request from: " + servletRequest.getRemoteAddr());
        logger.info("Attempt to invoke method \"" + methodName + "\"");
        if (methodName.equals("calculateAllBasicTrigonometricFunctions")) {
            logger.info("\tCalculate will be performed with parameters:");
            logger.info("\tAdjacent: "
                    + request.getFormParameters().get("adjacent"));
            logger.info("\tOpposite: "
                    + request.getFormParameters().get("opposite"));
            logger.info("\tHypotenusa: "
                    + request.getFormParameters().get("hypotenusa"));
        }
        if (methodName.equals("history")) {
            logger.info("Retrieving history...");
        }
        if (methodName.equals("clearAll")) {
            logger.info("User " + servletRequest.getRemoteUser()
                    + " is trying to clear the history...");
        }
        return null;
    }
}


The implemented method receives parameters which contains information about the request done and Java information about the JAX-RS method. Our LoggingInterceptor simple catch request and method information and then log it. Notice we can also inject resources into this interceptor, in this case we are injecting the ServletRequest. In the method implementation,

Adding RESTEasy Validation

As the basic trigonometric functions need all the right triangle information (adjacent, opposite and hypotenusa), we will perform validation before the JAX-RS method execution.

package org.mastertheboss.resteasy.resources.interceptors;

import static java.lang.Double.parseDouble;
import static java.lang.Math.pow;

import java.lang.reflect.Method;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class ValidationInterceptor implements PreProcessInterceptor,
        AcceptedByMethod {

    private double adjacent;
    private double opposite;
    private double hypotenusa;

    @SuppressWarnings("rawtypes")
    public boolean accept(Class c, Method m) {
        return m.getName().equals("calculateAllBasicTrigonometricFunctions");
    }

    public ServerResponse preProcess(HttpRequest request,
            ResourceMethod resourceMethod) throws Failure,
            WebApplicationException {

        // start server response as null and perform the validations, if it gets
        // some value, it will be a valid response and the interceptor will stop
        // the request
        ServerResponse response = null;
        try {
            adjacent = parseDouble(request.getFormParameters()
                    .get("adjacent").get(0));
            opposite = Double.parseDouble(request.getFormParameters()
                    .get("opposite").get(0));
            hypotenusa = Double.parseDouble(request.getFormParameters()
                    .get("hypotenusa").get(0));

            // Pythagoras
            boolean isValid = pow(adjacent, 2) + pow(opposite, 2) == pow(
                    hypotenusa, 2);
            // If it is not valid we will create a response with an appropriated
            // HTTP Code
            if (!isValid) {
                response = new ServerResponse("Not a valid right triangle",
                        400, new Headers<Object>());
            }
        } catch (Exception e) {
            response = new ServerResponse(
                    "Please verify the sent parameters, can't convert for use",
                    400, new Headers<Object>());
        }
        return response;
    }
}

 

  Notice this interceptor is implementing an the interface AcceptedByMethod. We also have to implement the method accept, where we receive the target method information and we can decide whether  if this interceptor will be executed or not. Also  notice that according the data sent by the client we will create a response which can stop the execution. If we return null the execution will flow normally, if we return a Server Response, the execution will stop in this method and the request will not get into your JAX-RS method neither in order interceptors.

 



RESTEasy Security Checking

For some methods we want to check security before executing it. For example, to clean the history we want to assure that a specific user can do this. Of course we could do this using Jaas or some application server built in mechanism. In this case we will use an interceptor that will check the presence of a parameter in the request and validate it against a fixed String.

package org.mastertheboss.resteasy.resources.interceptors;

import java.lang.reflect.Method;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class SecurityInterceptor implements PreProcessInterceptor,
        AcceptedByMethod {

    @SuppressWarnings("rawtypes")
    public boolean accept(Class c, Method m) {
        return m.getName().equals("clearAll");
    }

    public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
            throws Failure, WebApplicationException {
        ServerResponse response = null;

        String username = request.getFormParameters().get("username").get(0);
        // very simple security validation
        if (username == null || username.isEmpty()) {
            response = new ServerResponse(
                    "To access this method you need to inform an username",
                    401, new Headers<Object>());
        } else if (!"john".equals(username)) {
            response = new ServerResponse("User \"" + username
                    + "\" is not authorized to access this method.", 403,
                    new Headers<Object>());
        }
        return response;
    }
}


The logic of the interceptor is simple, we just create a server response according the content of the parameter “username”.

Running the code

You can download the attached a Maven project which contains the interceptors described in this article. You can build the code using the command mvn clean install and deploy it in JBoss AS 7. When you access the root context of this application it will be displayed a very simple HTML page aimed to test the JAX-RS methods we demonstrated.

JSR-339 is the specification for the second version of the “Java API for RESTful Web Services”. This new version of the JAX-RS API is covering Interceptors and Filters, but this specification is still in development. It’s possible to know more about this accessing the JSR 339 home page.
RESTEasy 3.0 will implement JAX-RS 2.0.

Conclusions

In this article we presented the RESTEasy interceptors model using a simple example. We also pointed that JAX-RS 2.0 will contain interceptors and filters, freeing us of implementations’ solutions. You can know more about RESTEasy in the project page.

0
0
0
s2sdefault