Articles
|
SOA Best Practices: The BPEL Cookbook |
|
Building BPEL Processes on the Fly
Generate BPEL processes on the fly by transforming parameters stored in the database into a BPEL XML definition file via XQuery.
Published November 2005 In Part 3 of this cookbook, you learned how to make BPEL processes dynamic by manipulating partner links using end point references. This approach is certainly useful for building processes that are shielded from partner relationship changes. However, in many processes, various other variables in addition to the partner links may need to change dynamically. Consider a company that has designed a workplace management solution for managing corporate real estate and facilities that helps clients answer the following types of questions.
Figure 1 Business process life cycle Building on-the-fly processes comprise several steps: An analyst uses a custom designer to graphically model the business process.
Figure 2 Cube Management Business Process in custom process designer As I described previously, the Oracle BPEL Process Manager engine has the unique capability to integrate with any designer/tool provided the latter can generate valid BPEL files. Storing the definition in the database. Once the business process has been created, the next step is to store the definition inside the database. Your database schema must be sufficiently flexible to store complex processes and allow for recreation of BPEL processes with multiple BPEL constructs such as flow, pick, invoke, and scope. Assume that you have designed a data model containing business process design information that corresponds to the real-life view provided by the custom designer. When a business analyst creates a business process using the designer, information defining the structure of the business process is saved to the database. Note that Oracle BPEL Process Manager does not come into play until deployment (more on that later). Figure 3 contains a high-level UML view of the data model used to persist process designs in our database. Figure 3 Simplified Data schema for storing Cube Management process definition The WorkflowProcess object stores the process design; it contains parameters and nodes, which define the sequence and type of activities in the process. The various WorkflowNode objects are design-time artifacts for the different types of process activities supported by your designer. Note that these are not BPEL constructs; rather, they are custom constructs that are then mapped to typically more than one BPEL activity. Once deployed using custom designer, the CubeManagement process details will be stored as follows in the database. WorkflowProcess Table
WorkflowParameter Table
WorkflowNode Table
These tables are read by the BPEL Generator to dynamically generate a BPEL file. The next section describes that process. Dynamically Deploying BPEL The most complex task in BPEL generation involves reading the process design from the database and generating a BPEL "suitcase" for the process. In this example, you will leverage XQuery to generate BPEL. XQuery is a powerful and convenient language for processing XML data. It can handle not only files in XML format, but also database data whose structure—nested, with named trees and attributes—is similar to XML. The following files, which make up a BPEL process suitcase for Oracle BPEL Process Manager, are generated dynamically.
<process>
<scope>
<!— start workflow node related activities -!>
</scope>
<scope>
<!— wait workflow node related activities -!>
</scope>
<scope>
<!— mail workflow node related activities -!>
</scope>
<scope>
<!— update workflow node related activities -!>
</scope>
</process>
Let's analyze Step 2 in greater detail. Every node equates to a series of BPEL activities. All this information is stored in the XQuery template file. There is an XQuery template for each node, which embodies all the BPEL constructs for that specific node. You have created start.xq, wait.xq, message.xq, update.xq and stop.xq corresponding to five nodes in the Cube Management process.
The following table describes the BPEL activities generated for start, wait, message, update, and stop nodes in the example process.
<scope>
<!-- generation for Message activity -->
<variables>
<!-- Define variables input, output and error variables-->
<variable name="emailRequest" messageType="services:EntityAsyncMsgWSRequestMessage"/>
<variable name="emailResponse" messageType="services:EntityAsyncMsgWSResultMessage"/>
<variable name="fault" messageType="services:EntityFaultMessage" />
</variables>
<!--Begin the BPEL activities -->
<sequence>
<!--This is the first assign activity. It configures the input variable with
<!-- identification of node ($nodekey) and function to be called -->
<!-- by the external webService (bpelSendEmail). -->
<assign>
<copy>
<from>
<ent:EntityCollectionElem xmlns:ent="http://www.centerstonesoft.com/schema/entity" >
<ent:Header/>
<ent:entityName/>
<ent:EntityElem>
<ent:entityKey>{ $nodeKey }</ent:entityKey>
<ent:method >
<ent:methodName>bpelSendEmail</ent:methodName>
<ent:parameter>
<ent:PropName>thread</ent:PropName>
<ent:Type>string</ent:Type>
<ent:Value/>
</ent:parameter>
</ent:method>
</ent:EntityElem>
</ent:EntityCollectionElem>
</from>
<to variable="emailRequest" part="payload"/>
</copy>
</assign>
<!--Second and third assign activity configure the input variable -->
<!--with identification of actual process instance -->
<assign>
<copy>
<from variable="input" part="parameters" query="//ent:Header"/>
<to variable="emailRequest" part="payload" query="//ent:Header"/>
</copy>
</assign>
<assign>
<copy>
<from variable="threadKey" part="value"/>
<to variable="emailRequest" part="payload" query="//ent:parameter[ent:PropName = 'thread']/ent:Value"/>
</copy>
</assign>
<!--After configuring input variable with node, instance and function to be called,
invoke the external service -->
<invoke name="invoke" partnerLink="EntityAsyncMsgWSLink"
portType="services:EntityAsyncMsgWS" operation="invoke" inputVariable="emailRequest"/>
<!--Check response from external service. If response indicates fault, throw exception. -->
<!--If no response is received for 10 days, throw exception -->
<pick name="deadlined_receive" >
<!--Set output variable if return result from external webservice is OK-->
<onMessage partnerLink="EntityAsyncMsgWSLink" portType="services:EntityAsyncMsgWSCallback"
operation="onResult" variable="emailResponse">
<empty/>
</onMessage>
<!--On fault, check the fault code and throw appropriate exception-->
<onMessage operation="onFault" partnerLink="EntityAsyncMsgWSLink"
portType="services:EntityAsyncMsgWSCallback" variable="fault">
<switch>
<case condition="bpws:getVariableData('fault', 'value',
'/EntityFault/faultcode') = 'MailException' ">
<throw faultName="ent:MailException" faultVariable="fault" />
</case>
<otherwise>
<throw faultName="ent:SystemException" faultVariable="fault" />
</otherwise>
</switch>
</onMessage>
<!--If no response for 10 days, throw exception-->
<onAlarm for="'P10D'">
<throw faultName="ent:SystemException"/>
</onAlarm>
</pick>
</sequence>
</scope>
After reviewing this XQuery template, you'll see how the document generates a single
<scope> activity that contains the implementation of the message process node. The
<scope> activity begins with a
<variables> section where variables are defined for the request as well a normal response or fault response. The variables section is followed by a
<sequence> where the request is generated using a sequence of <assign> statements. The first
<assign> writes the boilerplate for the request; within this
<assign>, the XQuery parameter
nodeKey is used to pass node identification to the external Web service.
Note that
nodeKey is generation time information. After the first
<assign> activity, the following two
<assign> activities, deal with run-time information. The second
<assign> copies header information from the initial request used to start the process. This header information identifies the eCenterOne and Oracle BPEL Process Manager process instance IDs as well as the customer that the process is for. The third
<assign> activity copies the process thread ID. (As mentioned earlier, similar XQuery templates exist for other nodes in the process.)
XQuery processing. Let's quickly look at how all these templates are processed by the XQuery processor and combined to form a BPEL file.
The
generateActivity() below accepts the list of nodes for which the BPEL activities need to be generated. For each node, it will locate the right XQuery template using the function
XQuery.newXQueryFromResource. The Saxon processor will then process this XQuery template and produce an XML String. If a node has a child, it will be processed in a recursive manner in the
while loop.
/**
* Recursively generates an activity including any child activities.
* @param activity the node that is being generated, the value is null for the top-level
* process node
* @return typically the root element of the generated XML document
* @throws EntityException
*/
protected String generateActivity(WorkflowNode activity) throws EntityException
{
//Find the next node in the list to be processed
BpelActivityType activityType = getBpeBpelActivityType(activity);
//Get the parameters to be passed to node
HashMap params = calculateParameters(activity, activityType);
try {
//Get corresponding XQuery file for the node
XQuery q = XQuery.newXQueryFromResource("bpelgen/" + activityType.getQuery());
//Call the Saxon XQuery Processor to run the query.
XQueryResult result = q.run(null, params, null);
//Convert the result into a string
String parentXml = result.serializeString();
if (logger.isDebugEnabled()) {
logger.debug("Generated for parent '" + activityType.getName() + "' is \n" + parentXml);
}
//If there are child nodes, recursively invoke generate activity
if (_process.hasChildren(activity)) {
//Call java relational class
Iterator it = _process.getChildrenIterator(activity);
//Recursively call generateActivity for child nodes. For ‘Cube Management Process’, these will be
//start, wait, message, update & end
while (it.hasNext()) {
WorkflowNode childActivity = (WorkflowNode) it.next();
String childXml = generateActivity(childActivity);
// now use Saxon XSLT to add the document generated for the children to the result document
// Get the Transformer used for this transformation
Transformer transformer = getPrependTransformer();
transformer.setParameter("inserted", DomUtils.getDocument(new ByteArrayInputStream(childXml.getBytes())));
StreamResult saveResult = new StreamResult(new ByteArrayOutputStream());
try {
StreamSource src = new StreamSource(new ByteArrayInputStream(parentXml.getBytes()));
// Transform the XML Source
transformer.transform(src, saveResult);
}
finally {
transformer.clearParameters();
}
parentXml = ((ByteArrayOutputStream)saveResult.getOutputStream()).toString();
if (logger.isDebugEnabled()) {
BpelActivityType childType = getBpeBpelActivityType(childActivity);
logger.debug("Preprocessed for parent '" + activityType.getName() + "' and child '" +
childType.getName() + "' is \n" + parentXml);
}
}
}
// remove the <insert-here> node
Transformer transformer = getRemovePrependTransformer();
StreamResult saveResult = new StreamResult(new ByteArrayOutputStream());
StreamSource src = new StreamSource(new ByteArrayInputStream(parentXml.getBytes()));
transformer.transform(src, saveResult);
parentXml = ((ByteArrayOutputStream)saveResult.getOutputStream()).toString();
if (logger.isDebugEnabled()) {
logger.debug("Generated for '"+activityType.getName()+"' is \n" + parentXml);
}
return parentXml;
}
catch (SAXException ex) {
throw new EntityException("Failed to run transform "+activityType.getQuery(), ex);
}
catch (TransformerException ex) {
throw new EntityException("Failed to run transform "+activityType.getQuery(), ex);
}
catch (IOException ex) {
throw new EntityException("Failed to load query "+activityType.getQuery(), ex);
}
}
The
generateActivity() method generates the final BPEL file in a string format. It is then written into a file by another Java program. You can download all XQuery templates as well as the BPEL file for the Cube Management process in the
sample code file.
Dynamic deployment with Ant. The final step is to use Oracle BPEL Process Manager deployment tools to compile and deploy the BPEL process. For deployment, you need to generate the following simple Ant build.xml file and then execute it (again using XQuery for its support of parameterized XML file).
<?xml version="1.0" encoding="UTF-8"?>
<project default="main" basedir="C:/DOCUME~1/jerry/LOCALS~1/Temp/cs_bpelgen_simple24369" name="BpelDeploy">
<target name="main">
<bpelc keepGenerated="true" deploy="jt_dev" rev="1.0" home="c:\orabpel"/>
</target>
</project>
The final step is to execute Ant programmatically from your application server. To do this, you can use code from the Ant task Execute (found in org.apache.tools.ant.taskdefs). This class provides industrial-strength support for programmatically executing command-line processes and handling good and error returns.
The following code fragment describes how to use code in the Ant Execute task to implement BPEL process deployment.
String args[] = new String[1];
args[0] = "c:/orabpelpm/integration/orabpel/bin/obant.bat";
Execute exe = new Execute();
exe.setCommandline(args);
exe.setWorkingDirectory(_deploydir);
int retval = exe.execute();
if ( retval != 0 ) {
throw new ExecuteException("process failed to deploy");
In this fragment, the
Execute class is used to execute the Oracle BPEL Process Manager command line file,
obant, directly. The batch file sets up the Windows environment appropriately and then invokes Ant on the build.xml file shown above.
A non-zero return code from this file is usually due to errors returned by the BPEL compiler. These errors are uncommon however and nearly always due to some internal error in the process implementation.
Conclusion
CenterStone Software lets property managers rapidly customize business processes using a custom designer. These processes are then dynamically converted into BPEL and deployed on Oracle BPEL Server. This approach should be seriously considered by any organization relying heavily on business users to design organizational processes. Not only does the custom designer approach shield users from the complexities of BPEL, but it also provides a unique platform to generate adaptive BPEL process on the fly. This enables organizations to enable and realize the benefits of SOA rapidly.
Jerry Thomas [
jthomas@centerstonesoft.com] is Chief Architect at
Centerstone Software, which helps many of the world's largest organizations automate and manage their real estate, facilities, personnel, assets, leases, and workplace operations more efficiently. Thomas focuses on CenterStone's enterprise workplace management product and Web services, BPEL, and system infrastructure. Prior to CenterStone, Thomas worked as a consultant and held principal development positions at Riverton, ONTOS, and Hewlett-Packard.
Send us your comments
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||