Wednesday, February 27, 2013

JavaUtils and Apache Common Collections

When it comes to the importance not to repeat yourself while writing code, it is even important to take a look at most common activities. For example, instead of writing a lot of nested for statements, making your code illegible, you can use already Java built-in features. Let's take a look at some really simple utils:

Tested with:
  • commons-collections-3.2.1
  • JUnit 4
  • Maven 3

  • CollectionUtils.select - If you want to retrieve one or more objects from a collection that meet your criteria (like id = 4 or age > 18)
  • CollectionUtils.find - If you want to retrieve only one object from a collection that meet your criteria (like id = 6)
  • Collections.max - If you want to retrieve only one object from a collection, the one which best meets one criteria
  • Collections.sort - If you want to have a collection ordered by some criteria

Data to base our examples on:
@Before
 public void init() {
  Person p1 = new Person(1, "ruben", 17);
  Person p2 = new Person(2, "carlos", 45);
  Person p3 = new Person(3, "ismael", 47);
  Person p4 = new Person(4, "raul", 30);

  personList.add(p1);
  personList.add(p2);
  personList.add(p3);
  personList.add(p4);
 }

CollectionUtils.select
@Test
 public void selectListTest() {
  AdultPredicate adultPredicate = new AdultPredicate();
  Collection adultPersons = CollectionUtils.select(personList, adultPredicate);
  
  Assert.assertNotNull(adultPersons);
  Assert.assertTrue(!adultPersons.isEmpty());
 }

public class AdultPredicate implements Predicate {
 
 @Override
 public boolean evaluate(Object arg0) {
  Person person = (Person) arg0;
  if (person.getAge() != null &&
   person.getAge() >= 18) {
   return true;
  }
  return false;
 }
}

CollectionUtils.find
@Test
 public void findObjectTest() {
  Predicate personNumberTwoPredicate = new PersonNumberTwoPredicate();
  Person personNumberTwo = (Person) CollectionUtils.find(personList, personNumberTwoPredicate);
  
  Assert.assertNotNull(personNumberTwo);
  Assert.assertTrue(personNumberTwo.getId().equals(2));
 }

public class PersonNumberTwoPredicate implements Predicate {

 private static final Integer PERSON_ID_TWO = 2;

 @Override
 public boolean evaluate(Object arg0) {
  Person person = (Person) arg0;
  if (person.getId().equals(PERSON_ID_TWO)) {
   return true;
  }
  return false;
 }
}

Collections.max
@Test
 public void oldestPersonTest() {
  PersonAgeComparator personAgeComparator = new PersonAgeComparator();
  Person oldestPerson = Collections.max(personList, personAgeComparator);
  
  Assert.assertNotNull(oldestPerson);
  Assert.assertTrue(oldestPerson.getAge().equals(47));
 }

public class PersonAgeComparator implements Comparator {

 @Override
 public int compare(Person o1, Person o2) {
  if (o1.getAge() > o2.getAge()) {
   return 1;
  }
  if (o1.getAge().equals(o2.getAge())) {
   return 0;
  }
  return -1;
 }
}

Collections.sort
@Test
 public void sortPersonsTest() {
  PersonAgeComparator personAgeComparator = new PersonAgeComparator();
  //Sort order age ascending
  Collections.sort(personList, personAgeComparator);
  //Sort order age descending
  Collections.sort(personList, ComparatorUtils.reversedComparator(personAgeComparator));
 }

Download the complete example here!

Checkout this: https://subversion.assembla.com/svn/pablo-examples/utils-test

Then from a Terminal get into that folder and run: mvn test
To import it into Eclipse IDE, from a Terminal run: mvn eclipse:eclipse and import it as a Java Project

Tuesday, December 4, 2012

Datasource and database driver in JBoss 7 without module definition

So, you just downloaded JBoss 7 and want to give it a try. You notice that the whole directory structure changed completely but do not want to "waste time with new administration stuff" and want to use another datasource other that the default one which comes with H2 (an in-memory database).

With this configuration, you will be able to easily create a new datasource for JBoss 7 using a new database driver with minimal configuration. This configuration has been tested only in standalone mode and may not be suitable for cluster/production environments. Note that this way will only work with JDBC4 drivers.

Just deploy your jar under <JBOSS_HOME>\standalone\deployments and add the following datasource definition to your <JBOSS_HOME>\standalone\configuration\standalone.xml

<datasource jndi-name="java:jboss/datasources/PostgreSQLDS" pool-name="PostgreSQLDS" enabled="true" use-java-context="true">
       <connection-url>jdbc:postgresql://<your_ip>:<your_db_port>/<your_db></connection-url>
       <driver>postgresql-9.1-901.jdbc4.jar</driver>
      <security>
            <user-name>postgres</user-name>
            <password>123456</password>
      </security>
