Saturday, February 26, 2011

Spring 3: REST Web Service Provider and Client Tutorial (Part 1)

In this tutorial we'll create a RESTful web service based on Spring 3. We'll be developing a REST provider using URI templates that returns XML and JSON representations of our data. We'll also add basic support for retrieving images and html pages. We'll test our provider via RESTClient to verify the service's availability. In part 2, we'll add an actual client based on RestTemplate and Spring MVC that will access our service.

What is REST?
Representational State Transfer (REST) is a style of software architecture for distributed hypermedia systems such as the World Wide Web. The term Representational State Transfer was introduced and defined in 2000 by Roy Fielding in his doctoral dissertation.

Source: http://en.wikipedia.org/wiki/Representational_State_Transfer

The Representational State Transfer (REST) style is an abstraction of the architectural elements within a distributed hypermedia system. REST ignores the details of component implementation and protocol syntax in order to focus on the roles of components, the constraints upon their interaction with other components, and their interpretation of significant data elements. It encompasses the fundamental constraints upon components, connectors, and data that define the basis of the Web architecture, and thus the essence of its behavior as a network-based application.

Source: Architectural Styles and the Design of Network-based Software Architectures, a dissertation of Roy Thomas Fielding,

REST vs SOAP
Unlike SOAP-based web services, there is no "official" standard for RESTful web services. This is because REST is an architecture, unlike SOAP, which is a protocol. Even though REST is not a standard, a RESTful implementation such as the Web can use standards like HTTP, URL, XML, PNG, etc.

Source: http://en.wikipedia.org/wiki/Representational_State_Transfer

What is REST in Spring 3?
For a great introduction, please see the following links:
- REST in Spring 3: @MVC
- REST in Spring 3: RestTemplate

The Provider

Our application is a simple CRUD system for managing a list of persons. We'll start our project with the provider service which produces XML and JSON representations of our data.

The Domain Layer

We'll be declaring two domain objects: Person and PersonList

Person.java
package org.krams.tutorial.domain;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="person")
public class Person {

 private Long id;
 private String firstName;
 private String lastName;
 private Double money;
 
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
 public Double getMoney() {
  return money;
 }
 public void setMoney(Double money) {
  this.money = money;
 }
}
Person is a simple POJO consisting of three properties. Notice we've annotated the class name with @XmlRootElement(name="person"). Its purpose is to help JAXB (the one responsible for marshalling/unmarshalling to XML) determine the root of our POJO.

PersonList.java
package org.krams.tutorial.domain;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="persons")
public class PersonList {

 @XmlElement(required = true)
 public List<person> data;

 @XmlElement(required = false)
 public List<person> getData() {
  return data;
 }

 public void setData(List<person> data) {
  this.data = data;
 }
}
PersonList is another simple POJO. It's purpose is to wrap a list of Person objects.

Ideally we should be able to return a List instead of a PersonList. However, JAXB has difficulties processing java.util.List. A simple Google search verifies this unwanted behavior, for example, http://stackoverflow.com/questions/298733/java-util-list-is-an-interface-and-jaxb-cant-handle-interfaces

To resolve this issue with JAXB, we wrap our List with another object. To learn more about JAXB please visit the following link: http://download.oracle.com/javaee/5/tutorial/doc/bnazf.html

The Controller Layer

The controller layer is the most important section of the provider service because here's where we define the RESTful services available to our clients.

ProviderController.java
package org.krams.tutorial.controller;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.imageio.ImageIO;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.krams.tutorial.domain.PersonList;
import org.krams.tutorial.service.PersonService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;

/**
 * REST service provider
 * 
 * Only GET and POST will return values
 * PUT and DELETE will not.
 */
@Controller
@RequestMapping("/provider")
public class RestProviderController {

