Posted on Jun 26, 2009

Simple webservices from within struts

When I started researching this, I thought it would be kind of a nightmare to integrate a web service into our current web architecture based on struts2. It turns out it’s really easy to build and maintain a solution that gives you the ability to use both REST and JSON types dynamically. I used the following pattern:

  • WSBaseAction – uses XStream to determine request type and serialize beans
  • WS*Action – action classes that extend WSBaseAction

WSBaseAction:

package com.example.actions;

import javax.servlet.http.HttpServletRequest;

import com.example.view.RequestView;
import com.example.util.RequestUtils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import org.apache.struts2.interceptor.ServletRequestAware;

public class WSBaseAction extends BaseAction implements ServletRequestAware {

private String alertMessage;
private Object payloadBean;
private String requestType;
private String alias;

public WSBaseAction(String alias) {
super(true, null, new RequestView());
this.setPublicCache(false);
this.alias = alias;
}

public String getPayload() {

if (getRequestType() == "json") {
return getJsonPayload();
}

return getXmlPayload();
}

private String getXmlPayload() {
XStream xstream = new XStream();
xstream.alias(alias, payloadBean.getClass());
return xstream.toXML(payloadBean);
}

private String getJsonPayload() {
XStream xstream = new XStream(new JsonHierarchicalStreamDriver());
xstream.alias(alias, payloadBean.getClass());
return xstream.toXML(payloadBean);
}

public Object getPayloadBean() {
return payloadBean;
}

public void setPayloadBean(Object payloadBean) {
this.payloadBean = payloadBean;
}

public String getAlertMessage() {
return alertMessage;
}

public void setAlertMessage(String alertMessage) {
this.alertMessage = alertMessage;
}

public String getRequestType() {
if (this.requestType == null) {
String url = RequestUtils.getRequestUrl(request);
if (url.endsWith(".json")) {
this.requestType = "json";
} else {
this.requestType = "rest";
}
}
return requestType;
}

public void setRequestType(String requestType) {
this.requestType = requestType;
}

public String getAlias() {
return alias;
}

public void setAlias(String alias) {
this.alias = alias;
}

private HttpServletRequest request;
public void setServletRequest(HttpServletRequest arg0) {
request = arg0;
}
}

WSExampleAction:

package com.example.actions;

import com.example.WebReaderConstants;
import com.example.services.DocumentMetadataService;
import com.example.view.RequestView;
import com.example.actions.data.*;

public class WSExampleAction extends WSBaseAction {

	private ExampleService exampleService;

	public WSExampleAction() {
		super("example_response");
	}

	@Override
    public String execute() throws Exception {

        RequestView view = (RequestView) this.getModel();

        exampleService.populateMetadata(view);
        ExampleBean bean = new ExampleBean();
        bean.setNumPages(view.getNumDocumentPages());
        bean.setTitle(view.getTitle());
        bean.setShortTitle(view.getShortTitle());
        setPayloadBean(bean);

        return getRequestType();
    }
}

struts.xml:

...

	<!-- Allows for rest and json webservice requests -->
	<constant name="struts.action.extension" value="action,xml,json" />
...
        <!-- Web Service Calls -->
	<action name="**WS*" class="com.example.actions.WS{2}Action">
	    <interceptor-ref name="webreaderMobileStack"/>
	    <result name="rest" type="freemarker">/templates/containers/restContainer.ftl</result>
	    <result name="json" type="freemarker">/templates/containers/jsonContainer.ftl</result>
	    <param name="alertMessage">${example.web.alert.message}</param>
	</action>
...

jsonContainer.ftl

${payload}

restContainer.ftl

<?xml version="1.0" encoding="UTF-8"?>
${payload}

So it works like this:

  1. Struts sees a request whose action starts with WS (for webservice) and redirects to the appropriate action
  2. The action gathers data from the service layer and populates our bean
  3. Struts fills out the template by calling getPayload
  4. The base action serializes the payload bean in XML or JSON depending on what the url extension was (WSExample.json or WSExample.xml)