I ended up using a dynamic proxy and doing a "little bit of magic" to
unpack the exceptions.

The essential problem is the RemoteException exception specification,
but yes, perhaps the cause should be initialised if it were possible.
It's not entirely straightforward, though, the following questions crop
up:

[i]     What to do about exception classes only on the server classpath?
[ii]    How do you know which constructor to invoke on this arbitrary
exception Class to re-create it correctly?
[iii]   How do you feel about exceptions that violate the exception spec
of the dynamically invoked method?

I had to do this, and in the end, I reluctantly took a simple view:

[i]     Tough - give them an special exception telling them something
bad happened
[ii]    Easy - it's the (String message) contructor always, so that
better be the right one!
[iii]   Tough - give them a special exception telling yet another bad
thing happened

Having taken the tough line, it's quite easy to use dynamic invocation
to allow arbritrary exceptions to be thrown from web services.
It also cleans up the client code (why do Jbuttons care about
AxisFault?).

For neatness, I ended up enhancing an Axis class to add in the
exception's properties so I can re-contruct it as completely as
possible.

This needed some more tough decisions, but by then I was getting good at
them.

Exception JavaBean style properties will get re-created, but only the
(String arg0) constructor is used for objects, and the output from
toString() had better be an acceptable input to the constructor.

Given that, it's settled down and we can do anything we need to do
without too much thinking about constraints. 

It's fairly easy to put a sub-class of Handler into the Axis handler
chain to improve the information in the SOAP:Fault and you're in
business after that in the client.
Get in touch if you're interested...

Hope this helps.

Patrick

-----Original Message-----
From: Westfall, Eric Curtis [mailto:[EMAIL PROTECTED] 
Sent: 30 December 2004 15:57
To: [EMAIL PROTECTED]
Subject: RE: Service Specific Exception not working


I've done some more investigating on this and this is what I've come up
with.  Hopefully, this will help others who have run into this same
issue :)

All of the following refer to the client.

1) Because of the signature for the Call.invoke methods in the JAX-RPC
spec, Axis cannot throw arbitrary checked exceptions.  Therefore, it
creates and throws AxisFault (which extends RemoteException) in most
fault cases.  This seems like an oversight in the JAX-RPC spec to me but
I'm sure the people who wrote that know more about it than I do ;)

2) If the AxisFault has a "detail" (or "cause") which is an instance of
RemoteException, it will throw that instead.  That's one of the reasons
I was able to get back the desired exception (instead of an AxisFault)
when extending RemoteException.

3) When constructing an AxisFault (in the cases of a non-RemoteException
server-specific exception), the "detail" on the AxisFault is not
properly initialized (it is null).  Therefore, there is no way to
retrieve the wrapped exception and re-throw it in the client.

4) The "deserialization" of faults happens in the
org.apache.axis.message.SOAPFaultBuilder class.  The workhorse in that
class is the "createFault" method.  At one point in this method, we see
the following code:

AxisFault f = null;
...
...
if (f == null && faultData instanceof RemoteException) {
        f = AxisFault.makeFault((RemoteException)faultData);
}

In the example I gave in my last email, at this point in the code
faultData is an instance of my TestException.  In the case where I
extended RemoteException, the resulting AxisFault will properly wrap my
TestException.  When it does not extend RemoteException, this case ends
up falling through and a generic AxisFault is created without the
wrapped TestException.  From what I can tell, this is the cause for the
issue in number 3 above.

Question for those who know more about this: Is there any reason that we
can't do AxisFault.makeFault() for ANY Exception that comes into
SOAPFaultBuilder?  Are there any exceptions we don't want to wrap into
an AxisFault in this manner?  I don't have a problem manually unwrapping
the exception from the AxisFault and rethrowing it in my client (dynamic
proxy would probably work well for this), but it's kind of hard to do
when its not there ;)

I was just curious about this and was wondering what others thought. I'm
new to Axis so I'm not really 100% sure what is going on inside.  It
seems like I may be able to patch my source to get it to do what I want.
Perhaps I should post a question about this on the developer list?

Thanks for your time,
Eric Westfall


-----Original Message-----
From: Westfall, Eric Curtis 
Sent: Wednesday, December 29, 2004 12:36 PM
To: [EMAIL PROTECTED]
Subject: Service Specific Exception not working

Hello, I've searched the list for answers to this issue and I haven't
been able to find any satisfactory ones.

I'm having trouble getting "Service Specific Exceptions" as defined in
the JAX-RPC specification to work properly.  If I define a custom
exception and have it extend RemoteException, then it works properly.
However, if it simply extends Exception, then I end up with an AxisFault
in my client, rather than an instance of the thrown exception.

For example, here is my exception definition:

package test;

