Wednesday, June 6, 2012

RESTEasy Tutorial Part-2: Spring Integration


RESTEasy Tutorial Series

RESTEasy Tutorial Part-1: Basics

RESTEasy Tutorial Part-2: Spring Integration

RESTEasy Tutorial Part 3 - Exception Handling


RESTEasy provides support for Spring integration which enables us to expose Spring beans as RESTful WebServices.

Step#1: Configure RESTEasy+Spring dependencies using Maven.

<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>com.sivalabs</groupId>
<artifactId>resteasy-demo</artifactId>
<version>0.1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.springframework.version>3.1.0.RELEASE</org.springframework.version>
<slf4j.version>1.6.1</slf4j.version>
<java.version>1.6</java.version>
<junit.version>4.8.2</junit.version>
<resteasy.version>2.3.2.Final</resteasy.version>
</properties>
<build>
<finalName>resteasy-demo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<version>2.3.0.GA</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring</artifactId>
<version>2.3.0.GA</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<artifactId>jaxb-impl</artifactId>
<groupId>com.sun.xml.bind</groupId>
</exclusion>
<exclusion>
<artifactId>sjsxp</artifactId>
<groupId>com.sun.xml.stream</groupId>
</exclusion>
<exclusion>
<artifactId>jsr250-api</artifactId>
<groupId>javax.annotation</groupId>
</exclusion>
<exclusion>
<artifactId>activation</artifactId>
<groupId>javax.activation</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.1.2</version>
</dependency>
</dependencies>
</project>


Step#2: Configure RESTEasy+Spring in web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<listener>
<listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</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>/rest/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest</param-value>
</context-param>
<!--While using Spring integration set resteasy.scan to false or don't configure resteasy.scan parameter at all -->
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>false</param-value>
</context-param>
</web-app>


Step#3: Create a Spring Service class UserService and update UserResource to use UserService bean.

package com.sivalabs.resteasydemo;
import java.util.List;
import org.springframework.stereotype.Service;
import com.sivalabs.resteasydemo.MockUserTable;
@Service
public class UserService
{
public void save(User user){
MockUserTable.save(user);
}
public User getById(Integer id){
return MockUserTable.getById(id);
}
public List<User> getAll(){
return MockUserTable.getAll();
}
public void delete(Integer id){
MockUserTable.delete(id);
}
}
package com.sivalabs.resteasydemo;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Path("/users")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public class UserResource
{
@Autowired
private UserService userService;
@Path("/")
@GET
public Response getUsersXML()
{
List<User> users = userService.getAll();
GenericEntity<List<User>> ge = new GenericEntity<List<User>>(users){};
return Response.ok(ge).build();
}
@Path("/{id}")
@GET
public Response getUserXMLById(@PathParam("id") Integer id) {
User user = userService.getById(id);
return Response.ok(user).build();
}
@Path("/")
@POST
public Response saveUser(User user) {
userService.save(user);
return Response.ok("<status>success</status>").build();
}
@Path("/{id}")
@DELETE
public Response deleteUser(@PathParam("id") Integer id) {
userService.delete(id);
return Response.ok("<status>success</status>").build();
}
}
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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.sivalabs.resteasydemo"/>
</beans>

Step#4: Same JUnit TestCase to test the REST Webservice described in Part-1.
package com.sivalabs.resteasydemo;
import java.util.List;
import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientResponse;
import org.jboss.resteasy.util.GenericType;
import org.junit.Assert;
import org.junit.Test;
import com.sivalabs.resteasydemo.User;
public class UserResourceTest {
static final String ROOT_URL = "http://localhost:8080/resteasy-demo/rest/";
@Test
public void testGetUsers() throws Exception
{
ClientRequest request = new ClientRequest(ROOT_URL+"users/");
ClientResponse<List<User>> response = request.get(new GenericType<List<User>>(){});
List<User> users = response.getEntity();
Assert.assertNotNull(users);
}
@Test
public void testGetUserById() throws Exception
{
ClientRequest request = new ClientRequest(ROOT_URL+"users/1");
ClientResponse<User> response = request.get(User.class);
User user = response.getEntity();
Assert.assertNotNull(user);
}
@Test
public void testSaveUser() throws Exception
{
User user = new User();
user.setId(3);
user.setName("User3");
user.setEmail("user3@gmail.com");
ClientRequest request = new ClientRequest(ROOT_URL+"users/");
request.body("application/xml", user);
ClientResponse<String> response = request.post(String.class);
String statusXML = response.getEntity();
Assert.assertNotNull(statusXML);
}
@Test
public void testDeleteUser() throws Exception
{
ClientRequest request = new ClientRequest(ROOT_URL+"users/2");
ClientResponse<String> response = request.delete(String.class);
String statusXML = response.getEntity();
Assert.assertNotNull(statusXML);
}
}

Important Things to Keep in mind:
1. org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap Listener should be registered before any other listener.

2. You should configure resteasy.servlet.mapping.prefix <context-param> if the HttpServletDispatcher servlet url-pattern is anything other than /*

3. While using Spring integration set resteasy.scan to false or don't configure resteasy.scan parameter at all.
    Otherwise you may get REST Resource instances(UserResource) from RestEasy instead of Spring container. While running JUnit Tests I observed this random behavior.

4. You should register REST Resource as Spring bean by annotating with @Component or @Service.

6 comments:

  1. Thank you for this post! I was struggling with the integration of RESTEasy into Spring for about an hour because the official documentation at http://docs.jboss.org/resteasy/docs/2.3.4.Final/userguide/html/RESTEasy_Spring_Integration.html was just too abstract for me to understand.

    But copying the concepts from your example my code finally worked :-)

    ReplyDelete
  2. Its Perfect. The example is clear and the explanation is kept so Simple

    ReplyDelete
  3. Does anyone got a problem when trying to use propertyplaceholderconfigurer and ${something} into a bean's property but the configurer is instantiate only after the bean from some reason? Spring post processor??

    ReplyDelete
  4. Hi Siva,
    This is works perfectly fine in Apache Tomcat 7.0.54 sever but when the same war file deployed in JBoss AS 7 ....deployment gets fail with error message "Caused by: java.lang.NoSuchMethodError: org.jboss.resteasy.spi.InjectorFactory.createPropertyInjector(Ljava/lang/Class;Lorg/jboss/resteasy/spi/ResteasyProviderFactory;)Lorg/jboss/resteasy/spi/PropertyInjector;"
    Any idea why behaviour .?? am using Spring-3.1.0.release and resteasy-3.0.8.Final
    TIA

    ReplyDelete
    Replies
    1. Hi,
      I guess this is due to conflict of JBoss Jar versions with some jars you packaged in war file.
      Make sure you have provided scope for resteasy jars in pom.xml while deploying on JBoss.

      Delete