Friday, May 7, 2010

Exception Handling in Web Services.... AKA Web Services suck - big time.

I have a WebService with a WebMethod

@WebMethod
public void updateCompany(Company company) throws CompanyException


where
public class CompanyException extends Exception


this generates this WSDL:

    <s0:operation name="updateCompany" parameterOrder="parameters">
      <s0:input message="s1:updateCompany"/>
      <s0:output message="s1:updateCompanyResponse"/>
      <s0:fault message="s1:CompanyException" name="CompanyException"/>
    </s0:operation>

______________________

If I have 2 exceptions:
public void updateCompany(Company company) throws CompanyException, NamingException

I get 2 faults
      <s0:fault message="s1:CompanyException" name="CompanyException"/>
      <s0:fault message="s1:NamingException" name="NamingException"/>

______________________

If I add the annotation javax.xml.ws.WebFault:
@WebFault(name="companyFault")

before the public class CompanyException extends Exception, the WSDL SHOULD become

    <s0:operation name="updateCompany" parameterOrder="parameters">
      <s0:input message="s1:updateCompany"/>
      <s0:output message="s1:updateCompanyResponse"/>
      <s0:fault message="s1:companyFault" name="companyFault"/>
    </s0:operation>

or something like that.... but unfortunately with WebLogic this doesn't seem to affect the WSDL!

______________________

Anyhow, if you construct the CompanyException without invoking the super(String message) constructor,
you will get this:

<env:Body>

<env:Fault>
<faultcode>env:Server</faultcode>
<faultstring/>

<detail>
<com:string xsi:nil="true"/>
</detail>
</env:Fault>
</env:Body>




otherwise, if you do super(message),and you invoke

Java:
throw new CompanyException("UNABLE_TO_EXECUTE_SQL")

SOAP Fault:

<env:Fault>
<faultcode>env:Server</faultcode>
<faultstring>UNABLE_TO_EXECUTE_SQL</faultstring>

<detail>
<com:string>UNABLE_TO_EXECUTE_SQL</com:string>
</detail>
</env:Fault>
</env:Body>


______________________

If your exception extends WebServiceException, AND you do super(message), you get something really exciting:


<env:Body>

<env:Fault>
<faultcode>env:Server</faultcode>

<faultstring>
Failed to invoke end component com.acme.dbaccess.CompanyDBWS (POJO), operation=insertCompany
 -> Failed to invoke method
 -> UNABLE_TO_EXECUTE_SQL
</faultstring>

<detail>

<bea_fault:stacktrace>
com.acme.dbaccess.CompanyException: UNABLE_TO_EXECUTE_SQL

    at com.acme.dbaccess.CompanyDBWS.insertCompany(CompanyDBWS.java:81)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
..................
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)

</bea_fault:stacktrace>
</detail>
</env:Fault>
</env:Body>



Better still if you use the super(message, Throwable) so you get also the stacktrace of the original exception!

YET the fault generated is not very usable.... the faultstring is very dirty and the faultcode useless....
I need to find a better way of doing this...


______________________


Now, if your service throws an Unchecked Exception (like NullPointerException), you will still get something decent:

Java:
throw new NullPointerException("I am a NPE") ;

SOAP Fault:

<env:Envelope>

<env:Body>
<env:Fault>
<faultcode>env:Server</faultcode>
<faultstring>
Failed to invoke end component com.acme.dbaccess.CompanyDBWS (POJO), operation=updateCompany
 -> Failed to invoke method
 -> I am a NPE
</faultstring>
<detail>
<bea_fault:stacktrace>
java.lang.NullPointerException: I am a NPE

    at com.acme.dbaccess.CompanyDBWS.updateCompany(CompanyDBWS.java:25)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
..............
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)

</bea_fault:stacktrace>
</detail>
</env:Fault>
</env:Body>
</env:Envelope>


_____________________________

If I extend CompanyException from SOAPException, I get this fault:

<env:Envelope>
<env:Body>
<env:Fault>
<faultcode>env:Server</faultcode>
<faultstring>UNABLE_TO_UPDATE</faultstring>
<detail>
<java:CompanyException/>
</detail>
</env:Fault>
</env:Body>
</env:Envelope>

that is, the Fault message is cleary readable (UNABLE_TO_UPDATE) but I have lost the Stacktrace.

_____________________________

On the whole, my impression is that Exception (Fault) generation and handling is, as everything else in WS, very poorly specified and implemented. Compare it to the level of technology in Java and you will only cry and feel lost in hyperspace with WS.

Let's face it, when you come from a Java background, you feel that Web Service technology has been designed by a bunch of fat old drunkards in a brothel running wildly after some young cheerful ladies in pink pajamas... two organs require a lot of blood: the brain and the penis, and we can only operate one at a time.

_______________

It is very educational to look at how a SOAP call is executed inside WebLogic:

    JavaClassComponent.invoke(String, Object[], MessageContext) line: 124   
    ComponentHandler.handleRequest(MessageContext) line: 84   
    HandlerIterator.handleRequest(MessageContext, int) line: 141   
    ServerDispatcher.dispatch() line: 114   
    WsSkel.invoke(Connection, WsPort) line: 80   
    SoapProcessor.handlePost(BaseWSServlet, HttpServletRequest, HttpServletResponse) line: 66   
    SoapProcessor.process(HttpServletRequest, HttpServletResponse, BaseWSServlet) line: 44   
    BaseWSServlet$AuthorizedInvoke.run() line: 285   
    WebappWSServlet(BaseWSServlet).service(HttpServletRequest, HttpServletResponse) line: 169   
    WebappWSServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 820   
    StubSecurityHelper$ServletServiceAction.run() line: 227   
    StubSecurityHelper.invokeServlet(ServletRequest, HttpServletRequest, ServletRequestImpl, ServletResponse, HttpServletResponse, Servlet) line: 125   
    ServletStubImpl.execute(ServletRequest, ServletResponse, FilterChainImpl) line: 292   
    ServletStubImpl.execute(ServletRequest, ServletResponse) line: 175   
    WebAppServletContext$ServletInvocationAction.run() line: 3498   
    AuthenticatedSubject.doAs(AbstractSubject, PrivilegedAction) line: 321   
    SecurityManager.runAs(AuthenticatedSubject, AuthenticatedSubject, PrivilegedAction) line: not available   
    WebAppServletContext.securedExecute(HttpServletRequest, HttpServletResponse, boolean) line: 2180   
    WebAppServletContext.execute(ServletRequestImpl, ServletResponseImpl) line: 2086   
    ServletRequestImpl.run() line: 1406   
    ExecuteThread.execute(Runnable) line: 201   
    ExecuteThread.run() line: 173   

MOST LIKELY it's the SoapProcessor who maps the Java Exception to the SOAP Fault.... ah, if only I had the source code!
_______________

JAX-WS specs define at least 4 exceptions (see http://www.ibm.com/developerworks/webservices/library/ws-jaxws-faults/index.html):

SOAPFaultException
javax.xml.ws.WebServiceException
ExecutionException
javax.xml.soap.SOAPException (one is redefined also in XMLBeans)


all this is simply ridiculous. Things have seriously gone out of control in WS technology.   

This post http://io.typepad.com/eben_hewitt_on_java/2009/07/using-soap-faults-and-exceptions-in-java-jaxws-web-services.html  is excellent, yet I keep HATING this technology. I am a strong believer of Convention Over Configuration and when I see all the verbosity in WS it makes me mad.

If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization. (Weinberg's Second Law)


Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. (Antoine de Saint-Exupery, French writer)





No comments: