Saturday, December 26, 2015

Asynchronous web service is a real thing

It has been so long for my wish to come true. And finally, it is proven that asynchronous web service can be done. First, I make a regular SEI:
package org.huahsin.webmethod;

…

@WebService
@SOAPBinding(style=Style.DOCUMENT)
public interface IHelloWorld {

 @WebMethod
 String sayHelloWorld();
}


package org.huahsin.webmethod;

…

@WebService(endpointInterface="org.huahsin.webmethod.IHelloWorld")
public class HelloWorldImpl implements IHelloWorld {

 @Override
 public String sayHelloWorld() {
  return "Hello World";
 }

}
And then construct the following wsimport plugin in POM.xml to generate Java artifact. But this will only generate synchronous method.
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxws-maven-plugin</artifactId>
        <executions>
            <execution>
                <goals>
                    <goal>wsimport</goal>
                </goals>
                <configuration>
                    <wsdlUrls>
                        <wsdlUrl>http://localhost:8080/ws1?wsdl</wsdlUrl>
                    </wsdlUrls>
                    <bindingDirectory>${basedir}/resources/jaxws</bindingDirectory>
                    <keep>true</keep>
                    <packageName>org.huahsin.jaxws</packageName>
                    <sourceDestDir>${basedir}/src</sourceDestDir>             
                </configuration>
            </execution>
        </executions>
    </plugin>
To generate asynchronous method, I would require additional file to bind onto wsimport. It is just a regular XML file located at the path where <buildingDirectory> is pointing to, and the content is as shown in below:
<bindings
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    wsdlLocation="http://localhost:8080/ws1?wsdl"
    xmlns="http://java.sun.com/xml/ns/jaxws">

    <!-- applies to wsdl:definitions node, that would mean the entire wsdl -->
    <enableAsyncMapping>false</enableAsyncMapping>

    <!-- wsdl:portType operation customization -->
    <bindings node="wsdl:definitions/wsdl:portType [@name='IHelloWorld']/wsdl:operation[@name='sayHelloWorld']">
        <enableAsyncMapping>true</enableAsyncMapping>
    </bindings>
  
</bindings>
It's job scope is to locate the sayHelloWorld method using XPath and then convert this method to support asynchronous. While not affecting to the other's behaviour, I remain the rest of the methods as synchronous. Once everything is ready, I would first need a publisher to start the engine before I can do the conversion:
public class Publisher {
 public static void main(String args[]) {
  Endpoint.publish("http://localhost:8080/ws1", new HelloWorldImpl());
 }
}
Then fire the command mvn compile to generate Java artifact. This will have additional two methods generated for asynchronous (as shown in the code snippet below) compare to the default generation.
public interface IHelloWorld {
    public Response<SayHelloWorldResponse> sayHelloWorldAsync();

    public Future<?>sayHelloWorldAsync(
        @WebParam(name = "asyncHandler", targetNamespace = "")
        AsyncHandler<SayHelloWorldResponse> asyncHandler);

    ...
}
Now, to test my code is really works? I would have this simple program firing the asynchronous method:
...

import org.huahsin.jaxws.HelloWorldImplService;
import org.huahsin.jaxws.IHelloWorld;
import org.huahsin.jaxws.SayHelloWorldResponse;

...


public class Client {
 static private String msg = "";
 
 public static void main(String[] args) … {
  System.out.println("before: " + msg);
  e.sayHelloWorldMethod();
  Thread.sleep(1000);
  System.out.println("after: " + msg);
 }

 private void sayHelloWorldMethod() throws MalformedURLException, InterruptedException, ExecutionException {
  URL url = new URL("http://localhost:8080/ws1?wsdl");
  QName qname = new QName("http://webmethod.huahsin.org/", "HelloWorldImplService");
  HelloWorldImplService service = new HelloWorldImplService(url, qname);
  IHelloWorld hello = service.getHelloWorldImplPort();
  
  Response res = hello.sayHelloWorldAsync();
  SayHelloWorldResponse output = (SayHelloWorldResponse) res.get();
  output.getReturn();
  
  hello.sayHelloWorldAsync(new AsyncHandler() {

   @Override
   public void handleResponse(Response res) {
    try {
     setMessage(((SayHelloWorldResponse)res.get()).getReturn());
    }
    catch (InterruptedException | ExecutionException e) {
     e.printStackTrace();
    }
   }
   
  });
 }

 private void setMessage(String msg) {
   this.msg = msg;
 }

}

No comments: