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:
- Struts sees a request whose action starts with WS (for webservice) and redirects to the appropriate action
- The action gathers data from the service layer and populates our bean
- Struts fills out the template by calling getPayload
- The base action serializes the payload bean in XML or JSON depending on what the url extension was (WSExample.json or WSExample.xml)