This text is about developing RESTful web services using Java, Maven, Json and Spring 3. You can read instructions on how to setup a development environment for this kind of project here. The source code is available on GitHub.

Rest

As stated in this text,

[With Rest], requests and responses are built around the transfer of representations of resources. Resources are identified by global ID’s that typically use a uniform resource identifier (URI). Client applications use Http methods (such as GET, Post, PUT, or Delete) to manipulate the resource or collection of resources. Generally, a GET method is used to get or list the resource or collection of resources, Post is used to create, PUT is used to update or replace, and Delete is for removing the resource.

The Amazing Salume Application™

You work at a small business, a supplier company specialized in the finest salumes in the world, with a few clients, mostly bistros and restaurants (actually, there are only two clients, one bistro and one restaurant, but describing the company that way makes it looks larger). They both are good clients and the sales of the company is quite impressive. As the sales are expected to continue to grow, it is time to use a software application to aid the sale process. You decided to provide a RESTful web service to allow clients to manipulate their salumes orders through requests/responses in Json format.

A Very Simple Example

This a simple example of how to implement a Rest service with Spring 3 and Json. The URI of this web service is http://localhost:8080/Salume/restService/order/.

GET Http Method

There goes a silly findOrder method which will return a mortela as an order no matter the id passed as argument.

@Controller
@RequestMapping("/order")
public class OrderController {

    // . . . Other services...

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    public @ResponseBody Order findOrder(@PathVariable("id") long id) {
        Order order = new Order();
        order.setProduct("mortadela");
        return order;
    }
}

The {id} defined in the @RequestMapping is an URI template variable which will be replaced by the value passed through the id parameter thanks to the PathVariable.

@ResponseBody Annotation will bound the Order instance returned by findOrder to the web response body (in a Json format in this case).

Just to illustrate, here is the Order class:

public class Order {
    private final long id = 1;
    private String product;

    public long getId() {
        return id;
    }

    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        this.product = product;
    }
}

Then simply add the Jackson Maven Plugin to the pom.xml and let the Spring automagically convert Order to a Json format.

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.5</version>
        </dependency>

cURL

Use cURL to test findOrder Rest service. If you are new to cURL, I  suggest the reading of REST-esting with cURL to get started with it. A good alternative to cURL to debug RESTful web services is a nice Firefox plug-in, the RESTClient

Type:

curl -HAccept:application/json http://localhost:8080/Salume/restService/order/2

and the result will be:

{"id":1,"product":"mortadela"}

The same result is achieved if one types in the browser http://localhost:8080/Salume/restService/order/2.

Post

Clients will request a new order using the Post method.

@Controller
@RequestMapping("/order")
public class OrderController {

    // . . . Other services...

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public Order add(@RequestBody Order order) {
        // . . . you may want to pass the order to a service...
        return order;
}

Since we are using the @RequestBody in order to let Spring convert a Json text to an Order instance, we must configure MappingJacksonHttpMessageConverter to be used as a HttpMessageConverter. (In the Salume project, the MappingJacksonHttpMessageConverter is configured in the restDispatcher-servlet.xml.)

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="jsonConverter" />
        </list>
    </property>
</bean>

<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
    <property name="supportedMediaTypes" value="application/json" />
</bean>

Now we can Post a new order; follows an example using cURL:

curl -v -H "Accept:application/json" -H "Content-Type:application/json;charset=UTF-8" -X POST -d '{"product":"columbus"}' http://localhost:8080/Salume/restService/order

Note the Content-Type header set to application/json;charset=UTF-8, which is necessary besides defining "Accept:application/json".

PUT

When updating an order, clients use the PUT method:

@Controller
@RequestMapping("/order")
public class OrderController {

    // . . . Other services...

    @RequestMapping(method = RequestMethod.PUT, value = "/{id}")
    @ResponseBody
    public Order update(@PathVariable("id") long id, @RequestBody Order order) {
        // . . . tell someone to update the order...
        return updated;
    }
}

Of course it is necessary to specify the resouce being update with its URI.

curl -v -H "Accept:application/json" -H "Content-Type:application/json;charset=UTF-8" -X PUT -d '{"product":"speciale"}' http://localhost:8080/Salume/restService/order/2

Delete

Not surprisingly, the Delete method is used to remove an order.

@Controller
@RequestMapping("/order")
public class OrderController {

    // . . . Other services...

