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.

Friday, July 6, 2012

REST services with Spring 3 (XML, JSON)

As an alternative to webservices, we can use REST. Both REST and webservices have their differences. While in a previous post I wrote an example of webservices with Spring 3, I will post here one way of implementing REST using Spring 3. This example consists of a service that can return either XML or JSON depending on the URL invoked. You will be able to check the return values via a browser or a spring client test (which is provided in the example). The example also includes a web page built with extJS that fills a grid with data obtained via the service that return JSON data. Let's see it!


Versions used:
  • Spring 3.1.1-RELEASE
  • XStream 1.4.2 (XML parser)
  • Jackson 1.9.7 (JSON parser)
  • Maven 3


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>spring-rest-example</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>spring-rest-example Maven Webapp</name>
 <url>http://maven.apache.org</url>

 <dependencies>

  <!-- Spring dependencies -->

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>
  
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-oxm</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <!-- JSON parser -->
  
  <dependency>
   <groupId>org.codehaus.jackson</groupId>
   <artifactId>jackson-mapper-asl</artifactId>
   <version>1.9.7</version>
   <scope>compile</scope>
  </dependency>

  <!-- XML parser -->
  
  <dependency>
   <groupId>com.thoughtworks.xstream</groupId>
   <artifactId>xstream</artifactId>
   <version>1.4.2</version>
   <scope>compile</scope>
  </dependency>

  <!-- For testing purposes -->

  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
  </dependency>

 </dependencies>

 <build>
  <finalName>spring-rest-example</finalName>
  
  <plugins>
  
   <!-- With this you can start the server by doing mvn jetty:run -->
   <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>maven-jetty-plugin</artifactId>
    <version>6.1.26</version>
    <configuration>
     <scanIntervalSeconds>3</scanIntervalSeconds>
    </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>

Book.java

@XStreamAlias("book")
public class Book {

 private Integer id;
 private String name;

 //Getters and setters ommited but must exist
}

We use the annotation @XStreamAlias("book") so that the XML generated output for the root book tag is <book>, else, it would be <ar.com.pabloExample.model.Book> because of the way XStream handles XML transformation.

<list>
 <book>
  <id>0</id>
  <name>book nro. 1</name>
 </book>
 <book>
  <id>1</id>
  <name>book nro. 2</name>
 </book>
 <book>
  <id>2</id>
  <name>book nro. 3</name>
 </book>
</list>

BookController.java

@Controller
@RequestMapping("/book")
public class BookController {

 @RequestMapping(value="/names", method=RequestMethod.GET)
 public List<Book> getNames() {
  
  return returnData();
 }
 
 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;
 }
}

With those @RequestMapping annotations we are able to create a REST service so that it returns the getNames() return value whenever this address is invoked:
http://localhost:8080/spring-rest-example/rest/book/names


Books.java

public class Books {
 
 @JsonProperty("bookList")
 private
 List<Book> books;

 public List<Book> getBooks() {
  return books;
 }

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

This entity is just a wrapper for our List<Book>, my JSON example wouldn't work managing the list directly.
Also, the @JsonProperty("bookList") annotation exists in order to map the return "bookList" to the "books" attribute.

{"bookList":[{"id":0,"name":"book nro. 1"},{"id":1,"name":"book nro. 2"},{"id":2,"name":"book nro. 3"}]}


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>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath*:applicationContext.xml</param-value>
 </context-param>

 <listener>
  <listener-class>
   org.springframework.web.context.ContextLoaderListener
  </listener-class>
 </listener>

 <servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/rest/*</url-pattern>
 </servlet-mapping>

</web-app>

This is an usual Spring web configuration context. Every http://localhost:8080/spring-rest-example/rest/* request will be attended by the spring-servlet.xml


spring-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.1.xsd">

 <mvc:annotation-driven/>

 <context:component-scan base-package="ar.com.pabloExample.controller" />

 <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="order" value="1" />
  <property name="mediaTypes">
   <map>
    <entry key="json" value="application/json" />
    <entry key="xml" value="application/xml" />
   </map>
  </property>

  <property name="defaultViews">
   <list>
    <!-- JSON View -->
    <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />

    <!-- XML View -->
    <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
     <constructor-arg>
      <bean class="org.springframework.oxm.xstream.XStreamMarshaller">
       <property name="autodetectAnnotations" value="true"/>
      </bean>
     </constructor-arg>
    </bean>
   </list>
  </property>
 </bean>

</beans>

Here, we configure ContentNegotiatingViewResolver to either accept XML and JSON when answering a request.
http://localhost:8080/spring-rest-example/rest/book/names - returns XML (default when no extension)
http://localhost:8080/spring-rest-example/rest/book/names.xml - returns XML
http://localhost:8080/spring-rest-example/rest/book/names.json - returns JSON


applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

</beans>

This XML file is mandatory in the Spring configuration. We put nothing here.


Let's test the services!


test-beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">  
      <property name="messageConverters">
       <list>
           <bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
            <property name="marshaller" ref="xstreamMarshaller"/>
            <property name="unmarshaller" ref="xstreamMarshaller"/>
           </bean>
           <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
       </list>
      </property>
    </bean>
    
 <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
  <property name="aliases">
         <props>
             <prop key="book">ar.com.pabloExample.model.Book</prop>
         </props>
     </property>
 </bean>

</beans>

The RestTemplate class has configuration for both XML and JSON message converters.
For XML marshalling we use XStream, and configure an alias so it is able to understand the <book> refers to <ar.com.pabloExample.model.Book>


RestClientTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test-beans.xml" })
public class RestClientTest {

 @Autowired
 @Qualifier("restTemplate")
 private RestTemplate restTemplate;
 
 @Test
 public void restJsonClientTest() {
  
  Books booksAux = restTemplate.getForObject("http://localhost:8080/spring-rest-example/rest/book/names.json", Books.class);
  List<Book> books = booksAux.getBooks();
  
  Assert.assertNotNull(books);
  Assert.assertTrue(books.size() > 0);
 }
 
 @SuppressWarnings("unchecked")
 @Test
 public void restXmlClientTest() {
  
  List<Book> books = (ArrayList<Book>) restTemplate.getForObject("http://localhost:8080/spring-rest-example/rest/book/names.xml", List.class);
  
  Assert.assertNotNull(books);
  Assert.assertTrue(books.size() > 0);
 }
}

Run mvn jetty:run to start jetty web server so that the webservice is exposed, then you can run RestClientTest doing mvn test.

http://localhost:8080/spring-rest-example/rest/book/names - returns XML (default when no extension)

http://localhost:8080/spring-rest-example/rest/book/names.xml - returns XML
http://localhost:8080/spring-rest-example/rest/book/names.json - returns JSON


You can access http://localhost:8080/spring-rest-example/example.html too to see an extJS data grid example that is filled with the JSON service we've just deployed.


Get the complete code!


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

  1. First, run mvn jetty:run to start jetty web server
  2. Then mvn test to run JUnit tests


Finally


I think there's too much boilerplate configuration to enable REST services with Spring, unless you're already using Spring to build your app. Maybe other products like RESTEasy are more suited if we don't want to use Spring for our webapp but still want to have services exposed via REST...

For more information about mvc:annotation-driven tag, @Controller annotation, @RequestMapping see:
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html