</datasource>

Now just run <JBOSS_HOME>/bin/standalone.sh (if you are using Linux/Mac, .bat if Windows) to start the server. You can check the datasource health in the new administration console.

Additional information on deploying datasources to JBoss 7 can be found here

Tuesday, October 2, 2012

@ViewScoped and CDI

If you were working with Seam 2 you sure were familiar with @Scope(ScopeType.PAGE) annotation for your action classes. This scope was used in those cases where the user stayed in the same page working until a redirect (or refresh) was done.

Now, taking a look at CDI you see that there is no out-of-the-box scope that does the same job. JSF 2 provides @ViewScoped but it is incompatible with CDI. You may try the following without success:

import javax.faces.bean.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped

public class AdministrarUsuariosAction implements Serializable {

Unlike @SessionScoped where you have the CDI proper annotation and the JSF 2 one:

import javax.enterprise.context.SessionScoped;
import javax.faces.bean.SessionScoped;

...for @ViewScoped you only have the JSF 2 one:

import javax.faces.bean.ViewScoped;

So, is there a way we can have a page scope just like in Seam 2? YES

Thanks to Seam 3 Faces module, we are able to use JSF 2 @ViewScoped annotation inside CDI beans. Just add these jars to the pom file:

<dependency>
  <groupid>org.jboss.seam.faces</groupid>
  <artifactid>seam-faces</artifactid>
  <version>3.1.0.Final</version>
</dependency>
  
<dependency>
  <groupid>org.jboss.seam.security</groupid>
  <artifactid>seam-security</artifactid>
  <version>3.1.0.Final</version>
</dependency>

I've tested this configuration under JBoss 7.1.1.Final using the archetype: jboss-javaee6-webapp. I've used this post from Adrian Paredes blog to adapt the archetype to work with JBoss 7.

Friday, August 24, 2012

JBoss ESB http-gateway service

We can call an ESB service in different ways; It can be invoked via queue messages, HTTP requests, webservices, and so on... In this post we will work with the JBoss ESB and see how we can invoke a service with a HTTP POST request.

Versions used:
- JBoss AS 5.1.0.GA (Community version)
- JBoss ESB 4.11 (not the standalone one, the version which is deployed in the AS)

Let's see the example!


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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>ar.com.pabloExample</groupId>
 <artifactId>helloworld-jboss-esb</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>helloworld-jboss-esb</name>
 <url>http://maven.apache.org</url>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <jboss.home>/opt/jboss</jboss.home>
 </properties>

 <dependencies>

  <dependency>
   <groupId>org.jboss.soa.bpel.dependencies.esb</groupId>
   <artifactId>jbossesb-rosetta</artifactId>
   <version>4.11</version>
   <scope>system</scope>
   <systemPath>${jboss.home}/server/esb-default/deployers/esb.deployer/lib/jbossesb-rosetta.jar</systemPath>
  </dependency>
  
  <!-- For testing purposes -->

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

  <dependency>
   <groupId>commons-httpclient</groupId>
   <artifactId>commons-httpclient</artifactId>
   <version>3.1</version>
   <scope>test</scope>
  </dependency>
  
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
   <scope>test</scope>
  </dependency>

 </dependencies>

 <build>
  <finalName>helloworld-jboss-esb</finalName>

  <plugins>

   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jboss-packaging-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
     <archiveName>helloworld-jboss-esb</archiveName>
    </configuration>
    <executions>
     <execution>
      <id>build-esb</id>
      <goals>
       <goal>esb</goal>
      </goals>
     </execution>
    </executions>
   </plugin>

   <!-- mvn jboss:hard-deploy -->
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jboss-maven-plugin</artifactId>
    <version>1.5.0</version>
    <configuration>
     <jbossHome>/opt/jboss</jbossHome>
     <serverName>esb-default</serverName>
     <fileName>target/helloworld-jboss-esb.esb</fileName>
    </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>

The jboss-maven-plugin is just a plugin to ease the process of copying the esb file generated to the jboss server. Of course this is not mandatory :)

The jboss-packaging-maven-plugin is the plugin responsible for creating the ESB file that will be deployed in the server.


