in the src/test/rules, create a package lesson2 and a rule resource named lesson2.drl and should look like this :
package droolscours
//list any import classes here.
import droolscours.AccountingPeriod;
import droolscours.CashFlow;
import droolscours.Account;
import util.OutputDisplay;
global OutputDisplay showResults;
rule "Your First Rule revisited again"
when
Account( )
then
showResults.showText("The account exists");
end
package droolscours;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.kie.api.event.rule.ObjectDeletedEvent;
import org.kie.api.event.rule.ObjectInsertedEvent;
import org.kie.api.event.rule.ObjectUpdatedEvent;
import org.kie.api.event.rule.RuleRuntimeEventListener;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.api.runtime.rule.FactHandle;
import util.KnowledgeSessionHelper;
@SuppressWarnings("restriction")
public class TestLesson2 {
static KieContainer kieContainer;
StatelessKieSession sessionStateless = null;
KieSession sessionStatefull = null;
@BeforeClass
public static void beforeClass(){
kieContainer=KnowledgeSessionHelper.createRuleBase();
}
@Before
public void setUp() throws Exception{
System.out.println("------------Before------------");
}
@After
public void tearDown() throws Exception{
System.out.println("------------After------------");
}
@Test
public void testdeuxFait1() {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer,"ksession-lesson2");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResults", display);
Account a = new Account();
sessionStatefull.insert(a);
AccountingPeriod period = new AccountingPeriod();
sessionStatefull.insert(period);
sessionStatefull.fireAllRules();
}
}
To see if it runs, select the TestLesson2 class, right click and run as Junit Test and the console should look like this :
Test Case
We are going to implement a test case with the following data : 1) Account with accountno=1 2) an Accounting period going from January first 2016 to march 31th 2016 3) 3 Cash Flow movements, credit 1000$ January 15th 2016, debit 500$ February 15th 2016 and a credit movement April 15th 2016 of 1000$.
The result should be a balance of 500$ for the accounting period.
Fact binding
package droolscours
//list any import classes here.
import droolscours.AccountingPeriod;
import droolscours.CashFlow;
import droolscours.Account;
import util.OutputDisplay;
global OutputDisplay showResults;
rule "Credit rule"
when
$cash :CashFlow(type == CashFlow.CREDIT )
$acc : Account( )
then
$acc.setBalance($acc.getBalance()+$cash.getAmount());
showResults.showText("Account no "+$acc.getAccountNo()+ " has now a balance of "+$acc.getBalance());
end
In front of the fact, we declare variables $cash and $acc like shown above and then those variables can be used in the then part as normal java variables. As shown above we can then update the balance.
In the TestTwoFacts test we insert an object of type Account and CashFlow mouvement of type credit. At the end, the balance should be 1000$. the console should look like this when running the test :
Attribute binding
Let us make now an example with a second CashFlow movement. Here is the test case :
@Test
public void testTwofactsTwocashFlowMovement() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer,"ksession-lesson2");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResults", display);
Account a = new Account();
a.setAccountNo(1);
a.setBalance(0);
sessionStatefull.insert(a);
CashFlow cash1 = new CashFlow();
cash1.setAccountNo(1);
cash1.setAmount(1000);
cash1.setMvtDate(DateHelper.getDate("2010-01-15"));
cash1.setType(CashFlow.CREDIT);
sessionStatefull.insert(cash1);
CashFlow cash2 = new CashFlow();
cash2.setAccountNo(2);
cash2.setAmount(1000);
cash2.setMvtDate(DateHelper.getDate("2010-01-15"));
cash2.setType(CashFlow.CREDIT);
sessionStatefull.insert(cash2);
sessionStatefull.fireAllRules();
Assert.assertEquals(a.getBalance(), 1000,0);
}
package droolscours
//list any import classes here.
import droolscours.AccountingPeriod;
import droolscours.CashFlow;
import droolscours.Account;
import util.OutputDisplay;
global OutputDisplay showResults;
rule "Credit rule"
when
$cash :CashFlow($accno : accountNo ,type == CashFlow.CREDIT )
$acc : Account( accountNo ==$accno )
then
$acc.setBalance($acc.getBalance()+$cash.getAmount());
showResults.showText("Account no "+$acc.getAccountNo()+ " has now a balance of "+$acc.getBalance());
end
The rule above uses a binding variable. We create an attribute variable called $accno on the attribute accountNo. We can define now a binding on the attribute accountNo of fact Account.
Calculating balance
Now we know how to link facts and use attribute variable via binding attributes to use them as constraint, we shall modify the credit rule and create a debit rule :
package cours
import droolscours.Account;
import droolscours.AccountingPeriod;
import droolscours.CashFlow;
import droolscours.util.OutputDisplay;
global OutputDisplay showResult;
rule "Credit rule"
when
$cash :CashFlow( $aDate : mvtDate, $no : accountNo ,type == CashFlow.CREDIT )
$acc : Account(accountNo ==$no )
$period : AccountingPeriod( startDate <= $aDate && endDate >= $aDate)
then
$acc.setBalance($acc.getBalance()+$cash.getAmount());
showResults.showText("Account no "+$acc.getAccountNo()+ " has now a balance of "+$acc.getBalance());
end
rule "Debit rule"
when
$cash :CashFlow( $aDate : mvtDate, $no : accountNo ,type == CashFlow.DEBIT )
$acc : Account(accountNo ==$no )
$period : AccountingPeriod( startDate <= $aDate && endDate >= $aDate)
then
$acc.setBalance($acc.getBalance()-$cash.getAmount());
showResults.showText("Account no "+$acc.getAccountNo()
+ " has now a balance of "+$acc.getBalance());
end
In the rule above, we add a constraint so that the mvtDate of the CashFlow is between the startDate and endDate of the AccountinPeriod.
and the test case
@Test
public void testcalculateBalance() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson2");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResults", display);
Account a = new Account();
a.setAccountNo(1);
a.setBalance(0);
sessionStatefull.insert(a);
CashFlow cash1 = new CashFlow();
cash1.setAccountNo(1);
cash1.setAmount(1000);
cash1.setMvtDate(DateHelper.getDate("2016-01-15"));
cash1.setType(CashFlow.CREDIT);
sessionStatefull.insert(cash1);
CashFlow cash2 = new CashFlow();
cash2.setAccountNo(1);
cash2.setAmount(500);
cash2.setMvtDate(DateHelper.getDate("2016-02-15"));
cash2.setType(CashFlow.DEBIT);
sessionStatefull.insert(cash2);
CashFlow cash3 = new CashFlow();
cash3.setAccountNo(1);
cash3.setAmount(1000);
cash3.setMvtDate(DateHelper.getDate("2016-04-15"));
cash3.setType(CashFlow.CREDIT);
sessionStatefull.insert(cash3);
AccountingPeriod period = new AccountingPeriod();
period.setStartDate(DateHelper.getDate("2016-01-01"));
period.setEndDate(DateHelper.getDate("2016-03-31"));
sessionStatefull.insert(period);
sessionStatefull.fireAllRules();
Assert.assertTrue(a.getBalance()==500);
}
Summary
In lesson 1, we learned the dynamic of the rule engine : how and when are the rules fired. In the current lesson, we started to link together conditions between facts and how to interact with the facts in the rule execution. In the next lesson, we will see how to express more complex constraints.
So now we know all is working we can continue our lesson.
We want to update the account balance for each CashFlow. We first put the CashFlow and select all CashFlow of type CREDIT. To do so, we add a constraint on the type attribute of java class CashFlow. Then we add a second constraint of type Account. The question is now how in the then part do the balance update. It is where the fact binding comes in :
When we run the test case, the test fails : and in the console, we see that the rule "Credit rule" is running twice :
The rule was fired twice because there are 2 (CashFlow,Account) couples. The first CashFlow mouvement concerns account number 1 and the seconde account number 2. We need to find a way to link the two facts. We shall modify the rule like this :
Now the rule "Credit Rule" is only fired once and the balance is corret.
As expected, the "Credit Rule" is fired once and the "Debit Rule" is fired once also. The CashFlow movement of April 15 2016 is ignored as it does not fulfill the constraints.