rev. 1.2 - May 19, 2006
Contents
Overview
Preliminary Setup
Getting Started
AssessLoan Web Service
Implementing the Custom Invoke Handler
Handling Multiple Operations
Complex Message Types
Returning Service Faults
Conclusions

Creating and Using Custom Invoke Handlers, Part 2: Advanced Concepts

Overview

Part one gives you an introduction to replacing the ActiveBPEL server's standard invocation mechanism (SOAP over HTTP) with a custom invocation mechanism when calling partner-provided Web services. If are not yet familiar with custom invoke handlers please take a look at part one first.

In this second installment we will explore more advanced topics of the custom invoke handler including WSDL definitions containing multiple operations for a given port type, messages containing complex message types rather than simple message types, and how to return service faults defined for a given operation.

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 included sample code. The example 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 JARs to successfully compile the custom invoke handler example. These JARs are: wsdl4j.jar; jaxen-1.1-beta-2.jar; axis.jar; 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 'ant ActiveBPEL|Tomcat|JBoss' 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

Getting Started

Let's begin by taking a look at the business process we are using in this article. It is an expanded version of the loan approval process we used in part 1 of this series. It starts by receiving a loan application from a borrower. If the amount of the loan is less than $10,000 then the application goes directly to an automatic underwriting service that decides if the borrower is considered a high risk or low risk. If the borrower is considered a low risk then their loan is automatically approved. If the amount borrowed is greater than $10,000 or the borrower is considered a high risk 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

There are two <invoke> activities in this BPEL process, ApproveLoan and AssessRisk. In part one of this series, we looked at creating and configuring a custom invoke handler for the ApproveLoan service using a plain old Java object. Now we will create a custom invoke handler for the AssessRisk service, circled in Figure 1 above, in which we will explore more advanced topics of the custom invoke handler dealing with multiple operations, complex message types, and returning faults.

AssessLoan Web Service

Before we can actually create our custom invoke handler for the AssessLoan service we need to have a better understanding of the underlying WSDL document for this service. The abstract functionality of the AssessLoan 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 AssessLoan WSDL Document

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="LoanAssessorService">
    <types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xs:import schemaLocation="loanprocess_2006-09.xsd" />
        </xs:schema>
    </types>

    <message name="assessRiskRequest">
        <part name="Document" element="types:assessRisk"/>
    </message>
   
    <message name="assessRiskResponse">
        <part name="Document" element="types:assessRiskResponse"/>
    </message>
       
    <message name="assessRiskLoanProcessFault">
        <part name="Document" element="types:LoanProcessFault"/>
    </message>
   
    <message name="statusInquiryRequest">
        <part name="Document" element="types:statusInquiry"/>
    </message>
   
    <message name="statusInquiryResponse">
        <part name="Document" element="types:statusInquiryResponse"/>
    </message>

    <portType name="LoanAssessorPortType">
        <operation name="assessRisk">
            <input message="tns:assessRiskRequest"/>
            <output message="tns:assessRiskResponse"/>
            <fault name="LoanProcessFault" message="tns:assessRiskLoanProcessFault"/>
        </operation>
        <operation name="statusInquiry">
            <input message="tns:statusInquiryRequest"/>
            <output message="tns:statusInquiryResponse"/>
        </operation>
    </portType>
<definitions>

In the above listing the portType, LoanAssessorPortType, defines two operations assessRisk and statusInquiry. So when either 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.

Implementing the AssessLoan Custom Invoke Handler

In creating our AssessLoan custom invoke handler we need to perform the same five basic steps as we did in part one (please refer back to part one in this series for the specifics)..

As a refresher the steps to implement the handler 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 either the service's output message or a particular service fault

However, the ApproveLoan WSDL's portType, used in part 1, defined only one operation. In cases where the WSDL's portType contains multiple operations there is one additional step that needs to be performed first and that is determining which operation is being invoked.

Handling Multiple Operations

Recall from part one that a custom invoke handler is effectively like a Java binding for a WSDL portType. And since the portType in listing 1 defines multiple operations then our custom invoke handler first needs to determine which operation is being invoked. We obtain which operation is being invoked by calling getOperation() on IAeInvoke, which is passed into the handleInvoke method. For clarity, we wrap each WSDL operation into its own Java method and perform all the work for that operation within that method. So when the operation being invoked is assessRisk then the doAssessRisk method is performed and so on for each defined operation.

Listing 2. Determining which operation on the portType is being invoked

String opName = invokeRequest.getOperation();
if (opName.equals("assessRisk")) {
   respMsg = this.doAssessRisk(invokeRequest, queryData);

} else if (opName.equals("statusInquiry")) {
   respMsg = this.doStatusInquiry(invokeRequest, queryData);

}

Complex Message Types

In part one, the WSDL's message parts were all defined using simple schema types (i.e., string, integer, etc.). As such, it made getting at the underlying values rather easy. This is not the case when the message parts are of complex types or elements, e.g., Customer type, Loan Application type, etc.

Referring back to Listing 1, the assessRisk operation's input message named assessRiskRequest has a single part named Document and is of type assessRisk. Listing 3 shows the relevant schema for the assessRisk type while Listing 4 depicts a sample instance document for assessRisk type.

Listing 3. Relevant Schema for the assessRisk type

<xs:element name="assessRisk">
   <xs:complexType>
      <xs:sequence>
         <xs:element ref="tns:LoanApplication" />
      </xs:sequence>
   </xs:complexType>
</xs:element>

<xs:element name="LoanApplication" type="tns:LoanApplicationType" />

