A Spring Web Services book that I technically reviewed has got published by Packt Publishing
The book is available through Amazon here
The book is available through Amazon here
xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
....
xsi:schemaLocation="...
http://cxf.apache.org/transports/http/configuration
http://cxf.apache.org/schemas/configuration/http-conf.xsd">
...
<http-conf:conduit name="*.http-conduit">
<http-conf:client ProxyServer="${proxy.server}" ProxyServerPort="${proxy.port}" />
</http-conf:conduit>
http-conf:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit"
<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>
<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>
<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>
...
@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;
}
...
<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>
@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;
}
<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>
<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>
@SoapFault(faultCode = FaultCode.SERVER, faultStringOrReason = "No Member Found - From @SoapFault annotation")
public class MemberDetailsFault extends RuntimeException{
<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver"/>
<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>
@Endpoint
public class GetMemberDetailsEndpoint {
@Resource 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());
......
}
<bean name="memberManager" class="org.easymock.EasyMock" factory-method="createMock"> <constructor-arg value="org.bk.memberservice.service.MemberManager"/> </bean>
MemberDetail memberDetail = new MemberDetail("john doe", "111-111-1111", "City", "State");
memberDetail.setId(1L);
expect(memberManager.findByMemberId(1L)).andReturn(memberDetail);
replay(memberManager);
mockClient = MockWebServiceClient.createClient(applicationContext);
Source requestPayload = new StringSource(
"<mem:MemberDetailsRequest xmlns:mem=\"http://bk.org/memberservice/\">"
+ "<mem:id>1</mem:id>"
+ "</mem:MemberDetailsRequest>");
Source responsePayload = new StringSource(
"<ns3:MemberDetailsResponse xmlns:ns3=\"http://bk.org/memberservice/\">"
+ "<memberDetail>"
+ "<id>1</id>"
+ "<name>john doe</name>"
+ "<phone>111-111-1111</phone>"
+ "<city>City</city>"
+ "<state>State</state>"
+ "</memberDetail>"
+ "</ns3:MemberDetailsResponse>");
mockClient.sendRequest(withPayload(requestPayload)).andExpect(payload(responsePayload));
verify(this.memberManager);
This completes the test, MockWebserviceClient would take care of packaging up the raw xml request, dispatching it the appropriate WS endpoint, getting the response and validating it. Updated codesample with integration test available at: git://github.com/bijukunjummen/memberservice-contractfirst.git
org.springframework.ws.transport.http.MessageDispatcherServletand the one used by Spring MVC is :
org.springframework.web.servlet.DispatcherServletTo have a combined Spring MVC and Spring-WS project, it is possible to configure these front controllers based on the URI pattern of the request, in the following way:
<servlet>
<servlet-name>member-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/META-INF/spring/applicationContext-ws.xml</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>member-web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/webmvc-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>member-ws</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>member-ws</servlet-name>
<url-pattern>*.wsdl</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>member-web</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<bean id="memberendpoint" class="org.bk.memberservice.endpoint.DefaultMemberEndpoint"/>
<jaxws:endpoint address="/memberservice" id="memberservicehttp" implementor="#memberendpoint" >
</jaxws:endpoint>
</beans>
This exposes the memberendpoint bean above as a fully configured webservice.package org.bk.memberservice.endpoint;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class UsernameTokenCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
Callback callback = callbacks[0];
WSPasswordCallback pc = (WSPasswordCallback) callback;// Retrieve and set the real password..which will be validated
// by CXF validator against the digest password
pc.setPassword("test");
System.out.println("Received cred: " + pc.getIdentifier() + " : " + pc.getPassword());
// validate the credentials
// boolean isValid = true;
// throw IO Exception if the credentials are not valid
// if (!isValid) {
// throw new IOException("Bad Credentials");
// }
}
}
<jaxws:endpoint address="/memberservice" id="memberservicehttp"
implementor="#memberendpoint">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordDigest" />
<entry key="passwordCallbackRef">
<ref bean="usernameTokenCallback" />
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
That's it!! the endpoint is now secured using UsernameToken profile. To test this, bring up the endpoint, use SOAP UI to send a normal request, it will fail with the message that a ws-security header is required - this is the username token soap header that is expected as part of the request.public class GetMemberDetailsEndpoint extends
AbstractMarshallingPayloadEndpoint {
private MemberManager memberManager;
protected Object invokeInternal(Object requestObject) throws Exception {
MemberDetailsRequest request = (MemberDetailsRequest) requestObject;
MemberDetail memberDetail = memberManager.getMemberDetails(request
.getId());
MemberDetailsResponse response = new MemberDetailsResponse(memberDetail);
return response;
}
......
}
With Spring-WS 2.0 the endpoint can be more intuitively defined with the following signature: @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.getMemberDetails(request
.getId());
MemberDetailsResponse response = new MemberDetailsResponse(memberDetail);
return response;
}
.....
}Updated code available at:<?xml version="1.0" encoding="UTF-8"?><wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ms="http://bk.org/memberservice/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="memberservice" targetNamespace="http://bk.org/memberservice/">
<wsdl:types>
<xsd:schema targetNamespace="http://bk.org/memberservice/" elementFormDefault="qualified">
<xsd:complexType name="MemberDetailType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="MemberDetailsRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="id" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="MemberDetailsResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="memberdetail" type="ms:MemberDetailType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="MemberDetailsRequest">
<wsdl:part element="ms:MemberDetailsRequest" name="parameters"/>
</wsdl:message>
<wsdl:message name="MemberDetailsResponse">
<wsdl:part element="ms:MemberDetailsResponse" name="parameters"/>
</wsdl:message>
<wsdl:portType name="memberservice">
<wsdl:operation name="GetMemberDetails">
<wsdl:input message="ms:MemberDetailsRequest"/>
<wsdl:output message="ms:MemberDetailsResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="memberserviceSOAP" type="ms:memberservice">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="GetMemberDetails">
<soap:operation soapAction="http://bk.org/memberservice/GetMemberDetails"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="memberservice">
<wsdl:port binding="ms:memberserviceSOAP" name="memberserviceSOAP">
<soap:address location="http://localhost:8081/memberservice/services/MemberDetailsRequest"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
package org.bk.memberservice.endpoint;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import org.bk.memberservice.message.MemberDetailsRequest;
import org.bk.memberservice.message.MemberDetailsResponse;
@WebService
public interface MemberEndpoint {
@WebMethod(operationName = "MemberDetailsRequest")
@WebResult(name = "MemberDetailsResponse", targetNamespace = "http://bk.org/memberservice/")
MemberDetailsResponse getMemberDetails(
@WebParam(name = "MemberDetailsRequest") MemberDetailsRequest memberDetailsRequest);
}
package org.bk.memberservice.message;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "MemberDetailsRequest", namespace="http://bk.org/memberservice/")
public class MemberDetailsRequest {
public MemberDetailsRequest() {
}
public MemberDetailsRequest(String id) {
this.id = id;
}
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
package org.bk.memberservice.message;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.bk.memberservice.types.MemberDetail;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "MemberDetailsResponse", namespace="http://bk.org/memberservice/")
public class MemberDetailsResponse {
public MemberDetailsResponse() {
}
@XmlElement(name="memberdetail", namespace="http://bk.org/memberservice/")
private MemberDetail memberDetail;
public MemberDetailsResponse(MemberDetail memberDetail) {
this.memberDetail = memberDetail;
}
public MemberDetail getMemberDetail() {
return memberDetail;
}
public void setMemberDetail(MemberDetail memberDetail) {
this.memberDetail = memberDetail;
}
}
This generates a wsdl fairly close to the manually generated one, but not quite there - there does not seem to be a good way of controlling the namespace of operation name, and the wrappers around the request and the response xml structures.