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.

4 comments:

  1. Hi Willem. Something I'm not clear on--since you are explicitly throwing org.apache.camel. wsdl_first.UnknownPersonFault (after filling it with an instance of the other "UnknownPersonFault") why is the @WebFault annotation on this class needed?

    Is the @WebFault annotation useful if you're throwing a SOAPFaultException explicitly, but you put a JAXB UnknownPersonFault in the detail element, so the JAX-WS runtime would know to throw a org.apache.camel.wsdl_first.UnknownPersonFault instead of SOAPFaultException?

    ReplyDelete
  2. Hi Glen,
    The @WebFault annotation is useful for the client side with code first developing module. If there is no @WebFault annotation, CXF client has no idea to map the SOAPFaultException into the rg.apache.camel.wsdl_first.UnknownPersonFault.

    ReplyDelete
  3. Hi Willem, Just wondering if you have used it in mule context. Will be a great help if you know Mule 3 Configs for handling CXF Exceptions.

    -Arindam Aluni (aaluni@yahoo.com)

    ReplyDelete
  4. Hi Willam,

    First place, Thank you very much for your blog.

    But in my case I need to catch the Timeout exception. I am catching the Timeout Exception in the Interceptors and I am trying to throw the custom response message.

    I have the Wrapped Exception Class and Exception class for the JAXB to use for Marshalling and UnMarshalling, but though I call the Wrapped Exception Class it is not creating the custom message. I am catching the TimeoutException by finding the instance of the cause to WebServiceException. So I tried to modify the Fault Message as below.

    Element elementNs3 =
    fault.getOrCreateDetail().getOwnerDocument().createElementNS("http://XXX/XXX",
    "ns3:ServiceException");

    Element messageElement =
    fault.getDetail().getOwnerDocument().createElement("messageId");
    messageElement.setTextContent("SomeMessage");

    Element textElement =
    fault.getDetail().getOwnerDocument().createElement("text");
    textElement.setTextContent("Server has timedout");

    Element variableElement =
    fault.getDetail().getOwnerDocument().createElement("variables");
    variableElement.setTextContent("ERR_0006");


    fault.getDetail().appendChild(elementNs3).appendChild(messageElement);
    fault.getDetail().appendChild(elementNs3).appendChild(textElement);

    fault.getDetail().appendChild(elementNs3).appendChild(variableElement);

    So I am getting the soap fault as below




    soap:Server
    Could not send Message.


    SomeMessage
    Server has timedout
    ERR_0006






    But I need to add

    Please can you let me know if the way which I am doing is right and it is as per the standard please can you let me know and if it is the right approach please can you help me how to add the second namespace.

    If it is not the right approach please can you help me to know what is the standard way to catch the Timeout Exceptions.

    Thank you,

    Brady...

    ReplyDelete