    @RequestMapping(method = RequestMethod.DELETE, value = "/{id}")
    @ResponseBody
    public Order remove(@PathVariable("id") long id) {
        // . . . delegating...
        return removed;
    }
}

Try something like:

curl -i -v -HAccept:application/json -X DELETE http://localhost:8080/Salume/restService/order/2

Piece of cake…

Changing a Little Bit the Domain Model

Let’s say an order contains one to many items and each item specifies a product and its quantity.

public class Order {

    private long id;

    private List<Item> items = new ArrayList<Item>();

    // . . . getters and setters
}
public class Item {

    private int quantity;

    private Product product;

    // . . . getters and setters
}
public class Product {

    private String name;

    private String description;

    private BigDecimal price;

    // . . . getters and setters
}

There’s no need to do any modification in the OrderController. What will change is the Json format to reflect the new order modeling. Thus, for example, when a client sends a PUT request to update an Order, it may send a Json string like this:

  {
     "id": 1,
     "items": [
       {
         "quantity": 2,
         "product": {
           "name": "Mortadela",
           "description": "Carne defumada",
           "price": 23.4
         }
       },
       {
         "quantity": 5,
         "product": {
           "name": "Salume Speciale",
           "description": "Gusto incredibile",
           "price": 53.9
         }
       }
     ]
   }

Unit and (Maven) Integration Tests

Unit Tests

Here’s an example of the use of Spring MVC Test project to implement unit tests.

@RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration defaults to OrderControllerTest-context.xml in the same package
@ContextConfiguration
public class OrderControllerTest {

    @Test
    public void testFindOrder() throws Exception {
        standaloneSetup(new OrderController())
                .build()
                .perform(get("/order/2").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().type(MediaType.APPLICATION_JSON))
                .andExpect(content().string(findOrderJsonResult()))
                .andExpect(jsonPath("$.id").value(1)).andDo(print());
    }

    // . . .

Note the #findOrderJsonResult method. Instead of inline a Json String, which can be very awkward especially in the case of long ones, the expected result is defined in a file and then extracted with a couple utility methods. For example, definition of the expected result in the testFindOrder is below.

  {
     "id":1,
     "items":[
       {
         "quantity":2,
         "product":{
           "name":"Mortadela",
           "description":"Carne defumada",
           "price":23.4
         }
       },
       {
         "quantity":5,
         "product":{
           "name":"Salume Speciale",
           "description":"Gusto Incredibile",
           "price":53.9
         }
       }
     ]
   }

(Maven) Integration Tests

The RestTemplate is an option when implementing integration tests for Rest services.

@Category(IntegrationTest.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("OrderControllerTest-context.xml")
public class OrderControllerIT {

    private static final String REST_SERVICE = "http://localhost:8080/Salume/restService/order";

    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testFindOrder() {
        Order expected = // . . . fixture here

        Order result = restTemplate.getForObject(REST_SERVICE + "/{id}", Order.class, 2);
        Assert.assertEquals(expected, result);
    }
}

Separating Unit from (Maven) Integration Tests

maven-surefire-plugin is used in the Maven test phase (before the build) to run unit tests. Instead, maven-failsafe-plugin will run acceptance tests in the Maven integration-test phase (after the build – by default failsafe search for test classes that end with *IT).

            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.9</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify</id>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

An app server up and running is needed to execute acceptance tests in the Maven integration-test phase. An option is to configure a Jetty instance to start right before the integration-test and stop it in the post-integration-test phase.

            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>8.1.2.v20120308</version>
                <configuration>
                    <scanIntervalSeconds>5</scanIntervalSeconds>
                    <stopKey>stop</stopKey>
                    <stopPort>9999</stopPort>
                    <webApp>
                        <contextPath>/Salume</contextPath>
                    </webApp>
                </configuration>
                <executions>
                    <execution>
                        <id>start-jetty</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <!-- stop any previous instance to free up the port -->
                            <goal>stop</goal>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <scanIntervalSeconds>0</scanIntervalSeconds>
                            <daemon>true</daemon>
                        </configuration>
                    </execution>
                    <execution>
                        <id>stop-jetty</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

Updates

  • (18/04/2013) You may prefer to use Rest Assured instead of RestTemplate.
  • (18/04/2013) Added section Separating Unit from (Maven) Integration Tests and other small changes.

Fonts

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s