Friday 9 May 2008

REST in Spring without using Spring Webservices

I quite like using Spring. Especially when using the Spring/Hibernate combo.

About a year ago I started using Spring for a whole heap of REST webservices I was writing. In the process I found it surprisingly easy. Various other colleagues experimented with RESTLET (http://www.restlet.org/) and Jersey (https://jersey.dev.java.net/). But as I wanted to use Hibernate easily and I was using Spring MVC to build the client component of the project I was working on, so I used Spring MVC to build the services. Pretty simple. (Even though I refer to Spring 2.5 in the pom file, I am not using the features of 2.5.)

Here's an example:

I have created a simple webapp using maven. The webapp is called rest-webapp. I have created a stupid service that retrieves the name of an evil programmer.

This means that the request would look like:
http://localhost:8080/rest-webapp/evil/programmers/1

and this would return:
Dr Evil

I wrote a simple class that does this and also returns a 404 error if the request for tne evil programmer does not exist.

This means that the request would look like:
http://localhost:8080/rest-webapp/evil/programmers/6

would return a 404 error with the message: HTTP Status 404 - This evil programmer does not exist!!



pom.xml

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>2.5</version>
</dependency>


web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<!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>rest-webapp</display-name>
<description>Test Rest Service</description>
<servlet>
<servlet-name>mytest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mytest</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>1</session-timeout>
</session-config>
</web-app>


mytest-servlet.xml (Spring Application Context)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/evil/programmers/*">myController</prop>
</props>
</property>
</bean>

<bean id="myController" class="au.bandaid.programming.controller.MyTestController">
</bean>

</beans>



MyTestController.java

package au.bandaid.programming.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import au.bandaid.programming.exception.HttpStatusException;

public class MyTestController implements Controller {

private Hashtable list;

public MyTestController() {
list = new Hashtable();
// Set an arbitary list
list.put("1", "Dr Evil");
list.put("2", "Big Bad Billy");
list.put("3", "Big Bad Billy");
list.put("4", "Stevie Exception");
list.put("4", "Frank Void");
}

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

// Determine the type of method
try {
if (request.getMethod().toUpperCase().equals("GET")) {
writeResponse(response, "text/xml", HttpServletResponse.SC_OK, get(request));
}
//else if (method.toUpperCase().equals("POST"))
//else if (method.toUpperCase().equals("DELETE"))
//else if (method.toUpperCase().equals("PUT"))

else {
throw new HttpStatusException(HttpServletResponse.SC_NOT_IMPLEMENTED, "Operation: " + request.getMethod() + " not supported.");
}
}
catch (HttpStatusException e) {
response.sendError(e.getStatusCode(), e.getMessage());
}
catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
}

return null;
}

private String get(HttpServletRequest request) throws HttpStatusException {

// Grab some parameters
// I am not using any now
String type = request.getParameter("type");

// Look at the request
// am expecting rest-webapp/programmers/1
Pattern p = Pattern.compile("[1-9]+");
Matcher m = p.matcher(request.getPathInfo());
String index = "";
if (m.find()) {
index = m.group();
}

if (!list.containsKey(index)) {
throw new HttpStatusException(HttpServletResponse.SC_NOT_FOUND, "This evil programmer does not exist!!");
}

return "" + list.get(index) + "";
}

private void writeResponse(HttpServletResponse response, String contentType, int statusCode, String ouput) throws IOException {
response.setContentType(contentType);
response.setStatus(statusCode);
PrintWriter out = new PrintWriter(response.getOutputStream());
out.println(ouput);
out.close();
}

}

Some extra reading:
http://jira.springframework.org/browse/SPR-4419 - Comprehensive REST Support
http://springframework.org/docs/MVC-step-by-step/Spring-MVC-step-by-step.html