Sunday, July 22, 2012

REST services with RESTEasy (XML, JSON)

In the previous post, I wrote about creating REST services (either JSON or XML) with Spring 3.1.1-Final. Now I will post about doing basically the same with RESTEasy which is another product, this time from JBoss.


Versions used:

- Maven 3
- RESTEasy 2.3.4.Final


pom.xml

<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>ar.com.pabloExample</groupId>
 <artifactId>resteasy-example</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>resteasy-example Maven Webapp</name>
 <url>http://maven.apache.org</url>
 
 <repositories>
  <repository>
   <id>jboss-releases</id>
   <name>JBoss Releases</name>
   <url>https://repository.jboss.org/nexus/content/repositories/releases/</url>
  </repository>
 </repositories>
 
 <dependencies>
 
  <!-- RESTEasy dependencies -->

  <dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jaxrs</artifactId>
   <version>2.3.4.Final</version>
  </dependency>
  
  <dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jaxb-provider</artifactId>
   <version>2.3.4.Final</version>
  </dependency>

  <dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jettison-provider</artifactId>
   <version>2.3.4.Final</version>
  </dependency>

  <dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-multipart-provider</artifactId>
   <version>2.3.4.Final</version>
  </dependency>
  
  <!-- For testing purposes -->
 
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
  </dependency>
  
 </dependencies>
 
 <build>
  <finalName>resteasy-example</finalName>
  
  <plugins>
  
   <!-- With this you can start the server by doing mvn jetty:run -->
   <!-- Somehow, running this test via MVN JETTY:RUN will not work -->
   <!-- You will have to run it as MVN JETTY:RUN-WAR -->
   <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>maven-jetty-plugin</artifactId>
    <version>6.1.26</version>
    <configuration>
     <scanIntervalSeconds>3</scanIntervalSeconds>
     <systemProperties>
      <systemProperty>
       <name>log4j.configurationFile</name>
       <value>file:${project.basedir}/src/test/resources/log4j.properties</value>
      </systemProperty>
     </systemProperties>
    </configuration>
   </plugin>
  
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>
   
  </plugins>
 </build>
</project>

web.xml

<!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>
 
 <!-- Auto scan REST service -->
 
 <context-param>
  <param-name>resteasy.scan</param-name>
  <param-value>true</param-value>
 </context-param>
 
 <!-- this has to match with resteasy-servlet url-pattern -->
 
 <context-param>
  <param-name>resteasy.servlet.mapping.prefix</param-name>
  <param-value>/rest</param-value>
 </context-param>

 <!-- to return data according to extension -->
 
 <context-param>
  <param-name>resteasy.media.type.mappings</param-name>
  <param-value>json : application/json, xml : application/xml</param-value>
 </context-param>
 
 <listener>
  <listener-class>
   org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
  </listener-class>
 </listener>

 <servlet>
  <servlet-name>resteasy-servlet</servlet-name>
  <servlet-class>
   org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
  </servlet-class>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>resteasy-servlet</servlet-name>
  <url-pattern>/rest/*</url-pattern>
 </servlet-mapping>

</web-app>

Every http://localhost:8080/resteasy-example/rest/* request is going to be read by the resteasy servlet

And we configured it to accept URL extension based services. This means that we create the service only once and if I make an URL request ending in .json it will return the data in JSON format, and if we send the request ending in .xml, it will return data in XML format.


Book.java

public class Book {

 private Integer id;
 private String name;
 

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

 @XmlAttribute
 public Integer getId() {
  return id;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 @XmlElement
 public String getName() {
  return name;
 }

}

These annotations in Book and Books class are able to be understood by both XML and JSON parser.


Books.java

@XmlRootElement
public class Books {
 
 private List<Book> books;

 
 @XmlElement(name="book")
 public List<Book> getBooks() {
  return books;
 }

 public void setBooks(List<Book> books) {
  this.books = books;
 }
 
}

This class acts as a Book list wrapper. To create a REST service or webservice that returns List<Book> directly may not work in all environments. So I find the easiest way is to create a wrapper to mitigate this kind of problems.


BookService.java

@Path("/services")
public class BookService {
 
 @GET
 @Path("/books")
 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 public Books getBooksXml() {
  
  Books books = new Books();
  List<Book> bookList = returnData();
  books.setBooks(bookList);
  
  return books;
 }
 
 private List<Book> returnData() {
  
  ArrayList<Book> names = new ArrayList<Book>();
  
  Book b1 = new Book();
  Book b2 = new Book();
  Book b3 = new Book();
  
  b1.setName("book nro. 1");
  b1.setId(0);
  b2.setName("book nro. 2");
  b2.setId(1);
  b3.setName("book nro. 3");
  b3.setId(2);
  
  names.add(b1);
  names.add(b2);
  names.add(b3);
  
  return names;
 }

}

Here you can see that I only write the service once, and with the web.xml configuration I did and the @Produces annotation, we can either return JSON or XML depending on the URL extension sent.


Let's test the REST service!


Firstly, run the server from a terminal: mvn jetty:run-war -Dmaven.test.skip=true

You can access via a browser:

http://localhost:8080/resteasy-example/rest/services/books.xml
http://localhost:8080/resteasy-example/rest/services/books.json



Then you can run BookServiceTest that acts as a client service with mvn test


Note: somehow, the command mvn jetty:run won't expose correctly your services, that's why we run mvn jetty:run-war skipping tests (because the precondition of the tests is that the server is up and running)


BookServiceTest.java

public class BookServiceTest {
 
 @Test
 public void bookServiceTest() throws Exception {
  
  ClientRequest clientRequest = new ClientRequest("http://localhost:8080/resteasy-example/rest/services/books.xml");
//  ClientRequest clientRequest = new ClientRequest("http://localhost:8080/resteasy-example/rest/services/books.json");
  ClientResponse<Books> clientResponse = clientRequest.get(Books.class);
  
  Books books = clientResponse.getEntity();
  List<Book> bookList = books.getBooks();
  
  Assert.assertTrue(bookList.size() > 0);
 }

}

Download the complete example!


https://subversion.assembla.com/svn/pablo-examples/resteasy-example


Finally...


I prefer implementing REST services with RESTEasy rather that using the Spring solution I proposed in the previous post, unless you are already using a Spring stack on your app. Even if you are already using Spring, it may be worth taking a look at the RESTEasy-Spring integration solution which we are not trying in this example.

No comments:

Post a Comment