 protected static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="personService")
 private PersonService personService;
 
 @RequestMapping(value = "/person/{id}", 
        method = RequestMethod.GET,
        headers="Accept=image/jpeg, image/jpg, image/png, image/gif")
 public @ResponseBody byte[] getPhoto(@PathVariable("id") Long id) {
  
  // Call service here
  try {
   // Retrieve image from the classpath
   InputStream is = this.getClass().getResourceAsStream("/bella.jpg"); 
   
   // Prepare buffered image
   BufferedImage img = ImageIO.read(is);
   
   // Create a byte array output stream
   ByteArrayOutputStream bao = new ByteArrayOutputStream();
   
   // Write to output stream
   ImageIO.write(img, "jpg", bao);
   
   logger.debug("Retrieving photo as byte array image");
   return bao.toByteArray();
   
  } catch (IOException e) {
   logger.error(e);
   throw new RuntimeException(e);
  }
 }
    
    @RequestMapping(value = "/person/{id}", 
        method = RequestMethod.GET, 
        headers="Accept=application/html, application/xhtml+xml")
 public String getPersonHTML(@PathVariable("id") Long id, Model model) {
     logger.debug("Provider has received request to get person with id: " + id);
  
  // Call service to here
  model.addAttribute("person",personService.get(id));
  
  return "getpage";
 }
    
 @RequestMapping(value = "/persons", 
        method = RequestMethod.GET, 
        headers="Accept=application/xml, application/json")
 public @ResponseBody PersonList getPerson() {
  logger.debug("Provider has received request to get all persons");
  
  // Call service here
  PersonList result = new PersonList();
  result.setData(personService.getAll());
  
  return result;
 }
 
    @RequestMapping(value = "/person/{id}", 
           method = RequestMethod.GET, 
           headers="Accept=application/xml, application/json")
 public @ResponseBody Person getPerson(@PathVariable("id") Long id) {
     logger.debug("Provider has received request to get person with id: " + id);
     
     // Call service here
  return personService.get(id);
    }
    
    @RequestMapping(value = "/person", 
           method = RequestMethod.POST, 
           headers="Accept=application/xml, application/json")
 public @ResponseBody Person addPerson(@RequestBody Person person) {
     logger.debug("Provider has received request to add new person");
     
     // Call service to here
     return personService.add(person);
    }
    
    
    @RequestMapping(value = "/person/{id}", 
           method = RequestMethod.PUT, 
           headers="Accept=application/xml, application/json")
 public @ResponseBody String updatePerson(@RequestBody Person person) {
     logger.debug("Provider has received request to edit person with id: " + id);
     
     // Call service here
     person.setId(id);
     return personService.edit(person).toString();
    }
    
    
    @RequestMapping(value = "/person/{id}", 
           method = RequestMethod.DELETE,
           headers="Accept=application/xml, application/json")
 public @ResponseBody String deleteEmployee(@PathVariable("id") Long id) {
     logger.debug("Provider has received request to delete person with id: " + id);
 
     // Call service here
     return personService.delete(id).toString();
    }
}

The URI Templates
Our controller has seven methods available. The first four are GET methods while the last three are POST, PUT, and DELETE methods.

The four GET methods correspond to the following:

1. Retrieving a single image based on a specific id
URL: http://localhost:8080/spring-rest-provider/krams/person/{id} 
Method: GET
Accept Header: image/jpeg, image/jpg, image/png, image/gif

2. Retrieving an HTML page based on a specific id
URL: http://localhost:8080/spring-rest-provider/krams/person/{id}
Method: GET
Accept Header: application/html, application/xhtml+xml

3. Retrieving an XML or JSON containing a list of persons
URL: http://localhost:8080/spring-rest-provider/krams/person
Method: GET
Accept Header: application/xml, application/json

4. Retrieving an XML or JSON containing a single person
URL: http://localhost:8080/spring-rest-provider/krams/person/{id}
Method: GET
Accept Header: application/xml, application/json

The last three methods correspond to the remaining CRUD services:

5. Adding a new person via XML and JSON
URL: http://localhost:8080/spring-rest-provider/krams/person
Method: POST
Accept Header: application/xml, application/json

6. Editing an existing person via XML and JSON
URL: http://localhost:8080/spring-rest-provider/krams/person/{id}
Method: PUT
Accept Header: application/xml, application/json

7. Deleting an existing person via its id
URL: http://localhost:8080/spring-rest-provider/krams/person/{id}
Method: DELETE
Accept Header: application/xml, application/json

HTTP Methods
Notice for most of the methods, we're just reusing the same URL http://localhost:8080/spring-rest-provider/krams/person and the only part that differs are the suffix {id} and the HTTP methods:

Method Purpose
GET Retrieves a representation of the requested resource
POST Creates a new representation of the requested resource
PUT Updates an existing representation of the requested resource
DELETE Deletes an existing representation of the requested resource

The Service Layer