public class TestException extends Exception implements
java.io.Serializable {

    private String message;
    
    public TestException() {
        super();
    }

    public TestException(String message) {
        setMessage(message);
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public String getMessage() {
        return message;
    }

}

I have a service with a method "testException" whose definition in the
service interface is:

public void testException() throws RemoteException, TestException;

And the implementation looks like this:

public void testException() throws TestException {
      throw new TestException("This is the TestException message"); }

If I execute the "testException" method in a client in the above case. I
get back an org.apache.axis.AxisFault with the following stack trace on
the client (run from a JUnit test):

AxisFault
 faultCode:
{http://schemas.xmlsoap.org/soap/envelope/}Server.userException
 faultSubcode: 
 faultString: test.TestException: This is the TestException message
 faultActor: 
 faultNode: 
 faultDetail: 
        {}test.TestException:<message xsi:type="soapenc:string"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/";>This is the
TestException message</message>
        {http://xml.apache.org/axis/}hostname:localhostname

test.TestException: This is the TestException message
        at
org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.ja
va:221)
        at
org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.jav
a:128)
        at
org.apache.axis.encoding.DeserializationContext.endElement(Deserializati
onContext.java:1083)
        at
org.apache.crimson.parser.Parser2.maybeElement(Parser2.java:1712)
        at org.apache.crimson.parser.Parser2.content(Parser2.java:1963)
        at
org.apache.crimson.parser.Parser2.maybeElement(Parser2.java:1691)
        at org.apache.crimson.parser.Parser2.content(Parser2.java:1963)
        at
org.apache.crimson.parser.Parser2.maybeElement(Parser2.java:1691)
        at
org.apache.crimson.parser.Parser2.parseInternal(Parser2.java:667)
        at org.apache.crimson.parser.Parser2.parse(Parser2.java:337)
        at
org.apache.crimson.parser.XMLReaderImpl.parse(XMLReaderImpl.java:448)
        at javax.xml.parsers.SAXParser.parse(SAXParser.java:345)
        at
org.apache.axis.encoding.DeserializationContext.parse(DeserializationCon
text.java:226)
        at org.apache.axis.SOAPPart.getAsSOAPEnvelope(SOAPPart.java:645)
        at org.apache.axis.Message.getSOAPEnvelope(Message.java:424)
        at
org.apache.axis.handlers.soap.MustUnderstandChecker.invoke(MustUnderstan
dChecker.java:62)
        at org.apache.axis.client.AxisClient.invoke(AxisClient.java:173)
        at org.apache.axis.client.Call.invokeEngine(Call.java:2719)
        at org.apache.axis.client.Call.invoke(Call.java:2702)
        at org.apache.axis.client.Call.invoke(Call.java:2378)
        at org.apache.axis.client.Call.invoke(Call.java:2301)
        at org.apache.axis.client.Call.invoke(Call.java:1758)
        at
org.apache.axis.client.AxisClientProxy.invoke(AxisClientProxy.java:163)
        at $Proxy0.testException(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.jav
a:39)
        at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessor
Impl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:324)
        at
org.springframework.remoting.rmi.RmiClientInterceptorUtils.doInvoke(RmiC
lientInterceptorUtils.java:93)
        at
org.springframework.remoting.rmi.RmiClientInterceptorUtils.invoke(RmiCli
entInterceptorUtils.java:62)
        at
org.springframework.remoting.jaxrpc.JaxRpcPortClientInterceptor.invoke(J
axRpcPortClientInterceptor.java:290)
        at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Ref
lectiveMethodInvocation.java:138)
        at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAo
pProxy.java:152)
        at $Proxy1.testException(Unknown Source)
        at test.WebServiceProxy.testException(WebServiceProxy.java:62)
        at test.ExceptionTest.testTestException(ExceptionTest.java:26)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.jav
a:39)
        at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessor
Impl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:324)
        at junit.framework.TestCase.runTest(TestCase.java:154)
        at junit.framework.TestCase.runBare(TestCase.java:127)
        at junit.framework.TestResult$1.protect(TestResult.java:106)
        at junit.framework.TestResult.runProtected(TestResult.java:124)
        at junit.framework.TestResult.run(TestResult.java:109)
        at junit.framework.TestCase.run(TestCase.java:118)
        at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTe
stRunner.java:421)
        at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRun
ner.java:305)
        at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRu
nner.java:186)



However, if I change TestException so that it extends RemoteException,
then a TestException rather than the above AxisFault is thrown to my
client.  This is the behaviour I'm looking for, but I don't want my
service specific exceptions to extend RemoteException.  I'm puzzled as
to why this is happening.  Are RemoteExceptions handled differently than
regular Exceptions in Axis even though the JAX-RPC specification states
that they should be supported?

I didn't include my server-deploy.wsdd file or my WSDL files in this
email.  I can include the relevant parts of those if that would help. In
my client and on the server I've got TestException set up to be
serialized and deserialized using the BeanSerializerFactory (and
BeanDeserializerFactory) which I believe is correct.

If anybody has any insight into why Exceptions don't work but
RemoteExceptions do, I would really appreciate it.  I've been trying to
figure this out for a while now :)

Thanks in advance for your time,
Eric Westfall

Reply via email to