Thursday, October 31, 2013

Dynamic SOAP Service Client

If you have written SOAP service client, you might know that you need the WSDL file; need to generate Java code for that,compile that Java classes and add it as dependency for your module.

What would you do if you have to incorporate your code with a new SOAP service every now and then?
What would you do if all you need is to consume the service and do a little processing on the output, i.e., you need the data in XML format?
What would you do if you don't have a complete WSDL?
What would you do if your service is in .NET whose WSDL is having problem while generating Java classes?
Is there a way to write a dynamic client which can consume any SOAP service?
.... YES!... there is a way.

Let's quickly write a web (SOAP) service.
Software used:
  • Java 7
  • NetBeans IDE 7.4
  • GlassFish 4.0
  • Maven
Create a web project and choose Glassfish as server.


Now add web service (not a rest service) as below.

Edit the SimpleService.java as follows.

package com.mycompany.webapp1;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.ejb.Stateless;

@WebService(serviceName = "SimpleService")
@Stateless
public class SimpleService {

    @WebMethod(action = "myAction", operationName = "hello")
    public String hello(@WebParam(name = "name") String txt) {
        return "Hello " + txt + " !";
    }
}

Run the application. Test the web service as shown below.

Copy and keep the input xml content which will be used below. How do you get your WSDL? Simple. Hit the following url.


Here is your WSDL.

<?xml version='1.0' encoding='UTF-8'?>
<definitions 
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:wsp="http://www.w3.org/ns/ws-policy" 
    xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" 
    xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
    xmlns:tns="http://webapp1.mycompany.com/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns="http://schemas.xmlsoap.org/wsdl/" 
    targetNamespace="http://webapp1.mycompany.com/" 
    name="SimpleService">
    <types>
        <xsd:schema>
            <xsd:import namespace="http://webapp1.mycompany.com/" 
                schemaLocation="http://localhost:8080/SimpleService/SimpleService?xsd=1"/>
        </xsd:schema>
    </types>
    <message name="hello">
        <part name="parameters" element="tns:hello"/>
    </message>
    <message name="helloResponse">
        <part name="parameters" element="tns:helloResponse"/>
    </message>
    <portType name="SimpleService">
        <operation name="hello">
            <input wsam:Action="http://webapp1.mycompany.com/SimpleService/helloRequest" 
                message="tns:hello"/>
            <output wsam:Action="http://webapp1.mycompany.com/SimpleService/helloResponse" 
                message="tns:helloResponse"/>
        </operation>
    </portType>
    <binding name="SimpleServicePortBinding" type="tns:SimpleService">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
        <operation name="hello">
            <soap:operation soapAction="myAction"/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>
    <service name="SimpleService">
        <port name="SimpleServicePort" binding="tns:SimpleServicePortBinding">
            <soap:address location="http://localhost:8080/SimpleService/SimpleService"/>
        </port>
    </service>
</definitions>
Now create a test class as follows.
import java.io.BufferedOutputStream;
import java.io.StringReader;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;

public class WSTest {

    static String request = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            + "<S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\" "
            + "     xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
            + "    <SOAP-ENV:Header/>"
            + "    <S:Body>"
            + "        <ns2:hello xmlns:ns2=\"http://webapp1.mycompany.com/\">"
            + "            <name>test</name>"
            + "        </ns2:hello>"
            + "    </S:Body>"
            + "</S:Envelope>";

    public static void main(String[] args) throws Exception {
        String targetNameSpace = "http://webapp1.mycompany.com/";
        QName serviceName = new QName(targetNameSpace, "SimpleService");
        QName portName = new QName(targetNameSpace, "SimpleServicePort");
        String endpointUrl = "http://localhost:8080/SimpleService/SimpleService";
        String SOAPAction = "myAction";

        SOAPMessage response = invoke(serviceName, portName, endpointUrl, SOAPAction, request);
        SOAPBody body = response.getSOAPBody();
        if (body.hasFault()) {
            System.out.println(body.getFault());
        } else {
            BufferedOutputStream out = new BufferedOutputStream(System.out);
            response.writeTo(out);
            out.flush();
            System.out.println();
        }
    }

    public static SOAPMessage invoke(QName serviceName, QName portName, String endpointUrl, 
            String soapActionUri, String data) throws Exception {
        Service service = Service.create(serviceName);
        service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointUrl);

        Dispatch dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);

        // The soapActionUri is set here. otherwise we get a error on .net based services.
        dispatch.getRequestContext().put(Dispatch.SOAPACTION_USE_PROPERTY, true);
        dispatch.getRequestContext().put(Dispatch.SOAPACTION_URI_PROPERTY, soapActionUri);

        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage message = messageFactory.createMessage();

        SOAPPart soapPart = message.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPBody body = envelope.getBody();

        StreamSource preppedMsgSrc = new StreamSource(new StringReader(data));
        soapPart.setContent(preppedMsgSrc);

        message.saveChanges();

        System.out.println(message.getSOAPBody().getFirstChild().getTextContent());
        SOAPMessage response = (SOAPMessage) dispatch.invoke(message);

        return response;
    }
}

I have hard coded the request content as you saw above. This is copied from the NetBeans test. Here it is for your reference.

<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <S:Body>
        <ns2:hello xmlns:ns2="http://webapp1.mycompany.com/">
            <name>test</name>
        </ns2:hello>
    </S:Body>
</S:Envelope>
Run the program (Shift+F6). Here is the output you will get.
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <S:Body>
        <ns2:helloResponse xmlns:ns2="http://webapp1.mycompany.com/">
            <return>Hello test !</return>
        </ns2:helloResponse>
    </S:Body>
</S:Envelope>

Courtesy: http://www.computing.dcu.ie/~mwang/DI/di.html