Friday, December 19, 2008

Custom ReplyTo headers in WS-Addressing

This post builds on code in my previous blog so take a quick look at that for some context.

One of the problems with dealing with asynchronous responses is having to deal with correlating the responses. Now you could do this using the message ID from the original message; but this is a pain and not very application specific. Instead the WS-Addressing specification allows you to specify application specific reference parameters that are used in the response. This can be any XML element so you have a lot of control over what is sent back.

For the purposes of this blog lets create a simple header bean class to be used in the communication. It needs to be a JAXB bean with @XmlRootElement and a namespace. (If you leave the namespace blank you get a null pointer exception somewhere deep within JAX-WS-RI):

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(namespace="http://www.example.org")
public class HeaderBean {
    String property;

    public void setProperty(String property) {
        this.property = property;
    }

    public String getProperty() {
        return property;
    }
}

Let modify the callback class from the previous blog to read the header value. In this case I am using @WebParam(header=true) for the convenience of mapping the header to java.

@WebService(targetNamespace = "http://project1/", name = "Hello")
@XmlSeeAlso( { ObjectFactory.class })
public class HelloCallback {
    @WebMethod
    @Action(input = "http://project1/Hello/sayHelloResponse")
    @RequestWrapper(localName = "sayHelloResponse",
                     targetNamespace = "http://project1/",
                     className = "project1.SayHelloResponse")
    @Oneway
    public void sayHello(
        @WebParam(targetNamespace = "", name = "return") String ret,
        @WebParam(header = true, targetNamespace = "http://www.example.org", name = "headerBean") HeaderBean hb) {
        
        System.out.println(ret);
        
    }
}

Now we modify the client to include the extra header. Unfortunately WSEndpointReference is in terms of Element so we have a little bit of extra code to marshal the bean in to XML. In this simple case it might have been easier to just write the XML directly; but I would see it is worth the extra work for more complicated constructs:

public static void main(String[] args) throws JAXBException {
    helloService = new HelloService();


    Endpoint e =
        Endpoint.publish("http://localhost:7890/endpoint", new HelloCallback());


    HeaderBean n = new HeaderBean();
    n.setProperty("applicationModuleID");


    DOMResult result = new DOMResult();
    JAXBContext.newInstance(HeaderBean.class).createMarshaller().marshal(n,
                                                                         result);

    WSEndpointReference replyTo =
        new WSEndpointReference(e.getEndpointReference((Element)result.getNode().getFirstChild()));


    Hello hello =
        helloService.getHelloPort(new WebServiceFeature[] { new OneWayFeature(true,
                                                                              replyTo) });


    // This method will return null
    //

    Object ret = hello.sayHello("Bob");
}

So here is the outgoing message, note that the extra header information is passed in the reference parameters field. You need to consider whether this information need to be encrypted in some way.

<?xml version = '1.0' encoding = 'UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Header>
      <To xmlns="http://www.w3.org/2005/08/addressing">http://localhost.localdomain:7101/Application1-Project1-context-root/HelloPort</To>
      <Action xmlns="http://www.w3.org/2005/08/addressing">http://project1/Hello/sayHelloRequest</Action>
      <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
         <Address>http://localhost:7890/endpoint</Address>
         <ReferenceParameters>
            <headerBean xmlns:ns2="http://www.w3.org/2005/08/addressing" xmlns="http://www.example.org" xmlns:wsa="http://www.w3.org/2005/08/addressing">
               <property>applicationModuleID</property>
            </headerBean>
         </ReferenceParameters>
         <Metadata/>
      </ReplyTo>
      <MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:3b9e7b20-3aa0-4a4a-9422-470fa7b9ada1</MessageID>
   </S:Header>
   <S:Body>
      <ns2:sayHello xmlns:ns2="http://project1/">
         <arg0>Bob</arg0>
      </ns2:sayHello>
   </S:Body>
</S:Envelope>

And here is the response:

<?xml version = '1.0' encoding = 'UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Header>
      <To xmlns="http://www.w3.org/2005/08/addressing">http://localhost:7890/endpoint</To>
      <Action xmlns="http://www.w3.org/2005/08/addressing">http://project1/Hello/sayHelloResponse</Action>
      <MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:9d0be951-79fc-4a56-b3e6-4775bde2bd82</MessageID>
      <RelatesTo xmlns="http://www.w3.org/2005/08/addressing">uuid:3b9e7b20-3aa0-4a4a-9422-470fa7b9ada1</RelatesTo>
      <headerBean xmlns:wsa="http://www.w3.org/2005/08/addressing" wsa:IsReferenceParameter="1" xmlns:ns2="http://www.w3.org/2005/08/addressing" xmlns="http://www.example.org">
         <property>applicationModuleID</property>
      </headerBean>
   </S:Header>
   <S:Body>
      <ns2:sayHelloResponse xmlns:ns2="http://project1/">
         <return>Bob</return>
      </ns2:sayHelloResponse>
   </S:Body>
</S:Envelope>

The reason I am looking into this is that we were wondering what the best way would be for a ADF JSF page to invoke a BPEL service and properly channel the response back to the originating AM. Using the reference parameters makes this job much easier as you can more easily relate it to the domain.

2 comments:

Clubtropicana said...

I noticed that many developers still have problematics on this topic, in particular for handling of asynchronous callback WS messagges. The send is simple, but the receive or polling is never explained.

Gerard Davison said...

Hey, erm, C12a

The previous blog posting to this shows the response being dealt with are you saying you would want more details? Generally I don't focus on polling as this seemed less of a issue programtically; but if you can give me an idea of scenarios / technology that you are looking at I would be happy to try put together a post in the new year,

Gerard

Gerard