Tuesday, November 12, 2013

Spring Integration - Calling SOAP Service using Gateway , Channel and Chain


WebService using Spring Integration

Article to show WebService call ( SOAP ) using  Spring Integration . It uses  Gateway ,  Channel and Router

What you will need 
1. Schema or WSDL to generate the Marshaller and UnMarshaller Objects . 
2. Java Interface to Call WebService
3. Bean configuration defining  gateway , channel , router etc
4. Java Transformer 

Generation of Marshaller and UnMarshaller

Marshaller and a UnMarshaller are basically Java representation of the Request and Response  , these can be generated by xjc utility on schema (or) wsimport on WSDL. If you don't have the schema , it can be generated by the xml using many tools like http://www.freeformatter.com/  Here is example of generating them from schema  using ant ( uses xjc )
 
  <?xml version = "1.0" encoding = "UTF-8"?>

<project name = "mywsproject" 
        default = "generate">
 <property file = "build.properties"/>
 
  <fileset id = "tools" 
           dir = "tools">
    <include name = "**/*.jar"/>
  </fileset> 
 
 <taskdef name = "xjc" classname = "com.sun.tools.xjc.XJCTask">
   <classpath>
     <fileset refid = "tools"/>
   </classpath>
 </taskdef>
 
 <target name = "generate">
  <xjc schema = "src/main/xml/country.xsd" 
      destdir = "src/main/java" 
      package = "com.my.services.model"/>
 </target> 
</project>
 WSDL will have the schema and "wsimport" utility will generate the Classes for Marshaling and Un-Marshaling ( details of the wsimport utility can be found here http://ajaxrocks.blogspot.com/2013/08/jax-ws-call-ws-using-maven.html) . If you are generating using the WSDL there will be the WebService and PortType classes , delete them after generation .

Java Interface to Call WebService

Interface to call the webservice , it uses the generated classes . My case the class is GetCountryRequest and GetCountryResponse
package com.my.services.api;
import javax.xml.bind.annotation.XmlElement;
import io.spring.guides.gs_producing_web_service.GetCountryRequest;
import io.spring.guides.gs_producing_web_service.GetCountryResponse;
public interface MyService {
GetCountryResponse  countryRequest(GetCountryRequest request);
}

Stuff to Configure ( full xml shown below )

Gateway

Specify's the interface and map the method to the header 

<int:gateway id="myService" service-interface="com.my.services.api.MyService"
  default-request-channel="myService_routingChannel">
  <int:method name="countryRequest">
   <int:header name="REQUEST_TYPE" value="countryRequest" />
  </int:method>
 </int:gateway>

Publish-Subscribe Channel

Specify the Channel to map this way we can map the header value to the channel
<int:publish-subscribe-channel id="myService_routingChannel" />

Header-Value Router

Route the call based on the header value to the chain

 <int:header-value-router input-channel="myService_routingChannel" header-name="REQUEST_TYPE">
  <int:mapping value="countryRequest" channel="myService_countryRequestChannel" />
 </int:header-value-router>
 
 <int:channel id="myService_countryRequestChannel" />

Chain


Service-Activation -  transformer and the method to be called in the java class
Soap-Actionws:header-enricher ) - soap-action to be called ( you will find this in the wsdl ). Example this one is commented out as soap-action is empty . You need to provide this otherwise
outbound-gateway  -  WebService end point with message-sender  and unmarshaller

 <int:chain input-channel="myService_countryRequestChannel"
output-channel="myService_countryRequestOutputChannel" >
 
  <int:service-activator ref="MyWSTransformer"
   method="countryRequestSOAPRequest" />
 
  <!--  <ws:header-enricher>
   <ws:soap-action value="" />
  </ws:header-enricher> -->
 
  <ws:outbound-gateway
   uri="http://localhost:8080/ws"
   message-sender="MY.SOAP-MessageSender"  unmarshaller="myrequestJAXBUnMarshaller"/>
 </int:chain>
 

Outbound gateway

Configure the outbound gateway
  <ws:outbound-gateway

   uri="${my.purchase.url}"

   message-sender="MY.SOAP-MessageSender"  unmarshaller="mypurchaseJAXBUnMarshaller"/>

 </int:chain>

Authentication can also be added using
 <beans:bean id = "org.apache.commons.httpclient.Credentials-Service"
               class = "org.apache.commons.httpclient.UsernamePasswordCredentials"
               c:userName = "${userName}"
               c:password = "${password}"/>
