Monday, November 12, 2007

Simple web services using a Groovy MultiActionController in Spring

There have been many times I have wanted to create a simple XML API on top of my Spring applications. One technique I find myself using a lot is to create a scripted MultiActionController that uses the Groovy MarkupBuilder to render XML. (requires Spring 2.0 and above)

So as an example, Lets assume we have a Spring application created that is for an online store. (Download link for source code is below) We want to provide an xml representation of the products we offer from the following url:

http://localhost:8080/store/api/products

This will provide an xml document that looks like the following:
<products>
<product id="1" name="Product 1" price="2.5"/>
<product id="2" name="Product 2" price="3.5"/>
<product id="3" name="Product 3" price="4.5"/>
</products>

So the first step to creating our xml api is to setup a dispatcher servlet. We will add the following servlet definition and mapping to the web.xml.
<servlet>
<servlet-name>api</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>api</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>

The next step is to create the dispatchers bean definition xml. This will be a file named api-servlet.xml that will be located in the WEB-INF folder of your application. The following are the contents of the api-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:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">

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

<lang:groovy id="apiController" script-source="/WEB-INF/groovy/APIController.groovy">
<lang:property name="productService" ref="productService" />
</lang:groovy>

</beans>

Next step is to create the scripted controller. This will be called APIController.groovy and located in the WEB-INF/groovy folder.


import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.springframework.web.servlet.mvc.multiaction.MultiActionController
import groovy.xml.MarkupBuilder

class APIController extends MultiActionController {

def productService

void products(HttpServletRequest req, HttpServletResponse res) {
def products = productService.findAllProducts()
res.contentType = "text/xml"
def markupBuilder = new MarkupBuilder(res.writer)
markupBuilder.products {
for (p in products) {
product(id:p.id, name:p.name, price:p.price)
}
}
}

}



Thats it.. We can now deploy and access our web service. Additional methods could now be added to the APIController specified above to expose other data elements from the online store. One proposed method might be a product method that loads a particular product by id.

I have included a link to all the source code in a completed deployable web app in a maven project. It also takes advantage of the maven cargo plugin to support running of the application from within a container. All you have to do is have maven2 installed and run "mvn clean package cargo:start". Be patient it might take a while to fetch Tomcat the first time it runs.

Sample Project Structure:
.
|-- pom.xml
`-- src
`-- main
|-- java
| `-- com
| `-- acme
| `-- store
| |-- domain
| | `-- Product.java
| `-- service
| |-- DefaultProductService.java
| `-- ProductService.java
`-- webapp
|-- WEB-INF
| |-- api-servlet.xml
| |-- applicationContext.xml
| |-- groovy
| | `-- APIController.groovy
| `-- web.xml
`-- index.jsp


Dowload Sample Project Source Code