Grape Hyacinth in Snow

Handling POX Errors in Spring WS – Part 1

A few months ago I used Spring Web Services to build a POX (Plain Old XML, not SOAP) web service for work.   It was a good experience, and Spring-WS made creating a POX service pretty easy.  The sample application provided with the Spring-WS dependencies download has a POX application that’s a good seed for starting a project.

Following the example of the sample application worked great for happy path testing.  What wasn’t so straightforward was handling errors.   You can use an  ExceptionResolver to handle problems your code encounters when processing a request.   However, it wasn’t obvious from the example how you could return your own error messages when the client sends bad XML.  Here’s an approach for elegant handling of  those situations.

These examples use the POX Contacts sample from Spring WS 1.5.9.

CountContacts Service

The POX CountContacts sample contains a simple service, that when passed an XML document containing a list of contacts, returns the number of contacts in that list.  A contact is just made up of a person’s name and phone number.   Here’s a sample request, pulled straight from the Spring WS example.

<?xml version="1.0" encoding="UTF-8"?>
<Contacts xmlns="http://www.springframework.org/spring-ws/samples/pox">
  <Contact>
    <Name>John Doe</Name>
    <Phone>626-555-3456</Phone>
  </Contact>
  <Contact>
    <Name>Jane Doe</Name>
    <Phone>626-555-6543</Phone>
  </Contact>
</Contacts>

And the response from the service tells you there were two contacts:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ContactCount xmlns="http://www.springframework.org/spring-ws/samples/pox">2</ContactCount>

Pretty cool.   But if clients start sending XML with errors, things begin to fall apart.

A Lenient XSD

Let’s first start with an XSD validation error.    Here’s the XSD provided in the Spring-WS POX example.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.springframework.org/spring-ws/samples/pox">
  <xsd:element name="Contacts">
    <xsd:annotation>
      <xsd:documentation> Defines a contact-list, with names and phone numbers.  </xsd:documentation>
    </xsd:annotation>
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Contact" minOccurs="0" maxOccurs="unbounded">
          <xsd:complexType>
            <xsd:all>
              <xsd:element name="Name" type="xsd:string"/>
              <xsd:element name="Phone" type="xsd:string"/>
            </xsd:all>
          </xsd:complexType>
        </xsd:element>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="ContactCount" type="xsd:integer"/>
</xsd:schema>

This XSD is very lenient.   It tells us that a ContactList can have zero or more Contact elements, and that each Contact can optionally contain Name and Phone strings.   It follows Postel’s robustness principle – “be conservative in what you send, liberal in what you accept” – and that’s probably OK for such a simple service.   But for something more complex, you might consider Mark Rose’s rebuttal to the robustness principle, and impose more restrictions.

The ContactStore Service and a Strict XSD

As an example, I’ll add a new Contact storage service that you could use to store names and phone numbers.   Since a call to store a contact isn’t much good without a name and a phone number, my XSD is going to be more strict:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.springframework.org/spring-ws/samples/pox">
  <xsd:element name="Contacts">
    <xsd:annotation>
      <xsd:documentation> Updates the contact list, by requiring a name an a phone number.  </xsd:documentation>
    </xsd:annotation>
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Contact" minOccurs="1" maxOccurs="unbounded">
          <xsd:complexType>
            <xsd:sequence>
              <xsd:element name="Name" type="xsd:string" minOccurs="1" maxOccurs="1"/>
              <xsd:element name="Phone" type="xsd:string" minOccurs="1" maxOccurs="1"/>
            </xsd:sequence>
          </xsd:complexType>
        </xsd:element>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="ContactCount" type="xsd:integer"/>
</xsd:schema>

In this  XSD, at least one Contact is required, and each Contact is required to have one and only one Name and one and only one Phone.

Here’s the Spring-WS endpoint for a ContactStore.  It’s basically a copy of the ContactCount service, which stores the contacts (OK, there’s just a comment about actually storing the contacts – you write it if you want it so bad), and returns the count of the contacts it stored.

package org.springframework.ws.samples.pox.ws;

import ...

public class ContactStoreEndpoint extends AbstractSaxPayloadEndpoint {

    private static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/pox";

    private static final String CONTACT_NAME = "Contact";

    private static final String CONTACT_COUNT_NAME = "ContactCount";

    private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

    public ContactStoreEndpoint() {
        documentBuilderFactory.setNamespaceAware(true);
    }

    protected ContentHandler createContentHandler() throws Exception {
        return new ContactStoreResults();
    }

