Create an Asynchronous JAX-WS Web Service and call it from Oracle BPEL 11g

This posting is the result of a simple requirement,
take an existing JAX-WS Web service, convert it to be asynchronous and call it from Oracle BPEL 11g

It turned out that this is not a trivial task...
BPEL has some very specific expectations about the WSDL for an asynchronous process.

One approach is to develop the service starting from a WSDL document that meets BPEL's requirements.
This is possible but requires considerable WSDL authoring skills.
The other approach is to modify the WSDL generated by Web Service Annotations in Java code (Bottom up development) and
instruct JAX-WS to use that WSDL instead of auto generating one from annotations.

This is the approach taken in this article. This posting details how to:


BPEL's WSDL expectations

BPEL expects the WSDL of an Asynchronous web service to be structured in a specific way.


Modifying an existing Synchronous Service

In this post we will modify an existing Department Details Service easily developed using a previous post.

Create a simple Web Service from Java Code using JDeveloper

The DepartmentDetails Service contains a DepartmentFinder class with three methods:

public DepartmentDetails getDepartmentDetails(String deptNumber)
public DepartmentDetails getDepartmentByMgrEmailId(String mgrEmailId)
public List<String>          getDepartments() 


By following these steps we will convert the getDeparmentByMgrEmailId method to be asynchronous with a callback.
  1. Modify the DepartmentFinder.java class annotations and modify the getDepartmentByMgrEmailId method
  2. Add a new annotated Java class named DepartmentFinderResponse.java to drive the generation of the callback method in the WSDL.
  3. Generate two new WSDL files from the annotations in DepartmentFinder and DepartmentFinderResponse.
  4. Combine the two WSDLs into a single WSDL that JAX-WS will utilize instead of dynamically generating a WSDL.
  5. Add a jax-ws-catalog.xml file so the client code will use the wsdl file located in the project.
  6. Generate a Client using the JAX-WS wsimport utility.  The client is used to perform the callback to BPEL.
  7. Modify the web.xml to add a mapping for the callback port.
  8. Compile and Deploy


1) Modify DepartmentFinder.java

We start by adding a @Resource annotation to the source so that we can access SOAP runtime headers,
and we modify the @WebService annotation to add a wsdlLocation attribute.
This will direct JAX-WS to return the static WSDL file we will create shortly rather than generate a WSDL from annotations.
The modified annotations surrounding the class definition appear below.


@WebService(serviceName = "DepartmentFinderService",  targetNamespace = "http://departmentdetailsservice/",
                      portName = "DepartmentFinderPort", wsdlLocation = "/WEB-INF/wsdl/DepartmentFinderService.wsdl")

public class DepartmentFinder {

  @Resource
      WebServiceContext context;
 

Next we will change the getDepartmentsByMgrEmailId  method so the return type is void.
We will also add a @Oneway annotation to indicate that no response is returned by the method.

  @WebMethod
  @Oneway
  public void getDepartmentByMgrEmailId(@WebParam(name = "mgrEmailId")
    String mgrEmailId)  {

Finally we will add a callback() method which implements the callback.
The callback Method() will utilize a client/proxy generated later using the wsimport utility. 

Here is the updated DepartmentFinder.java file



2) Add a new class DepartmentFinderResponse.java to advertise the callback

Add a new class named DepartmentFinderResponse.java to the departmentdetailsserivce package.
This class and its annotations will drive the callback method to be added to the WSDL.
Note that this class does not implement the callback.   More on that later.

            package departmentdetailsservice;

            import javax.jws.Oneway;
            import javax.jws.WebMethod;
            import javax.jws.WebParam;
            import javax.jws.WebService;

            @WebService(serviceName = "DepartmentFinderService",targetNamespace = "http://departmentdetailsservice/",
                                  portName = "DepartmentFinderCallbackPort",   wsdlLocation = "/WEB-INF/wsdl/DepartmentFinderService.wsdl")
            public class DepartmentFinderResponse {

              public DepartmentFinderResponse() {
                super();
              }

              @Oneway
              @WebMethod
              public void getDepartmentByMgrCallback(@WebParam(name = "departmentByMgrCallback",
                                                         targetNamespace = "http://departmentdetailsservice/")
                                                         DepartmentDetails dept)  {
                  // Not implemented
                }
            }


Here is the completed DepartmentFinderResponse.java file





3) Generate the two WSDL files


Add a new WSDL file named DepartmentFinderService.wsdl to the WEB-INF/wsdl  folder in your DepartmentDetailsService project
If the WSDL file is added using JDeveloper the file will be automatically saved in the WEB-INF/wsdl folder.

Next we will generate two wsdl files, one for each port in the service.  We'll save the combined results in the new file.

In the JDeveloper Application Navigator right click on the DepartmentFinder.java file and select
"Show WSDL for Web Service Annotations"
Cut the entire wsdl from the resulting window and save it to the DepartmentFinderService.wsdl  file.

In the JDeveloper Application Navigator right click on the DepartmentFinderResponse.java file and select
"Show WSDL for Web Service Annotations"

4) Combine the WSDL files

Follow these steps to move sections from the wsdl in the generated window into the DepartmentFinderService.wsdl file.
This might look complicated, but it's pretty quick and mechanical.

Copy the <wsdl:port name="DepartmentFinderCallbackPort"> section and insert it after the port in the combined file.
Copy the <wsdl:binding name="DepartmentFinderResponseSoapHttp> section and insert it after the binding in the combined file..
Copy the <wsdl:portType name="DepartmentFinderResponse>  section and insert it after the portType in the combined file.
Copy the <wsdl:message name="getDepartmentByMgrCallbackInput"> section and insert it after the wsdl:message sections in the combined file.
Copy the <wsdl:message name="getDepartmentByMgrCallbackInput"> section and insert it after the wsdl:message sections in the combined file.
Copy the <xs:schema> section and insert it after the existing xs:schema section.
Remove the <xs:complexType name="departmentDetails"> definition since it was already in the combined file.

Three final changes for the combined wsdl file

Add an action to the  wsdl:operation name="getDepartmentByMgrEmailId" element.
Update the the soapAction as follows:
soap:operation soapAction="http://departmentdetailsservice/DepartmentFinder/getDepartmentByMgrEmailIdRequest"

Add the plink:partnerLinkType section above the  <wsdl:types>

<plnk:partnerLinkType name="DepartmentDetailsLink">
        <plnk:role name="DetailsRequestor">
            <plnk:portType name="tns:DepartmentFinder"/>
        </plnk:role>
        <plnk:role name="DetailsReponse">
            <plnk:portType name="tns:DepartmentFinderResponse"/>
        </plnk:role>
    </plnk:partnerLinkType>

Finally add the required namespace import to the <wsdl:definitions> element at the start of the file.
 xmlns:plnk="http://schemas.xmlsoap.org/ws/2003/05/partner-link/"

Here is the completed combined WSDL



5) Add a jax-ws-catalog.xml file

Add a file named jax-ws-catalog.xml to the WEB-INF folder of the project.
Add the following and save the file

<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="system">
        <system systemId="http://localhost/wsdl/DepartmentFinder.wsdl"
            uri="wsdl/DepartmentFinderService.wsdl"/>
 </catalog>



6) Generate the Client used for the callback

Open a command window and change the current directory to the project home.
The src directory should be visible in a directory listing.
The wsimport command ships with JDK6

wsimport -p departmentdetailsservice.client -d classes/ -s src/ -verbose -catalog ./jax-ws-catalog.xml -wsdllocation http://localhost/wsdl/DepartmentFinder.wsdl ./public_html/WEB-INF/wsdl/DepartmentFinderService.wsdl

This will add a package named deparmentdetailsservice.client to the project along with a number of generated Java classes.
No modification to the generated files is required.
In JDeveloper, refresh the view in the Application Navigator to see the newly generated files.


7) Modify the web.xml file to add the callback port mapping

Add a <servlet-mapping> for the DepartmentFindercallBackPort
The updated web.xml is shown below.

<?xml version = '1.0' encoding = 'UTF-8'?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
  <servlet>
    <servlet-name>DepartmentFinderResponse</servlet-name>
    <servlet-class>departmentdetailsservice.DepartmentFinder</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DepartmentFinderResponse</servlet-name>
    <url-pattern>/DepartmentFinderResponse</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>DepartmentFinderResponse</servlet-name>
    <url-pattern>/DepartmentFinderCallbackPort</url-pattern>
  </servlet-mapping>
</web-app>


8) Compile and Deploy

At this point the project should successfully compile and can be deployed to a running WebLogic Server.
In JDeveloper right click on the project name in the Application Navigator and choose deploy.




Call the Asynchronous Service from Oracle BPEL 11g



Based on the new project we can create a new SOA Suite 11g Composite project with a BPEL component that calls
the Asynchronous method and receives the callback.



composite.xml


The Partner link for the departmentDetailsSvc contains values for both the Port Type and the Callback Port Type
based on the PartnerLink section added to the wsdl.

WebService_PartnerLink


The BPEL implementation uses a standard Invoke node to call the service and a Receive node to receive the reply.
Correlation is handled automatically by BPEL using WS-Address Headers.


BPEL_Process



Following is an example of a BPEL trace for a BPEL process that calls the geDepartmetnByMgrEmailId_InputVariable

Flow_trace


Well, clearly this is a ton of fiddling, so...
following is a link to the completed Service and a BPEL component to call it.
Both are available in a single zip from GitHub  Here

See the Readme.txt file in the zip for installation instructions.



