Friday, August 10, 2007

More than meets the eye, or JAX-WS and the not so obvious interfaces.

I really enjoy using JAX-WS I have only come to web services relatively recently but I have found the programming model to be relatively easy to use. Certainly removing the need to create n xml mapping files has made the developer's life much easier.

One problem I have with the design of JAX-WS it the way that you end up having to cast common objects to other helper interfaces. Now most of the implementation is done using proxies so in most debuggers you are going to have a hard time understanding the objects you are passed. Lets just knock up a quick method to list all the interfaces for a bit of debugging:

public class Util
{
    public static void dumpInterfaces(Class t)
    {
       System.out.println("=== Interfaces for " + t.getSimpleName());
       List interfaces = new ArrayList();
       interfaces.add(t);
       for (int i=0; i < interfaces.size(); i++) {
           Class it = interfaces.get(i);
           for (Class sp : it.getInterfaces()) {
               if (!interfaces.contains(sp))
               {
                 interfaces.add(sp);
               }
           }
       }
       
       // List them out
       //
       
       for (Class sp : interfaces)
       {
         System.out.println(sp.getName());
       }
    }

    public static  void dumpMap(String name, Map map) 
    {
        System.out.println("=== " + name);
        for(Map.Entry e : map.entrySet()) {
            System.out.println(e.getKey() + " : " + e.getValue());
        }
    }
}

On the server side lets take a look at what interfaces that might not otherwise be obvious, take this simple web service example:

@WebService
public class Hello {
    
    @Resource
    private WebServiceContext context;
    
    
    public String sayHello(String name) {
        
        Util.dumpInterfaces(context.getClass());
        Util.dumpInterfaces(context.getMessageContext().getClass());
        
        Util.dumpMap("Dumping message context properties",context.getMessageContext());        
        
        return "Hello " + name;
    }
}

This gives the following output for the interfaces. The web service context is less interesting that the message context. This can be cast to SOAPMessageContext which can give you access to the original SOAP message along with the headers. This is an alternative to using Handlers in order to process headers.

=== Interfaces for WebServiceContextImpl
oracle.j2ee.ws.server.jaxws.WebServiceContextImpl
javax.xml.ws.WebServiceContext
=== Interfaces for SOAPMessageContextImpl
oracle.j2ee.ws.common.jaxws.SOAPMessageContextImpl
javax.xml.ws.handler.soap.SOAPMessageContext
javax.xml.ws.handler.MessageContext
java.util.Map

The since the message context is also a map it is relatively easy to list all of the properties defined in there. Here is what I see when I run the service defined above. Part of the problem with JAX-WS is that these properties are extensible so it is hard to get a definitive list of all of them. Something for a future blog I think.

=== Dumping message context properties
javax.xml.ws.binding.attachments.inbound : {}
javax.xml.ws.http.request.pathinfo : null
incomming.addressing.properties : amespace=http://www.w3.org/2005/08/addressing
javax.xml.ws.servlet.context : Application1-Project1-webapp web-app
javax.xml.ws.http.request.querystring : null
javax.xml.ws.http.response.code : null
javax.xml.ws.http.response.headers : null
javax.xml.ws.servlet.request : com.evermind.server.http.EvermindHttpServletRequest@109b23b
oracle.j2ee.ws.context.Message : oracle.j2ee.ws.saaj.soap.soap11.Message11@1945696
javax.xml.ws.binding.attachments.outbound : {}
javax.xml.ws.servlet.response : com.evermind.server.http.EvermindHttpServletResponse@1c0bec5
javax.xml.ws.http.request.headers : {SOAPACTION=[""], CONNECTION=[Keep-Alive, TE], CONTENT-LENGTH=[202], USER-AGENT=[Oracle HTTPClient Version 10h], TE=[trailers, deflate, gzip, compress], ACCEPT-ENCODING=[gzip, x-gzip, compress, x-compress], CONTENT-TYPE=[text/xml; charset=ASCII], HOST=[localhost:8988], ECID-CONTEXT=[HIINUXQV`[YegG@DKKGSLNWWZZ[Zbef@BKCAGSLNS[TQ]}
javax.xml.ws.http.request.method : POST
javax.xml.ws.handler.message.outbound : false

We can do something similar with the on the client side, so if we were to create a simple client:

public class Client {
    public Client() {
    }
    
    public static void main(String argv[]) {
        HelloService hs = new HelloService();
        Hello h = hs.getHelloPort();
        Util.dumpInterfaces(h.getClass());
        
        BindingProvider bp = (BindingProvider)h;
        Util.dumpInterfaces(bp.getBinding().getClass());
        Util.dumpMap("Request context",bp.getRequestContext());
        Util.dumpMap("Response context",bp.getResponseContext());
    }
}

We can see that the port is implemented using a proxy object that implement both the SEI and the BindingProvider interface. The BindingProvider interface is perhaps not that much of a secret, although I would have thought that a more usable design for the client generation would have seen the SEI extend this interface to make the programming model cleaner.

=== Interfaces for $Proxy12
$Proxy12
project1.proxy.Hello
javax.xml.ws.BindingProvider
=== Interfaces for SOAPBindingImpl
oracle.j2ee.ws.client.jaxws.SOAPBindingImpl
javax.xml.ws.soap.SOAPBinding
javax.xml.ws.Binding
=== Request context
javax.xml.ws.binding.attachments.outbound : {}
=== Response context

It is worth noting that again there is a SOAP variant that you need to cast to in order to get all of the functionality available to you. Of course you have to be careful that you understand the context of your service. For example if you were to using the HTTP binding than the Binding object would of course be HTTP not SOAPBinding.

Finally where I have dumped out a the contents of a map, these represent the current state when running on an interim build of OC4J 11. As noted before there are many more properties that are available that can be added depending on what platform and extensions you are using.

Update: A nice summary document for the standard MessageContext and BindingContext parameters can be found here.

No comments: