My Garbage Collector

Hamdi Makni's Blog

One of the most important phases in the web service request execution process is the SOAP document validation. In fact, like forms in web application, it is recommended to validate received information from client before proceeding to the real action.
One year ago, I had a consulting mission with a firm specialised in booking train ticket services.
The project consisted of exposing some services to a WAP application. We chose Xfire framework as a web service stack, because the competitive intelligence team advise it as the best framework in java web services implementation word (they says).
I already know that we can make web service payload validation in SpringWS with an interceptor. The interceptor called PayloadValidator, is wired in a web service endpoint, and validates automatically the payload document, based on a schema definition document, and using an xml framework with validation function like Jaxb.
I looked into Xfire implementation, but I didn't find a satisfactory solution. So I decided to pick up the Spring-Ws Interceptor idea and code, and adapt it to work as an Xfire handler. I just made some changes on the spring code, especially to read the document and write fault in the response.

Like the Spring-WS Interceptor, I have an abstract super class named AbstractPayloadValidator, with an invoke abstract method, implemented by the subclasses. This method is the main handler method.


import java.util.Locale;
import javax.xml.namespace.QName;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Document;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.xml.namespace.QNameUtils;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;

public abstract class AbstractPayloadValidator extends AbstractHandler {

public static final QName DEFAULT_DETAIL_ELEMENT_NAME = QNameUtils
.createQName("http://springframework.org/spring-ws",
"ValidationError", "spring-ws");
public static final String DEFAULT_FAULTSTRING_OR_REASON = "Validation error";
private boolean addValidationErrorDetail = true;
private QName detailElementName = DEFAULT_DETAIL_ELEMENT_NAME;
private String faultStringOrReason = DEFAULT_FAULTSTRING_OR_REASON;
private Locale faultStringOrReasonLocale = Locale.ENGLISH;
private String schemaLanguage = XmlValidatorFactory.SCHEMA_W3C_XML;
private Resource[] schemas;
protected XmlValidator validator;
protected Document doc;
public AbstractPayloadValidator() throws Exception {
super();
System.setProperty(
"javax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema",
"org.apache.xerces.jaxp.validation.XMLSchemaFactory");
}

public void afterPropertiesSet() throws Exception {
Assert.notEmpty(schemas,"setting either the schema or schemas property is required");
Assert.hasLength(schemaLanguage, "schemaLanguage is required");
for (int i = 0; i < schemas.length; i++) {
Assert.isTrue(schemas[i].exists(), "schema [" + schemas[i] + "] does not exist");
}
if (log.isDevLevelEnabled()) {
log.debug("Validating using " + StringUtils.arrayToCommaDelimitedString(schemas));
}
validator = XmlValidatorFactory.createValidator(schemas, schemaLanguage);
}
public abstract void invoke(MessageContext messageContext) throws Exception;
}

RequestPayloadValidator is the request validator implementation of my abstract class. Normally, I must implement another class to validate the response.
In the invoke method, It will call the handelRequest method to do the validation. In the validation fails, a SOAP fault is sent to the client with the detailed message of the validation rules.

import java.io.IOException;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.exchange.InMessage;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.handler.Phase;
import org.codehaus.xfire.soap.handler.ReadHeadersHandler;
import org.codehaus.xfire.util.jdom.StaxBuilder;
import org.codehaus.xfire.util.stax.JDOMStreamReader;
import org.jdom.Element;
import org.jdom.transform.JDOMSource;
import org.springframework.util.ObjectUtils;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import ….PayloadValidationException;

public class RequestPayloadValidator extends AbstractPayloadValidator {

public RequestPayloadValidator() throws Exception {
super();
setPhase(Phase.PARSE);
before(ReadHeadersHandler.class.getName());

}

protected Source getValidationRequestSource(MessageContext request)
throws XMLStreamException {
InMessage message = request.getInMessage();
XMLStreamReader streamReader = message.getXMLStreamReader();
StaxBuilder builder = new StaxBuilder();

doc = builder.build(streamReader);
Element body = ((Element) doc.getRootElement().getChildren().get(0));
if(!body.getName().equals("Body")) body = (Element) doc.getRootElement().getChildren().get(1);
Element payload = (Element) body.getChildren().get(0);
return new JDOMSource(payload);
}
protected boolean handleRequestValidationErrors(
MessageContext messageContext, SAXParseException[] errors)
throws TransformerException, XFireFault, PayloadValidationException {
String faultMessage = "";
for (int i = 0; i < errors.length; i++) {
faultMessage += errors[i].getMessage();
log.debug("XML validation error on request: " + errors[i].getMessage());
}

messageContext.getExchange().getFaultMessage().setBody(new XFireFault(faultMessage, new PayloadValidationException(),XFireFault.SENDER));
return false;

}

public boolean handleRequest(MessageContext messageContext)
throws IOException, SAXException, TransformerException,
XMLStreamException, XFireFault, PayloadValidationException {
Source requestSource = getValidationRequestSource(messageContext);
if (requestSource != null) {
SAXParseException[] errors = validator.validate(requestSource);
if (!ObjectUtils.isEmpty(errors)) {
return handleRequestValidationErrors(messageContext, errors);
} else if (log.isDevLevelEnabled()) {
log.debug("Request message validated");
}
}
return true;
}

public void invoke(MessageContext messageContext) throws Exception {
afterPropertiesSet();
if (handleRequest(messageContext))
log.debug("Validation of requested payload finished by SUCCESS");
else
log.debug("Validation of requested payload finished by FAULT");
messageContext.getInMessage().setXMLStreamReader(
new JDOMStreamReader(doc));

}
}

I used this handler named ErrorValidationHandler to throw the fault and log it.

import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.handler.AbstractHandler;
import org.codehaus.xfire.handler.Phase;
import org.codehaus.xfire.soap.handler.SoapBodyHandler;

public class ErrorValidationHandler extends AbstractHandler {

protected final Log log = new Log(getClass());

public ErrorValidationHandler() {
super();
setPhase(Phase.DISPATCH);
after(SoapBodyHandler.class.getName());
}

public void invoke(MessageContext messageContext) throws Exception {
XFireFault fault = (XFireFault) messageContext.getExchange()
.getFaultMessage().getBody();
if (fault != null){
log.debug("fault received from the payload validator handler.");
throw fault;
}
}
}

In my spring applicationContext file, I wired my handlers in the service exporter, as an inHandlers.






com…...MYWs


myWs.wsdl









I set the schema file location in the validator bean

class="com…...validation.RequestPayloadValidator">




class="com…...validation.ErrorValidationHandler">

Like the other web service stacks, Xfire make a first level validation of received request document, but with this handler we can validate any type of rules with a rich xsd file.
An amelioration of this solution is to get dynamically the shema definition part from the WSDL file.