Working with SOAP Faults



Working with SOAP Faults

As you know from Chapter 4, faults are a special kind of SOAP message that contains error information. SOAP faults are always delivered from the receiver back to the sender. In SAAJ, SOAP fault messages are constructed in basically the same way as a plain SOAP message, except the SOAPBody object contains a SOAPFault instead of a SOAPBodyElement. SOAPFault is actually a subtype of SOAP BodyElement that specializes the behavior to support the structure of a SOAP Fault element. Several interfaces play a role in creating SOAP fault messages with SAAJ. Figure shows these interfaces in the context of all the other SAAJ types.

Inheritance Diagram of SAAJ SOAP Fault Types (Fault types are in gray.)

graphics/13fig04.gif

13.4.1 The SOAPFault Type

Every instance of the SOAPFault type is contained by a SOAPBody element. It describes an error generated by the receiver while processing a SOAP message.

The SOAPFault interface (Listing 13-22) defines several methods for setting and getting the faultactor, faultcode, and faultstring (description) elements of the Fault element, as well as creating and accessing detail elements.

Listing 13-22 The javax.xml.soap.SOAPFault Interface
package javax.xml.soap;
import java.util.Locale;

public interface SOAPFault extends SOAPBodyElement {

    public Detail addDetail() throws SOAPException;
    public Detail getDetail();
    public String getFaultActor();
    public String getFaultCode();
    public Name getFaultCodeAsName();
    public String getFaultString();
    public Locale getFaultStringLocale();
    public void setFaultActor(String faultActor) throws SOAPException;
    public void setFaultCode(String faultCode) throws SOAPException;
    public void setFaultCode(Name faultCode) throws SOAPException;
    public void setFaultString(String faultString) throws SOAPException;
    public void setFaultString(String faultString, Locale local)
     throws SOAPException;

}

As an example, imagine that the ultimate receiver of the BookQuote SOAP message determines that the ISBN number declared in the Body of an incoming message is invalid. The receiver will generate a SOAP Fault message and deliver it to the sender immediately before it in the message path. The Fault message might look like the one in Listing 13-23.

-23 A SOAP Fault Message
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:Client</faultcode>
      <faultstring>The ISBN contains an invalid character(s)</faultstring>
      <faultactor>
        http://www.Monson-Haefel.omc/BookQuote_WebService
      </faultactor>
      <detail>
        <mh:InvalidIsbnFaultDetail
               xmlns:mh="http://www.Monson-Haefel.com/jwsbook/BookQuote">
          <offending-value>19318224-D</offending-value>
          <conformance-rules>
               The first nine characters must be digits. The last
               character may be a digit or the letter 'X'. Case is not
               important.
          </conformance-rules>
        </mh:InvalidIsbnFaultDetail>
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

This fault message can be constructed fairly easily using SAAJ, as shown in Listing 13-24, SaajExample_7.

-24 Building a Fault Message with SAAJ
package com.jwsbook.saaj;
import javax.xml.soap.*;

public class SaajExample_7 {
  public static void main(String [] args) throws SOAPException {
    // Create SOAPMessage
    MessageFactory msgFactory = MessageFactory.newInstance();
    SOAPMessage message = msgFactory.createMessage();
    message.getSOAPHeader().detachNode();

    // Create Fault message
    SOAPBody body = message.getSOAPBody();

    SOAPFault fault = body.addFault();
    fault.setFaultCode("soap:Client");
    fault.setFaultString("The ISBN contains an invalid character(s)");
    fault.setFaultActor("http://www.Monson-Haefel.org/BookQuote_WebService");

    Detail detail = fault.addDetail();
    SOAPFactory soapFactory = SOAPFactory.newInstance();
    Name errorName = soapFactory.createName(
                     "InvalidIsbnFaultDetail","mh",
                     "http://www.Monson-Haefel.com/jwsbook/BookQuote");

    DetailEntry detailEntry = detail.addDetailEntry(errorName);
    SOAPElement offendingValue =
      detailEntry.addChildElement("offending-value");
    offendingValue.addTextNode("19318224-D");
    SOAPElement conformanceRules =
      detailEntry.addChildElement("conformance-rules");
    conformanceRules.addTextNode(
       "The first nine characters must be digits.  The last character "+
       "may be a digit or the letter 'X'. Case is not important.");

    SaajOutputter.writeToScreen(message);
  }
}

