rev. 1.3 - May 19, 2006
Contents
Overview
Preliminary Setup
Standard vs. Custom Invocation
Getting Started
ApproveLoan Web Service
IAeInvokeHandler
Implementing the Custom Invoke Handler
Registering the Custom Invoke Handler
Deploying
Conclusions

Creating and Using Custom Invoke Handlers, Part 1: Fundamentals

Overview

In the first of a three-part series you will get an introduction to replacing the ActiveBPEL server's standard invocation mechanism of SOAP over HTTP with a custom invocation mechanism when calling partner-provided Web services. This article demonstrates creating a basic custom invoke handler using a plain old Java object (POJO).

In part two, the process will be expanded to include an underwriting section to assess risk level of a borrower. We will create a custom invoke handler for this AssessRisk service in which we will explore more advanced topics of the custom invoke handler including complex message types, multiple service operations, and fault handling.

In part three, we will re-create the custom invoke handler for the ApproveLoan service as an Enterprise JavaBean and understand how to configure and deploy this to ActiveBPEL Enterprise running in a full J2EE application server.

To get the most from this article, you need a reasonable understanding of BPEL and the standard deployment mechanisms of BPEL processes to an ActiveBPEL server.

Preliminary Setup

All code segments shown in this article are from the code included with the sample. The sample contains the complete implementation including project files for use in Eclipse and/or ActiveBPEL Designer. The custom invoke handler API classes you need are located in ae_wsio.jar. In addition to this JAR you need to reference a few other supporting JAR files to successfully compile the custom invoke handler example. These JARs are wsdl4j.jar, jaxen-1.1-beta-2.jar, axis.jar, and jaxrpc.jar. All of these files are located in the engine distribution's lib folder.

If you plan to use Eclipse to view and compile the example project then you need to import the existing project included with the download into Eclipse as follows:

  1. From Eclipse, select File / Import / Existing project into workspace and navigate to the custom_invoke folder where you unzip'd the example project and click the OK button
  2. At this point you may see errors about "Unbound classpath variable", to correct this right click on the project in the Package Explorer and select Properties. Next, select Java Build Path, select the Libraries tab, and click the Add Variable... button. Click the Configure Variables... button in the dialog that appears, and then click the New... button. Create a variable named AESAMPLES_LIB that points to the sample library directory. Note that you may need to run the 'ProOPPeddddd' build target in that directory to get the currently active libraries for the ActiveBPEL product you're using. Click here or more information on the Sample Library.

If you plan to use ActiveBPEL Designer to view, simulate, or deploy the example process then you need to import the exiting project included with the download as follows:

  1. From ActiveBPEL Designer, select File / Import / Existing project into workspace and navigate to the custom_invoke folder where you unzip'd the example project and click the OK button

If you're not using either of these tools, you can still use the files in this sample. Here's an explanation of the archive's directory structure:

custom_invoke
├─── doc
├─── src
│    └─── org
│         └─── activebpel
│              └─── samples
│                   └─── custom_invoke  
│                        └─── ejb      <-- Source files for EJB classes
│                        └─── pojo     <-- Source files for custom invoke handlers
│                        └─── util     <-- Source files for utility classes
└─── support
     └─── bpel_process
          ├─── bpel      <-- BPEL source files
          ├─── META-INF  <-- WSDL catalog files used for deployment
          └─── wsdl      <-- Web Service definition files

Standard vs. Custom Invocation of Service Endpoints

The ActiveBPEL server's standard invocation framework is enabled by default when a BPEL process is deployed. In this scenario, a service's endpoint is identified in the process deployment descriptor (PDD) with standard Partner Role attributes. When an invoke activity is performed from within your BPEL process, the engine uses its internal Web service framework to package the message into a SOAP request and send it over the HTTP protocol to the designated service endpoint address. Instead of using this standard invocation mechanism, you can implement a custom invoke handler to bypass the creation and transport of SOAP messages over HTTP.