Final Notes



Asynchronous from the client's perspective?
The web service in this example is implemented using a Servlet and therefore runs in the Servlet Container.
The Service uses the same request thread to perform the callback. 
Depending on the client, this may result in the client waiting on the request until the callback is complete.
For example when testing with SoapUI, the request processing window will remain busy until the callback is
received by the mockService.
But this is not the case when calling the service from Oracle BPEL 11g,
BPEL will make the oneway call and immediately move to the Receive node and wait for the reply.

For clients that wait for the http response before proceeding, the solution is to modify the web service to perform
the callback on a different thread.
One approach to achieve this is to pass the request to JMS and have a different process dequeue the request and handle the callback.


BPEL's use of WS-Address
By default BPEL will utilize WS-Address headers to send correlation information to the web service callback.
The ReplyTo Address points back to the SOA BPEL engine and contains parameters to identify the running (or dehydrated) process.
BPEL also passes additional context information using "Reference Parameters"
The sample code provided in this post forwards the "Reference Parameters" in the callback.

Here is a sample SOAP request sent by BPEL to the DepartmentDetails Service


<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing">
 <env:Header>
   <wsa:To>http://10.0.2.15:7001/WebServiceExamples-DepartmentDetailsService-context-root/DepartmentFinderCallbackPort</wsa:To> 
   <wsa:Action>http://departmentdetailsservice/DepartmentFinder/getDepartmentByMgrEmailIdRequest</wsa:Action>
   <wsa:MessageID>urn:0E2F0180C13311E1BFF5A56483ADC285</wsa:MessageID>
   <wsa:RelatesTo>urn:0E2F0180C13311E1BFF5A56483ADC285</wsa:RelatesTo>
   <wsa:ReplyTo>
   <wsa:Address>https://soahc-vm:7002/soa-infra/services/default/CallDepartmentDetails!1.0*
                soa_2f3c7dbb-924a-43f0-aa61-ab77ad85c9b8/departmentDetailsSvc%23BPELProcess1/departmentDetailsSvc
   </wsa:Address>
   <wsa:ReferenceParameters>
       <orasoa:PortType xmlns:ns1="http://departmentdetailsservice/" xmlns:orasoa="http://xmlns.oracle.com/soa">
            ns1:DepartmentFinderResponse
       </orasoa:PortType>
       <instra:tracking.ecid xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0">
           11d1def534ea1be0:-48ccee47:1382e7dbc75:-8000-000000000000d24d
           </instra:tracking.ecid>
       <instra:tracking.conversationId xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0">urn:0E2F0180C13311E1BFF5A56483ADC285
       </instra:tracking.conversationId>
       <instra:tracking.parentComponentInstanceId xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0">
           reference:460014</instra:tracking.parentComponentInstanceId>
       <instra:tracking.compositeInstanceCreatedTime xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0">2012-06-28T08:08:02.288-07:00
       </instra:tracking.compositeInstanceCreatedTime>
   </wsa:ReferenceParameters></wsa:ReplyTo>
 </env:Header>
 <env:Body>
     <getDepartmentByMgrEmailId xmlns="http://departmentdetailsservice/">
         <mgrEmailId xmlns="">peterBaines@westco.com</mgrEmailId>
     </getDepartmentByMgrEmailId>
 </env:Body>
</env:Envelope>>



And the corresponding callback Message:


<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<orasoa:PortType xmlns:orasoa="http://xmlns.oracle.com/soa" xmlns:wsa="http://www.w3.org/2005/08/addressing"
                 wsa:IsReferenceParameter="1" xmlns:ns1="http://departmentdetailsservice/"
                 xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">ns1:DepartmentFinderResponse</orasoa:PortType>
<instra:tracking.ecid xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0" xmlns:wsa="http://www.w3.org/2005/08/addressing"
                      wsa:IsReferenceParameter="1" xmlns:ns1="http://departmentdetailsservice/"
                      xmlns:orasoa="http://xmlns.oracle.com/soa"
                      xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
      11d1def534ea1be0:-48ccee47:1382e7dbc75:-8000-0000000000011348
</instra:tracking.ecid>
<instra:tracking.conversationId xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0"
                                xmlns:wsa="http://www.w3.org/2005/08/addressing" wsa:IsReferenceParameter="1"
                                xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
      urn:FFB191F0C14F11E1BFF5A56483ADC285
</instra:tracking.conversationId>
<instra:tracking.parentComponentInstanceId xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0"
                                           xmlns:wsa="http://www.w3.org/2005/08/addressing"
                                           wsa:IsReferenceParameter="1" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
      reference:460015
</instra:tracking.parentComponentInstanceId>
<instra:tracking.compositeInstanceCreatedTime xmlns:instra="http://xmlns.oracle.com/sca/tracking/1.0"
                                              xmlns:wsa="http://www.w3.org/2005/08/addressing"
                                              wsa:IsReferenceParameter="1"
                                              xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
      2012-06-28T11:35:13.395-07:00
