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:
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.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:
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 :-)
© 2010 Ant Kutschera