Thursday, January 27, 2011

How to map the SOAP fault message with a custom exception?

I was working on some customer issues about throwing a SOAP fault back to the client in CXF recently. It made me think it's a common issue that I should blog about it.
First, what does the SOAP Fault message look like ?











Just like the SOAP message, the SOAP fault has a generic message structure,  it consists of faultcode, faultstring and detail elements.

How can SOAP fault message turn into a custom Exception that you can you can use in the Java world? The key is the child element of the detail element.
From the upper example, you can see there an UnknownPersonFault element in the detail element, and this Fault message will be mapped to the UnknownPersonFault exception as we want when  the CXF client receives this message.

Let's take a look at the UnknowPersonFault Java code to see how this part works.

package org.apache.camel.wsdl_first;

import javax.xml.ws.WebFault;
@WebFault(name = "UnknownPersonFault", targetNamespace = "http://camel.apache.org/wsdl-first/types")
public class UnknownPersonFault extends Exception {
    public static final long serialVersionUID = 20110126200613L;
    
    private org.apache.camel.wsdl_first.types.UnknownPersonFault unknownPersonFault;

    public UnknownPersonFault() {
        super();
    }
    
    public UnknownPersonFault(String message) {
        super(message);
    }
    
    public UnknownPersonFault(String message, Throwable cause) {
        super(message, cause);
    }

    public UnknownPersonFault(String message, org.apache.camel.wsdl_first.types.UnknownPersonFault unknownPersonFault) {
        super(message);
        this.unknownPersonFault = unknownPersonFault;
    }

    public UnknownPersonFault(String message, org.apache.camel.wsdl_first.types.UnknownPersonFault unknownPersonFault, Throwable cause) {
        super(message, cause);
        this.unknownPersonFault = unknownPersonFault;
    }

    public org.apache.camel.wsdl_first.types.UnknownPersonFault getFaultInfo() {
        return this.unknownPersonFault;
    }
}

Wait a minute, why there is another UnknownPersonFault? Let's dig into the new found one.
package org.apache.camel.wsdl_first.types;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for anonymous complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType>
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element name="personId" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "personId"
})
@XmlRootElement(name = "UnknownPersonFault")
public class UnknownPersonFault {

    @XmlElement(required = true)
    protected String personId;

    /**
     * Gets the value of the personId property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getPersonId() {
        return personId;
    }

    /**
     * Sets the value of the personId property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setPersonId(String value) {
        this.personId = value;
    }

}

Oh, this one is the real UnknownPersonFault which can be used by JAXB to marshal and unmarshal the message. And it is wrapped by an exception which is annotated with @WebFault. This @WebFault will be helpful when CXF wants to create a custom exception from a SOAP fault message. Because CXF can look up the detail child element QName for it.

And when CXF tries to marshal this custom exception, it can easily write the detail child element by marshaling the exception.getFaultInfo().So when we throw the exception from the SEI , we should use the wrapped exception instead of the JAXB annotated class which is not a real exception, and you also need to pass the JAXB fault class instance into the exception to fill the detail element like this.
org.apache.camel.wsdl_first.types.UnknownPersonFault faultDetail = new org.apache.camel.wsdl_first.types.UnknownPersonFault();
                        faultDetail.setPersonId("");
                        UnknownPersonFault fault = new UnknownPersonFault("Get the null value of person name", faultDetail);
                        throw fault;
    
If the detail element has no child element, the CXF client will just create a common  SOAPFaultExceptionbecause it has no idea about how to map the SOAP Fault message into a custom exception.