JAX-WS asynchronous Web Services made simple

Developing rigorous and responsive web service client applications has always been a challenge for architects and developers. JAX-WS 2.0 comes with one effective solution to this problem: asynchronous web service invocation. In this article, we will provide an exposition of this technology with examples built upon the reference implementation.

The JAX-WS programming model offers two models for invoking operations asynchronously – polling and callback. Both methods allow the client to continue processing while waiting for a response.

jboss web services async

Let’s see an example taken from the JAX-WS suite of examples:

package org.jboss.test.ws.jaxws.samples.asynchronous;

import java.util.concurrent.Future;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

@WebService(name = "Endpoint", targetNamespace = "http://org.jboss.ws/jaxws/asynchronous") @SOAPBinding(style = Style.RPC) 

public interface Endpoint {
  @WebMethod(operationName = "echo") 
  public Response echoAsync(@WebParam(name = "String_1") String string1);

  @WebMethod(operationName = "echo") 
  public Future echoAsync(@WebParam(name = "String_1") String string1, @WebParam(name = "asyncHandler") AsyncHandler asyncHandler);
  
  @WebMethod @WebResult(name = "result") 
  public String echo(@WebParam(name = "String_1") String string1);
}

And this is the Service Implementation class:

package org.jboss.test.ws.jaxws.samples.asynchronous;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import org.jboss.logging.Logger;

@WebService(name = "Endpoint", targetNamespace = "http://org.jboss.ws/jaxws/asynchronous") @SOAPBinding(style = Style.RPC) public class EndpointBean {
  private static Logger log = Logger.getLogger(EndpointBean.class);

  @WebMethod @WebResult(name = "result") 
  public String echo(@WebParam(name = "String_1") String msg) {
    log.info("echo: " + msg);
    return msg;
  }
}

As you can see the SEI has two methods echoAsynch each one is implementing a different strategy:

public Response<String> echoAsync(@WebParam(name = "String_1") String string1);

This will use a polling or “pull strategy”. Once the initial request is made the client is free to pursue other work until the request completes. This enables the client to execute other tasks that don’t depend on the outcome of the request. Once the request completes, the response is made available to the client.

On the other hand, using this method:

public Future<?> echoAsync(@WebParam(name = "String_1") String string1, @WebParam(name = "asyncHandler") AsyncHandler<String> asyncHandler);

The actual response is processed by the specified AsyncHandler implementation. Once the response is made available to the callback handler, the code flow is much the same as with polling client.

Callback calls process the response on a different thread from the one the client is running on. This allows the client to gain some control over performance if there are many concurrent asynchronous requests.

Tip: Asynchronous polling clients are especially useful for implementing Web services based work flows. Asynchronous callback clients are helpful when implementing batch processing and notification consumer applications

The web service client

One can implement a JAX-WS asynchronous client using two strategy: one high level which hide the details of the invocation (Dynamic Proxy) and a low level Api called Dispatch.

Web Service client with Dynamic Proxy

We’ll examine at first the Dynamic Proxy approach: the dynamic proxy is the high level API to access web services, here you create an instance of a client proxy using one of getPort methods on the Service:

package org.jboss.test.ws.jaxws.samples.asynchronous;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.xml.namespace.QName;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;
import javax.xml.ws.Service;
import junit.framework.Test;
import org.jboss.wsf.test.JBossWSTest;
import org.jboss.wsf.test.JBossWSTestSetup;
public class AsynchronousProxyTestCase extends JBossWSTest {
  private String targetNS = "http://org.jboss.ws/jaxws/asynchronous";
  private Exception handlerException;
  private boolean asyncHandlerCalled;
  public static Test suite() {
    return new JBossWSTestSetup(AsynchronousProxyTestCase.class, "jaxws-samples-asynchronous.war");
  }
  public void testInvokeAsync() throws Exception {
    Endpoint port = createProxy();
    Response response = port.echoAsync("Async");
    // access future 
    String retStr = (String) response.get();
    assertEquals("Async", retStr);
  }
  public void testInvokeAsyncHandler() throws Exception {
    AsyncHandler handler = new AsyncHandler() {
      public void handleResponse(Response response) {
        try {
          String retStr = (String) response.get(1000, TimeUnit.MILLISECONDS);
          assertEquals("Hello", retStr);
          asyncHandlerCalled = true;
        } catch (Exception ex) {
          handlerException = ex;
        }
      }
    };
    Endpoint port = createProxy();
    Future future = port.echoAsync("Hello", handler);
    future.get(1000, TimeUnit.MILLISECONDS);
    if (handlerException != null) throw handlerException;
    assertTrue("Async handler called", asyncHandlerCalled);
  }
  private Endpoint createProxy() throws MalformedURLException {
    URL wsdlURL = new URL("http://" + getServerHost() + ":8080/jaxws-samples-asynchronous?wsdl");
    QName serviceName = new QName(targetNS, "EndpointBeanService");
    Service service = Service.create(wsdlURL, serviceName);
    return (Endpoint) service.getPort(Endpoint.class);
  }
}