The custom invoke handler can be thought of as a pseudo custom WSDL binding. So instead of binding the abstract WSDL definitions to SOAP over HTTP we instead bind the abstract WSDL definitions to our custom invoke handler. Except that the custom invoke handler binding is defined within the Process Deployment Descriptor and not in the WSDL definition.

You may want to create a custom invoke handler to:

Getting Started

Let's begin by taking a look at the business process we are using in this article. It is a simplified loan approval process for a lending institution. It starts by receiving a loan application from a borrower. Then the application goes to a manual approval process. Depending on the outcome of the manual approval process the loan is either approved or not. This process is illustrated in Figure 1.

Figure 1. The loan approval process

In the first part of this series, we look at creating and configuring a custom invoke handler for the ApproveLoan service, circled in Figure 1 above, using a plain old Java object.

ApproveLoan Web Service

Before we can actually create our custom invoke handler for the ApproveLoan service we need to first have an understanding of the underlying WSDL document for this service. The abstract functionality of the ApproveLoan service, shown in Listing 1, describes a single operation named approve which takes three arguments as input: firstName, lastName, and amount. Collectively these arguments represent the loan application details. The operation returns a Boolean value indicating whether or not the loan has been approved.

Listing 1. The ApproveLoan WSDL Document

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="LoanApproverService">
   <message name="approveRequest">
      <part name="firstName" type="xs:string"/>
      <part name="lastName" type="xs:string"/>
      <part name="amount" type="xs:float"/>
   </message>

   <message name="approveResponse">
      <part name="approved" type="xs:boolean"/>
   </message>

   <portType name="LoanApproverPortType">
      <operation name="approve"
                      parameterOrder="firstName lastName amount">
         <input message="tns:approveRequest"/>
         <output message="tns:approveResponse"/>
      </operation>
   </portType>
<definitions>

In the above listing there is only one operation we need to worry about, that is the approve operation. So when the approve operation is invoked from the process the ActiveBPEL server delegates the handling of this service to our custom invoke handler, a plain old Java object, rather than going through the standard SOAP over HTTP invocation.

IAeInvokeHandler

The first thing that our plain old Java object needs to do in order to become a custom invoke handler is to implement the org.activebpel.wsio.invoke.IAeInvokeHandler interface, shown in Listing 2. This interface is packaged as part of ActiveBPEL's Web service input/output Java library.

Listing 2. The org.activebpel.wsio.invoke.IAeInvokeHandler interface

public interface IAeInvokeHandler
{                                            
   IAeWebServiceResponse handleInvoke(IAeInvoke invokeReqest,
                                      String queryData);
}

As you might expect, the ActiveBPEL server is responsible for calling the handleInvoke method on our custom invoke handler passing in the request message and if needed, additional business or configuration information. Listing 3 shows our custom invoke handler implementing the IAeInvokeHandler interface.

Listing 3. Our Initial Custom Invoke Handler

public class LoanApproverInvokeHandler implements IAeInvokeHandler {

   public IAeWebServiceResponse handleInvoke(IAeInvoke invokeRequest,  
                                             String queryData)
   {

   }
}

Implementing the ApproveLoan Custom Invoke Handler

In creating our ApproveLoan custom invoke handler there are five basic steps we must perform.

They are:

  1. Extract the message data from the service's input message
  2. Extract any extra query string information from the invoke (optional)
  3. Perform the required business logic
  4. Create the service's output message
  5. Return the service's output message

The first thing we do is to extract the message parts being passed in from the process. At this point we must defer to the WSDL document for the ApproveLoan service to help us. Referring back to Listing 1, the approve operation's input message named approveRequest has three parts each of a schema simple type associated with it: firstName, lastName, and amount. It is these three parts we want to extract and use in our custom invoke handler. Using the IAeInvoke object, which is passed into the handleInvoke method, we gain access to the input message data by calling the getInputMessageData() method which returns an IAeWebServiceMessageData object. We then call getMessageData() on IAeWebServiceMessageData which returns a Java Map which we use to extract the message parts, shown in Listing 4. Notice that the key-name used in the Map's get method is the same name as the message part name.

