Ant is a freelance Java architect and developer. He has been writing a blog and white papers since 2004 and writes about anything he finds interesting, related to Java or software. Most recently he has been working on enterprise systems involving Eclipse RCP, Google GWT, Hibernate, Spring and J2ME. He believes very strongly in being involved in all parts of the software life cycle. Ant is a DZone MVB and is not an employee of DZone and has posted 27 posts at DZone. You can read more from them at their website. View Full User Profile

JAX-WS Payload Validation, and Websphere 7 Problems

09.13.2010
| 12102 views |
  • submit to reddit

A WSDL file contains a reference to an XSD document which defines the data structures which can be sent to the service over SOAP. In an XSD, you can define a Type for an element, or things like the elements cardinality, whether its optional or required, etc.

When the web server hosting a web service is called, it receives a SOAP envelope which tells it which web service is being called. It could (and you might expect it does) validate the body of the SOAP message against the XSD in the WSDL... but it doesn't.

Is this bad? Well, most clients will be generated from the WSDL, so you can assume that the type safety is respected. Saying that, it's not something the server can guarantee, so it needs to check that say a field that is supposed to contain a date, really does contain a date, and not some garbled text that is meant to be a date. But more importantly, something which a client does not guarantee, is whether all required fields in the data structure are actually present. To check this, you can validate incoming SOAP bodies against the XSD.

The way to do this, is by using "Handlers". The JAX-WS specification defines two kinds, namely, SOAP Handlers and Logical Handlers. The SOAP kind is useful for accessing the raw SOAP envelope, for example to log the actual SOAP message. The logical kind is useful for accessing the payload as an XML document. To configure a handler, you add the HandlerChain annotation to your web service, passing it the name of the handler configuration file:

@WebService
@HandlerChain(file="handler.xml")
public class GislerService {

  private static final String FORMAT_DD_MMM_YYYY_HH_MM_SS = "dd. MMM yyyy HH:mm:ss";

  @WebMethod
  public String formatDate(ObjectModel om){
    SimpleDateFormat sdf = new SimpleDateFormat(FORMAT_DD_MMM_YYYY_HH_MM_SS);
    return "Formatted, its " + sdf.format(om.date);
  }
}


So, in order to validate a body against an XSD (i.e. to ensure that the "ObjectModel" instance in the above method is valid, before your method gets called by the WS framework), you use a logical handler to grab the payload as a javax.xml.transform.Source and pass it to a javax.xml.validation.Validator which you create using a javax.xml.validation.SchemaFactory and javax.xml.validation.Schema, based on the XSD from the WSDL. If the validation is successful, you let your handler return "true", otherwise you throw a javax.xml.ws.WebServiceException passing it the validation exception's text, so that any client making an invalid call can work out why it's invalid. It's something like this:

public class MyLogicalHandler implements LogicalHandler {

  private Validator validator;

  public MyLogicalHandler(){
    try{
      long start = System.nanoTime();					
      SchemaFactory schemaFac = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
      Schema schema = schemaFac.newSchema(new URL("http://localhost:8084/Gisler/GislerServiceService?xsd=1"));
      validator = schema.newValidator();
      System.out.println("created validator in " + ((System.nanoTime()-start)/1000000.0) + "ms");
    } catch (IOException e) {
      e.printStackTrace();
      throw new WebServiceException(e.getMessage());  //cant validate/initialise
    } catch (SAXException e) {
      e.printStackTrace();
      throw new WebServiceException(e.getMessage());  //cant validate/initialise
    }
  }
.
.
.
  public boolean handleMessage(LogicalMessageContext context) {

    if (((Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)).booleanValue()) {
      return true; // only validate incoming messages
    }

    try{
      LogicalMessage lm = context.getMessage();
      Source payload = lm.getPayload();
      validator.validate(payload);
      System.out.println("validated ok");
    }catch(SAXParseException e){
      System.out.println("validated failed: " + e.getMessage());
      throw new WebServiceException(e.getMessage());  //invalid, so tell the caller!
    } catch (SAXException e) {
      e.printStackTrace();
      throw new WebServiceException(e.getMessage());  //cant validate/initialise
    } catch (IOException e) {
      e.printStackTrace();
      throw new WebServiceException(e.getMessage());  //cant validate/initialise
    }
		
    return true;
  }
}
The attached Eclipse Projects were tested using GlassFish v3 (Glassfish Tools Bundle for Eclipse 1.2), and they worked well. To test required but missing elements, I wrote a JUnit test, which is in the client project. To test badly formed dates, I captured the SOAP HTTP Requests from the JUnit using the Eclipse TCP/IP Monitor, and modified them before resending them, with an invalid date string. Note that to make this work, you also need to modify the HTTP header's "content-length" using the monitor, otherwise you get some very strange errors, because the stream terminates early! An alternative is to use a tool like SoapUI.

