Pages

Wednesday, May 21, 2014

How to use the WS-Addressing in OneWay method with JAX-WS

It is a continuation of the previous post about WS-Addressing where I describe how to enable the WS-Addressing in client . Here I am going to show you how to make use of it. In this example I will create the Web Service with two one way methods. Here is my SEI:

import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

@WebService(wsdlLocation = "wsdlWithoutReplyToHeadr.wsdl")
public interface Hello {

    @WebMethod
    @Oneway
    public void callbackMessage(String msg);

    @WebMethod
    @Oneway
    public void sayHello(String name,
            @WebParam(header = true,
                    name = "ReplyTo",
                    targetNamespace = "http://www.w3.org/2005/08/addressing")
                    W3CEndpointReference w3cEpr);
}

The first doesn't required explanation. But there is an interesting thing in the second one. The special class W3CEndpointReference is used as the parameter of that method. From the WebParam annotation you see that this class will be created from the {http://www.w3.org/2005/08/addressing}ReplyTo header, which is the one defined in WS-Addressing recommendation which is pointing to the endpoint to which the response should be send to. There is one little problem with this SEI. If we generate the WSDL from it then there would be included this ReplyTo header explicitly. WS-Addressing should be described as a Policy, so this is not acceptable. To handle this we need to create our own WSDL without ReplayTo header in it and set it in wsdlLocation parameter. From such an WSDL we can generate the valid SEI for client. I will not bother with that and I will just create it. I will name it HelloClient:
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService(name = "Hello")
public interface HelloClient {

    @WebMethod
    @Oneway
    public void callbackMessage(String msg);

    @WebMethod
    @Oneway
    public void sayHello(String name);
}

Now lets take a look at the implementation of the service:
import javax.jws.WebService;
import javax.xml.ws.soap.Addressing;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

@Addressing(required = true)
@WebService(endpointInterface = "com.example.Hello")
public class HelloImpl implements Hello {

    @Override
    public void sayHello(String name,
            W3CEndpointReference w3cEpr) {
        HelloClient proxy = w3cEpr.getPort(HelloClient.class);
        proxy.callbackMessage("Hello "+name);
    }

    @Override
    public void callbackMessage(String msg) {
        System.out.println(msg);
    }

}

The method callbackMessage is simply print the message which it gets. And yes, I am going to call it from the sayHello method, but not directly but as the call to the service. I am using SEI HelloClient create before to create the proxy to the service which implements it. The endpoint address, WSDL and another information required to create it would be get from the W3CEndpointReference provided to this method, which would be build from ReplayTo header from SOAP request. And the last part, lets see the client, which initiates the message exchange:
import com.sun.xml.ws.api.addressing.OneWayFeature;
import com.sun.xml.ws.api.addressing.WSEndpointReference;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.soap.AddressingFeature;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;


public class Client {

    public static void main(String[] args) throws Exception {               
        System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
        URL url = new URL("http://localhost:8080/ws/HelloService?wsdl");
        QName qname = new QName("http://com.example/", "HelloImplService");

        Service service = Service.create(url, qname);
        W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
        builder.address("http://localhost:8080/ws/HelloService");
        builder.serviceName(qname);
        builder.interfaceName(new QName("http://com.example/","HelloImpl"));
        builder.endpointName(new QName("http://com.example/","HelloImplPort"));
        builder.wsdlDocumentLocation("http://localhost:8080/ws/HelloService?wsdl");
         WSEndpointReference replyTo =  
        new WSEndpointReference(builder.build());  
        
        HelloClient hello = service.getPort(new QName("http://com.example/","HelloImplPort"),
                HelloClient.class,
                new AddressingFeature(),
                new OneWayFeature(true,replyTo));        
        hello.sayHello("Michal");
    }
}
The JAX-WS doesn't provide a way to set the header directly in client (you need to use SOAPHandlers to achive this), so I've used an Metro specific API, which is WSEndpointReference and OneWayFeature. This will cause to set the create EPR to be set as ReplyTo header.

When you publish the service and run the client then the message would look like:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Header>
      <To xmlns="http://www.w3.org/2005/08/addressing">http://localhost:8080/ws/HelloService</To>
      <Action xmlns="http://www.w3.org/2005/08/addressing">http://com.example/Hello/sayHello</Action>
      <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
         <Address>http://localhost:8080/ws/HelloService</Address>
         <ReferenceParameters />
         <Metadata xmlns:wsdli="http://www.w3.org/ns/wsdl-instance" wsdli:wsdlLocation="http://localhost:8080/ws/HelloService?wsdl">
            <wsam:InterfaceName xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:wsns="http://com.example/">wsns:HelloImpl</wsam:InterfaceName>
            <wsam:ServiceName xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:wsns="com.example/" EndpointName="HelloImplPort">wsns:HelloImplService</wsam:ServiceName>
         </Metadata>
      </ReplyTo>
      <MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:f1928b28-73b3-41e1-bc95-1a0757af9f19</MessageID>
   </S:Header>
   <S:Body>
      <ns2:sayHello xmlns:ns2="http://com.example/">
         <arg0>Michal</arg0>
      </ns2:sayHello>
   </S:Body>
</S:Envelope>
The EPR would be parsed and provide to service method in w3cEpr parameter and then all the information about the service will be used to create the new client in service (which only for simplicity points to itself) and make a call. And the result will be:
Hello Michal