Invoke an Asynchronous Web Service from Java (using only Java SE, no Java EE container) image38

Invoke an Asynchronous Web Service from Java (using only Java SE, no Java EE container)

A recent article (http://blog.soasuitehandbook.org/implement-an-asynchronous-web-service-using-jax-ws-in-jdeveloper/) described how to publish an Asynchronous Web Service from Java (using JAX-WS). It also showed how such a Web Service can be tested using SoapUI. A crucial aspect in the interaction with the Asynchronous Web Service is the use of WS-Addressing headers to communicate the callback address (where the response can be sent) and the message id (for the correlation of that response to whatever thread or session invoked the web service).

This article describes how we can invoke an asynchronous Web Service from ‘plain Java’. In this case too, we have to set the WS-Addressing headers. We also have to expose the callback service – and we will do so without the use of a Java EE container. Finally we have to find a way to correlate the callback response – received by a random thread in the JVM – to the thread that made the original call.

image

The implementation is done in these steps:

  • Create a JAX-WS Web Service client / proxy to invoke the one way (request) operation on the asynchronous interface
  • Create a JAX-WS Web Service based on the callback interface specified in the WSDL document for the asynchronous service
  • Edit the Web Service client to make it publish the callback service (outside of a Java EE container) add a self-chose port and path (using the Endpoint class)
  • Edit the Web Service client to set the required WS-Addressing headers in the call of the Asynchronous Web Service to direct in calling back
  • Modify the Callback Service implementation to make the response available to the calling thread – using the message id for correlation
  • Modify the Web Service client to poll the Callback Service implementation class for the response (note: here we could have used an observer pattern for a more elegant implementation)

Note: this article is a cross post from the Oracle SOA Suite Handbook blog.

The Web Service interface we work with in this article is the ThoughtfulGreeterService – an asynchronous version of hello world. Though simple, it represents most of the essentials of Java based invocation of an asynchronous web service.

http://blog.soasuitehandbook.org/wp-content/uploads/2014/06/SNAGHTML1c7c2772.png

We assume that an implementation of the ThoughtfulGreeterService has been created an the service is live, up an running. The article http://blog.soasuitehandbook.org/implement-an-asynchronous-web-service-using-jax-ws-in-jdeveloper/ describes how to implement this service and contains all the source code (a JDeveloper application) for running it.

1. Create JDeveloper application

image

image

2. Create a JAX-WS Web Service client / proxy for ThoughtfulGreeterService

to invoke the one way (request) operation on the asynchronous interface

image

Copy and paste the URL for the WSDL of the live web service to be invoked:

image

Accept all defaults:

image

image

image

image

Edit the PortClient class: create a request with some content.

image

public static void main(String[] args) {
ThoughtfulGreeterService thoughtfulGreeterService = new ThoughtfulGreeterService();
ThoughtfulGreeter thoughtfulGreeter = thoughtfulGreeterService.getThoughtfulGreeterSOAP11BindingPort();

// Add your code to call the desired methods.
GreetingRequestType payload = new GreetingRequestType();
payload.setName("Maarten");
thoughtfulGreeter.helloToWorld(payload);

}
}

Run this class. The web service will be invoked.

In this case, it is the service described in the article mentioned. This service write a line to the console whenever it receives a request:

image

3. Create a JAX-WS Web Service based on the callback interface of ThoughtfulGreeterService

specified in the WSDL document for the asynchronous service

(to prevent having to remove stuff we do not need later on, I have commented out the synchronous portType and its binding in the WSDL document; the only portType and binding to be used for the creation of the Java Web Service is the callback one).

image

image

image

image

image

image

Edit the annotations generated on class ThoughtfulGreeterCallbackImpl. Only leave the @WebService annotation and the targetNamespace and name attributes:

image

