webservice Posts

Using ClientInterceptor to Modify WSDL String Responses on Spring Boot

Usually we are connecting to a middleware product which are able to provide us a prefect and working webservice request and response. But there are rare cases where response are not perfect, therefore breaking the whole application functionality.

In order to prevent that, sometimes we need to replace and sanitize all the response coming from middleware. But how to do it in Spring Boot?

Actually Spring Boot have an interceptor class for “intercepting” all message that comes, it is called a ClientInterceptor class. We could create a new custom class extending on the ClientInterceptor class to handle all WSDL responses, and do all the modification needed.

So first lets create a simple WSDL for this example,

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="Employee"
                  targetNamespace="http://bestpay.payroll/employee" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:tns="http://bestpay.payroll/employee" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <wsdl:types>
        <xsd:schema>
            <xsd:import namespace="http://bestpay.payroll/employee"
                        schemaLocation="employee.xsd" />
        </xsd:schema>
    </wsdl:types>

    <wsdl:message name="employeeLookupRequest">
        <wsdl:part element="tns:EmployeeIdList" name="employeeIdList" />
    </wsdl:message>

    <wsdl:message name="employeeLookupResponse">
        <wsdl:part element="tns:EmployeeInfoList" name="employeeInfoList" />
    </wsdl:message>

    <wsdl:portType name="employeeLookupService">
        <wsdl:operation name="employeeLookup">
            <wsdl:input message="tns:employeeLookupRequest" />
            <wsdl:output message="tns:employeeLookupResponse" />
        </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="employeeLookupBinding" type="tns:employeeLookupService">
        <soap:binding style="document"
                      transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="employeeLookup">
            <soap:operation soapAction="http://bestpay.payroll/employee/employeeLookup" />
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="employeeLookupService">
        <wsdl:port binding="tns:employeeLookupBinding" name="employeeLookupPort">
            <soap:address location="http://localhost" />
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

Next is creating a java project by using maven pom.xml,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath/>
    </parent>

    <groupId>com.edw</groupId>
    <artifactId>WSDLReplaceString</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot-bom.version>2.1.6.Final-redhat-00004</spring-boot-bom.version>
        <start-class>com.edw.Main</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>me.snowdrop</groupId>
                <artifactId>spring-boot-bom</artifactId>
                <version>${spring-boot-bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>0.14.0</version>
                <executions>
                    <execution>
                        <id>HelloWorld</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaDirectory>${project.basedir}/src/main/resources/wsdl/</schemaDirectory>
                            <schemaIncludes>
                                <schemaInclude>*.wsdl</schemaInclude>
                            </schemaIncludes>
                            <generateDirectory>
                                ${project.build.directory}/generated-sources
                            </generateDirectory>
                            <generatePackage>com.edw.wsdl.bean.hello</generatePackage>
                            <extension>true</extension>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Based on above pom files, we could see that Im using JAXB2 maven plugin to convert WSDL file into Java Object, and using Spring WebService for handling all WS request and response.

Only four java classes involved here, and let me start with the first one. It’s going to be our main class.

package com.edw;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class);
    }
}

And a controller class,

package com.edw.controller;

import com.edw.service.ExternalRequestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class MainController {

    @Autowired
    private ExternalRequestService externalRequestService;

    @GetMapping("/")
    public Map index() {
        return new HashMap(){{
            put("response", externalRequestService.getName());
        }};
    }
}

A service class, this is where i put my WSDL endpoint. Basically it would return the employee’s firstname field from WSDL response,

package com.edw.service;

import com.edw.wsdl.bean.hello.EmployeeIdWrapper;
import com.edw.wsdl.bean.hello.EmployeeInfoWrapper;
import com.edw.wsdl.bean.hello.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

import javax.xml.bind.JAXBElement;

@Service
public class ExternalRequestService extends WebServiceGatewaySupport {

    @Autowired
    private WebServiceTemplate webServiceTemplate;

    public String getName() {

        EmployeeIdWrapper employeeIdWrapper = new EmployeeIdWrapper();
        employeeIdWrapper.getEid().add("001002");

        JAXBElement<EmployeeInfoWrapper> employeeInfoJaxB = (JAXBElement<EmployeeInfoWrapper>) webServiceTemplate
                .marshalSendAndReceive("http://localhost:8082",
                        new ObjectFactory().createEmployeeIdList(employeeIdWrapper));

        return employeeInfoJaxB.getValue()
                .getEmployeeInfo()
                .get(0)
                .getFirstName();
    }
}

But all of those code wont be running without a configuration class,

