Creating a RuleFlow
In this example, we are going to create a simple RuleFlow and add it to our Banking example from Part I.
Paul Browne has already posted an excellent blog on this topic, which has a slightly more complicated example.
Caveat
This example is so simple, that it would actually be easier done with an Agenda-Group, but the purpose of this post is to demonstrate a very simple example of RuleFlow. I will post a more complicated one later on.
Pre-Requisites
In order to run this example you will need Eclipse with the latest Drools plug-in (Eclipse Workbench). Eclipse can be downloaded from http://www.eclipse.org/ and the Eclipse Workbench can be downloaded from http://labs.jboss.com/drools/downloads.html.
Step 1 - Creating the Rules
For those who have already downloaded the examples in Drools Part I, I am going to use that code as the starting point for this.
Our first task, is to create a rule and allocate it to a ruleflow-group so that we can use the RuleFlow to include and exclude
rules. To do this, copy rule07.drl to rule08.drl and rename the rules to be prefixed with "Rule 08" instead of "Rule 07". Now, add the ruleflow-group entries for "Rule 08 - Credit" and "Rule 08 - Debit":
rule08.drl
package simple;
import net.tplusplus.drools.bankingondrools1.*;
rule "Rule 08 - Credit"
ruleflow-group "CreditGroup"
salience 100
when
AccountingPeriod( $start : start, $end : end )
$cashflow : AllocatedCashflow( $account : account, $date : date <= $end, $amount : amount, type==TypedCashflow.CREDIT )
not AccountingPeriod( start < $start)
then
System.out.println("Credit: "+$date+" :: "+$amount);
$account.setBalance($account.getBalance()+$amount);
System.out.println("Account: "+$account.getAccountNo()+" - new balance: "+$account.getBalance());
retract($cashflow);
end
rule "Rule 08 - Debit"
ruleflow-group "DebitGroup"
salience 100
when
AccountingPeriod( $start : start, $end : end )
$cashflow : AllocatedCashflow( $account : account, $date : date <= $end, $amount : amount, type==TypedCashflow.DEBIT )
not AccountingPeriod( start < $start)
then
System.out.println("Debit: "+$date+" :: "+$amount);
$account.setBalance($account.getBalance()-$amount);
System.out.println("Account: "+$account.getAccountNo()+" - new balance: "+$account.getBalance());
retract($cashflow);
end
rule "Rule 08 - Retract Accounting Period"
salience 50
when
$accountingPeriod : AccountingPeriod( $start : start, $end : end )
not AccountingPeriod( start < $start)
then
System.out.println("Retracting Accounting Period: "+$start+" - "+$end);
retract($accountingPeriod);
end
Open the class net.tplusplus.drools.bankingondrools1.RuleRunner and add the following method:
RuleRunner.java
public static void simple8()
throws Exception
{
Account acc1 = new Account(1);
Account acc2 = new Account(2);
Object[] facts =
{
new AllocatedCashflow(acc1,new SimpleDate("01/01/2007"), TypedCashflow.CREDIT, 300.00),
new AllocatedCashflow(acc1,new SimpleDate("05/02/2007"), TypedCashflow.CREDIT, 100.00),
new AllocatedCashflow(acc2,new SimpleDate("11/03/2007"), TypedCashflow.CREDIT, 500.00),
new AllocatedCashflow(acc1,new SimpleDate("07/02/2007"), TypedCashflow.DEBIT, 800.00),
new AllocatedCashflow(acc2,new SimpleDate("02/03/2007"), TypedCashflow.DEBIT, 400.00),
new AllocatedCashflow(acc1,new SimpleDate("01/04/2007"), TypedCashflow.CREDIT, 200.00),
new AllocatedCashflow(acc1,new SimpleDate("05/04/2007"), TypedCashflow.CREDIT, 300.00),
new AllocatedCashflow(acc2,new SimpleDate("11/05/2007"), TypedCashflow.CREDIT, 700.00),
new AllocatedCashflow(acc1,new SimpleDate("07/05/2007"), TypedCashflow.DEBIT, 900.00),
new AllocatedCashflow(acc2,new SimpleDate("02/05/2007"), TypedCashflow.DEBIT, 100.00),
new AccountingPeriod(new SimpleDate("01/01/2007"),new SimpleDate("31/03/2007")),
new AccountingPeriod(new SimpleDate("01/04/2007"),new SimpleDate("30/06/2007"))
};
new RuleRunner().runRules(new String[]{"/simple/rule08.drl"},facts);
}
This gives us the ability to use a RuleFlow to include rule "Rule 08 - Credit" and not rule "Rule 08 - Debit" by specifying the ruleflow-group "CreditGroup", and vice versa by specifying the ruleflow-group "DebitGroup".
Now, modify the main method in RuleRunner to comment out calls to all the methods except rule08(). This will just tidy up the output, and exclude output from Banking On Drools - Part I:
RuleRunner.java
public static void main(String[] args)
throws Exception
{
//simple1();
//simple2();
//simple3();
//simple4();
//simple5();
//simple6();
//simple7();
simple8();
}
So far, all we have done is allocate two rules to RuleFlow groups. What happens if we run the code as it stands without introducing a RuleFlow?
Run the class RuleRunner, and we get the output:
output:
Loading file: /simple/rule08.drl
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],
date=Mon Jan 01 00:00:00 GMT 2007,type=Credit,amount=300.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],
date=Mon Feb 05 00:00:00 GMT 2007,type=Credit,amount=100.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],
date=Sun Mar 11 00:00:00 GMT 2007,type=Credit,amount=500.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],
date=Wed Feb 07 00:00:00 GMT 2007,type=Debit,amount=800.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],
date=Fri Mar 02 00:00:00 GMT 2007,type=Debit,amount=400.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],
date=Sun Apr 01 00:00:00 BST 2007,type=Credit,amount=200.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],
date=Thu Apr 05 00:00:00 BST 2007,type=Credit,amount=300.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],
date=Fri May 11 00:00:00 BST 2007,type=Credit,amount=700.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],
date=Mon May 07 00:00:00 BST 2007,type=Debit,amount=900.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],
date=Wed May 02 00:00:00 BST 2007,type=Debit,amount=100.0]
Inserting fact: AccountingPeriod[start=Mon Jan 01 00:00:00 GMT 2007,
end=Sat Mar 31 00:00:00 BST 2007]
Inserting fact: AccountingPeriod[start=Sun Apr 01 00:00:00 BST 2007,
end=Sat Jun 30 00:00:00 BST 2007]
Retracting Accounting Period: Mon Jan 01 00:00:00 GMT 2007
- Sat Mar 31 00:00:00 BST 2007
Retracting Accounting Period: Sun Apr 01 00:00:00 BST 2007
- Sat Jun 30 00:00:00 BST 2007
From the output, we can see that neither the Credit Rule nor the Debit Rule ran.
So, if we allocate a Rule to a RuleFlow Group, then we need to find some way of specifying the RuleFlow Group to run if we are to include these rules.
Step 2 - Modifying the RuleRunner
Our next step is to modify the RuleRunner runRules() method to allow us to specify a all the RuleFlows for the rule engine, and the initial RuleFlow Group to use.
To do this we need to overload the RunRules() method in RuleRunner to provide the RuleFlows and the process to start, as follows:
RuleRunner.java
public void runRules(String process, String[] ruleflows, String[] rules, Object... facts)
throws Exception
{
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
PackageBuilder builder = new PackageBuilder();
for(String ruleflow : ruleflows)
{
builder.addRuleFlow( new InputStreamReader(
RuleRunner.class.getResourceAsStream( ruleflow ) ) );
}
for(String ruleFile : rules)
{
System.out.println("Loading file: "+ruleFile);
builder.addPackageFromDrl(new InputStreamReader(this.getClass().getResourceAsStream(ruleFile)));
}
Package pkg = builder.getPackage();
ruleBase.addPackage(pkg);
WorkingMemory workingMemory = ruleBase.newStatefulSession();
for(Object fact : facts)
{
System.out.println("Inserting fact: "+fact);
workingMemory.insert(fact);
}
workingMemory.startProcess(process);
workingMemory.fireAllRules();
}
Step 3 - Creating the RuleFlow
By now we have allocated two of our rules to RuleFlow Groups and have provided a runRules() method that allows us to specify RuleFlows and a starting RuleFlow Group. Now, we need to create the RuleFlow.
To do this we are going to use the Eclipse Workbench (for Drools).
Open the Drools perspective and first let's create a source folder for our ruleflows:
Create a new source folder called ruleflows:
In Package Explorer, right click on the new ruleFlow folder and select New -> Other. From the Wizard select RuleFlow File.
Click on Next, and enter the filename creditFlows
Click on finish and eclipse will open the creditFlows.rf document in the graphical editor as follows:
Our next step is to create a simple ruleFlow for processing credits only.
The editor pane already contains a Start element. Next, we need to place a RuleFlowGroup on the editor, by clicking on RuleFlowGroup element in the left pane and then clicking on the edit pane below the Start element.
Repeat the process with the End element and place them in the editor as shown below:
Now we need to create the connections between the elements. Click on the ConnectionCreation element in the left pane and click on the Start element in the editor pane. Move the cursor to the RuleSet element and click again. A connection will be created between the two elements.
Repeat the process to connect the RuleSet element to the End element. Your edit pane should now look like:
Ok. So far we have a rule flow diagram with a Start, RuleFlow, and End element connected. This is all pretty much meaningless, however, until we set the properties in the ruleflow so that it is associated with a ruleflow group.
We want this ruleflow to execute the Credit rule that we created earlier in rule08.drl
rule08.drl
rule "Rule 08 - Credit"
ruleflow-group "CreditGroup"
salience 100
when
AccountingPeriod( $start : start, $end : end )
$cashflow : AllocatedCashflow( $account : account, $date : date <= $end, $amount : amount, type==TypedCashflow.CREDIT )
not AccountingPeriod( start < $start)
then
System.out.println("Credit: "+$date+" :: "+$amount);
$account.setBalance($account.getBalance()+$amount);
System.out.println("Account: "+$account.getAccountNo()+" - new balance: "+$account.getBalance());
retract($cashflow);
end
This means we need to set up our ruleflow to call the ruleflow-group "CreditGroup".
So, here we go:
Click on the RuleSet element in the editor pane and then click on the properties tab.
Set the Name to CreditFlow, and set the RuleFlowGroup to CreditGroup (same as the ruleflow-group in the rule above).
The screen shot is show below:
The name simply gives our element a display name that should be meaningful to us as we review the ruleflow diagram. The important property is the RuleFlowGroup which should be the same as the RuleFlowGroup of the rules that we want to include in this RuleFlow.
Finally, we need to set the properties for the CreditFlows ruleFlow itself. Click anywhere in the design screen to display the properties for the ruleFlow.
Set the properties as follows:
Connection Layout = Shortest Path
Id = credits
Name = creditFlows
Package = simple
Version = 1
The Id is the id by which we will indentify this ruleflow when we want to invoke it. To start this process we will call workingMemory.startProcess("credits"). The name is simply the name by which we want to identify this ruleflow. The package is the same package as that to which our rules belong (see rule08.drl), and the version is our latest version.
Specifying the RuleFlow to Run
Now let’s modify our simple8() method in RuleRunner to call our ruleflow.
public static void simple8()
throws Exception
{
Account acc1 = new Account(1);
Account acc2 = new Account(2);
Object[] facts =
{
new AllocatedCashflow(acc1,new SimpleDate("01/01/2007"), TypedCashflow.CREDIT, 300.00),
new AllocatedCashflow(acc1,new SimpleDate("05/02/2007"), TypedCashflow.CREDIT, 100.00),
new AllocatedCashflow(acc2,new SimpleDate("11/03/2007"), TypedCashflow.CREDIT, 500.00),
new AllocatedCashflow(acc1,new SimpleDate("07/02/2007"), TypedCashflow.DEBIT, 800.00),
new AllocatedCashflow(acc2,new SimpleDate("02/03/2007"), TypedCashflow.DEBIT, 400.00),
new AllocatedCashflow(acc1,new SimpleDate("01/04/2007"), TypedCashflow.CREDIT, 200.00),
new AllocatedCashflow(acc1,new SimpleDate("05/04/2007"), TypedCashflow.CREDIT, 300.00),
new AllocatedCashflow(acc2,new SimpleDate("11/05/2007"), TypedCashflow.CREDIT, 700.00),
new AllocatedCashflow(acc1,new SimpleDate("07/05/2007"), TypedCashflow.DEBIT, 900.00),
new AllocatedCashflow(acc2,new SimpleDate("02/05/2007"), TypedCashflow.DEBIT, 100.00),
new AccountingPeriod(new SimpleDate("01/01/2007"),new SimpleDate("31/03/2007")),
new AccountingPeriod(new SimpleDate("01/04/2007"),new SimpleDate("30/06/2007"))
};
String[] ruleFiles = { "/simple/rule08.drl" };
String[] ruleFlows = { "/creditFlows.rfm" };
new RuleRunner().runRules("credits",ruleFlows,ruleFiles,facts);
}
Finally, let's compile the project and run it.
output:
Loading file: /simple/rule08.drl
...
Credit: Sun Mar 11 00:00:00 GMT 2007 :: 500.0
Account: 2 - new balance: 500.0
Credit: Mon Feb 05 00:00:00 GMT 2007 :: 100.0
Account: 1 - new balance: 100.0
Credit: Mon Jan 01 00:00:00 GMT 2007 :: 300.0
Account: 1 - new balance: 400.0
Retracting Accounting Period: Mon Jan 01 00:00:00 GMT 2007
- Sat Mar 31 00:00:00 BST 2007
Retracting Accounting Period: Sun Apr 01 00:00:00 BST 2007
- Sat Jun 30 00:00:00 BST 2007
Notice, now we have all the Credits being applied, but no Debits!!