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:
- 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 '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:
- 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
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:
- Provide a more efficient technique for sending and receiving messages between processes and services on the same machine
- Provide messaging to an application that is not exposed as a Web service but may be accessible as an EJB, JMS, JCA or some other another custom interface
- Take advantage of your specific SOAP client stack's specific features
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:
- 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 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.
Copyright © 2004–2007 Active Endpoints, Inc.