The service layer is the one that does the actual processing of data. Here we're just managing an in-memory list to make the tutorial simpler to understand. Translating this to a real database isn't really that difficult

PersonService.java
package org.krams.tutorial.service;

import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.springframework.stereotype.Service;

@Service("personService")
public class PersonService {

 protected static Logger logger = Logger.getLogger("service");
 
 // In-memory list
 private List<Person> persons = new ArrayList<Person>();
 
 public PersonService() {
  logger.debug("Init database");
  
  // Create in-memory list
  Person person1 = new Person();
  person1.setId(0L);
  person1.setFirstName("John");
  person1.setLastName("Smith");
  person1.setMoney(1000.0);
  
  Person person2 = new Person();
  person2.setId(1L);
  person2.setFirstName("Jane");
  person2.setLastName("Adams");
  person2.setMoney(2000.0);
  
  Person person3 = new Person();
  person3.setId(2L);
  person3.setFirstName("Jeff");
  person3.setLastName("Mayer");
  person3.setMoney(3000.0);
  
  persons.add(person1);
  persons.add(person2);
  persons.add(person3);
 }
 
 /**
  * Retrieves all persons
  */
 public List<Person> getAll() {
  logger.debug("Retrieving all persons");
  
  return persons;
 }
 
 /**
  * Retrieves a single person
  */
 public Person get( Long id ) {
  logger.debug("Retrieving person with id: " + id);
  
  for (Person person:persons) {
   if (person.getId().longValue() == id.longValue()) {
    logger.debug("Found record");
    return person;
   }
  }
  
  logger.debug("No records found");
  return null;
 }
 
 /**
  * Adds a new person
  */
 public Person add(Person person) {
  logger.debug("Adding new person");
  
  try {
   // Find suitable id
   Long newId = 0L;
   Boolean hasFoundSuitableId = false;
   while(hasFoundSuitableId == false) {
    for (int i=0; i <persons.size(); i++) {
     if (persons.get(i).getId().longValue() == newId.longValue()) {
      newId++;
      break;
     }
     
     // Exit while loop
     if(i==persons.size()-1) {
      logger.debug("Assigning id: " + newId);
      hasFoundSuitableId = true;
     }
    }
   }
   
   // Assign new id
   person.setId(newId);
   // Add to list
   persons.add(person);
   
   logger.debug("Added new person");
   return person;
  } catch (Exception e) {
   logger.error(e);
   return null;
  }
 }
 
 /**
  * Deletes an existing person
  */
 public Boolean delete(Long id) {
  logger.debug("Deleting person with id: " + id);
  
  try {
   for (Person person:persons) {
    if (person.getId().longValue() == id.longValue()) {
     logger.debug("Found record");
     persons.remove(person);
     return true;
    }
   }
   
   logger.debug("No records found");
   return false;
   
  } catch (Exception e) {
   logger.error(e);
   return false;
  }
 }
 
 /**
  * Edits an existing person
  */
 public Boolean edit(Person person) {
  logger.debug("Editing person with id: " + person.getId());
  
  try {
   for (Person p:persons) {
    if (p.getId().longValue() == person.getId().longValue()) {
     logger.debug("Found record");
     persons.remove(p);
     persons.add(person);
     return true;
    }
   }
   
   logger.debug("No records found");
   return false;
   
  } catch (Exception e) {
   logger.error(e);
   return false;
  }
 }
}

Configuration

Configuring REST support in Spring isn't really different from configuring your usual Spring MVC. That's because REST in Spring are additional "features to Spring MVC itself" (See Rest in Spring 3: @MVC blog).

