Wednesday 6 May 2009

JAX-WS: Throwing generic SOAPFaults under WLS 10.3

(With apologies I accidentally deleted this post. This is a repost).

Typically thanks to the WSDL operations including a <soap:fault> element, when generating JAX-WS 2.1.x endpoints from the WSDL, the JAX-WS engine will also create @WebFault classes to mirror the <soap:fault>. As such the programmer can throw the @WebFault class to cause a SOAP fault to be returned to the caller.

However what if the WSDL operation doesn't include an explicitly defined fault for the operation? How does the programmer throw a fault without a @WebFault class? The simple answer is add it to the WSDL and regenerate, but some of us don't have that luxury, the WSDLs are given to us.

If you're using SOAP 1.1 the following code will allow you throw a generic SOAP fault from your JAX-WS endpoint:

SOAPFactory fac = SOAPFactory.newInstance();
SOAPFault sf = fac.createFault("Your error message", new QName("http://schemas.xmlsoap.org/soap/envelope/", "Client"));
throw new SOAPFaultException(sf);
Note the QName uses the SOAP 1.1 namespace and "Client" fault code.

The SOAP 1.2 equivalent code:
 
SOAPFactory fac = SOAPFactory.newInstance();
SOAPFault sf = fac.createFault("Your error message", new QName("http://www.w3.org/2003/05/soap-envelope", " Receiver"));
throw new SOAPFaultException(sf);

In this example the QName uses the SOAP 1.2 namespace and "Receiver" fault code.

(Later edit – for the record both these mechanisms can also be used in a custom JAX-WS protocol (SOAP handler) or logical handler).

In the SOAP 1.2 case you'll see something like the following SOAP fault returned from the web service call:
 




S:Receiver


Your error message



javax.xml.ws.soap.SOAPFaultException: Your error message








Specifically for Oracle's WebLogic Server (WLS) v10.3, we need to make a slight change to the SOAP 1.2 code by supplying the specific SOAP protocol to the SOAPFactory.newInstance call:

SOAPFactory fac = SOAPFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
SOAPFault sf = fac.createFault("Your error message", new QName("http://www.w3.org/2003/05/soap-envelope", " Receiver"));
throw new SOAPFaultException(sf);
If you don't do this the web service will return an empty HTTP body (though the HTTP header will include a "500 internal server error"), and you'll see the following Java Stack Trace:

java.lang.RuntimeException: javax.xml.ws.soap.SOAPFaultException: Your error message

6/05/2009 10:18:23 com.sun.xml.internal.messaging.saaj.soap.ver1_1.Fault1_1Impl getFaultSubcodes
SEVERE: SAAJ0303: Operation getFaultSubcodes not supported by SOAP 1.1
6/05/2009 10:18:23 com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit handle
SEVERE: Not supported in SOAP 1.1
java.lang.UnsupportedOperationException: Not supported in SOAP 1.1
at com.sun.xml.internal.messaging.saaj.soap.ver1_1.Fault1_1Impl.getFaultSubcodes(Fault1_1Impl.java:220)
at com.sun.xml.ws.fault.SOAPFaultBuilder.createSOAP12Fault(SOAPFaultBuilder.java:404)
at com.sun.xml.ws.fault.SOAPFaultBuilder.createSOAPFaultMessage(SOAPFaultBuilder.java:172)
at com.sun.xml.ws.server.WSEndpointImpl$2.process(WSEndpointImpl.java:249)
at com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit.handle(HttpAdapter.java:444)
at com.sun.xml.ws.transport.http.HttpAdapter.handle(HttpAdapter.java:244)
at com.sun.xml.ws.transport.http.servlet.ServletAdapter.handle(ServletAdapter.java:134)
at weblogic.wsee.jaxws.HttpServletAdapter$AuthorizedInvoke.run(HttpServletAdapter.java:272)
at weblogic.wsee.jaxws.HttpServletAdapter.post(HttpServletAdapter.java:185)
at weblogic.wsee.jaxws.JAXWSServlet.doPost(JAXWSServlet.java:180)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at weblogic.wsee.jaxws.JAXWSServlet.service(JAXWSServlet.java:64)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175)
at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3498)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(Unknown Source)
at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2180)
at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2086)
at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1406)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
(I don't usually include the complete stack trace in a blog post, but in this case the error is so obscure, it'll help others with Google searches)

I'm currently unclear on why this occurs under WLS. Having tested the original code on Netbeans 6.5 & Glassfish 2, it doesn't have the same issue. I note WLS 10.3 uses JAX-WS 2.1.3 and Glassfish 2 uses JAX-WS 2.1.4, so it may be a simple fix in the later JAX-WS release.

Thanks very much to Gerard Davison from Oracle UK for his assistance on resolving this one.

2 comments:

Unknown said...

How are you able to throw a SOAPFaultException weblogic keeps yelling that JAXB can't handle interfaces so it can't serialize javax.xml.soap.SOAPFault

Chris Muir said...

Hmmm, not sure Shane. I think there's a hint in your question though. My SOAPFault is only thrown in the JAX-WS endpoint classes I've created, not in any JAXB classes. I've also tested it using the JAX-WS Provider API. How and where are you throwing the fault?

CM.