and specify the ref as credentials-ref in the ws:outbound-gateway

Full XML


<?xml version = "1.0" encoding = "UTF-8"?>

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans  xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:http="http://www.springframework.org/schema/integration/http"
 xmlns:ws="http://www.springframework.org/schema/integration/ws"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:si-xml="http://www.springframework.org/schema/integration/xml"
  xmlns:oxm = "http://www.springframework.org/schema/oxm"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
  http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/integration/ws http://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd
     http://www.springframework.org/schema/integration/xml http://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd
     http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm.xsd">
 

<!-- <beans:bean id="myManager"
     class="com.my.services.manager.MyServiceImpl" >
    <beans:property name="myService" ref="myService" />
 </beans:bean>
  -->
  <beans:bean id="MyWSTransformer"
     class="com.my.services.transformer.MyWSTransformer" />
 
 <beans:bean id="MY.SOAP-MessageSender"
  class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
  <beans:property name="connectionTimeout" value="3000" />
  <beans:property name="readTimeout" value="5000" />
 </beans:bean>
 
  <int:publish-subscribe-channel id="myService_routingChannel" />
  
 <int:gateway id="myService" service-interface="com.my.services.api.MyService"
  default-request-channel="myService_routingChannel">
  <int:method name="countryRequest">
   <int:header name="REQUEST_TYPE" value="countryRequest" />
  </int:method>
 </int:gateway>
  
 <int:header-value-router input-channel="myService_routingChannel" header-name="REQUEST_TYPE">
  <int:mapping value="countryRequest" channel="myService_countryRequestChannel" />
 </int:header-value-router>
 
 <int:channel id="myService_countryRequestChannel" />
 
 <int:chain input-channel="myService_countryRequestChannel"
output-channel="myService_countryRequestOutputChannel" >
 
  <int:service-activator ref="MyWSTransformer"
   method="countryRequestSOAPRequest" />
 
  <!--  <ws:header-enricher>
   <ws:soap-action value="" />
  </ws:header-enricher> -->
 
  <ws:outbound-gateway
   uri="http://localhost:8080/ws"
   message-sender="MY.SOAP-MessageSender"  unmarshaller="myrequestJAXBUnMarshaller"/>
 </int:chain>
 
 
   <si-xml:unmarshalling-transformer id="countryRequestUnmarshaller"
  input-channel="myService_countryRequestOutputChannel"
  unmarshaller="myrequestJAXBUnMarshaller" />
 
   <oxm:jaxb2-marshaller id = "myrequestJAXBUnMarshaller">
    <oxm:class-to-be-bound name = "io.spring.guides.gs_producing_web_service.GetCountryResponse"/>
   <oxm:class-to-be-bound name = "io.spring.guides.gs_producing_web_service.Country"/>
   <oxm:class-to-be-bound name = "io.spring.guides.gs_producing_web_service.Currency"/>
  <!--   <oxm:class-to-be-bound name = "io.spring.guides.gs_producing_web_service.GetCountryRequest"/> -->
  
 </oxm:jaxb2-marshaller>  
 
 
</beans:beans>

Java Transformer

Transformer has the method that makes the SOAP Request call , its clear if you look at the requestXML string.
GetCountryRequest is already marshaled and available as a input parameter . 

package com.my.services.transformer;



import java.text.SimpleDateFormat;
 
import java.util.Calendar;
 

import org.slf4j.Logger;
 
import org.slf4j.LoggerFactory;
 
import org.springframework.integration.annotation.Header;
import org.springframework.stereotype.Component;

import io.spring.guides.gs_producing_web_service.GetCountryRequest;
 
 
@Component
public class MyWSTransformer {
 
 static private final Logger logger = LoggerFactory.getLogger(MyWSTransformer.class);
 
 public static String countryRequestSOAPRequest(GetCountryRequest request)
 
 {
 
 logger.info("Calling MyWSTransformer with GetCountryRequest " +request.getName());
  String requestXml =" <gs:getCountryRequest xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">"+
         "  <gs:name>"+request.getName()+"</gs:name> "+
      " </gs:getCountryRequest>";
    
  return requestXml; 
 
 }
 
}

2 comments:

Anonymous said...

Hi, I did check the code and looks like stubs are generated? If my interpretation is correct then it is not spring integration.

Arun said...

Yes , Stubs are generated and you can use wsimport or utlitity from your IDE