Friday, January 25, 2013

JAXWS proxy

JDK 6 has the wsimport tool that can be used to generate java classes based on the given wsdl file. Then the webservice client can use those classes to call the web service. The following is the example code:
public void testMyService(){
  MyService service = new MyService();
  port = service.getMyPort();
  port.doSomething();
}
In the above, the class MyService is generated by wsimport. It has many annotations related to web service. It is a subclass of javax.xml.ws.Service. MyService is actually a proxy. The following is from the javadoc of the class Service:

Service objects provide the client view of a Web service.

Service acts as a factory of the following:

  • Proxies for a target service endpoint.
  • Instances of {@link javax.xml.ws.Dispatch} for dynamic message-oriented invocation of a remote operation.
Now the questions are these. How is the proxy actually created in the runtime? How are those annotations used? The following is the stack of the method calls at the point when the proxy is created:
 Thread [Main Thread] (Suspended (breakpoint at line 574 in Proxy)) 
  Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler) line: 574 
  AnnotationParser.annotationForMap(Class, Map) line: 239 
  AnnotationParser.parseAnnotation(ByteBuffer, ConstantPool, Class, boolean) line: 229 
  AnnotationParser.parseAnnotations2(byte[], ConstantPool, Class) line: 69 
  AnnotationParser.parseAnnotations(byte[], ConstantPool, Class) line: 52 
  Class<T>.initAnnotationsIfNecessary() line: 3070 
  Class<T>.getAnnotation(Class<A>) line: 3029 
  AnnotationType.<init>(Class<?>) line: 113 
  AnnotationType.getInstance(Class) line: 66 
  AnnotationParser.parseAnnotation(ByteBuffer, ConstantPool, Class, boolean) line: 202 
  AnnotationParser.parseAnnotations2(byte[], ConstantPool, Class) line: 69 
  AnnotationParser.parseAnnotations(byte[], ConstantPool, Class) line: 52 
  Class<T>.initAnnotationsIfNecessary() line: 3070 
  Class<T>.getAnnotation(Class<A>) line: 3029 
  RuntimeInlineAnnotationReader.getClassAnnotation(Class<A>, Class, Locatable) line: 106 
  RuntimeInlineAnnotationReader.getClassAnnotation(Class, Object, Locatable) line: 57 
  RuntimeModelBuilder(ModelBuilder<T,C,F,M>).getTypeInfo(Ref<T,C>) line: 330 
  JAXBContextImpl.getTypeInfoSet() line: 460 
  JAXBContextImpl.<init>(JAXBContextImpl$JAXBContextBuilder) line: 298 
  JAXBContextImpl.<init>(JAXBContextImpl$JAXBContextBuilder, JAXBContextImpl$1) line: 141 
  JAXBContextImpl$JAXBContextBuilder.build() line: 1163 
  ContextFactory.createContext(Class[], Map) line: 145 
  NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method] 
  NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39 
  DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25 
  Method.invoke(Object, Object...) line: 597 
  ContextFinder.newInstance(Class[], Map, String) line: 202 
  ContextFinder.find(Class[], Map) line: 363 
  JAXBContext.newInstance(Class[], Map) line: 574 
  JAXBContext.newInstance(Class...) line: 522 
  ProviderImpl$2.run() line: 209 
  ProviderImpl$2.run() line: 206 
  ProviderImpl.getEPRJaxbContext() line: 206 [local variables unavailable] 
  ProviderImpl.() line: 77 [local variables unavailable] 
  NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method] 
  NativeConstructorAccessorImpl.newInstance(Object[]) line: 39 
  DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 27 
  Constructor<T>.newInstance(Object...) line: 513 
  Class<T>.newInstance0() line: 355 [local variables unavailable] 
  Class<T>.newInstance() line: 308 [local variables unavailable] 
  FactoryFinder.newInstance(String, ClassLoader) line: 31 
  FactoryFinder.find(String, String) line: 128 
  Provider.provider() line: 83 [local variables unavailable] 
  MyService(Service).<init>(URL, QName) line: 56 
  MyService.<init>() line: 46 
  SimpleClient.testMyService() line: 86 
  SimpleClient.main(String[]) line: 108 
This stack gives some clue about how the proxy is created.
  1. MyService is the class generated by wsimport.
  2. The following call to the constructor
         service = new MyService()
    
    calls the constructor of the super class Service. And the following code is executed:
       protected Service(java.net.URL wsdlDocumentLocation, QName serviceName) {
            delegate = Provider.provider().createServiceDelegate(wsdlDocumentLocation,
                    serviceName,
                    this.getClass());
        }
        
    This corresponds to
            MyService(Service).<init>(URL, QName) line: 56 
         
    in the stack.
  3. Now you can see that the Provider picks up the duty and will do all the heavy work. It uses the input parameters wsdlDocumentLocation, serviceName and Class to create the delegate object. The Provider is javax.xml.ws.spi.Provider. My guess is that the Class object this.getClass() contains all the annotation information of the class MyService. The stack shows the process uses the following classes:
    (1)RuntimeInlineAnnotationReader (package: com.sun.xml.bind.v2.model.annotation)
    (2)AnnotationParser (package: sun.reflect.annotation)

  4. These classes process the annotations.

Monday, January 21, 2013

How to set handlers on the JAX-WS client side

In webservice, handlers can be used for logging messages, implmenting security,etc. For a JAX-WS client, there are several ways to set handlers.

Use xml binding

The handlers can be set by binding a handler XML file.
import javax.xml.ws.Service;

@WebServiceClient(name = "MyService", targetNamespace = "...", wsdlLocation = "...")
@HandlerChain(file = "MyService_handler.xml")
public class MyService extends Service{
...
}
MyService_handler.xml:
<?xml version="1.0" encoding="UTF-8"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee">
  <javaee:handler-chain>
    <javaee:handler>
      <javaee:handler-class>com.my.SOAPHandler1</javaee:handler-class>
    </javaee:handler>
    <javaee:handler>
      <javaee:handler-class>com.my.SOAPHandler2</javaee:handler-class>
    </javaee:handler>
  
  </javaee:handler-chain>
</javaee:handler-chains>

Set handlers programmatically

The handlers can also be set programmatically. You can use BindingProvider to set the handlers.
import javax.xml.ws.BindingProvider;

 MyService service = new MyService(wsdlLoc, qName);
 port = service.getXXX();
 BindingProvider bp = (BindingProvider) port;
 Binding binding = bp.getBinding();
 List<Handler> handlerList = binding.getHandlerChain();
 MySOAPHandler myHandler = new MySOAPHandler();
 handlerList.add(myHandler );
 bp.getBinding().setHandlerChain(handlerList);

 port.callXYZ(...);
        
You can also use HandlerResolver to set handlers.

    import javax.xml.ws.handler.Handler;
    import javax.xml.ws.handler.HandlerResolver;
    import javax.xml.ws.handler.PortInfo;

        MyService service = new MyService(wsdlLoc, qName);
       
        service.setHandlerResolver(new HandlerResolver() {
        @Override public List getHandlerChain(PortInfo portInfo) { 
         List<Handler> handlerChain = new ArrayList<Handler>(); 
         handlerChain.add(new MySOAPHandler()); 
         return handlerChain; 
       } 
     });
     
      port = service.getXXX();
      port.callXYZ(...);
But be careful that the code "port = service.getXXX()" must be put after service.setHandlerResolver(...). If it is put before the call setHandlerResolver(...), the code won't work.

References

  1. http://www.javaworld.com/javaworld/jw-02-2007/jw-02-handler.html
  2. http://jax-ws.java.net/nonav/2.2.7/docs/index.html