Drools JBoss Rules 5.0 Flow (Part 2)

Exclusive offer: get 50% off this eBook here
Drools JBoss Rules 5.0 Developer's Guide

Drools JBoss Rules 5.0 Developer's Guide — Save 50%

Develop rules-based business logic using the Drools platform

$29.99    $15.00
by Michal Bali | July 2009 | Java Open Source

In this two-part article by Michal Bali, we'll look at Drools flow in detail by building a loan approval process. In the first part we covered: faults, subflows, and decisions.

In this part of the article we'll cover: Transfer Funds work Item, human tasks, and other aspects of ruleflow.

Transfer Funds work item

We'll now jump almost to the end of our process. After a loan is approved, we need a way of transferring the specified sum of money to customer's account. This can be done with rules, or even better, with pure Java as this task is procedural in nature. We'll create a custom work item so that we can easily reuse this functionality in other ruleflows. Note that if it was a once-off task, it would probably be better suited to an action node.

The Transfer Funds node in the loan approval process is a custom work item. A new custom work item can be defined using the following four steps (We'll see how they are accomplished later on):

  1. Create a work item definition. This will be used by the Eclipse ruleflow editor and by the ruleflow engine to set and get parameters. For example, the following is an extract from the default WorkDefinitions.conf file that comes with Drools. It describes 'Email' work definition. The configuration is written in MVEL. MVEL allows one to construct complex object graphs in a very concise format. This file contains a list of maps—List<map<string, Object>>. Each map defines properties of one work definition. The properties are: name, parameters (that this work item works with), displayName, icon, and customEditor (these last three are used when displaying the work item in the Eclipse ruleflow editor). A custom editor is opened after double-clicking on the ruleflow node.
    import org.drools.process.core.datatype.impl.type.StringDataType;
    [
    [
    "name" : "Email",
    "parameters" : [
    "From" : new StringDataType(),
    "To" : new StringDataType(),
    "Subject" : new StringDataType(),
    "Body" : new StringDataType()
    ],
    "displayName" : "Email",
    "icon" : "icons/import_statement.gif",
    "customEditor" : "org.drools.eclipse.flow.common.editor.
    editpart.work.EmailCustomEditor"
    ]
    ]

    Code listing 13: Excerpt from the default WorkDefinitions.conf file.

    Work item's parameters property is a map of parameterName and its value wrappers. The value wrapper must implement the org.drools.process.core.datatype.DataType interface.

  2. Register the work definitions with the knowledge base configuration. This will be shown in the next section.
  3. Create a work item handler. This handler represents the actual behavior of a work item. It will be invoked whenever the ruleflow execution reaches this work item node. All of the handlers must extend the org.drools.runtime.process.WorkItemHandler interface. It defines two methods. One for executing the work item and another for aborting the work item. Drools comes with some default work item handler implementations, for example, a handler for sending emails: org.drools.process.workitem.email.EmailWorkItemHandler. This handler needs a working SMTP server. It must be set through the setConnection method before registering the work item handler with the work item manager (next step). Another default work item handler was shown in code listing 2 (in the first part)-SystemOutWorkItemHandler.
  4. Register the work item handler with the work item manager.

After reading this you may ask, why doesn't the work item definition also specify the handler? It is because a work item can have one or more work item handlers that can be used interchangeably. For example, in a test case, we may want to use a different work item handler than in production environment.

We'll now follow this four-step process and create a Transfer Funds custom work item.

Work item definition

Our transfer funds work item will have three input parameters: source account, destination account, and the amount to transfer. Its definition is as follows:

import org.drools.process.core.datatype.impl.type.ObjectDataType;
[
[
"name" : "Transfer Funds",
"parameters" : [
"Source Account" : new ObjectDataType("droolsbook.bank.
model.Account"),
"Destination Account" : new ObjectDataType("droolsbook.bank.
model.Account"),
"Amount" : new ObjectDataType("java.math.BigDecimal")
],
"displayName" : "Transfer Funds",
"icon" : "icons/transfer.gif"
]
]

Code listing 14: Work item definition from the BankingWorkDefinitions.conf file.

The Transfer Funds work item definition from the code above declares the usual properties. It doesn't have a custom editor as was the case with email work item. All of the parameters are of the ObjectDataType type. This is a wrapper that can wrap any type. In our case, we are wrapping Account and BigDecimal  types. We've also specified an icon that will be displayed in the ruleflow's editor palette and in the ruleflow itself. The icon should be of the size 16x16 pixels.

Work item registration

First make sure that the BankingWorkDefinitions.conf file is on your classpath. We now have to tell Drools about our new work item. This can be done by creating a drools.rulebase.conf file with the following contents:

drools.workDefinitions = WorkDefinitions.conf BankingWorkDefinitions.conf

Code listing 15: Work item definition from the BankingWorkDefinitions.conf file (all in one one line).

When Drools starts up, it scans the classpath for configuration files. Configuration specified in the drools.rulebase.conf file will override the default configuration. In this case, only the drools.workDefinitions setting is being overridden. We already know that the WorkDefinitions.conf file contains the default work items such as email and log. We want to keep those and just add ours. As can be seen from the code listing above, drools.workDefinitions settings accept list of configurations. They must be separated by a space. When we now open the ruleflow editor in Eclipse, the ruleflow palette should contain our new Transfer Funds work item.

If you want to know more about the file based configuration resolution process, you can look into the org.drools.util.ChainedProperties class.

Work item handler

Next, we'll implement the work item handler. It must implement the org. drools.runtime.process.WorkItemHandler interface that defines two methods: executeWorkItem and abortWorkItem. The implementation is as follows:

/**
* work item handler responsible for transferring amount from
* one account to another using bankingService.transfer method
* input parameters: 'Source Account', 'Destination Account'
* and 'Amount'
*/
public class TransferWorkItemHandler implements
WorkItemHandler {
BankingService bankingService;

public void executeWorkItem(WorkItem workItem,
WorkItemManager manager) {
Account sourceAccount = (Account) workItem
.getParameter("Source Account");
Account destinationAccount = (Account) workItem
.getParameter("Destination Account");
BigDecimal sum = (BigDecimal) workItem
.getParameter("Amount");

try {
bankingService.transfer(sourceAccount,
destinationAccount, sum);
manager.completeWorkItem(workItem.getId(), null);
} catch (Exception e) {
e.printStackTrace();
manager.abortWorkItem(workItem.getId());
}
}

/**
* does nothing as this work item cannot be aborted
*/
public void abortWorkItem(WorkItem workItem,
WorkItemManager manager) {
}

Code listing 16: Work item handler (TransferWorkItemHandler.java file).

The executeWorkItem method retrieves the three declared parameters and calls the bankingService.transfer method (the implementation of this method won't be shown). If all went OK, the manager is notified that this work item has been completed. It needs the ID of the work item and optionally a result parameter map. In our case, it is set to null. If an exception happens during the transfer, the manager is told to abort this work item.

The abortWorkItem method on our handler doesn't do anything because this work item cannot be aborted.

Please note that the work item handler must be thread-safe. Many ruleflow instances may reuse the same work item instance.

Work item handler registration

The transfer work item handler can be registered with a WorkItemManager as follows:

TransferWorkItemHandler transferHandler = 
new TransferWorkItemHandler();
transferHandler.setBankingService(bankingService);
session.getWorkItemManager().registerWorkItemHandler(
"Transfer Funds", transferHandler);

Code listing 17: TransferWorkItemHandler registration (DefaultLoanApprovalServiceTest.java file).

A new instance of this handler is created and the banking service is set. Then it is registered with WorkItemManager in a session.

Next, we need to 'connect' this work item into our ruleflow. This means set its parameters once it is executed. We need to set the source/destination account and the amount to be transferred. We'll use the in-parameter mappings of Transfer Funds to set these parameters.

Drools JBoss Rules 5.0 Flow (Part 2)

As we can see the Source Account is mapped to the loanSourceAccount ruleflow variable. The Destination Account ruleflow variable is set to the destination account of the loan and the Amount ruleflow variable is set to loan amount.

Testing the transfer work item

This test will verify that the Transfer Funds work item is correctly executed with all of the parameters set and that it calls the bankingService.transfer method with correct parameters. For this test, the bankingService service will be mocked with jMock library (jMock is a lightweight Mock object library for Java. More information can be found at http://www.jmock.org/). First, we need to set up the banking service mock object in the following manner:

mockery = new JUnit4Mockery();
bankingService = mockery.mock(BankingService.class);

Code listing 18: jMock setup of bankingService mock object (DefaultLoanApprovalServiceTest.java file).

Next, we can write our test. We are expecting one invocation of the transfer method with loanSourceAccount and loan's destination and amount properties. Then the test will set up the transfer work item as in code listing 17, start the process, and approve the loan (more about this is discussed in the next section). The test also verifies that the Transfer Funds node has been executed. Test method's implementation is as follows:

@Test
public void transferFunds() {
mockery.checking(new Expectations() {
{
one(bankingService).transfer(loanSourceAccount,
loan.getDestinationAccount(), loan.getAmount());
}
});

setUpTransferWorkItem();
setUpLowAmount();
startProcess();
approveLoan();

assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_WORK_ITEM_TRANSFER));
}

Code listing 19: Test for the Transfer Funds work item (DefaultLoanApprovalServiceTest.java file).

The test should execute successfully.

Drools JBoss Rules 5.0 Developer's Guide Develop rules-based business logic using the Drools platform
Published: July 2009
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

 

Human task

Let's go back to the loan approval ruleflow. We've finished after the Rating? node.Our next step is to implement the Process Loan node. This is where the humanactors will be involved. We've done what we could with our automated process,now is the time for tasks that a computer can't or shouldn't do.

Drools supports human tasks though Web Services Human Task specification (The WS-HumanTask is an OASIS specification and can be downloaded from http://download.boulder.ibm.com/ibmdl/pub/software/dw/specs/wsbpel4people/WS-HumanTask_v1.pdf). With this specification, we can define human tasks that will be automatically created when the ruleflow reaches this ruleflow node. After they are created, they will appear on the 'task list screen' of designated users than can 'claim' these tasks and start working on them until they are completed. They can also suspend or abort these tasks. Once the task reaches the final state (complete/abort), the ruleflow continues execution. Please note that this is a simplified view; the WS-HumanTask specification defines a more complex life cycle of a task.

From the ruleflow perspective, WS-HumanTask is just a special case of work item. Once it is triggered, the ruleflow simply waits for the end result, be it success or failure. Drools comes with a simple work item handler implementation for human task called WSHumanTaskHandler. It is far from implementing all of the features of WS-HumanTask specification, but it gives us a starting point and a direction.

Human task support is part of the drools-process-task module.

The human task ruleflow node allows us to specify actorId, which is the ID of a person/group that will have the role of potentialOwner as defined by WS-HumanTask. Also, some comment can be specified, which will become the 'subject' and 'description' of a human task. If a task can be skipped then priority and option can be also defined.

The WSHumanTaskHandler provides no support for some WS-HumanTask user roles such as task initiators, excluded owners, task stakeholders, business administrators or recipients. Nor does it support attachments, multiple comments, task delegations, start/end deadlines with their escalations, notifications, and user reassignments. If needed, the WSHumanTaskHandler can be extended to provide the features we need. For the purpose of our loan approval example, we'll use this WSHumanTaskHandler unchanged.

The core part of the WS-HumanTask specification is the server that receives the tasks and manages them. WSHumanTaskHandler is kept lightweight. It is a simple client that creates a task based on properties set in the ruleflow and registers this task with the server together with a callback. As has been said earlier, it then waits for success or failure of the task. It can take some time for a human task to finish; therefore, a more advanced implementation could, for example, persist the ruleflow to some permanent storage in order to free up the resources.

On the other side, the server is a more or less complete implementation of the WS-HumanTask specification. It goes even further by giving us the ability to send standard iCalendar VEVENT notifications (iCalendar is a RFC 2445 standard for calendar exchange. More information about iCalendar VEVENTs can be found at http://en.wikipedia.org/wiki/Icalendar#Events_.28VEVENT.29).

Test for the human task

So far it was only theory—a test will hopefully make it clearer. In order to write some tests for the Process Loan human task, we'll need a server that will receive these tasks. Other clients will then connect to this server and work on these tasks and when they are completed our ruleflow will be able to continue.

Due to its size, the test will be divided into three parts—server setup, client setup, and client 'working on the task'.

We'll start with the server setup (see the following code listing). It will initialize the server, register a human task work item handler, and start the loan approval process.

@Test
public void processLoan() throws Exception {
EntityManagerFactory emf = Persistence
.createEntityManagerFactory("org.drools.task");

TaskService taskService = new TaskService(emf,
SystemEventListenerFactory.getSystemEventListener());
MockUserInfo userInfo = new MockUserInfo();
taskService.setUserinfo(userInfo);

TaskServiceSession taskSession = taskService
.createSession();
taskSession.addUser(new User("Administrator"));
taskSession.addUser(new User("123"));
taskSession.addUser(new User("456"));
taskSession.addUser(new User("789"));

MinaTaskServer server = new MinaTaskServer(taskService);
Thread thread = new Thread(server);
thread.start();
Thread.sleep(500);

WorkItemHandler htHandler = new WSHumanTaskHandler();
session.getWorkItemManager().registerWorkItemHandler(
"Human Task", htHandler);
setUpLowAmount();
startProcess();

Code listing 20: Test for the Process Loan node—setup of server and process start-up (DefaultLoanApprovalServiceTest.java file).

As part of the server setup, the test creates a JPA EntityManagerFactory (JPA stands for Java Persistence API. More information can be found at http://en.wikipedia.org/wiki/Java_Persistence_API) from a persistence unit named org.drools.task (the configuration for this persistence unit is inside drools-process-task.jar module in /META-INF/persistence.xml. By default, it uses an in-memory database). It is used for persisting human tasks that are not currently needed. There may be thousands of human task instances running concurrently and each can take minutes, hours, days, or even months to finish. Persisting them will save us resources.

Next, TaskService is created. It takes EntityManagerFactory and SystemEventListener.

org.drools.SystemEventListener
The SystemEventListener  provides callback style logging of various Drools system events. The listener can be set through  SystemEventListenerFactory. The default listener prints everything to the console.

TaskService represents the main server process. A UserInfo object is set to taskService. It has methods for retrieving various information about users and groups of users in our organization that taskService needs (it is, for example, used when sending the iCalendar notifications). For testing purposes, we're using only a mock implementation—MockUserInfo.

TaskService can be accessed by multiple threads. Next, TaskServiceSession represents one session of this service. This session can be accessed by only one thread at a time. We use this session to create some test users. Our Process Loan task is initially assigned to actorIds: 123, 456, and 789. This is defined in the Process Loan ruleflow node's properties. Next, the server thread is started wrapped in a MinaTaskServer. It is a lightweight server implementation that listens on a port for clients requests. It is based on Apache MINA. (More information about Apache MINA can be found at http://mina.apache.org/).

The current thread then sleeps for 500ms, so that the server thread has some time to initialize. Then a default Drools WSHumanTaskHandler is registered, a new loan application with low amount is created, and the ruleflow is started. The ruleflow will execute all the way down to Process Loan human task where the WSHumanTaskHandler takes over. It creates a task from the information specified in the Process Loan node and registers this task with the server. It knows how to connect to the server. The ruleflow then waits for the completion of this task.

The next part of this test represents a client (bank employee) that is viewing his/her task list and getting one task. First, the client must connect to the server.

Because all of the communication between the client and the server is asynchronous and we want to test it in one test method, we will use some blocking response handlers that will simply block until the response is available. These response handlers are from the drools-process-task module.

Next, the client.getTasksAssignedAsPotentialOwner method is called and we wait for a list of tasks that the client can start working on. The test verifies that the list contains one task and that the status of this task is Ready.

MinaTaskClient client = new MinaTaskClient("client 1",
new TaskClientHandler(
SystemEventListenerFactory.getSystemEventListener()));
NioSocketConnector connector = new NioSocketConnector();
SocketAddress address = new InetSocketAddress("127.0.0.1",
9123);
client.connect(connector, address);

BlockingTaskSummaryResponseHandler summaryHandler =
new BlockingTaskSummaryResponseHandler();
client.getTasksAssignedAsPotentialOwner("123", "en-UK",
summaryHandler);
List<TaskSummary> tasks = summaryHandler.getResults();
assertEquals(1, tasks.size());
TaskSummary task = tasks.get(0);
assertEquals("Process Loan", task.getName());
assertEquals(3, task.getPriority());
assertEquals(Status.Ready, task.getStatus());

Code listing 21: Test for the Process Loan node—setup of a client and task list retrieval (DefaultLoanApprovalServiceTest.java file).

The final part of this test represents a client (bank employee) that 'claims' one of the task from the task list, then 'starts' this task, and finally 'completes' this task.

BlockingTaskOperationResponseHandler operationHandler = 
new BlockingTaskOperationResponseHandler();
client.claim(task.getId(), "123", operationHandler);
operationHandler.waitTillDone(10000);

operationHandler =
new BlockingTaskOperationResponseHandler();
client.start(task.getId(), "123", operationHandler);
operationHandler.waitTillDone(10000);

operationHandler =
new BlockingTaskOperationResponseHandler();
client.complete(task.getId(), "123", null,
operationHandler);
operationHandler.waitTillDone(10000);

assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_JOIN_PROCESS_LOAN));
}

Code listing 22: Test for the Process Loan node—client is claiming, starting, and completing a task (DefaultLoanApprovalServiceTest.java file).

After the task is completed, the test verifies that the ruleflow continues execution through the next join node.

Final Approval

As you may imagine, before any money is paid out to the loan requester, a final check is needed from a supervisor. This is represented in the ruleflow by the Approve Event node. It is an event node from the ruleflow palette. It allows a process to respond to the external events. This node has no incoming connections; in fact, the events can be created/signaled through the process instance's signalEvent method. The method needs event type and the event value itself.

Parameters of the Event node include event type and variable name that hold this event. The variable must be itself declared as a ruleflow variable.

Test for the 'Approve Event' node

A test will show us how all this works. We'll setup a valid loan request. The dummy SystemOutWorkItemHandler will be used to get through the Transfer Funds and Process Loan work items. The execution should then wait for the approve event. Then we'll signal the event using the processInstance.signalEvent("LoanApprovedEvent", null) method and verify that the ruleflow finished successfully.

@Test
public void approveEventJoin() {
setUpLowAmount();
startProcess();
assertEquals(ProcessInstance.STATE_ACTIVE, processInstance
.getState());
assertFalse(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_WORK_ITEM_TRANSFER));
approveLoan();
assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_WORK_ITEM_TRANSFER));

assertEquals(ProcessInstance.STATE_COMPLETED,
processInstance.getState());
}

Code listing 23: Test for the Approve Event node (DefaultLoanApprovalServiceTest.java file).

Before sending the approved event, we've verified that the process is in active state and that the Transfer Funds work item hasn't been called yet.

After sending the approved event, the test verifies that the Transfer Funds work item was actually executed and the ruleflow reached its final COMPLETED state.

Banking service

The final step is to implement the approveLoan service that represents the interface to our loan approval process. It ties everything that we've done together. The approveLoan method takes a Loan and a Customer, which is requesting the loan.

KnowledgeBase knowledgeBase;
Account loanSourceAccount;

/**
* runs the loan approval process for a specified
* customer's loan
*/
public void approveLoan(Loan loan, Customer customer) {
StatefulKnowledgeSession session = knowledgeBase
.newStatefulKnowledgeSession();
try {
//TODO: register workitem/human task handlers
Map<String, Object> parameterMap =
new HashMap<String, Object>();
parameterMap.put("loanSourceAccount",loanSourceAccount);
parameterMap.put("customer", customer);
parameterMap.put("loan", loan);
session.insert(loan);
session.insert(customer);
ProcessInstance processInstance =
session.startProcess("loanApproval", parameterMap);
session.insert(processInstance);
session.fireAllRules();
} finally {
session.dispose();
}
}

Code listing 24: approveLoan service method of BankingService (DefaultLoanApprovalService.java file).

The service creates a new session. It should then set-up and register all of the work item handlers that we've implemented. This part is left out. Normally, it would involve setting up configuration parameters such as the IP address of a SMTP server for the email work item handler and so on.

Next, the loan and the customer are inserted into the session, the ruleflow is started, and the rules are fired. When the ruleflow completes, the session is disposed. Please be aware that with this solution, the knowledge session is held in memory from the time when the ruleflow starts up to the time when it finishes.

Disadvantages of a ruleflow

A ruleflow may potentially do more work than it should do. This is a direct consequence of how the algorithm behind Drools works. All of the rule constraints are evaluated at fact insertion time. For example, if we have a ruleflow with many nodes and 80% of the time the ruleflow finishes at the second node, most of the computation is wasted.

Another disadvantage is that the business logic is now spread across at least two places. The rules are still in the .drl file; however, the ruleflow is in the .rf file. The ruleflow file also contains split node conditions and actions. If somebody wants to get the full understanding of a process, he/she has to look back and forth between these files. This may be fixed in future by having better integration in the Drools Eclipse plugin between the .drl file editor and the .rf file editor (for example, it would be nice to see the rules that belong to a selected ruleflow group).

Summary

We've designed a loan approval service that involves validation of the loan request, customer rating calculation(in the first part), approval events from a supervisor, and finally, custom domain specific work item for transferring money between accounts.

We've seen Drools Flow support of human tasks through the WS-HumanTask specification. This allows for greater interoperability between systems from different vendors.

All in all, Drools Flow represents an interesting approach to rules and processes.

If you have read this article you may be interested to view :

Drools JBoss Rules 5.0 Developer's Guide Develop rules-based business logic using the Drools platform
Published: July 2009
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Michal Bali

Michal Bali, freelance software developer, has more than 8 years of experience working with Drools and has an extensive knowledge of Java, JEE. He designed and implemented several systems for a major dental insurance company. He is an active member of the Drools community and can be contacted at michalbali@gmail.com.

Books From Packt

JBoss Drools Business Rules
JBoss Drools Business Rules

Flex 3 with Java
Flex 3 with Java

JBoss Tools 3 Developers Guide
JBoss Tools 3 Developers Guide

WordPress 2.7 Cookbook
WordPress 2.7 Cookbook

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development

JBoss Portal Server Development
JBoss Portal Server Development

Apache Struts 2 Web Application Development
Apache Struts 2 Web Application Development

Alfresco 3 Enterprise Content Management Implementation
Alfresco 3 Enterprise Content Management Implementation

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software