Marty Andrews

artful code

Tuesday, December 14, 2004

Web Services from a Spring Enabled Web-App

I spent some time recently working on an web-application using spring and webwork. (<tangent>this combination created the core of one of the cleanest architectures I've worked with</tangent>). Someone asked how easy it might be to expose some functionality as a web service, so I thought I'd give it a go.

Grabbing a copy of Apache Axis was my first step. It's documentation guided me most of the way, but the only missing bit was how to get at a spring-created bean from an axis-aware class. Here's what I did.

After putting the required libraries (axis-ant.jar, axis.jar, commons-discovery.jar, commons-loggin.jar, jaxrpc.jar, wsdl4j.jar, saaj.jar) on the classpath, I had to declare all of the appropriate axis stuff in my web.xml file.

<web-app><listener><listener-class>org.apache.axis.transport.http.AxisHTTPSessionListener</listener-class></listener><servlet><servlet-name>axis</servlet-name><servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class></servlet><servlet-mapping><servlet-name>axis</servlet-name><url-pattern>/services/*</url-pattern></servlet-mapping><mime-mapping><extension>wsdl</extension><mime-type>text/xml</mime-type></mime-mapping></web-app>

The next step was to create a class which would be exposed as a web service by Axis:

public class TodoItemWebService {public String getTodoItemDescription(String title) {return "dummy description";}}

To get the service to run, I also had to create a file named server-config.wsdd in the WEB-INF directory of my web application. I figured out what this looked like by going through the Axis tutorial which dynamically registers a service in your app. Statically creating the file works fine though too. Here's what mine looked like:

<?xml version="1.0" encoding="UTF-8"?><deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"><handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/><handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/><handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/><service name="items" provider="java:RPC" style="wrapped" use="literal"><operation name="getTodoItemDescription" qname="ns1:getTodoList" returnQName="ns1:getTodoItemDescriptionResult"returnType="xsd:string" soapAction="" xmlns:ns1="http://wrytradesman.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><parameter qname="ns1:TodoItemTitle" type="xsd:string"/></operation><parameter name="allowedMethods" value="*"/><parameter name="className" value="com.wrytradesman.todolist.web.service.TodoItemWebService"/><parameter name="wsdlTargetNamespace" value="http://wrytradesman.com/"/></service><transport name="http"><requestFlow><handler type="URLMapper"/><handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/></requestFlow><parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler"/><parameter name="qs:wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/><parameter name="qs.list" value="org.apache.axis.transport.http.QSListHandler"/><parameter name="qs.method" value="org.apache.axis.transport.http.QSMethodHandler"/><parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler"/><parameter name="qs.wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/></transport><transport name="local"><responseFlow><handler type="LocalResponder"/></responseFlow></transport></deployment>

The service definition in bold is what I had to write to get the service running. All of the surrounding info is setup stuff that is just needed to configure axis. At this stage of the game, I could deploy my web application and hit a url http://localhost//services/items?wsdl to get the WSDL definition for my service. In the space of about 5 minutes, a cow-orker had used that WSDL to create a .NET based client which called the service and got my hard-coded response.

With my web service now up and running, the only remaining problem was to create a "real" implementation that used one of my spring managed objects instead of a hard-coded response. The trick here is to get a reference to the application context first. Everything from that point is easy. The simplest path to that is get a reference to the servlet from the Axis message context, then use the servlet context to get the application context from spring. Then just call your spring bean as per normal. Here's what my final code looks like:

public class TodoItemWebService {public String getTodoItemDescription(String title) {HttpServlet servlet = (HttpServlet) MessageContext.getCurrentContext().getProperty(HTTPConstants.MC_HTTP_SERVLET);ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servlet.getServletContext());Map beans = context.getBeansOfType(TodoListRepository.class, false, false);Iterator iterator = beans.values().iterator();TodoListRepository repository = (TodoListRepository) iterator.next();TodoItem item = repository.getTodoList().getItem(title);return item.getDescription();}}

Thats it. Pretty straight forward really. There's a few corner cases to be covered off, but vertical slice is complete. The whole exercise took less than a days effort, which included learning about how Axis hangs together.