Compared to Glassfish, Websphere didn't do so well - it has a nasty bit of functionality built in. Validating using a handler works fine, until your first invalid request comes in. Then, it still works, until another valid request is processed. After that, it completely ignores invalid elements like dates, if they are marked as optional (which is the default!). How come? Well, that took a while to work out, but basically, it's optimising the incoming message by arguing that if an element is optional, and happens to be present but invalid, the invalid data can be thrown away, because it's... well, optional... OK, I couldn't believe it either, but that's what it does, the SOAP handler that logs to System.out simply had the invalid element missing. So a little searching around on the internet, and a little luck, and hey presto this link which is a bug fix for Websphere 7 fix pack 9. By setting the system property "jaxws.payload.highFidelity=true", Websphere guarantees that the message passed to the handlers is exactly that which came over the wire. Tests showed that it indeed did fix the problem.

So what does all this mean, in the grand scheme of SOAP things? Well, when designing a service, you need to consider how important it is to have valid data. If you allow optional fields which are strongly typed, such as dates or complex types, then you could have a problem. Without adding a validating handler, it is possible that the caller could pass you optional but invalid data, and you wouldn't receive it in your web service, which is given a null reference, instead of invalid data! If you don't work with optional data, then you could skip validation, and just let null pointer exceptions fly around, in cases where a caller has passed invalid required data. By logging the incoming and outgoing communications, it makes it easier to debug such problems, but it is a little impolite to build a public interface which handles invalid requests like this.

The last consideration is performance. Creating a validator for a simple schema like in this demo only took around 20ms on my laptop. The actual validation took less than a millisecond for a valid request, and just over a millisecond for an invalid response. The reader is left to draw their own conclusions :-)

From http://blog.maxant.co.uk/pebble/2010/09/09/1284065640000.html

Published at DZone with permission of Ant Kutschera, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Marco Seine replied on Wed, 2012/12/05 - 5:29am

 Hi,

this is a very good article. I was just in the situation of writing a custom LogicalHandler for a JAX-WS Webservice on Websphere Appserver 7 fixpack 17.

But I have one problem: in the handleMessage method, it is only possible to throw an unchecked exception like WebserviceException, ProtocolException, RuntimeException etc.

But I need to throw an application level fault as exception (generated from WSDL) called ValidationException, which is a checked Exception. Do you have any idea how it would be possible to return this custom ValidationException (and this is made into a validation fault defined in the wsdl/xsd) by the jax-ws framework?

I also tried not to use a handler, but to do the xml validation in the java class that implements the Portbinding (having the @Webservice annotation). But there I only have access to the injected WebserviceContext. From there I can access the SOAPMessage, also the SOAPBody, but it is not possible to get an javax.xml.transform.Source Object in order to give it to the schema validator, so I am stuck here...

Any help would be appreciated

UPDATE: already found the solution. It is possible within the LogicalHandler.handleMessage() to set a scoped property to the messagecontext (has to be "application" scope). If the validation inside this method fails, I don't throw an exception, but set the property to the LogicalMessageContext:

context.put("validationfault", e.getMessage());
context.setScope("validationfault", MessageContext.Scope.APPLICATION);

The handleMessage() method then just return "true", so that the JAX-WS Framework will call the @Webservice Portbinding class. In this webservice, I can now retrieve the MessageContext from the injected WebserviceContext, and then get the "validationfault" property's content (e.g. the validation message).

When I throw my custom ValidationException from within the Webservice class now, the JAX-WS Framework turns this into a custom SoapFault, containing not only the standard Soapfault-Details, but my custom ValidationFaultInfo, which is part of my WSDL.

So just in case anyone is interested in that solution ... :-)


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.