@WebService(targetNamespace = "saibot.airport/services/pr",
name = "ThoughtfulGreeterCallback")
@XmlSeeAlso({ ObjectFactory.class })
public class ThoughtfulGreeterCallbackImpl {
public ThoughtfulGreeterCallbackImpl() {
}

@WebMethod(action = "saibot.airport/services/pr/helloFromWorld")
@SOAPBinding(parameterStyle = ParameterStyle.BARE)
@Action(input = "saibot.airport/services/pr/helloFromWorld")
@Oneway
public void helloFromWorld(@WebParam(name = "responseMessage", partName = "payload",
targetNamespace = "saibot.airport/services/pr") GreetingResponseType payload) {
System.out.println("Received the asynchronous reply");

4. Edit the Web Service client to make it publish the callback service

(outside of a Java EE container) add a self-chose port and path (using the Endpoint class)

image

5. Edit the Web Service client to set the required WS-Addressing headers

in the call of the Asynchronous Web Service to direct in calling back

image

package airport.saibot.services.pr;

import airport.saibot.callbackservices.ThoughtfulGreeterCallbackImpl;

import com.sun.xml.ws.api.addressing.AddressingVersion;
import com.sun.xml.ws.api.addressing.WSEndpointReference;
import com.sun.xml.ws.developer.WSBindingProvider;
import com.sun.xml.ws.message.StringHeader;

import java.util.UUID;

import javax.xml.ws.Endpoint;

public class ThoughtfulGreeterSOAP11BindingPortClient {

private static final AddressingVersion WS_ADDR_VER = AddressingVersion.W3C;

public static void main(String[] args) {
ThoughtfulGreeterService thoughtfulGreeterService = new ThoughtfulGreeterService();
ThoughtfulGreeter thoughtfulGreeter = thoughtfulGreeterService.getThoughtfulGreeterSOAP11BindingPort();

/* the next lines are added to publish the callback service */
// publish the callback service at the address specified
String callbackServiceAddress = "http://localhost:7898/callbackEndpoint";
Endpoint e = Endpoint.publish(callbackServiceAddress, new ThoughtfulGreeterCallbackImpl());

// Add your code to call the desired methods.
GreetingRequestType payload = new GreetingRequestType();
payload.setName("Maarten");

// Get the request context to set the outgoing addressing properties
WSBindingProvider wsbp = (WSBindingProvider) thoughtfulGreeter;
WSEndpointReference replyTo = new WSEndpointReference(callbackServiceAddress, WS_ADDR_VER);
String uuid = "uuid:" + UUID.randomUUID();

wsbp.setOutboundHeaders(new StringHeader(WS_ADDR_VER.messageIDTag, uuid),
replyTo.createHeader(WS_ADDR_VER.replyToTag));

thoughtfulGreeter.helloToWorld(payload);

}
}

If we now run the main method, the remote web service will write the WS Addressing details it has received to the console:

image

6. Modify the Callback Service implementation to process the WS-Addressing headers

image

import javax.xml.ws.WebServiceContext;
import javax.xml.ws.soap.Addressing;
import com.sun.xml.ws.api.addressing.AddressingVersion;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.api.message.HeaderList;

import javax.jws.soap.SOAPBinding.ParameterStyle;

import javax.xml.ws.Action;

@WebService(targetNamespace = "saibot.airport/services/pr",
name = "ThoughtfulGreeterCallback")
@XmlSeeAlso({ ObjectFactory.class })
@Addressing(enabled = true, required = false)
public class ThoughtfulGreeterCallbackImpl {
@Resource
private WebServiceContext wsContext;

private static final AddressingVersion WS_ADDR_VER =
AddressingVersion.W3C;
public ThoughtfulGreeterCallbackImpl() {
}

@WebMethod(action = "saibot.airport/services/pr/helloFromWorld")
@SOAPBinding(parameterStyle = ParameterStyle.BARE)
@Action(input = "saibot.airport/services/pr/helloFromWorld")
@Oneway
public void helloFromWorld(@WebParam(name = "responseMessage", partName = "payload",
targetNamespace = "saibot.airport/services/pr") GreetingResponseType payload) {
System.out.println("Received the asynchronous reply");

// get the messageId to correlate this reply with the original request
HeaderList headerList =
(HeaderList) wsContext.getMessageContext()
.get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
Header realtesToheader = headerList.get(WS_ADDR_VER.relatesToTag, true);
String relatesToMessageId = realtesToheader.getStringContent();
System.out.println("RelatesTo message id: " + relatesToMessageId);
System.out.println("payload: '" + payload + "'");
}
}

Note: In the @Addressing annotation, I have set required = false to handle the MissingAddressingHeaderException was getting in the callback class for the Action header.

image

7. Modify the Callback Service implementation to make the response available to the calling thread

– using the message id for correlation

image

// static map (cross thread) to hold the asynchronous responses with as key the MessageId
public static Map<String,Object> asynchResponses = new HashMap<String,Object>();

private static final AddressingVersion WS_ADDR_VER =
AddressingVersion.W3C;
public ThoughtfulGreeterCallbackImpl() {
}

@WebMethod(action = "saibot.airport/services/pr/helloFromWorld")
@SOAPBinding(parameterStyle = ParameterStyle.BARE)
@Action(input = "saibot.airport/services/pr/helloFromWorld")
@Oneway
public void helloFromWorld(@WebParam(name = "responseMessage", partName = "payload",
targetNamespace = "saibot.airport/services/pr") GreetingResponseType payload) {
System.out.println("Received the asynchronous reply");

// get the messageId to correlate this reply with the original request
HeaderList headerList =
(HeaderList) wsContext.getMessageContext()
.get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
Header realtesToheader = headerList.get(WS_ADDR_VER.relatesToTag, true);
String relatesToMessageId = realtesToheader.getStringContent();
System.out.println("RelatesTo message id: " + relatesToMessageId);

System.out.println("payload: '" + payload + "'");
// make response available to interested parties
// publishing an event would be even neater!
asynchResponses.put(relatesToMessageId, payload);

}

8. Modify the Web Service client to poll the Callback Service implementation class for the response

(note: here we could have used an observer pattern for a more elegant implementation)

image

...
GreetingResponseType response = null;
// now poll ThoughtfulGreeterCallbackImpl.asynchResponses to see whether the response for uuid is already in
boolean responseReceived = false;
long stopPollingTime = System.currentTimeMillis() + 30000; // actual time in milliseconds from Jan 1st 1970.
while (!responseReceived && System.currentTimeMillis() < stopPollingTime) {
System.out.println("Still Polling");
try {
if (ThoughtfulGreeterCallbackImpl.asynchResponses.containsKey(uuid)) {
response = (GreetingResponseType) ThoughtfulGreeterCallbackImpl.asynchResponses.get(uuid);
responseReceived = true;
ThoughtfulGreeterCallbackImpl.asynchResponses.remove(uuid);
}
Thread.sleep(200);

} catch (InterruptedException ie) {
}
} //while
System.out.println("Response received from asyncn callback " + response.getGreeting());
...

9. Run the Web Service client

and wait for the response to come in:

image

This diagram shows what happened during this conversation:

image

Resources

Download JDeveloper 12c Application with the sources: AsynchrounousServiceInvokerJDeveloper12cApplication.

Publish a WebService from a POJA (plain old Java application) – that is: out of the container using EndPoint class– Lucas Jellema on the AMIS Technology Blog

Implement an Asynchronous Web Service using JAX-WS in JDeveloper – on the SOA Suite Handbook Blog

Invoke a single port async service using JAX-WS and WS-Addressing – Gerard Davison

How to call a webservice directly from Java (without webservice library) – Frank Houweling on the AMIS Technology Blog

Building an asynchronous web service with JAX-WS – Edwin Biemond

One Response

  1. Moorthy June 5, 2014