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:
- From Eclipse, select File / Import / Existing project into
workspace and navigate to the
custom_invokefolder where you unzip'd the example project and click the OK button - 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:
- 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:
├─── 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:
- Extract the message data from the service's input message
- Extract any extra query string information from the invoke (optional)
- Perform the required business logic
- Create the service's output message
- 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:
- DOM API - Using "tree-walking" operations of the DOM API to extract the relevant data from the document
- XPath Processing - Using XPath expressions to extract the relevant data from the document
- Java/XML Binding Frameworks - Using frameworks like JAXB, XMLBeans, or Castor to serialize the Document to a native Java object, typically a JavaBean, then use the accessor methods of the JavaBean to obtain the relevant data
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:
- XML String - Concatenate a series of string values representing the XML structure and business data and then parse this on the fly to a new Document object
- DOM API - Create a new Document using the DOM API and use the appropriate DOM API calls to add the required elements and data
- Java/XML Binding Frameworks - Using frameworks like JAXB, XMLBeans, or Castor to de-serialize the native Java object, typically a JavaBean, to a Document
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.
Copyright © 2004–2007 Active Endpoints, Inc.