Listing 4. Extracting the message data from the service's request message

Map reqMsgParts = invokeRequest.getInputMessageData().getMessageData();

String firstName = (String) reqMsgParts.get("firstName");
String lastName = (String) reqMsgParts.get("lastName");
Float amount = (Float) reqMsgParts.get("amount");

The next step, although optional, can prove useful in many situations where data above and beyond what appears in the service's input message is needed by the custom invoke handler. The data is statically passed to the custom invoke handler using standard query string parameters appended to the handler's URL when it is registered during deployment of the process. How this registration is done will be covered in the next section, but for now lets focus on how we are using this information in our custom invoke handler. Part of the approval process is to determine if the amount of the loan is greater than a threshold value. It is this threshold value, the maximum loan amount, which we are going to pass in from the process as a query string parameter (i.e., ?maxLoan=1000000). The way to get access to these parameters is shown in Listing 5.

Listing 5. Extracting any extra query string information from the invoke

Map queryDataMap = AeInvokeHelper.parseQueryData(queryData);
Float maxLoanAmount = new Float((String) queryDataMap.get("maxLoan"));

The code in Listing 5 utilizes the parseQueryData method on a helper class (AeInvokeHelper) that is included as part of this example. This method takes the query string parameters that are passed into the handleInvoke method as a string and converts it to a Map object making it easier to extract the values.

Next up is the most important part of our custom invoke handler, the actual service's business logic. Using both the service's input message data and optionally any additional query string parameters, implement the logic necessary to satisfy the invocation of the service. There are many different ways of implementing the business logic. For example, the logic can be directly part of the custom invoke handler or the logic can be extracted into a separate object, an Enterprise JavaBean (EJB), or the message can placed in a JMS Queue or Topic.

Now that the business logic has been performed we need to create and populate the appropriate response message. Again we need to defer to the WSDL document for the ApproveLoan service to help us create the response. Referring back to Listing 1, the approve operation's output message named approveResponse has a single part associated with it named approved. A sample document representing a valid response for the approve operation is shown in Listing 6.

Listing 6. Valid Sample Response Document

<ns1:approveResponse xmlns:ns1="
http://docs.active-endpoints.com/activebpel/sample/wsdl/LoanApproval/2005/ 
             06/loanapprover_2006-09.wsdl">
   <approved>true</approved>
</ns1:approveResponse>

Using the message element name and its namespace we create a QName object representing the fully qualified message name for the approve operation's output message. To create the message parts we create a Java Map object. For each part, we put into the Map the name of the part and the part's value. In our example there is only one part named approved and it takes a Boolean to indicate if the loan was approved. Both of these steps are shown in Listing 7.

Listing 7. Creating the Service's Output Message

