PDA

View Full Version : Is it possible to throw custom WSDL faults?



konrad
Sep 25th, 2006, 08:51 AM
We would like to define our own custom faults in our WSDL- mainly just to differentiate the types of errors that are occurring rather than providing any complex structure on the fault.
So far we have only been able get Spring-WS to send SOAP faults (i.e <soapenv:fault/> elements) back to the client - we cannot get it to return our custom faults.
The relevant pieces of WSDL looks like this:

...
<types...
<schema...
<xsd:element name="authenticatonFault" type="xsd:string"/>
...
<message name="authenticatonFault">
<part name="authenticatonFault" element="tns:authenticatonFault"/>
</message>

<portType...
<operation...
<fault message="tns:authenticatonFault" name="authenticatonFault"/>

<binding...
<operation...
<fault name="authenticatonFault">
<soap:fault name="authenticatonFault" use="literal"/>
</fault>

We have defined the following spring bean:
<bean id="endpointExceptionResolver" class="org.springframework.ws.soap.endpoint.SoapFaultMapp ingExceptionResolver">
<property name="defaultFault">
<value>RECEIVER,Server error</value>
</property>
<property name="exceptionMappings">
<props>
<prop key="com.whatever.AuthenticatonException">{http://www.whatever.com/ws/ourNS}ns1:authenticationFault,Invalid login</prop>

</props>
</property>
</bean>

We have looked at forum entries, and cannot see any example of someone else trying to use custom wsdl faults, although the SoapFaultDefinition editor seems to suggest that you can supply the qname of your own fault.
Can anyone tell us if this is currently possible with Spring-WS m2 or is it in the plan, or are we maiking some error?

Thanks,
Konrad.

Arjen Poutsma
Sep 26th, 2006, 02:49 AM
A WSDL fault is stored in a SOAP fault as a detail entry. Apart from this detail, the soap fault has a fault code, and fault string, both of which you can customize. For details how to accomplish this, see this thread. It basically involves creating your own EndpointExceptionResolver, which sets the detail entry.

Some people has requested an easier way to use a marshaller for converting an object into a detail entry, and i'm looking into that.

konrad
Sep 26th, 2006, 03:33 AM
Thanks ... unfortunately I was looking for this easier solution using marshaller.
Regards
Konrad

konrad
Sep 26th, 2006, 05:48 AM
We solved it by making our own AbstractEndpointExceptionResolver listed below (almost same as SoapFaultMappingExceptionResolver, just lines 66 - 67 were added):
This code just adds an empty element as a child of detail (i.e. the exception fields are not being serialized out), but mostly this is a marker exception for our case. This QNamed element is sufficient for our (XFire) client to serialize the fault to the correct generated exception.

And we are on track :)

Not sure if it is the best solution - but it works for us.



package com.mycompany.webservice;

import org.springframework.ws.context.MessageContext;
import org.springframework.ws.endpoint.AbstractEndpointEx ceptionResolver;
import org.springframework.ws.soap.SoapBody;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.context.SoapMessageCon text;
import org.springframework.ws.soap.endpoint.SoapFaultDefi nition;
import org.springframework.ws.soap.endpoint.SoapFaultDefi nitionEditor;
import org.springframework.ws.soap.soap11.Soap11Body;

import java.util.*;

public class MyEndpointExceptionResolver extends AbstractEndpointExceptionResolver
{
private Properties exceptionMappings;

private SoapFaultDefinition defaultFault;

public void setExceptionMappings(Properties mappings)
{
exceptionMappings = mappings;
}

public void setDefaultFault(SoapFaultDefinition defaultFault)
{
this.defaultFault = defaultFault;
}

protected boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex)
{
if (!(messageContext instanceof SoapMessageContext))
{
throw new IllegalArgumentException("SoapFaultMappingExceptionResolver requires a SoapMessageContext");
}
SoapFaultDefinition definition = getFaultDefinition(ex);
if (definition == null)
{
return false;
}
SoapMessageContext soapContext = (SoapMessageContext) messageContext;
SoapMessage response = soapContext.getSoapResponse();
SoapBody soapBody = response.getSoapBody();

if (SoapFaultDefinition.SERVER.equals(definition.getF aultCode()) ||
SoapFaultDefinition.RECEIVER.equals(definition.get FaultCode()))
{
soapBody.addServerOrReceiverFault(definition.getFa ultStringOrReason(), definition.getLocale());
}
else if (SoapFaultDefinition.CLIENT.equals(definition.getF aultCode()) ||
SoapFaultDefinition.SENDER.equals(definition.getFa ultCode()))
{
soapBody.addClientOrSenderFault(definition.getFaul tStringOrReason(), definition.getLocale());
}
else
{
// custom code, only supported for SOAP 1.1
if (soapBody instanceof Soap11Body)
{
Soap11Body soap11Body = (Soap11Body) soapBody;
soap11Body.addFault(definition.getFaultCode(), definition.getFaultStringOrReason(),
definition.getLocale());
}

SoapFaultDetail soapFaultDetail = soapBody.getFault().addFaultDetail();
soapFaultDetail.addFaultDetailElement(definition.g etFaultCode());
}

return true;
}