<xs:complexType name="LoanApplicationType">
   <xs:sequence>
      <xs:element name="firstName" type="xs:string" />
      <xs:element name="lastName" type="xs:string" />
      <xs:element name="amount" type="xs:float" />
   </xs:sequence>
</xs:complexType>

Listing 4. Sample instance document for assessRisk

<assessRisk
       xmlns="http://schemas.active-endpoints.com/
       sample/LoanApproval/2006/09/loanprocess_2006-09.xsd">
   <LoanApplication>
      <firstName>C.D.</firstName>
      <lastName>Moon</lastName>
      <amount>10200.53</amount>
   </LoanApplication>
</assessRisk>

Getting access to the service's input message parts is no different than how we did it previously in step one of the LoanApproverInvokeHandler.java example. The one big difference is that what we will always get a W3C Dom object back when we get a particular part based on its name from the Java Map object as shown in Listing 5 (Note: the key-name used in the Map's get method is the same name as the message part name).

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

Map reqMsgParts = invokeRequest.getInputMessageData().getMessageData();
Document reqDoc = (Document) reqMsgParts.get("Document");

Once you have a reference to the W3C DOM object (i.e., the Document) you can pull the relevant data from it using one of several different approaches including:

There is no correct approach to extracting the data from the Document and there are probably other methods you can use as well as those listed above. It is beyond the scope of this article to explain these approaches in detail. However you can look at the LoanAssessorInvokeHandler.java code example for working example of some of the approaches to extract the data from the Document. It is important to note that the custom invoke handler framework provides the ability to receive and send Document objects (DOM) only.

On the flip side of getting access to the input message is returning the output message back to the calling process. In particular, once the business logic has been performed we need to return a message and a single part named Document which is of a complexType.

Referring back to Listing 1, the assessRisk operation's output message named assessRiskResponse has a single part named Document and is of type assessRiskResponse. Listing 6 shows the relevant schema for the assessRiskResponse type while Listing 7 depicts a sample instance document for assessRiskResponse type.

Listing 6. Relevant Schema for the assessRiskResponse type

<xs:element name="assessRiskResponse">
   <xs:complexType>
      <xs:sequence>
         <xs:element ref="tns:RiskAssessment" />
      </xs:sequence>
   </xs:complexType>
</xs:element>

<xs:element name="RiskAssessment" type="tns:RiskAssessmentType" />

<xs:simpleType name="RiskAssessmentType">
   <xs:restriction base="xs:string">
      <xs:enumeration value="high" />
      <xs:enumeration value="low" />
   </xs:restriction>
</xs:simpleType>

Listing 7. Sample instance document for assessRiskResponse

<assessRiskResponse
       xmlns="http://schemas.active-endpoints.com/
       sample/LoanApproval/2006/09/loanprocess_2006-09.xsd">
   <RiskAssessment>high</RiskAssessment>
</assessRiskResponse>

The custom invoke handler framework is expecting the message part to be a Document conforming to the assessRiskResponse type. A new Document therefore must be created which conforms to the schema and contains the business data for the risk assessment (e.g., low or high). There are many approaches to creating this Document including:

It is beyond the scope of this article to detail these approaches but have a look at the LoanAssessorInvokeHandler.java code example for working examples of some of the approaches to creating a message output Document object.

Once the Document is created representing the message part, all that is left is to create the service's output message and then return the service's output message to the calling process. The code do to this is no different than how we did this in the LoanApproverInvokeHandler.java example (See Steps 4 and 5 from that example).

Returning Service Faults

When a WSDL document defines one or more fault messages you have the opportunity to return one of those faults back to the calling process instead of the normal output message. As shown in Listing 1, the LoanAssessor WSDL defines one fault named LoanProcessFault which refers to the assessRiskLoanProcessFault message which contains one part named Document of type LoanProcessFault.

Creating the message and message parts is identical to how you do this for normal output messages. Keep in mind fault message parts like normal message parts can either be of a simple schema type or complex schema type so how you go about creating the message part is dependent on what is defined in the types section of the WSDL In the case of the LoanAssessor WSDL the message part is defined as a complexType. This is shown in Listing 8.

Listing 8. Creating a Service Fault Message

QName faultMsqQName = new QName(
   "http://docs.active-endpoints.com/activebpel/
   sample/wsdl/LoanApproval/2006/09/loanassessor_2006-09.wsdl",
    "assessRiskLoanProcessFault");
Map faultMsgParts = new HashMap();
faultMsgParts.put("Document", faultDocument);
AeWebServiceMessageData faultMsgData =
    new AeWebServiceMessageData(faultMsgQName, faultMsgParts);

Since there can be multiple faults defined for a given WSDL you must indicate which fault name you are throwing. You do this by creating a QName object representing the fault name. Then instead of calling the setMessageData() on the AeInvokeResponse object for returning the normal output message we call the setFaultData() which takes the qualified name (QName) of the operation's fault and the AeWebServiceMessageData created previously.

Listing 9. Returning a Service's Fault Message to the Process

QName faultQName = new QName(
   "http://docs.active-endpoints.com/activebpel/
   sample/wsdl/LoanApproval/2006/09/loanassessor_2006-09.wsdl",
    "LoanProcessFault");

AeInvokeResponse respMsg = new AeInvokeResponse();
respMsg.setFaultData(faultQName, faultMsgData);
retunr respMsg;

Conclusions

With an understanding of these advanced concepts you can create a custom invoke handler binding for almost any type of WSDL defined Web service including WSDL portTypes containing multiple operations and; WSDL Messages which define types that are complex. You also have the ability to return faults as defined in a portType's operation.