QName respMsqQname = new
      QName("http://docs.active-endpoints.com/activebpel/sample/wsdl/LoanApproval/2005/           
             06/loanapprover_2006-09.wsdl", "approveResponse");
Map respMsgParts = new HashMap();
respMsgParts.put("approved", Boolean.valueOf(isLoanApproved));

The final step is to return the output message back to the calling process. This is achieved by first creating an instance of AeWebServiceMessageData which takes the qualified name (QName) of the output message and the Map containing the output message's parts. Then create an instance of AeInvokeResponse and associate the AeWebServiceMessageData created previously to the AeInvokeResponse instance using setMessageData(). Finally, return the AeInvokeResponse message from the handleInvoke method back to the calling process. This step is shown in Listing 8.

Listing 8. Returning the service's output message to the process

AeWebServiceMessageData msgData =
    new AeWebServiceMessageData(respMsqQname, respMsgParts);
AeInvokeResponse respMsg = new AeInvokeResponse();
respMsg.setMessageData(msgData);
return respMsg;

Registering theCustom Invoke Handler

Now that the custom invoke handler has been completed, we need to indicate to the ActiveBPEL server that is should use the custom invoke handler we just created when calling the ApproveLoan service. This is accomplished by modifying the process deployment descriptor (PDD). A standard PDD using static endpoint references is shown in Listing 9.  Note that the ApproveLoan service defines a partner link partner role named Approving

Listing 9. PDD fragment for the loan approval process.

<process ...>
  ...
  <partnerLinks>
    <partnerLink name="Approving">
      <partnerRole endpointReference="static">
        <wsa:EndpointReference xmlns:s="http://docs.active-endpoints.com/activebpel/
            sample/wsdl/LoanApproval/2006/09/loanapprover_2006-09.wsdl">
          <wsa:Address>http://no.target.address.specified</wsa:Address>
          <wsa:ServiceName PortName="LoanApproverSOAPBinding">
            s:LoanApproverService
          </wsa:ServiceName>
        </wsa:EndpointReference>
      </partnerRole>
    </partnerLink>

    <partnerLink name="LoanProcessing">
      <myRole allowedRoles="" binding="MSG" service="LoanProcessingService"/>
    </partnerLink>
 </partnerLinks>
 ...
</process>

To tell the ActiveBPEL server not to use SOAP over HTTP to invoke the ApproveLoan service we need to modify the partnerRole element for the ApproveLoan service. An additional attribute named customInvokeURL needs to be added to the partnerRole element. The customInvokeURL's value is comprised of the particular protocol to use when invoking the custom invoke handler, the resource name itself (i.e., the custom invoke handler), and optionally a query string containing additional parameters to pass in to the custom invoke handler. The protocol is either going to be java or ejb to indicate how the custom invoke handler is implemented (as either a plain old Java object or as an Enterprise JavaBean respectively). Because we have created a custom invoke handler using a plain old Java object we specify the java protocol. The resource name is the fully qualified Java class name representing our custom invoke handler. And the query string provides the ability to pass additional information above and beyond what appears in the service's input message. Recall that our custom invoke handler uses the maxLoan parameter to pass in the maximum loan amount the lender is able to approve. It is the query string where you would set this up to be passed in to the custom invoke handler. The updated PDD with the modification highlighted is shown in Listing 10.

Listing 10. Updated PDD fragment specifying a custom invoke handler.

<process ...>
  ...
  <partnerLinks>
    <partnerLink name="Approving">
      <partnerRole endpointReference="static" 
invokeHandler="java:
            org.activebpel.samples.custom_invoke.pojo.LoanApproverInvokeHandler
            ?maxLoan=1000000"
>
        <wsa:EndpointReference xmlns:s="http://docs.active-endpoints.com/
         activebpel/sample/wsdl/LoanApproval/2006/09/loanapprover_2006-09.wsdl">
          <wsa:Address>http://no.target.address.specified</wsa:Address>
          <wsa:ServiceName PortName="LoanApproverSOAPBinding">
            s:LoanApproverService
          </wsa:ServiceName>
        </wsa:EndpointReference>
      </partnerRole>
    </partnerLink>

    <partnerLink name="LoanProcessing">
      <myRole allowedRoles="" binding="MSG" service="LoanProcessingService"/>
    </partnerLink>
  </partnerLinks>
  ...
</process>

Deploying the process and the custom invoke handler

There is nothing special in deploying a BPEL process using a custom invoke handler other than the modifications to the PDD discussed in the previous section.

To deploy the custom invoke handler requires that it and any supporting classes be available somewhere on the server's class path either as a series of .class files or a Java archive. This location will vary depending on the type of application server and configuration you have. For example, if you were using ActiveBPEL Engine (or ActiveBPEL Enterprise for Tomcat) then putting the classes into a Java Archive and placing the archive in %CATALINA_HOME%/shared/lib would do the trick.

At this point you should be able to call the BPEL process from a client application. If all went well your new custom invoke handler for theApproveLoan service will be called directly by theActiveBPEL server bypassing the SOAP over HTTP invocation layer.

Conclusions

ActiveBPEL server provide the ability to bypass the standard invocation of services using SOAP over HTTP and directly invoke the service's implementation class using the Custom Invoke Handler API.