membersound
membersound

Reputation: 86777

How to catch any exceptions in a SOAP webservice method?

I'm offering a SOAP @WebMethod using Spring and CXF. And I would like to catch any exceptions (checked and unchecked) and transform them to a custom @WebFault.

Can I somehow assign a erro-handler/interceptor to my @WebSerivce class so that I don't have to provide extra try-catch blocks for every webserivce method?

<jaxws:endpoint implementor="de.MyService" address="/MyService" />

@Component
@WebService
public class MyService {
    @WebMethod
    public void test() throws MyException {
        try {
            service.run();
        } catch (Exception e) {
            throw new MyException("test");
        }
    }
}


@WebFault
public class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}

So that with some kind of interceptor, my methods would look like this:

    @WebMethod
    public void test() {
        service.run();
    }

Is that possible?

Upvotes: 3

Views: 8786

Answers (2)

kan
kan

Reputation: 28961

As I see it is not SOAP specific, you just want a java method to convert one exception to another. So it means that a bean methods should be wrapped into an exception converter. So, just use AOP to wrap around the bean and do custom exception processing by @AfterThrowing or something similar.

Upvotes: 1

GPI
GPI

Reputation: 9328

Well, first one could suggest a "low tech" approach : have a base class for all your webservices that handles the try/catch in a invoke method. Have this method delegate to an abstract doInvoke method, and make it a policy for all your JAXWS implementations to only call invoke.

@kan's solutions involving AOP also sure is a possible solution.


However, if you want to build custom error interception, you can do so at the CXF level.

When used with JAX-WS, CXF in its simplest form can be thought of as a (complex) interceptor chain around the JAX WS engine : the interceptor chain(s) are where all the "meat" of CXF goes.

CXF Interceptors are arranged into chains (the "in" chain, "in fault" chain, "out" and "out fault" chains).

Each chain has various "phases", for example : RECEIVE, (PRE/USER/POST)_STREAM, READ, (PRE/USER/POST)PROTOCOL, UNMARSHAL, (PRE/USER/POST)LOGICAL, PRE_INVOKE, INVOKE, POST_INVOKE are the default phases of the incomming chains.

Interceptors are executed "in order" (the phases are associated with priorities, and interceptor implementations declare which phase they belong to. Inside a phase, each interceptor can opt in the be placed before or after a certain other interceptor class).

Of most importance in your case, the ServiceInvokerInterceptor, which belongs to the INVOKE phase, is responsible for calling the @Webservice. When all interceptors in the "in" chain are processed, CXF handles the response object to the "out" chain for serializing the output (or stops everything here if you have a one way SOAP method, which is a special case).

If an exception occurs anywhere in the standard chain, CXF will do two things :

  1. It will stop the chain where it is and invoke all interceptors that have been processed in reverse order, using the handleFault method
  2. It will then forward control to the fault interceptor chain ("in fault", "out fault").

Therefore, a possible way for you to add your own SOAP fault "catch all" error handling is to use an interceptor based in this lifecycle.

You create an Interceptor implementation (AbstractSoapInterceptor is good for that) You bind it to the INVOKE phase, before the ServiceInvokerInterceptor

public class YourInterceptor extends AbstractSoapInterceptor {
    public YourInterceptor() {
        super(Phase.INVOKE);
        addBefore(Arrays.asList(ServiceInvokerInterceptor.class.getName()));
        // This means handleMessage will be called juste before your @WebMethod
        // If it fails, you will be the first to be noticed through #handleFault()
    }
}

This interceptor is not to do anything when a "normal message passes" :

@Override
public void handleMessage(SoapMessage message) throws Fault {
    // Do nothing
}

But it is to handle the faults :

@Override
public void handleFault(SoapMessage message) {
    // Every exception will be wrapped into a Fault object by CXF
    Fault f = (Fault) message.getContent(Exception.class);
    // You should inspect its g.getCause() to maybe identify what went wrong
    // A CXF Fault also much ressembles a SOAPFault element
    f.setMessage("Your SOAP Fault message");
    // You can access the DOM detail of the fault
    Element detail = f.getOrCreateDetail();
    Element newDetailEntry = detail.getOwnerDocument().createElementNS("detailNs", "detailName");
    newDetailEntry.setTextContent("Content for your soap fault detail");
    detail.appendChild(newDetailEntry);
    // And so on. f.setFaultCode(qName);...
}

An alternative implementation would be to swap the original Fault by a custom SoapFault, which is also a subclass of Fault, if it makes more sense to you.

This admitedly is more difficult that launching your own exception, but it allows you to build precise, meaningfull soap faults. Please note however that it is good practice to launch only SOAP Faults that exist as part of your WSDL, so to play "nicely" with clients, do not build here faults that do not match your WSDL (in your case, the @Webfault definition).

You finally have to declare your interceptor to be added to the chains. There are multiple ways of doing so : on a per bean basis :

<bean id="myIt" class="com.yourInterceptor" />
<jaxws:endpoint implementor="de.MyService" address="/MyService">
    <jaxws:inInterceptors>
      <ref bean="myIt"/>
    </jaxws:inInterceptor>
</jaxws:endpoint>

Or at the bus level.

<cxf:bus>
    <cxf:inInterceptors>
        <ref bean="myIt"/>
    </cxf:inInterceptors>
</cxf:bus>

Upvotes: 7

Related Questions