    protected Source getResponse(ContentHandler contentHandler) throws Exception {
        ContactStoreResults results = (ContactStoreResults) contentHandler;

        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document response = documentBuilder.newDocument();
        Element contactStoreElement = response.createElementNS(NAMESPACE_URI, CONTACT_COUNT_NAME);
        response.appendChild(contactStoreElement);
        contactStoreElement.setTextContent(Integer.toString(results.contactCount));

        return new DOMSource(response);
    }

    private static class ContactStoreResults extends DefaultHandler {

        private int contactCount = 0;

        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
            if (NAMESPACE_URI.equals(uri) && CONTACT_NAME.equals(localName)) {
                contactCount++;

                //store contact somewhere...
            }
        }
    }
}

We’ll also need to update the Spring context with our new endpoint by adding this line to spring-ws-servlet.xml

<bean id="contactStoreEndpoint" class="org.springframework.ws.samples.pox.ws.ContactStoreEndpoint"/>

And we’ll also change the default endpoint to the ContactStore.

<property name="defaultEndpoint" ref="contacStoreEndpoint"/>

Handling XSD Validation Errors

Let’s pretend we have a new customer signed on for our contact service, and their client code has a bug in it that so that the Phone element isn’t added to a contact.   So their request just has the Name in it, like this.

<?xml version="1.0" encoding="UTF-8"?>
<Contacts xmlns="http://www.springframework.org/spring-ws/samples/pox">
  <Contact>
    <Name>John Doe</Name>
  </Contact>
</Contacts>

How does the sample POX application’s PayloadValidatingInterceptor handle this bad request with the more strict XSD? Well, when I submit this with the Firefox Poster extension, I get this.

Status: 200 OK
<?xml version="1.0" encoding="UTF-8" standalone="no"?>

Not good.   The service just told the client everything was OK with a HTTP 200 code, but there was really an error in the request.  We have to look in the logs  to see what the problem was.

May 14, 2011 12:39:19 PM org.springframework.ws.soap.server.endpoint.interceptor.AbstractFaultCreatingValidatingInterceptor handleRequestValidationErrors
WARNING: XML validation error on request: cvc-complex-type.2.4.b: The content of element 'Contact' is not complete. One of '{"http://www.springframework.org/spring-ws/samples/pox":Phone}' is expected.

The Phone element is missing, of course.   But I don’t want the developers of the new client to contact me and ask me to look at the log file each time they send a request with formatting error.   I’d rather have the server tell the client what the error was so I can spend my time writing code, not looking at log files.

Communicating Errors to the Client

But if we want to communicate errors back to our client, we need to make a spot for error messages in the XSD so we can send back errors that validate against the XSD.   I’ll add an error element:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
            targetNamespace="http://www.springframework.org/spring-ws/samples/pox">
    <xsd:element name="Contacts">
        <xsd:annotation>
            <xsd:documentation>
                Updates the contact list, by requiring a name an a phone number.
            </xsd:documentation>
        </xsd:annotation>
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="Contact" minOccurs="1" maxOccurs="unbounded">
                    <xsd:complexType>
                        <xsd:sequence>
                            <xsd:element name="Name" type="xsd:string" minOccurs="1" maxOccurs="1"/>
                            <xsd:element name="Phone" type="xsd:string" minOccurs="1" maxOccurs="1"/>
                        </xsd:sequence>
                    </xsd:complexType>
                </xsd:element>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="ContactCount" type="xsd:integer"/>
    <xsd:element name="error" type="xsd:string"/>
</xsd:schema>

If you want to customize how Spring-WS handle XSD errors, you need to extend the Spring-WS AbstractValidatingInterceptor class and set that as the validatingInterceptor bean in the Spring context.  A ValidatingInterceptor provides hooks for handling validation errors that occur during a POX request or response.   Here’s an example that works with the ContactStore example:

package org.springframework.ws.samples.pox.ws;

import ...

/**
 * Interceptor that can return an error for XML that
 * doesn't validate.  Note that this only handles messages that fail XSD
 * validation. Corrupt XML is handled by the PoxMessageDispatcher servlet
 *
 * @author lreeder
 */
public class ContactStoreValidatingInterceptor extends AbstractValidatingInterceptor
{

    private static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/pox";

   @Override
   protected Source getValidationRequestSource(WebServiceMessage webServiceMessage)
   {
      return webServiceMessage.getPayloadSource();
   }

   @Override
   protected Source getValidationResponseSource(WebServiceMessage webServiceMessage)
   {
      return webServiceMessage.getPayloadSource();
   }

   @Override
   protected boolean handleRequestValidationErrors(MessageContext messageContext,
                                                SAXParseException[] errors)
                                         throws TransformerException
   {
      return handleResponseValidationErrors(messageContext, errors);
   }