</instra:tracking.compositeInstanceCreatedTime>
<work:WorkContext xmlns:work="http://oracle.com/weblogic/soap/workarea/">
      rO0ABXoAAAKcADBvcmFjbGUuZG1zLmNvbnRleHQuaW50ZXJuYWwud2xzLldMU0NvbnRleHRGYW1pbHkAAAFPAAAAKXdlYmxvZ2ljL
      ndvcmthcmVhLlNlcmlhbGl6YWJsZVdvcmtDb250ZXh0AAABh6ztAAVzcgAxd2VibG9naWMud29ya2FyZWEuU2VyaWFsaXphYmxlV2
      9ya0NvbnRleHQkQ2Fycmllcv1CAh9z5wpPAwACWgAHbXV0YWJsZUwADHNlcmlhbGl6YWJsZXQAFkxqYXZhL2lvL1NlcmlhbGl6YWJ
      sZTt4cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB3BAAAAAFzcgBBb3JhY2xlLmRtcy5jb250ZXh0LmludGVybmFsLnds
      cy5XTFNDb250ZXh0RmFtaWx5JFNlcmlhbGl6YWJsZUltcGwAAAAAAAAAAAMAAUwAEW1XTFNDb250ZXh0RmFtaWx5dAAyTG9yYWNsZ
      S9kbXMvY29udGV4dC9pbnRlcm5hbC93bHMvV0xTQ29udGV4dEZhbWlseTt4cQB+AAJ3RABCMS4xMWQxZGVmNTM0ZWExYmUwOi00OG
      NjZWU0NzoxMzgyZTdkYmM3NTotODAwMC0wMDAwMDAwMDAwMDExMzQ4O3YweHcBAXgAJndlYmxvZ2ljLmRpYWdub3N0aWNzLkRpYWd
      ub3N0aWNDb250ZXh0AAABfwAAADJ3ZWJsb2dpYy5kaWFnbm9zdGljcy5jb250ZXh0LkRpYWdub3N0aWNDb250ZXh0SW1wbAAAAD0x
      MWQxZGVmNTM0ZWExYmUwOi00OGNjZWU0NzoxMzgyZTdkYmM3NTotODAwMC0wMDAwMDAwMDAwMDExMzQ4AAAAAAAAAAAAAAA=
</work:WorkContext>
</S:Header>
<S:Body>
   <ns2:getDepartmentByMgrCallback xmlns:ns2="http://departmentdetailsservice/">
    <departmentByMgrCallback>
      <departmentCostCenter>22</departmentCostCenter>
         <departmentManagerEmail>peterBaines@westco.com</departmentManagerEmail>
      <departmentName>Inside Sales</departmentName>
      <departmentNumber>2</departmentNumber>
      <departmentOrg>Sales</departmentOrg>
    </departmentByMgrCallback>
   </ns2:getDepartmentByMgrCallback>
</S:Body>
</S:Envelope>



Use of  the @Addressing(enabled=true, required=true)  annotation
The @Addressing annotation can be added as a Class level annotation to indicate to the caller that
WS-Addressing is in use.  If the required property is set to true then the JAX-WS runtime will return
SOAP Faults if the necessary WS-Address headers are not set.
This annotation was not used in this example because only one method is asynchronous, and setting the
annotation would demand WS-Address headers for all methods.
Depending on your use case this annotation can be useful to verify WS-Address information needed for the callback.


Anonymous
In accordance with the WS-Address specification, BPEL may submit a replyTo Address value of
     http://www.w3.org/2005/08/addressing/anonymous  
if the method is called Synchronously.
If this value appears in log files it indicates that BPEL did not think the operation was an asynchronous call.


Don't do this ...
- Only use the "Show WSDL for Annotations" command to view the WSDL.
  Dont try to regenerate wsdl using the JDeveloper Create Web Service command or even check the
  Web Services types using Web Service Properties editor, in both cases, JDeveloper will overwrite the
  customomized WSDL file pointed to by the wsdlLocation property of the @WebService annotation.
  You might want to keep a copy of the wsdl file of the combined wsdl file just in case.

- Use the wsimport to create the client code.
  Dont use the JDeveloper Generate a Client command by righting click on DepartmentFinderResponse.java
  The command does not appear to understand the jax-ws-catalog.xml file.
  The wsdl paths in the generated code will not be correct.
 
- The Java Web Service Editor in JDeveloper only supports one port per service.
  So expanding the DepartmentFinderService nodes in the Application Navigator  will show only the DepartmentFinder
  and not DepartmentFinderResponse.
  If this is confusing or annoying simply delete the DepartmentFinderService.jaxrpc  file in the src/departmentdetailservice folder.