As this class extends JBossWS test suite, it will implement a “suite” method used to setup and deploy your web service on JBoss:

 public static Test suite()
   {
     return
      new JBossWSTestSetup(AsynchronousProxyTestCase.class, "jaxws-samples-asynchronous.war");
   }

The client has 2 test method each one use a different approach to retrieve the data: the first testInvokeAsync uses the pull strategy :

public void testInvokeAsync() throws Exception {
 
  Endpoint port = createProxy();
      Response response = port.echoAsync("Async");     
      String retStr = (String) response.get();
      assertEquals("Async", retStr);
}

Here asynchronous operation invocations are decoupled from the BindingProvider instance at invocation time such that the response context is not updated when the operation completes. Instead a separate response context is made available using the Response interface.

And the other testInvokeAsyncHandler employs a callback mechanism to handle process invocation result.

 public void testInvokeAsyncHandler() throws Exception
   {
      AsyncHandler<String> handler = new AsyncHandler<String>()
      {
         public void handleResponse(Response response)
         {
            try
            {
               String retStr = (String) response.get(1000, TimeUnit.MILLISECONDS);
               assertEquals("Hello", retStr);
               asyncHandlerCalled = true;
            }
            catch (Exception ex)
            {
               handlerException = ex;
            }
         }
      };   
      Endpoint port = createProxy();
      Future future = port.echoAsync("Hello", handler);
 ....
}

Web Service client with Dispatcher

The higher level JAX-WS APIs are designed to hide the details of converting between Java method invocations and the corresponding XML messages, but in some cases operating at the XML message level is desirable.
Dispatch is a low level API that requires clients to construct messages or message payloads as XML and requires an intimate knowledge of the desired message or payload structure. We use this class in order to support input and output of messages or message payloads of any type.

package org.jboss.test.ws.jaxws.samples.asynchronous;
import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Response;
import javax.xml.ws.Service;
import javax.xml.ws.Service.Mode;
import junit.framework.Test;
import org.jboss.wsf.test.JBossWSTest;
import org.jboss.wsf.test.JBossWSTestSetup;
import org.jboss.wsf.common.DOMUtils;
import org.jboss.wsf.common.DOMWriter;
import org.w3c.dom.Element;
public class AsynchronousDispatchTestCase extends JBossWSTest {
  private String targetNS = "http://org.jboss.ws/jaxws/asynchronous";
  private String reqPayload = "<ns2:echo xmlns:ns2="
  " + targetNS + "
  "><string_1></string_1>Hello</ns2:echo>";
  private String expPayload = "<ns2:echoresponse xmlns:ns2="
  " + targetNS + "
  "><result></result>Hello</ns2:echoresponse>";
  private Exception handlerException;
  private boolean asyncHandlerCalled;
  public static Test suite() {
    return new JBossWSTestSetup(AsynchronousDispatchTestCase.class, "jaxws-samples-asynchronous.war");
  }
  public void testInvokeAsynch() throws Exception {
    System.out.println("testInvokeAsynch");
    Source reqObj = new StreamSource(new StringReader(reqPayload));
    Response response = createDispatch().invokeAsync(reqObj);
    verifyResponse((Source) response.get(3000, TimeUnit.MILLISECONDS));
  }
  public void testInvokeAsynchHandler() throws Exception {
    System.out.println("testInvokeAsynchHandler");
    AsyncHandler handler = new AsyncHandler() {
      public void handleResponse(Response response) {
        try {
          verifyResponse((Source) response.get());
          asyncHandlerCalled = true;
        } catch (Exception ex) {
          handlerException = ex;
        }
      }
    };
    StreamSource reqObj = new StreamSource(new StringReader(reqPayload));
    Future future = createDispatch().invokeAsync(reqObj, handler);
    future.get(1000, TimeUnit.MILLISECONDS);
    if (handlerException != null) throw handlerException;
    assertTrue("Async handler called", asyncHandlerCalled);
  }
  private Dispatch createDispatch() throws MalformedURLException {
    URL wsdlURL = new URL("http://" + getServerHost() + ":8080/jaxws-samples-asynchronous?wsdl");
    QName serviceName = new QName(targetNS, "EndpointBeanService");
    QName portName = new QName(targetNS, "EndpointPort");
    Service service = Service.create(wsdlURL, serviceName);
    Dispatch dispatch = service.createDispatch(portName, Source.class, Mode.PAYLOAD);
    return dispatch;
  }
  private void verifyResponse(Source result) throws IOException {
    Element resElement = DOMUtils.sourceToElement(result);
    String resStr = DOMWriter.printNode(resElement, false);
    assertTrue("Unexpected response: " + resStr, resStr.contains("<result></result>Hello"));
  }
}

Dispatch supports two usage modes, identified by the constants javax.xml.ws.Service.Mode.MESSAGE and javax.xml.ws.Service.Mode.PAYLOAD respectively:

Message: In this mode, client applications work directly with protocol-specific message structures. E.g., when used with a SOAP protocol binding, a client application would work directly with a SOAP message.

Message Payload (as in this sample): client applications work with the payload of messages rather than the messages themselves. E.g., when used with a SOAP protocol binding, a client application would work with the contents of the SOAP Body rather than the SOAP message as a whole.