Version 1.2 of SAAJ modified the SOAPFault interface and added a couple of methods that are QName-oriented. For example, when you call the setFault Code() method, you can pass it a Name parameter that represents a proper QName, instead of a String. The following snippet illustrates.

SOAPFault fault = body.addFault();
SOAPFactory soapFactory = SOAPFactory.newInstance();

Name faultCode = soapFactory.createName("Client","soap",
                             SOAPConstants.URI_NS_SOAP_ENVELOPE);
fault.setFaultCode(faultCode);
fault.setFaultString("The ISBN contains an invalid character(s)");

You can also access fault codes as either String values or Name objects. The getFaultCodeAsName() method returns the fault code as a SAAJ Name object, rather than a String value. This book tends to use the String methods because they are easier to read in example code, but use the Name object methods if you wish.

To support international applications, the getFaultString() and set FaultString() methods are complemented by java.util.Locale style methods, namely getFaultStringLocale() and an overloading of setFaultString() that expects both a String and a Locale. These methods access or assign an xml:lang attribute to the faultstring value. If you do not use a Locale method, the default locale is used. A java.util.Locale represents a geographic, cultural, or political region (for example, the French-speaking area of Canada, or Simplified Chinese).

The legal values for the xml:lang attribute are specified in the IETF standard RFC 1766 and its successors (currently RFC 3066), which are based in part on ISO 639:1988. Language values begin with a primary two-character language identifier, optionally followed by a series of hyphen-delimited sub-ids for country or dialect identification; the ids are not case-sensitive. Examples include "en-us" for United States English and "fr-ca" for Canadian French. You don't have to use the sub-ids if the nature of the application doesn't require you to; "en" or "fr" by itself is fine.

The simplest way to add a SOAPFault to a message is to use the overloaded addFault() methods, which allow you to initialize the fault code, fault string, and language (Locale) of the fault string when it's created. For example, the following snippet shows how to create a complete SOAPFault object in one operation.

SOAPFactory soapFactory = SOAPFactory.newInstance();

Name faultCode = soapFactory.createName("Client","soap",
                             SOAPConstants.URI_NS_SOAP_ENVELOPE);
SOAPFault fault = body.addFault(faultCode,
                                "The ISBN contains an invalid character(s)",
                                Locale.US);

If you're using the default Locale, you can just invoke the addFault(Name,String) overloading of the method instead of passing the default Locale object explicitly.

13.4.2 The Detail Type

A SOAPFault object contains an object of type Detail, which in turn contains one or more DetailEntry objects. As shown in Listing 13-25, the Detail type defines two methods: one for adding new DetailEntry objects and one for accessing existing ones.

Listing 13-25 The javax.xml.soap.Detail Interface
package javax.xml.soap;
import java.util.Iterator;

public interface Detail extends SOAPFaultElement {
    public DetailEntry addDetailEntry(Name name) throws SOAPException;
    public Iterator getDetailEntries()
}

The Detail object was used in SaajExample_7, as shown in the following snippet from Listing 13-24.

Detail detail = fault.addDetail();
Name errorName = envelope.createName("InvalidIsbnFaultDetail","mh",
                          "http://www.Monson-Haefel.com/jwsbook/BookQuote");

DetailEntry detailEntry = detail.addDetailEntry(errorName);

13.4.3 The SOAPFaultElement Type

SOAPFaultElement is the supertype of SOAPDetail type (see Figure). It defines no methods or fields; it's an empty interface. It provides the same kind of typing benefit that SOAPBodyElement does: some assurance of backward-compatibility if the SOAP protocol changes, and some type safety. Its definition appears in Listing 13-26.

Listing 13-26 The javax.xml.soap.SOAPFaultElement Interface
package javax.xml.soap;
public interface SOAPFaultElement extends SOAPElement {}

SOAPFaultElement is not usually used directly in your code, but it has utility because it extends SOAPElement, and thus inherits all the methods of that interface.

13.4.4 The DetailEntry Type

DetailEntry, defined in Listing 13-27, is another empty interface. It has no methods or fields, and is useful only for type safety (the Detail object may contain DetailEntry objects only). Its supertype, SOAPElement, defines all the methods you need to work with detail entries. It also offers some flexibility for the future, if new versions of SOAP add new restrictions on detail elements.

Listing 13-27 The javax.xml.soap.DetailEntry Interface
package javax.xml.soap;
public interface DetailEntry extends SOAPElement {}