Here are all the XML configurations we're using for this project.

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
 <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>/krams/*</url-pattern>
 </servlet-mapping>

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

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:p="http://www.springframework.org/schema/p" 
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 
 <!-- Declare a view resolver -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />

</beans>

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:context="http://www.springframework.org/schema/context"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd
   http://www.springframework.org/schema/mvc 
   http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
 
 <!-- Activates various annotations to be detected in bean classes -->
 <context:annotation-config />
 
 <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
  For example @Controller and @Service. Make sure to set the correct base-package-->
 <context:component-scan base-package="org.krams.tutorial" />
 
 <!-- Configures the annotation-driven Spring MVC Controller programming model.
 Note that, with Spring 3.0, this tag works in Servlet MVC only!  -->
 <mvc:annotation-driven />

</beans>

Run the Application

To run the application, you'll need a client application, like a browser. Also you must access directly the URLs to reach a specific action.

Browser Issues

By default, you can only send a GET request in your browser without resorting to special plugins or tools. When you type a URL in the browser's address field, you're actually specifying a GET request. Using a browser we can only retrieve XML and HTML representations of our GET request (again without resorting to special plugins). Try it out and see what your browser will return.

On Chrome, typing the following URL
http://localhost:8080/spring-rest-provider/krams/person/1
yields an XML document:

On Firefox, typing the following URL
http://localhost:8080/spring-rest-provider/krams/person/1
yields an HTML document:

Testing with RESTClient

Using a browser we can only test the GET portion of the REST service. To fully test all methods, either we write a client application or reuse an existing client. Luckily for us there's a great Java client: RESTClient

What is RESTClient?
RESTClient is a Java application to test RESTful webservices. It can be used to test variety of HTTP communications. From version 2.3 Beta 1, it has two executable Jars:

- GUI version (restclient-ui-X.jar download)
- Cli version for batch execution of .rcq files (restclient-cli-X.jar download)

Source: http://code.google.com/p/rest-client/

Let's start by running RESTClient

Retrieve a single record (GET method)

1. To retrieve a record, select the GET method:

2. Click the Headers tab and enter the following information:
Key:  Accept
Value: application/xml or application/json


Make sure to hit the plus button to enter the values:

3. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/person/1
You should see the following results depending on the Accept header you provided earlier:

XML result

JSON result

Add a new record (POST method)

1. Under the Method tab, select the POST method.

2. Under the Headers tab, enter the following information:
Key:  Accept
Value: application/xml or application/json

3. Under the body tab, enter the following content (depending on the value field in step #2)
XML:
<person>
    <firstName>Roy</firstName>
    <lastName>Jones</lastName>
    <money>5000</money>
</person>
JSON:
{
  "firstName":"Roy"
  "lastName":"Jones"
  "money":5000
}

4. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/person
You should see the following results depending on the Accept header you provided earlier:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<person>
    <firstName>Roy</firstName>
    <lastName>Jones</lastName> 
    <money>5000.0</money>
</person>
JSON:
{
  "firstName" : "Roy",
  "lastName" : "Jones",
  "money" : 5000.0
}

Update an existing record (PUT method)

1. Under the Method tab, select the PUT method.

2. Under the Headers tab, enter the following information:
Key:  Accept
Value: application/xml or application/json

3. Under the body tab, enter the following content (depending on the value field in step #2)
XML:
<person>
    <firstName>Roy</firstName>
    <lastName>Jones</lastName>
    <money>5000</money>
</person>
JSON:
{
  "firstName":"Roy",
  "lastName":"Jones",
  "money":5000
}

4. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/person
You should see the following results depending on the Accept header you provided earlier:
XML:
true
JSON:
true
This means we've successfully updated record #1 with the new information we've provided. To verify if the record has been updated, create a GET request with the following URL:
http://localhost:8080/spring-rest-provider/krams/provider/person/1
Notice the number 1 at the end. That's the same number we used in the PUT method. You should see the updated record.

Delete an existing record (DELETE method)

1. Under the Method tab, select the DELETE method.

2. Under the Headers tab, enter the following information:
Key:  Accept
Value: application/xml or application/json

3. Enter the following URL:
http://localhost:8080/spring-rest-provider/krams/person/1
You should see the following results depending on the Accept header you provided earlier:
XML:
true
JSON:
true
This means we've successfully deleted record #1. To verify if the record has been deleted, create a GET request with the following URL:
http://localhost:8080/spring-rest-provider/krams/provider/person/1

Notice the number 1 at the end. That's the same number we used in the DELETE method. You should see an empty result.

Conclusion

That's it. We've managed to create a REST service using Spring 3. We've also shown how we can retrieve different representations of our resource by using a unified URL with varying HTTP methods. We've also tested the application using RESTClient. In Part 2 of the tutorial, we'll explore how to develop a client application using Spring's RestTemplate support.

To see Part 2 of this tutorial, click the following link: Spring 3: REST Web Service Provider and Client Tutorial (Part 2)

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-rest-guide/

You can download the project as a Maven build. Look for the spring-rest-provider.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3: REST Web Service Provider and Client Tutorial (Part 1) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share