private SoapFaultDefinition getFaultDefinition(Exception ex)
{
SoapFaultDefinition definition = null;
if (exceptionMappings != null)
{
String definitionText = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();)
{
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if (depth >= 0 && depth < deepest)
{
deepest = depth;
definitionText = exceptionMappings.getProperty(exceptionMapping);
}
}
if (definitionText != null)
{
SoapFaultDefinitionEditor editor = new SoapFaultDefinitionEditor();
editor.setAsText(definitionText);
definition = (SoapFaultDefinition) editor.getValue();
}
}
if (definition != null || defaultFault == null)
{
return definition;
}

return defaultFault;
}

public int getDepth(String exceptionMapping, Exception ex)
{
return getDepth(exceptionMapping, ex.getClass(), 0);
}

private int getDepth(String exceptionMapping, Class exceptionClass, int depth)
{
if (exceptionClass.getName().contains(exceptionMappin g))
{
return depth;
}
if (exceptionClass.equals(Throwable.class))
{
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
}


Best regards
Konrad

konrad
Sep 26th, 2006, 05:56 AM
Small correction:


// custom code, only supported for SOAP 1.1
if (soapBody instanceof Soap11Body)
{
Soap11Body soap11Body = (Soap11Body) soapBody;
soap11Body.addFault(definition.getFaultCode(), definition.getFaultStringOrReason(),
definition.getLocale());
if (definition.getFaultCode() != null)
{
SoapFaultDetail soapFaultDetail = soapBody.getFault().addFaultDetail();
soapFaultDetail.addFaultDetailElement(definition.g etFaultCode());
}
}

jeremyhare
Apr 2nd, 2008, 05:49 PM
Hi,

This is a lot easier now. If you just need to create an custom element to differentiate between faults, you can do something like this:

Create an annotation:


import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CustomSoapFaultDetailElement{
String namespace();
String localName();
}

Create an exception resolver


public class CustomSoapFaultDetailAnnotationExceptionResolver extends SoapFaultAnnotationExceptionResolver {
@Override
protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
CustomSoapFaultDetailElement faultDetailAnnotation = ex.getClass().getAnnotation(CustomSoapFaultDetailE lement.class);
if (faultDetailAnnotation != null) {
SoapFaultDetail faultDetail = fault.addFaultDetail();
faultDetail.addFaultDetailElement(new QName(faultDetailAnnotation.namespace(), faultDetailAnnotation.localName()));
}
}
}


Then add the new annotation and the Spring SoapFault annotation to your exceptions:



@SoapFault(...)
@CustomSoapFaultDetailElement(namespace="...", localName="...")
public class MoonInWrongPhaseException extends Exception {}

gbayfield
May 29th, 2008, 08:31 AM
Would like tio use WSDL Faults but in spring-ws 1.50 understand the resultant soap fault message does not (in this release) populate the <detail> element in fault the WSDL Fault element structure (as is desired here).

Interested to run this custom exception resolver class above but find in my 1.5.0 spring env that SoapMessageContext has been removed from spring release now ?, I am not familiar with the replacement approach, can anyone pls point me in the right direction here, thanks !

mpilone
Jul 2nd, 2008, 03:12 PM
So from my understanding of this discussion and other threads on this forum, there is no way to add any XML to the detail of the SOAP fault, correct?

My application currently defines complex objects that we serialize into XML (multiple levels of XML) and put it into the detail element for the client to process. Looking at the SoapDetail API, I can only add elements at the top level and these elements only support attributes or text.

Is there no way to serialize a complex object into the fault detail? I'm willing to do my own marshalling in my resolver but I need a way to stick that information into the SoapDetail.

-mike

mpilone
Jul 3rd, 2008, 07:48 AM
I read through some more of the documentation and the examples and hacked my way to a solution. In the end it turned out pretty nice. The key is just directly writing to the SoapDetail Result rather than using the addDetailElement call. Here's my code:



SoapMessage response = (SoapMessage) messageContext.getResponse();
SoapBody soapBody = response.getSoapBody();

SoapFault soapFault =
soapBody.addClientOrSenderFault(ex.getMessage(), Locale.ENGLISH);

SoapFaultDetail faultDetail = soapFault.addFaultDetail();
Result result = faultDetail.getResult();

// My detail XML object
InvalidArgumentFault fault = new InvalidArgumentFault();
fault.setErrorCode("Custom Error Code");
fault.setOpsMessage("This is the ops message");
fault.setSystemMessage("This is the system message");

// Marshal the detail. We have to use the ObjectFactory which isn't
// marshaller agnostic because the detail element doesn't have an
// XmlRootElement tag as required by JAXB.
ObjectFactory of = new ObjectFactory();
mMarshaller.marshal(of.createInvalidArgumentFault( fault), result);

I hope that helps someone. It would be nice if this was in the exception handling documentation of Spring WS.

Thanks,
-mike