This is a follow up to the simple webservice described in
this post
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