   @Override
   protected boolean handleResponseValidationErrors(MessageContext messageContext,
      SAXParseException[] errors)
   {
      //Build up a string showing all the SAX validation errors
      StringBuilder errorBuilder = new StringBuilder();
      for (SAXParseException error : errors)
      {
         if(errorBuilder.length() > 0)
         {
            errorBuilder.append(" ");
         }
         errorBuilder.append(error.getMessage());
      }

       WebServiceMessage errorMessage = createErrorMessage(errorBuilder.toString());
       messageContext.setResponse(errorMessage);

      return false;
   }

    public static WebServiceMessage createErrorMessage(String error)
    {

        final String ENCODING = "UTF-8";

        //TODO - escape XML characters in your error string
        String errorXml =  "<?xml version=\"1.0\" encoding=\"" + ENCODING + "\"?>\n"  +
                "<error xmlns=\"" + NAMESPACE_URI + "\">\n" +
                error +
                "</error>\n";

       ByteArrayInputStream bs = null;
       try
       {
          //set string encoding to match XML encoding
          bs = new ByteArrayInputStream(errorXml.getBytes(ENCODING));
       }
       catch (UnsupportedEncodingException e)
       {
          //Should never happen because we've set the encoding to UTF-8
          // which is supported Just in case, we wrap in a RuntimeException
          // and rethrow
          throw new RuntimeException("Error converting converting string to ByteArrayInputStream, encoding "
                                     + ENCODING + " is not supported.", e);
       }

       WebServiceMessage webServiceMessage = null;

      DomPoxMessageFactory messageFactory = new DomPoxMessageFactory();
       try
       {
          webServiceMessage = messageFactory.createWebServiceMessage(bs);
       }
       catch (IOException ioe)
       {
          //Should never happen because the ByteArrayInputStream is memory-only
          //Just in case, we wrap in a RuntimeException and rethrow
          throw new RuntimeException("Error creating encoding error message", ioe);
       }

       return webServiceMessage;
    }

}

Note that I’m not replacing potential XML characters (<,&,>) in the error string.  You should provide code to do that in a production environment.  Now update the Spring context and replace the PayloadValidatingInterceptor in spring-ws-servlet.xml with the ContactStoreValidatingInterceptor:

<bean id="validatingInterceptor"
 class="org.springframework.ws.samples.pox.ws.ContactStoreValidatingInterceptor">
 <description>
 This interceptor validates both incoming and outgoing message contents
 according to the 'contacts.xsd' XML Schema file.
 </description>
 <property name="schema" value="/WEB-INF/contacts.xsd"/>
 <property name="validateRequest" value="true"/>
 <property name="validateResponse" value="true"/>
 </bean>

Now what happens when we send our bad store request without the Phone element? We get this back:

Status: 200 OK

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  <error xmlns="http://www.springframework.org/spring-ws/samples/pox">
cvc-complex-type.2.4.b: The content of element 'Contact' is not complete.
One of '{"http://www.springframework.org/spring-ws/samples/pox":Phone}' is expected.
</error>

Much better!   We’ve communicated to the client that there was an error with their request XML, and a phone number was missing.     This approach will handle your XSD validation errors, but for the message to even be checked for correctness, it must be valid XML.   In the next post, I’ll show you how to communicate errors back to a client who is not even sending good XML.

2 thoughts on “Handling POX Errors in Spring WS – Part 1

  1. Pingback: Handling POX Errors in Spring-WS Part 2 | Developer's Garden

  2. alexis.c.oliveros

    Hi,

    Can you provide the complete spring-ws-servlet.xml?

    I am encountering the following error and not sure what I’ve missed out:

    java.lang.IllegalArgumentException: AbstractSoapFaultDefinitionExceptionResolver requires a SoapMessage Object of class [org.springframework.ws.pox.dom.DomPoxMessage] must be an instance of interface org.springframework.ws.soap.SoapMessage
    at org.springframework.util.Assert.isInstanceOf(Assert.java:337)
    at org.springframework.ws.soap.server.endpoint.AbstractSoapFaultDefinitionExceptionResolver.resolveExceptionInternal(AbstractSoapFaultDefinitionExceptionResolver.java:61)
    at org.springframework.ws.server.endpoint.AbstractEndpointExceptionResolver.resolveException(AbstractEndpointExceptionResolver.java:106)
    at org.springframework.ws.server.MessageDispatcher.processEndpointException(MessageDispatcher.java:331)
    at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:247)
    at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:173)
    at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:88)
    at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:59)
    at org.springframework.ws.transport.http.MessageDispatcherServlet.doService(MessageDispatcherServlet.java:292)
    at com.integrosys.sml.webservice.transport.http.PoxMessageDispatcherServlet.doService(PoxMessageDispatcherServlet.java:32)

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *