Creating a Simple Web Service Client with JAX-WS using Spring WS
SOAP Web Services provide a platform agnostic integration mechanism that
allows disparate systems to exchange data regardless of the platform
they are running on. For example, SOAP web services are commonly used to
integrate .NET applications with applications running on the Java
platform. Almost all modern platforms and frameworks (Java, .Net, Ruby,
PHP, etc)
This section shows how to build and deploy a simple web service client.
Communication between a JAX-WS Web Service and a Client
Spring Web Services aims to facilitate contract-first SOAP service development, allowing for the creation of flexible web services using one of the many ways to manipulate XML payloads. The product is based on Spring itself, which means you can use the Spring concepts such as dependency injection as an integral part of your Web service.
As the best way to define the data contract is xml schema hence we would consider that we have XSD provided with us and if not we can create an XSD from the sample
documents conforming the requirements specs. Any good XML editor or Java IDE offers this functionality.
Basically, these tools use some sample XML documents, and generate a
schema from it that validates them all. The end result certainly needs
to be polished up, but it's a great starting point.
Consider we have the XSD with us, then we would need following:
- Generate the WSDL from the xsd provided
- Generate domain objects based on a WSDL (Considering WSDL is available else one needs to have a XSD shared by the WebService Producer)
- Spring configuration - To inject WebServiceTemplate into the client class.
- An implementation class - This class will be actually the webservice client
- Calling the Web Service client
1. Generating WSDL from the supplied XSD:
a. We can use Spring to help us generate wsdl - we can configure spring-ws-servlet.xml in our Spring based web application we can have below "WSDL - Generator code"
<!-- WSDL Generator -->
<bean id="anyservices"
class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="createSoap12Binding" value="true" />
<property name="schema">
<bean class="org.springframework.xml.xsd.SimpleXsdSchema" p:xsd="classpath:ANY_XXX_XX.xsd"/>
</property>
<property name="portTypeName" value="ackEventPort" />
<property name="locationUri"
value="https://anyendpoint.domain.com:1234/ANY/AnyContext" />
</bean>
<sws:dynamic-wsdl id="anyservices" portTypeName="ackEventPort" locationUri="/">
<sws:xsd location="classpath:ANY_XXX_XX.xsd"/>
</sws:dynamic-wsdl>
b. Deploying the Spring Web App on any web container (I have used Tomcat 6)
following URL will generate the WSDL for you, consider the bean id as context (anyServices) and dynamic wsdl id as wsdl name anyservices.wsdl :
http://localhost:8080/ApplicationWAR/anyService/anyservices.wsdl
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
If you want to validate the request / response against schema (xsd) , you can add the below in the spring ws servlet xml.
<!-- Schema validation -->
<bean id="validatingInterceptor"
class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="schemas">
<value>classpath:ANY_XXX_XX.xsd</value>
</property>
<property name="validateRequest" value="true" />
<property name="validateResponse" value="true" />
</bean>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
If we want to set the properties or as in read values from the properties file for some parameters we can set as below in the same spring ws servlet .xml file
<!-- @Start : Property Mappings for Application -->
<bean id="account-property-mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties" ref="account-configuration" />
</bean>
<!-- Composite configuration -->
<bean id="account-configuration"
class="org.springmodules.commons.configuration.CommonsConfigurationFactoryBean">
<property name="configurations">
<list>
<!-- pick from classpath -->
<bean id="applicationProperties"
class="org.apache.commons.configuration.PropertiesConfiguration">
<constructor-arg type="java.io.File"
value="classpath:webservices.properties" />
</bean>
<!-- The db -->
<bean id="ackdatabaseConfiguration" class="org.apache.commons.configuration.DatabaseConfiguration">
<constructor-arg type="javax.sql.DataSource" ref="dataSource"/>
<constructor-arg index="1" value="PROPERTY"/>
<constructor-arg index="2" value="NAME"/>
<constructor-arg index="3" value="VALUE"/>
</bean>
</list>
</property>
</bean>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2. Generating the Domain Objects from the WSDL generated from above step:
We can use wsimport utility of your JDK installed on your system and we have below command which will also generate the .java files at prescribed location::
wsimport -keep -s C:\SoapTestWSServiceSample\SVN_1.2\ApplicationsWAR\src -p org.utkarsh.ws.jaxb.generated C:\WSDLSample\TestWebService\accountservices.wsdl
This command will be generating the required domain objects which will then be used as a client to consume the web-service exposed and defined under the contract via .wsdl / .xsd file.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3. Writing the Implementation Service Client :
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceTemplate;
import com.pnc.lon.app.exception.WebServiceException;
@Service("wsClientServiceImpl")
public class WSClientServiceImpl {
private static XLogger log = XLoggerFactory.getXLogger(WSClientServiceImpl.class);
@Autowired
private WebServiceTemplate webServiceTemplateV2;
public AnyModel validateAnyRequest(AnyModel anyModel) throws WebServiceException {
ValidateAnyResponse validateAnyResponse = null;
try{
validateAnyResponse = (ValidateAnyResponse) webServiceTemplateV2.marshalSendAndReceive(convertValidateAccountRequest("11456",anyModel));
if(validateAnyResponse == null){
log.error("No Data found. Reponse is null");
return null;
}
//If response status is error, then throw WebServiceException
if(validateAnyResponse != null && (ApplicationConstants.ER.equals(validateAnyResponse.getResult().getStatus().toUpperCase()))){
//throw the exception to UI layer
WebServiceException wsException = new WebServiceException(validateAnyResponse.getResult().getStatus());
throw wsException;
}
} catch (Exception e) {
log.error("Exception thrown while calling validateAnyRequest() web service for MessageID : " + messageId + " : " + e.toString());
WebServiceException wsException = new WebServiceException(e.getMessage() + "for MessageID : " + messageId);
throw wsException;
}
return convertValidateAnyResponse(validateAnyResponse);
}
private ValidateAnyRequest convertValidateAccountRequest(String messageId,AnyModel anyModel) {
ValidateAnyRequest validateAnyRequest = new ValidateAnyRequest();
validateAnyRequest.setAccountCurrency(anyModel.getCurrency());
validateAnyRequest.setAccountName(anyModel.getAccountName());
validateAnyRequest.setAccountNumber(anyModel.getAccountNumber());
return validateAnyRequest;
}
private AnyModel convertValidateResponse(ValidateAnyResponse response) {
AnyModel anyModel = new AnyModel();
anyModel.setName(response.getAccountName());
anyModel.setNumber(response.getAccountNumber());
return anyModel;
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
The Spring documentation requires the client class to extend org.springframework.ws.client.core.support.WebServiceGatewaySupport, which is rather ugly. Instead, I prefer to have WebServiceTemplate injected into my client class.
The
WebServiceTemplate
bean is configured like this in the Service context or wherever you are performing the dependency injection :-<!-- TEST -->
<oxm:jaxb2-marshaller id="wsMarshallerV2" contextPath="com.pnc.lon.ws.jaxb.generated"/>
<bean id="webServiceTemplateV2" class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="marshaller" ref="wsMarshallerV2"/>
<property name="unmarshaller" ref="wsMarshallerV2"/>
<property name="defaultUri" value="https://anyendpoint.domain.com:1234/ANY/AnyContext"/>
<property name="messageSenders">
<list>
<ref bean="httpMsgSender"/>
</list>
</property>
<property name="interceptors">
<list>
<ref bean="webSecurityInterceptor" />
</list>
</property>
</bean>
<!-- Configure Security as in user id password to connect to Web Service -->
<bean id="webSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementMustUnderstand" value="true"/>
<property name="securementUsernameTokenElements" value="Nonce Created"/>
<property name="securementActions" value="UsernameToken"/>
<property name="securementPasswordType" value="PasswordText"></property>
<property name="securementUsername" value="serviceid"/>
<property name="securementPassword" value="@passwdDV"/>
</bean>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4. Invoking the Web Service Client :
a. Set the Request :
AnyModel anyModel = new AnyModel ();
anyModel.setCurrency("USD");
anyModel.setAccountNumber("3000003977");
anyModel = wsClientServiceImpl.validateAnyRequest(false, anyModel) ;
b. Printing any information from the response model object
System.out.println(anyModel.getBalance());
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++