Showing posts with label Maven 2. Show all posts
Showing posts with label Maven 2. Show all posts

Tuesday, June 1, 2010

JAXB-JSON Rest API using RESTEasy on JBoss EAP

In this second part of my evaluation of JBoss RESTEasy, I focus on adapting the JAXB-JSON samples provided by RESTEasy for JBoss Enterprise Application Platform 5.0.1.

Earlier I find myself to adapt the maven POM file to have the proper dependencies for the Twitter RESTEasy client.

Initially this simple JAXB-JSON sample had been designed to run on Jetty Web Server which run fine out-of-the box. However I had to make some modifications to the original project structure to have the code running as a simple eclipse project that can be deploy on JBoss EAP 5.0.X from RedHat (this will also work on Jboss community edition).

The new project (eclipse) structure looks as below:





Notice that I have also moved the code for both packages:  org.jboss.resteasy.annotations.providers.jaxb.json
org.jboss.resteasy.plugins.providers.jaxb.json
at the root of my project, since I am not using the remaining code of the example.

I also made some additional adaptations for JBoss to some of the project files including:
  • pom.xml file
  • web.xml
I also added a small test suite to test the REST API operations using JUnit which will work after the first deployment (I run JBoss locally on http://localhost:8080).

By the way, make sure you have src/main/resources/META-INF/services/javax.ws.rs.ext.Providers included in your project.









Here is the content of my new pom.xml file for JBoss:

<?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>jaxb-json</artifactId>
    <version>0.1.0</version>
    <packaging>war</packaging>
    <name/>
    <description/>

    <repositories>
        <repository>
            <id>java.net</id>
            <url>http://download.java.net/maven/1</url>
            <layout>legacy</layout>
        </repository>
        <repository>
            <id>maven repo</id>
            <name>maven repo</name>
            <url>http://repo1.maven.org/maven2/</url>
        </repository>
        <!-- For resteasy -->
        <repository>
            <id>jboss</id>
            <name>jboss repo</name>
            <url>http://repository.jboss.org/maven2</url>
        </repository>
    </repositories>
    <dependencies>
    
        <!-- core library -->
        
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>1.2.1.GA</version>
            <!-- filter out unwanted jars -->
            <exclusions>
                <exclusion>
                    <groupId>commons-httpclient</groupId>
                    <artifactId>commons-httpclient</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>tjws</groupId>
                    <artifactId>webserver</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <!-- optional modules -->
        
      <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jettison-provider</artifactId>
            <version>1.2.1.GA</version>
        </dependency>
        
        <!-- modules already provided by Java 6.0 -->
         
   <dependency>
       <groupId>javax.xml.bind</groupId>  
       <artifactId>jaxb-api</artifactId>
       <version>2.1</version>
       <scope>provided</scope>
   </dependency>
   
    <dependency>
         <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.1</version>
                 <scope>test</scope>
             </dependency>
     
    </dependencies>

    <build>
        <finalName>jaxb-json</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jboss-maven-plugin</artifactId>
                <version>1.4</version>
                <configuration>
                    <jbossHome>C:\JBoss\EnterprisePlatform-5.0.0.GA\jboss-as</jbossHome>
             <contextPath>/</contextPath>
             <serverName>default</serverName>
             <fileName>target/jaxb-json.war</fileName>
          </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


I modified the web.xml for the URL looks more simple by removing the mapping to reasteasy:

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
   <display-name>Archetype Created Web Application</display-name>

   <context-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>org.jboss.resteasy.examples.service.LibraryApplication</param-value>
   </context-param>

   <context-param>
      <param-name>resteasy.servlet.mapping.prefix</param-name>
      <param-value>/</param-value>
   </context-param>

   <listener>
      <listener-class>
         org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
      </listener-class>
   </listener>

   <servlet>
      <servlet-name>Resteasy</servlet-name>
      <servlet-class>
         org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
      </servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>Resteasy</servlet-name>
      <url-pattern>/</url-pattern>
   </servlet-mapping>

</web-app>

The JUnit code looks like this:

package org.jboss.resteasy.examples.test;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
  
public class LibraryTest extends TestCase
{
    /**
     * Create the test case
     *
     * @param testName name of the test case
     */
    public LibraryTest( String testName )
    {
        super( testName );
    }

    /**
     * @return the suite of tests being tested
     */
    public static Test suite()
    {
        return new TestSuite( LibraryTest.class );
    }

      
    /**
     * Testing the Library REST API
     */
    
    public void testGetMapped()  
    {
     validateRESTCall("GET", "http://localhost:8080/jaxb-json/library/books/mapped");
     assertTrue( true );
    }
    

    public void testGetBadger()  
    {
     validateRESTCall("GET", "http://localhost:8080/jaxb-json/library/books/badger");
     assertTrue( true );
    }
    
    private void validateRESTCall(String method, String url) {
     
     try {
         System.out.println("*** "+method);
         URL resURL = new URL(url);
         System.out.println("URL: " + url.toString());
         HttpURLConnection connection = (HttpURLConnection) resURL.openConnection(); 
         connection.setRequestMethod(method);
         System.out.println("Content-Type: " + connection.getContentType());
         
         BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
 
         String line = reader.readLine();
         while (line != null)
         {
            System.out.println(line);
            line = reader.readLine();
         }
         Assert.assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode());
         connection.disconnect();
     } catch (Exception err) { 
         System.out.print("Error in VHRResourceTest.validateRESTCall : " + err); 
        };
    }
}

To build I am using Maven (I recommend to install the maven eclipse plugin) with the goals mvn clean install compile package. Make sure also that before that you do a mvn eclipse:eclipse to update the dependencies in your project.

To deploy jaxb-json.war file from eclipse (so I don't have to manually copy the war file from the target folder), I have installed the JBoss eclipse plugin. As a result I can make it deployable (accessible by a right-click) and it appear in the Eclipse JBoss server view:


The REST API JSON Library resources are then accessible directly on a browser via http://localhost:8080/jaxb-json/library/books/mapped or http://localhost:8080/jaxb-json/library/books/badger.

If you want to compress your response, RESTEasy provides GZIP Compression/Decompression support using a very simple @GZIP annotation:

   @GET
   @Path("books/mapped")
   @Produces("application/json")
   @GZIP
   public BookListing getBooksMapped()
   {
      return getListing();
   }

Just import the following class:

import org.jboss.resteasy.annotations.GZIP;

Overall the adaptation from Jetty to JBoss was easy and the documentation very clear.

Additional discussions, recommendations and information can be found on the JBoss Community.

For an example of using REST architecture for Mobile Applications (HealthCare) see this post.

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;
       }
   }
}