jboss-esb.xml
<jbossesb
 xmlns="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://anonsvn.labs.jboss.com/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.0.1.xsd http://anonsvn.jboss.org/repos/labs/labs/jbossesb/trunk/product/etc/schemas/xml/jbossesb-1.2.0.xsd"
 parameterReloadSecs="5">
 
 <services>

  <service name="helloWorldService" category="helloWorldCategory" description="Hello World" invmScope="GLOBAL">
  
   <listeners>
    <http-gateway name="http-gateway" payloadAs="STRING"/>
   </listeners>
  
   <actions mep="RequestResponse">

    <!-- This action prints the content of the message -->
    <action name="action1" class="org.jboss.soa.esb.actions.SystemPrintln">
     <property name="printfull" value="false" />
    </action>
    
    <action name="action2" class="ar.com.pabloExample.service.MessageCustomAction" process="process" />

    <!-- This action logs a user text and the service name -->
    <action name="action3" class="org.jboss.soa.esb.actions.ServiceLoggerAction">
     <property name="text" value="Message arrived" />
     <property name="log-payload-location" value="true" />
    </action>

   </actions>
  </service>
  
 </services>

</jbossesb>

In jboss-esb.xml we define a service with 3 actions:
"action1" & "action3" : they do some logging
"action2" : takes the "name" query parameter from the URL and creates the "hello <query parameter>" string which is put in the ESB message body

A listener is defined too. The HTTP listener will trigger the service whenever a POST request is make to a special URL (see the test section to see which is that URL).


MessageCustomAction.java
public class MessageCustomAction extends AbstractActionPipelineProcessor {

 public MessageCustomAction(final ConfigTree config) {
  // extract configuration
 }

 @Override
 public Message process(Message message) throws ActionProcessingException {

        HttpRequest req = HttpRequest.getRequest(message);
        Map<String, String[]> params = req.getQueryParams();
        
        String name = params.get("name")[0];
        
        message.getBody().add("Hello " + name);
  
        return message;
 }

}

Here we define the way to process the ESB message, we interpret it as a HTTP request and retrieve the "name" query parameter.


Let's test the ESB service!



HttpRequestServiceTest.java
public class HttpRequestServiceTest {
 
 private static Logger logger = Logger.getLogger(HttpRequestServiceTest.class);
 
 @Test
 public void httpRequestServiceTest() throws HttpException, IOException {
  
 HttpClient client = new HttpClient();
        PostMethod post = new PostMethod("http://localhost:8080/helloworld-jboss-esb/http/helloWorldCategory/helloWorldService");
        post.setQueryString("?name=pablito");
        client.executeMethod(post);
        
        String result = post.getResponseBodyAsString();
        post.releaseConnection();

        logger.info("Service returned: " + result);
 }
}

In this test we simply do a HTTP post to the following URL:
http://localhost:8080/helloworld-jboss-esb/http/helloWorldCategory/helloWorldService

Note that using the default http-gateway the pattern to use would be like this:
http://<server>:<port>/<deployed esb file>/http/<category name>/<service name>


To run this test, create the ESB file and deploy it in the server:
  1. Set the jboss.home property in the pom.xml
  2. mvn clean package -Dmaven.test.skip=true jboss:hard-deploy
Now run the test with mvn test and you're done!


Download the example:


https://subversion.assembla.com/svn/pablo-examples/helloworld-jboss-esb

Tuesday, August 7, 2012

Installing JBoss ESB in JBoss AS 5.1

We will see the steps to install JBoss ESB in a JBoss AS instance. The ESB can be run in a standalone mode which we are not analyzing in this post. The steps to install the JBoss ESB are really simple and the installation already includes JBPM3 within it. In another post we will see different ESB examples.

Versions used:

JBoss AS 5.1.0.GA - Download
JBoss ESB 4.11 Community - Download
Eclipse Indigo SR2 - Download
JBoss Tools 3.3 for SOA - Download
Ant 1.8.4 - Download


Create a new JBoss instance, we will create esb-default based on default
cd /opt/jboss/server
cp default esb-default -r

In your download folder, extract the JBoss ESB zip file:
cp jbossesb-4.11.zip /opt
cd /opt/jbossesb-4.11
unzip jbossesb-4.11.zip

Now, let's get into the install directoy and create a new deployment.properties file
cd /opt/jbossesb-4.11/install/
cp deployment.properties-example deployment.properties

Now edit the new deployment.properties file and modify the following lines:
org.jboss.esb.server.config=default
...
org.jboss.esb.server.home=/jbossesb-server-4.5.GA
to these
org.jboss.esb.server.config=esb-default
...
org.jboss.esb.server.home=/opt/jboss-5.1.0.GA

Now, while in the install directory let's install the ESB in the AS:
ant deploy

To run the server:
./run.sh -c esb-default

In Eclipse IDE with JBoss Tools installed, in order to create a new ESB example:
File -> New -> Other -> ESB Project

In target runtime create a new server based on your esb-default instance. At the end of the wizard, choose Server Supplied ESB Runtime (because we've installed the ESB in the JBoss esb-default instance)

The following guides are essential while working with JBoss ESB:
ESB Tools Reference Guide
ESB Services Guide
ESB Programmers Guide

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