Just to recap, my webservice was responsible for returning the details of a "member" given the identifier for the member. If a member was not found for the identifier, the service did not return a member.
For this request, which matches a member in the system:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mem="http://bk.org/memberservice/"> <soapenv:Header/> <soapenv:Body> <mem:MemberDetailsRequest xmlns:mem="http://bk.org/memberservice/"> <mem:id>1</mem:id> </mem:MemberDetailsRequest> </soapenv:Body> </soapenv:Envelope>
this is the response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <ns2:MemberDetailsResponse xmlns:ns2="http://bk.org/memberservice/"> <ns2:memberdetail> <ns2:id>1</ns2:id> <ns2:name>john doe</ns2:name> <ns2:phone>111-111-1111</ns2:phone> <ns2:city>City</ns2:city> <ns2:state>State</ns2:state> </ns2:memberdetail> </ns2:MemberDetailsResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
and for a case where there is no match, this is the response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <ns2:MemberDetailsResponse xmlns:ns2="http://bk.org/memberservice/"/> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Now, what I want to do is, instead of giving a response of this form, I want my contract to explicitly declare a Fault, in case a member is not found.
There are a couple of different ways of doing it.
The first way is to simply throw a RuntimeException, this would be trapped by the Spring-WS stack(using a SimpleFaultMessageResolver) and returned as a Soap fault.
So now my endpoint implementation is:
... @Endpoint public class GetMemberDetailsEndpoint { @Autowired private MemberManager memberManager; @PayloadRoot(namespace = "http://bk.org/memberservice/", localPart = "MemberDetailsRequest") @ResponsePayload public MemberDetailsResponse getMemberDetails(@RequestPayload MemberDetailsRequest request) throws Exception { MemberDetail memberDetail = memberManager.findByMemberId(request.getId()); if (memberDetail==null){ throw new RuntimeException("Member Not Found"); } MemberDetailsResponse response = new MemberDetailsResponse(memberDetail); return response; } ...
and the response for a case where a member is not found is a clean Soap fault with the message set in the exception as the fault detail:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Server</faultcode> <faultstring xml:lang="en">Member Not Found</faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
The second way is to define a custom exception class, and throw this custom exception from the endpoint:
@PayloadRoot(namespace = "http://bk.org/memberservice/", localPart = "MemberDetailsRequest") @ResponsePayload public MemberDetailsResponse getMemberDetails(@RequestPayload MemberDetailsRequest request) throws Exception { MemberDetail memberDetail = memberManager.findByMemberId(request.getId()); if (memberDetail==null){ throw new MemberDetailsFault("Member Not Found"); } MemberDetailsResponse response = new MemberDetailsResponse(memberDetail); return response; }
and define a resolver, which will map exceptions of this new type(MemberDetailsFault) to a more descriptive text:
<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver"> <property name="defaultFault" value="SERVER"/> <property name="exceptionMappings"> <value> org.bk.memberservice.message.MemberDetailsFault=SERVER,No Member-message from application context </value> </property> </bean>
the exceptionresolver is discovered by type, so there is no need to give the bean a name. With this in place, the Soap fault, will have the fault detail based on the exceptionMapping:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Server</faultcode> <faultstring xml:lang="en">No Member-message from application context</faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
A third way, is to annotate the custom exception class with a @SoapFault annotation, describing the exception to be returned as part of the faultdetail:
@SoapFault(faultCode = FaultCode.SERVER, faultStringOrReason = "No Member Found - From @SoapFault annotation") public class MemberDetailsFault extends RuntimeException{
A new resolver is required to interpret exception with annotations to a Soap Fault, SoapFaultAnnotationExceptionResolver:
<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver"/>
With this in place, the response is:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Server</faultcode> <faultstring xml:lang="en">No Member Found - From @SoapFault annotation</faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
I personally prefer the third approach, it is a little more concise.
Sample available at this location: git://github.com/bijukunjummen/memberservice-contractfirst.git