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.
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.
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
2. Create a JAX-WS Web Service client / proxy for ThoughtfulGreeterService
to invoke the one way (request) operation on the asynchronous interface
Copy and paste the URL for the WSDL of the live web service to be invoked:
Accept all defaults:
Edit the PortClient class: create a request with some content.
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:
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).
Edit the annotations generated on class ThoughtfulGreeterCallbackImpl. Only leave the @WebService annotation and the targetNamespace and name attributes:
@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)
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
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:
6. Modify the Callback Service implementation to process the WS-Addressing headers
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.
7. Modify the Callback Service implementation to make the response available to the calling thread
– using the message id for correlation
// 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)
... 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:
This diagram shows what happened during this conversation:
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
It is very nice material and useful for all. Author Lucas is having very rich experience in Presentation