Reputation: 86777
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
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
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 :
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