The Oracle BPEL Console provides a great tool for monitoring the status of in-flight and completed process.
However it is often a requirement to present this information to a business user within a business specific view. One way of achieving this is to use the Oracle BPEL Portlets.
This is fine where you want to give the user a specific list of process instances, for example a list of all currently running Purchase Order processes. But what about when you need to give them a list more filtered to their specific business requirements, for example:
How do I find all open purchase order processes for a specific customer?
How do I find all help desk processes being managed by a specific customer service representative?
How do I find all open expense processes that are waiting approval?
In addition once you’ve located a process instance, how do I provide the user with access to relevant data contained within the process? Again the BPEL Console provides a mechanism for drilling into the process and looking at the audit trail for the data. But often we want to provide this data in a summarised business view designed specifically for the needs of the business user.
The Oracle BPEL PM Server provides a series of API’s that enable you to meet these requirements.
In fact what is not often realised is that the BPEL Console itself makes use of these API’s, giving you the flexibility to completely re-write the console if that is what’s required!!!
In reality this is rarely if ever the case, typically the requirement is to provide business users with a simplified view tailored to their specific needs. A simple way of achieving this is through the use of these API’s; this is the subject of this article.
Locating a Process Instance
The Oracle BPEL PM Client API provides a Locator class for enabling a client application to search for processes, instances and activities. The Locator class provides a number of constructors, which enable you to connect to a BPEL domain hosted on either a local or remote J2EE Server.
For the purpose of locating specific process instances it provides two very useful methods:
listInstances(WhereCondition wc)
listInstancesByIndex(WhereCondition wc)
Each method returns an array of objects of type InstanceHandle, which can then be used to perform operations on the corresponding process instance.
The key parameter for each of these methods is the WhereCondition, which is used to build up a query to restrict which instances are returned by the method.
Note: Where condition objects may be concatenated together to form a larger query. The methods append and prepend allow the user to add a clause (in String format) or even a whole WhereCondition object to the beginning or end of the current where condition.
The following code snippet shows how to construct a WhereCondition to return all running process instances for the “LoanFlowProcess” where it’s current status is “CheckingCredit”.
String pProcessId = "LoanFlowProcess";
String pStatus = "CheckingCredit";
// Constructs a where condition that searches for open instances
WhereCondition where = WhereConditionHelper.whereInstancesOpen();
// Extend the where condition to filter on process id
WhereCondition whereProcessId = new WhereCondition( "process_id = ?" );
whereProcessId.setString(1, pProcessId);
where.append(whereProcessId);
// Extend the condition to filter on processes with the status ‘CheckingCredit’
WhereCondition whereStatus = new WhereCondition( "status = ‘" + pStatus + " );
whereStatus.setString(1, pStatus);
where.append("and").append(whereStatus)
// Find Instances
IInstanceHandle[] instanceHandles = locator.listInstances( where );
In the final step, the actual locator class is performing a query against the BPEL Dehydration store, similar to the one illustrated below:
“select cikey from cube_instance where " + whereCondition.getClause();
We use the WhereCondition (which wraps a SQL prepared statement where condition), in order to restrict the result set returned by the query.
It’s worth exploring in a bit more detail the various parts of the WhereCondition.
For the first we use the WhereConditionHelper class to restrict the query to only currently running processes. This is a simple utility class which provides a variety of Static methods for creating various query fragments (e.g. return process instance whose state is open, completed, aborted, stale, etc) which can then be appended to additional where conditions to create the required query.
For our second condition we are literally adding the condition
cube_instance.process_id = “LoanFlowProcess”
to our prepared statement. Here you can specify pretty much any of the columns in the database table cube_instance (e.g. Process_Id, Revision_Tag, Priority, Status).
In reality, rather than naming this column explicitly, we should use the appropriate constants defined in com.oracle.bpel.client.util.SQLDefs (e.g. SQLDefs.CI_process_id for our example).
The final statement is similar to the second in that we are querying on the process status. But what is the process status? Well it shouldn’t be confused with process state, which we queried on in the first WhereCondition.
Rather the process status is a variable that keeps track of where in the process a particular process instance is. When the process is first initiated, this value is set to ‘initiated’. This value is then updated every time you enter a new scope within a process to contain the name of the scope.
Note: When you have nested scopes, it will contain the name of the lower most nested scope that the process is in, i.e. it contains the last scope that was entered. Also when a process leaves a scope the status value is NOT reset, i.e. it will still contain the name of the previous scope until it enters a new scope.
Process Indexes
The listInstances method is very useful but it still doesn’t allow us to perform a query based on actual data held in the process instance, e.g. just return the loan flow process for ‘Dave’.
To solve this problem, BPEL allows for a process to have up to 6 indexes and to create a where condition across one or more of these indexes.
Essentially there are two steps to this; first you need to set the index values on the actual process instance; secondly you use the index values in a query to pull back all processes for a particular index value in a similar fashion to above.
Setting the Index Value
The simplest way to achieve this is to embed a piece of Java (using the Java Embed Task) at the start of the process to call the setIndex API to set the index value based on a value in the initial message, as shown in the example below:
// Set Index1 for Customer Name
String customerName = ((com.collaxa.cube.xml.dom.CubeDOMText)
getVariableData("input", "payload", "/auto:loanApplication/auto:customerName/text()")).getText();
setIndex(1, customerName);
Note: The getVariableData method is used to retrieve the customerName from the “input” variable; the syntax of the parameters is similar to the “from” component within an assign construct.
Querying Processes
The following code snippet shows how to construct a WhereCondition to return all running process instances where index_1 is equal to ‘Dave’.
String pCustomerName = "Dave";
// Constructs a where condition that searches on index 1
WhereCondition where = new WhereCondition(SQLDefs.CX_index_1 + " = ?");
where.setString(1, pCustomerName);
// Find Instances
IInstanceHandle[] instanceHandles = locator.listInstancesByIndex( where );
However there is one minor issue with this; under the covers the listInstances method is performing a query on the cube_instance database table, whilst the listInstancesByIndex is performing a query on the ci_indexes database table.
The issue here is if we want to perform a query that is a join across these two tables, i.e. show me all LoanFlow process for Dave. The WhereCondition API doesn’t (naturally) allow for joins; however there are two possible workarounds:
The first is to set the index values to hold the additional data required by the query, e.g. set index_1 to hold the process name and index_2 to hold the customer id.
The second is to extend the where condition passed to the listInstance method to have an IN condition that queries against the ci_indexes database table, as show below:
// Extend the where condition to only return open processes with the
WhereCondition whereIndex = new WhereCondition( "cikey in (select cikey from ci_indexes where index_1 = ?");
whereIndex.setString(1, pCustomerName);
where.append("and").append(whereIndex)
Note: I’ve used table and column names for clarity, but in reality you should use the constants defined by SQLDefs.
Using an Index to set status
Earlier in the article we looked ay how we can use process status to keep track of where we currently are in a process (remember status contains the name of the last scope that we entered).
However, whilst this is useful it has a couple of drawbacks; one is that if we use the listInstancesByIndex method to locate a process, we can’t actually filter on the state of the process. However the other is that we rely on insuring that the scopes are correctly named, designed, etc to keep track of where we are in the process. However in reality we may only have a few key milestones that we are interested in, and these may span several scopes or we may have more than one milestone contained within the same scope.
An alternative is to use an Index to hold the status of the process, and just update the status of the process using the setIndex method at appropriate points within the process.
Accessing Process Data
Once we have our list of processes instances, the final stage is to actually access the relevant data contained within the instance to display to the business user.
This is simply the case of iterating through our array of instance handles. Once you have the instanceHandle for a process, you can then use this to access process variables contained within the process instance using the getField method.
However you need to take care that whatever variable you are trying to access is currently visible within the active scope of the process. I find the simplest way to do this is to define a global variable (i.e. define the variable at the process level) and initialise it at the beginning of the process based upon the content of the initial message received by the process. Then during the lifetime of the process update the variable as required to reflect the true state of the process.
The following code snippet shows how we can process each instance returned by the locator and access the variable “LoanApplicationSummary” defined in the BPEL process.
// Find Instances
IInstanceHandle[] instanceHandles = locator.listInstancesByIndex( where );
// Process each instance
for (int i = 0; i < instanceHandles.length; i++ )
{
IInstanceHandle instanceHandle = instanceHandles[ i ];
// Get Loan Application Summary Variable
Element loanApplicationElement = (Element) instanceHandle.getField(“LoanApplicationSummary”);
// Create Loan Application Bean
LoanApplication loanApplication = LoanApplicationFactory.createFacade(loanApplicationElement);
// Process Loan Application Bean as required
…
}
To access the variable you use getField method on your instance Handle, as show below:
// Get Loan Application Summary Variable
Element loanApplicationElement = (Element) instanceHandle.getField(“LoanApplicationSummary”);
This will return a DOM (Document Object Model) representing the XML contained within the process variable. You can use the actual DOM API to parse and manipulate the XML content but for any complex structure this can be quite tricky.
To make this simpler, Oracle BPEL Process Manager provides a lightweight JAXB-like Java object model on top of XML; a so called XML façade. The façade provides a Java bean-like front end for an XML document/element. Façade classes have a corresponding Factory class which parse the XML document/element to create the façade, as show in the code snippet below:
// Create Loan Application Bean
LoanApplication loanApplication = LoanApplicationFactory.createFacade(loanApplicationElement);
Once the façade has been created, you can use its getter methods to access the required data.
Note: Façades are generated using the schemac tool shipped with Oracle BPEL Process Manager. You can use schemac to generate the façades from WSDL or XSD files (see the Oracle BPEL PM Developer guide for further details).
Summary
As we have seen Oracle BPEL PM provides a powerful Client API that makes it relatively simple to build a business specific “console” tailored to the needs of the user.
For further information on the API you should see the Oracle BPEL Process Manager Client API Reference
Posted by Matt Wright at 14:51 10 comments
Wednesday, 14 February 2007
"Private" BPEL Processes
When developing any BPEL based solution, good practice dictates that you take a modular approach to process design, which allows the sharing of sub-processes among higher level processes. For example a payment process may be used by both the Expenses Process and Order Process.
As a result you will often end up with BPEL processes that you only intend to be called by other BPEL processes, and typically BPEL processes with at least some knowledge about the underlying process. So how do you prevent other ‘clients’ from directly invoking these processes?
Now initially this may sound like a security issue, and Oracle BPEL Process Manager provides a number of ways of securing BPEL Processes; in addition Oracle Web Services Manager provides a comprehensive solution for adding policy-driven security to all Web services (not just BPEL Processes).
However security is typically intended for enabling controlled secured access to a BPEL Process (or Web Service) by authorized clients. However in this case we don’t actually want any client directly accessing the process. Now admittedly we could take a standard based security approach to this, but is there a simpler way?
Now many programming languages such as Java provide the concept of private or protected methods that control what has access to them. For example, in Java a class may declare some of its methods as being protected; indicating that only other classes in this package can access these methods. The great thing about this approach is that the developer is actually signalling a level of intent, i.e. this method should not be called directly except by related classes (or sub-classes) that can be trusted to use the method correctly.
Ideally it would be great if BPEL provided similar functionality, however unfortunately it doesn’t. So is there a way of achieving something similar?
Well it turns out there is a fairly straight forward way of getting close to the desired effect. The approach makes use of the Oracle HTTP Server embedded within the Oracle Application Server (as such this won’t work for the Developer install) to prevent access to a specific URL pattern, plus the use of domains with Oracle BPEL PM to enable us to create a simple URL pattern for all “private” processes.
Configuring Oracle HTTP Server
Oracle HTTP Server is the Web server component of Oracle Application Server and is based on the Apache infrastructure.
Any one familiar with Apache administration is aware that it provides Allow and Deny directives which let you either allow or deny access to a particular URL (or pattern) based on the host name, IP address (or partial IP address) or a fully qualified domain name (or partial domain name).
By specifying a |
Wednesday, May 9, 2012
Querying BPEL Process Instances
########
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment