Showing posts with label resteasy-jaxb-provider. Show all posts
Showing posts with label resteasy-jaxb-provider. Show all posts

Monday, April 2, 2012

RESTEasy Interceptors



RESTEasy can intercept JAX-RS invocations and route them through listener-like objects called interceptors. There are four interception points on the server side:
  • wrapping around MessageBodyWriter invocations
  • wrapping around MessageBodyReader invocations
  • through pre-processors, which intercept the incoming request before unmarshalling occurs
  • through post-processors, which are invoked immediately after the JAX-RS method finishes 
An interceptor can be very useful to implement crosscutting concerns such as logging and security. Although interceptors are not as powerful as Aspect Oriented Programming (AOP) - (e.g. no type-safety or debugging) , they still offer a quick way to create more compact and maintainable code.

In this article I will describe how to use RESTEasy interceptors to log incoming requests into a REST API.

I am currently using RESTEasy to expose HealthCare web services for consumption by mobiles applications, and there is a need to log transactions for auditing purpose, including for HIPAA compliance.

The first step is to specify in your web.xml file, that you are going to use a new interceptor  - (e.g. named LogInterceptor):
<web-app>
   ...  
   <context-param>
      <param-name>resteasy.providers</param-name>
      <param-value>com.acme.api.rest.service.LogInterceptor</param-value>
   </context-param>
   ...
</web-app>


Then you create the corresponding java class for LogInterceptor:
package com.acme.api.rest.service;
  
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.WebApplicationException;

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

...
 
@Provider
@ServerInterceptor
public class LogInterceptor implements PreProcessInterceptor {
 
 // An handler to the Transaction log
 private static final Logger translog = Logger.getLogger(JNDIMessageLoggingHandler.class);

 // Getting the request as a context
 @Context HttpServletRequest request;

 @Override
 public ServerResponse preProcess(HttpRequest arg0, ResourceMethod method) throws Failure, WebApplicationException {
  final TransactionLogEntry transactionLogEntry = new TransactionLogEntry(Long.toString(Thread.currentThread().getId()),
                         System.currentTimeMillis(), 
                         getClientIP(request), 
                         request.getRequestURL().toString());
      translog.info(transactionLogEntry);
 }  
 
}

In this example, the TransactionLogEntry constructor takes :
  • a transaction ID
  • a timestamp
  • the client that initiated the request  (e.g. request.getRemoteAddr()+":"+request.getRemotePort())
  • the request URL
but you can also log additional information such as the body content of POST requests (storing XML or JSON content).

Tuesday, May 18, 2010

Enhanced POM for JBoss RESTEasy Twitter API Client Sample

I recently looked at JBOSS RESTEasy as a way to create and test RESTful APIs. The platform looks very promising with a lot of praise from developers. Also the documentation seems very extensive and precise.

I started by downloading RESTEasy 1.2.1 GA and tried the sample code. I started with a java client to access existing RESTful Web Services and APIs. Among the api-clients, there is a Twitter small client that works out-of-the box (located under /RESTEASY_1_2_1_GA/examples/api-clients/src/main/java/org/jboss/resteasy/examples/twitter).

However when I started to extract the code and wanted to create a Maven 2 based stand-alone project, I encountered some issues related to JAR dependency conflicts, including the following error message also described here.

java.lang.NoClassDefFoundError: Could not initialize class com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl 

The project (eclipse) structure looks as below:


















I managed to fix these issues by modifying the POM file as follow:

<?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>org.jboss.resteasy.examples</groupId>
 <artifactId>api-clients</artifactId>
 <version>1.2.1.GA</version>
  
  <dependencies>
    <!-- Resteasy Core -->
    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-jaxrs</artifactId>
    </dependency>
    <!-- JAXB support -->
   <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-jaxb-provider</artifactId>
   </dependency>
    
  </dependencies>
  <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-bom</artifactId>
                <version>1.2.1.GA</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
   </dependencyManagement>
   
   <!-- Build Settings --> 
   <build>
    <plugins>  
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
   </build>
  
  <!-- Environment Settings -->
  <repositories>
    <repository>
      <id>jboss</id>
      <name>jboss repo</name>
      <url>http://repository.jboss.org/maven2</url>
     </repository>
   </repositories>
  
</project>

The most important piece, beside the cleaning of the POM file, was to include a pom that can be imported so the versions of the individual modules do not have to be specified (see RESTEasy documentation - Chapter 43. Maven and RESTEasy).

I also made sure to have correct dependencies for resteasy-jaxrs and resteasy-jaxb-provider.

As a result, I was able to compile the whole project without any errors (mvn clean compile) and run it to access the Twitter REST API

mvn exec:java -Dexec.mainClass="org.jboss.resteasy.examples.twitter.TwitterClient" -Dexec.args="<userid> <password>"
(Replace last parameters by your twitter user and password).

The small client in question leverages JAX-RS annotations to read and write the Twitter API resources:

package org.jboss.resteasy.examples.twitter;

import java.util.Date;
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 javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.core.executors.ApacheHttpClientExecutor;
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

public class TwitterClient
{
   static final String friendTimeline = "http://twitter.com/statuses/friends_timeline.xml";

   public static void main(String[] args) throws Exception
   {
      RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
      final ClientExecutor clientExecutor = new ApacheHttpClientExecutor(createClient(args[0], args[1]));
      TwitterResource twitter = ProxyFactory.create(TwitterResource.class,
            "http://twitter.com", clientExecutor);
      System.out.println("===> first run");
      printStatuses(twitter.getFriendsTimelines());
      
      twitter
      .updateStatus("I programmatically tweeted with the RESTEasy Client at "
            + new Date());
      
      System.out.println("===> second run");
      printStatuses(twitter.getFriendsTimelines());
   }

   public static interface TwitterResource
   {
      @Path("/statuses/friends_timeline.xml")
      @GET
      Statuses getFriendsTimelines();

      @Path("/statuses/update.xml")
      @POST
      Status updateStatus(@FormParam("status") String status);
   }

   private static void printStatuses(Statuses statuses)
   {
      for (Status status : statuses.status)
         System.out.println(status);
   }

   private static HttpClient createClient(String userId, String password)
   {
      Credentials credentials = new UsernamePasswordCredentials(userId,
            password);
      HttpClient httpClient = new HttpClient();
      httpClient.getState().setCredentials(AuthScope.ANY, credentials);
      httpClient.getParams().setAuthenticationPreemptive(true);
      return httpClient;
   }

   @XmlRootElement
   public static class Statuses
   {
      public List<Status> status;
   }

   @XmlRootElement
   public static class Status
   {
      public String text;
      public User user;

      @XmlElement(name = "created_at")
      @XmlJavaTypeAdapter(value = DateAdapter.class)
      public Date created;

      public String toString()
      {
         return String.format("== %s: %s (%s)", user.name, text, created);
      }
   }

   public static class User
   {
      public String name;
   }

}


The small DateAdapter class is a utility class for date formatting:

package org.jboss.resteasy.examples.twitter;

import java.util.Date;
import java.util.List;
package org.jboss.resteasy.examples.twitter;
 
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.jboss.resteasy.util.DateUtil;

public class DateAdapter extends XmlAdapter<String, Date> {

   @Override
   public String marshal(Date date) throws Exception {
       return DateUtil.formatDate(date, "EEE MMM dd HH:mm:ss Z yyyy");
   }

   @Override
   public Date unmarshal(String string) throws Exception {
       try {
           return DateUtil.parseDate(string);
       } catch (IllegalArgumentException e) {
           System.err.println(String.format(
                   "Could not parse date string '%s'", string));
           return null;
       }
   }
}