package com.edw.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;

@Configuration
public class SOAPConfig {
    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setPackagesToScan("com.edw.wsdl.bean.hello");
        return marshaller;
    }

    @Bean
    public WebServiceTemplate webServiceTemplate() {
        WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
        webServiceTemplate.setMarshaller(marshaller());
        webServiceTemplate.setUnmarshaller(marshaller());

        return webServiceTemplate;
    }
}

Im using SoapUI for simulating a middleware product which provide wsdl response,

And try do some rest api call with curl

$ curl -kv http://localhost:8081/
*   Trying ::1:8081...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> GET / HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: keep-alive
< Transfer-Encoding: chunked
< Content-Type: application/json
< Date: Wed, 03 Mar 2021 09:53:51 GMT
<
* Connection #0 to host localhost left intact
{"response":"FOO"}                    

As we can see, the response is FOO. Now here comes the scenario where i need to change on-the-fly the response into something else, for this example it would be BAR.

First we need to create a java class to handle all the interception,

package com.edw.config;

import org.springframework.ws.client.WebServiceClientException;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.soap.SoapVersion;
import org.springframework.ws.soap.saaj.SaajSoapMessage;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;

import javax.xml.soap.MessageFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class SOAPClientInterceptor implements ClientInterceptor {

    @Override
    public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
        return true;
    }

    @Override
    public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
        return false;
    }

    @Override
    public void afterCompletion(MessageContext messageContext, Exception e) throws WebServiceClientException {
        OutputStream s = null;
        InputStream is1 = null;
        try {
            SaajSoapMessage message = (SaajSoapMessage) messageContext.getResponse();
            s = new ByteArrayOutputStream();
            message.writeTo(s);

            String response = s.toString();
            System.out.println("SOAP RESPONSE: " + response);

            // do all string replacement here
            response = response.replace("FOO", "BAR");

            is1 = new ByteArrayInputStream(response.getBytes());

            SaajSoapMessageFactory saaj = new SaajSoapMessageFactory();
            saaj.setSoapVersion(SoapVersion.SOAP_12);
            saaj.setMessageFactory(MessageFactory.newInstance());

            messageContext.clearResponse();
            messageContext.setResponse(saaj.createWebServiceMessage(is1));

            System.out.println("New Response has been SET");
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if(s != null)
                try {
                    s.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            if(is1 != null)
                try {
                    is1.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
        }
    }
}

And register this newly created class in our SOAPConfig java class,

    @Bean
    public WebServiceTemplate webServiceTemplate() {
        WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
        webServiceTemplate.setMarshaller(marshaller());
        webServiceTemplate.setUnmarshaller(marshaller());

        // register our interceptor here
        webServiceTemplate.setInterceptors(new ClientInterceptor[]{new SOAPClientInterceptor()});

        return webServiceTemplate;
    }

And we can try to do a rest api call with curl to see whether response has changed or not,

$ curl -kv http://localhost:8081/
*   Trying ::1:8081...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
> GET / HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: keep-alive
< Transfer-Encoding: chunked
< Content-Type: application/json
< Date: Wed, 03 Mar 2021 10:15:49 GMT
<
* Connection #0 to host localhost left intact
{"response":"BAR"}                                      

As we can see, our previous FOO has been changed into BAR which shows that finally we can replace WS response on-the-fly.

Code can be found here,

https://github.com/edwin/spring-boot-ws-response-modification
Google+

Changing JAX-WS Webservice Client’s Target Endpoint Url

Let say you have a wsdl that you get from an url, but you want to fire the webservice generated from it to a different url, see my code below for example,

<service name="TestingService">
	<port name="TestingServicePort" binding="tns:TestingServicePortBinding">
		<soap:address location="http://localhost:8084/WSDLTest/TestingService" />
	</port>
</service>

as you can see, my wsdl is pointing at “http://localhost:8084/WSDLTest/TestingService”. But what if i want to fire my webservice client into another url, without changing the original wsdl. For example, my new url would be http://192.168.0.101/WSDLTest/TestingService.

Well, it’s actualy quite easy. All you need to do is only casting the service interface into interface BindingProvider, and add a new url property. This is what my code would look like,


             TestingService_Service tss = new TestingService_Service();
             TestingService testingService = tss.getTestingServicePort();
             ((BindingProvider) testingService).getRequestContext()
                .put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://192.168.0.101/WSDLTest/TestingService");

If you fire your service again, it will point to a new url (http://192.168.0.101/WSDLTest/TestingService) instead of the old one (http://localhost:8084/WSDLTest/TestingService).

Google+