Adding more constraints in facts
We concentrate on how the rule engine works in the first lesson. In the second lesson, we introduced how to express constraint between facts.
In this lesson, we will concentrate on all the drools language possibilities to write constraints on facts for more complex cases.
The reader has to create a test classes called TestLesson3 like for lesson2, a new package lesson3 in the src/test/rules and add in the kmodule.xml a new session declaration
<kbase name="rules3" packages="lesson3">
<ksession name="ksession-lesson3"/>
</kbase>
While building the examples, you will see more rules fired than the shown examples. As drools is a declarative language, as soon as the constraint are satisfied, the rule can fire.
Some more classes
To be able to see some more advanced features, we are going to add 2 new classes in src/main/java droolscours package.
package droolscours;
public class Customer {
private String name;
private String surname;
private String country;
public Customer(String name, String surname, String country) {
super();
this.name = name;
this.surname = surname;
this.country = country;
}
public Customer() {
super();
// TODO Auto-generated constructor stub
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Override
public String toString() {
StringBuffer buff = new StringBuffer();
buff.append("-----Customer-----)\n");
buff.append("Name=" + this.name + "\n");
buff.append("Surname Name=" + this.surname + "\n");
buff.append("Country=" + this.country + "\n");
buff.append("-----Customer end-)");
return buff.toString();
}
}
package droolscours;
public class PrivateAccount extends Account {
private Customer owner;
public Customer getOwner() {
return owner;
}
public void setOwner(Customer owner) {
this.owner = owner;
}
@Override
public String toString() {
StringBuffer buff = new StringBuffer();
buff.append("-----Private Account-)");
buff.append(super.toString());
if (this.owner != null) {
buff.append(this.owner.toString());
}
buff.append("-----Private Account end-)");
return buff.toString();
}
}
In Constraint
This allows to validate an attribute is a list of values
package cours
//#list any import classes here.
import droolscours.CashFlow;
import util.OutputDisplay;
//#declare any global variables here
global OutputDisplay showResult;
rule "The cashFlow can be a credit or a debit"
when
$cash :CashFlow(type in ( CashFlow.DEBIT,CashFlow.CREDIT) )
then
showResult.showText("The cashFlow is a credit or a debit");
end
@Test
public void testInConstrait() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
CashFlow cashFlow = new CashFlow();
cashFlow.setType(CashFlow.CREDIT);
sessionStatefull.insert(cashFlow);
sessionStatefull.fireAllRules();
} }
And the console should look as follows :
Nested Accessor
This allows to add a constraint to a attribute class without the need to add the linked object to the session.
rule "Accessor"
when
$cash :PrivateAccount( owner.name =="Héron" )
then
showResult.showText("Account is owned by Héron");
end
@Test
public void testNestedAccessor() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
Customer customer = new Customer();
customer.setName("Héron");
customer.setSurname("Nicolas");
PrivateAccount pAccount = new PrivateAccount();
pAccount.setOwner(customer);
sessionStatefull.insert(pAccount);
sessionStatefull.fireAllRules();
}
As seen here, we do not add the customer instance to the drools session.
The rule has been fired.
And/or
It is possible to do constraints on attribute like in java.
rule "infixAnd"
when
( $c1 : Customer ( country=="GB") and PrivateAccount( owner==$c1))
or
( $c1 : Customer (country=="US") and PrivateAccount( owner==$c1))
then
showResult.showText("Person lives in GB or US");
end
@Test
public void testInOrFact() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
Customer customer = new Customer();
customer.setCountry("GB");
sessionStatefull.insert(customer);
PrivateAccount pAccount = new PrivateAccount();
pAccount.setOwner(customer);
sessionStatefull.insert(pAccount);
sessionStatefull.fireAllRules();
}
not
This allows to test if no fact of a type is in the session.
rule "no customer"
when
not Customer( )
then
showResult.showText("No customer");
end
@Test
public void testNotCondition() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
sessionStatefull.fireAllRules();
}
exist
On the contrary of previous syntax, this allows to test if there at least one fact type is in the session.
rule "Exists"
when
exists Account( )
then
showResult.showText("Account exists");
end
@Test
public void testExistsCondition() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
Account pAccount = new Account();
sessionStatefull.insert(pAccount);
Customer c = new Customer();
sessionStatefull.insert(c);
sessionStatefull.fireAllRules();
}
ForAll
We would like to verify that every cashflow instance is linked to an Account instance.
rule "ForAll"
when
forall ( Account( $no : accountNo )
CashFlow( accountNo == $no)
)
then
showResult.showText("All cashflows are related to an Account ");
end
In this rule, in the forall condition, we link the CashFLow instance to the Account instance.
We are going to do a test case where all objects are related
@Test
public void testForALl() throws Exception {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSessionWithCallback(kieContainer, "ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
Account a = new Account();
a.setAccountNo(1);
a.setBalance(0);
sessionStatefull.insert(a);
CashFlow cash1 = new CashFlow();
cash1.setAccountNo(1);
sessionStatefull.insert(cash1);
CashFlow cash2 = new CashFlow();
cash2.setAccountNo(1);
sessionStatefull.insert(cash2);
Account a2 = new Account();
a2.setAccountNo(2);
a2.setBalance(0);
sessionStatefull.insert(a2);
CashFlow cash3 = new CashFlow();
cash3.setAccountNo(2);
sessionStatefull.insert(cash3);
sessionStatefull.fireAllRules();
}
When running the test, you should see the following logging.
Just modify the test case by updating the test case :
sessionStatefull.insert(cash2);
Account a2 = new Account();
a2.setAccountNo(2);
a2.setBalance(0);
sessionStatefull.insert(a2);
CashFlow cash3 = new CashFlow();
cash3.setAccountNo(1);
sessionStatefull.insert(cash3);
sessionStatefull.fireAllRules();
When you run the test case, the rule ForAll will not be fired.
From
It is sometimes needed to access data from outside the drools session.
As it is not possible to insert all objects in the session, we can use the from instruction in the when part.
First let us create a CustomerService class in package Droolscours.sercice
package droolscours.service;
import droolscours.Customer;
import java.util.ArrayList;
import java.util.List;
public class CustomerService {
public List<Customer> getListCustomer() {
List<Customer> result = new ArrayList<Customer>();
result.add(new Customer("Héron", "Nicolas", "Fr"));
result.add(new Customer("Héron", "James", "GB"));
result.add(new Customer("Héron", "Nicolas", "GB"));
return result;
}
}
then we shall create the rule that uses the from
// add import for the service
import droolscours.service.CustomerService;
// add the global
global CustomerService serviceCustomer;
rule "FromCondition"
when
$c : Customer()
$cc : Customer(name ==$c.name,surname==$c.surname,country !=$c.country) from serviceCustomer.getListCustomer();
then
showResult.showText("Found same customer in 2 countries");
end
and the following test case :
@Test
public void testFromLHS() throws Exception {
sessionStatefull = KnowledgeSessionHelper.getStatefulKnowledgeSessionWithCallback(kieContainer,
"ksession-lesson3");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
sessionStatefull.setGlobal("serviceCustomer", new CustomerService());
Customer c = new Customer("Héron", "Nicolas", "A");
sessionStatefull.insert(c);
sessionStatefull.fireAllRules();
}
The rule is fired twice as in the service there are two customers with the same name and with a different country.
Collecting
The purpose is to collect a set of fact and constraint if the constraints are true. Let us see the following example int the rule "More then 2 CashFlow Line". in this rule, we want to collect all CashFlow that are in the correct time period and the good account number. The "from collect" syntax returns an arrayList. It is possible to add a condition as in the first rule where we add a constraint that we expect at least 2 items. In the second rule, we do not add this constraint.
rule "More then 2 CashFlow Line"
when
$c : Account( $acc : accountno )
$p : AccountingPeriod ($sDate : startDate ,$eDate : endDate )
$number : ArrayList(size >= 2 )
from collect( CashFlow( mvtDate >= $sDate && mvtDate <= $eDate,accountNo == $acc ) )
then
showResult.showText("Found more than 2 CashFlow Lines");
showResult.showText("<<<<<<<<<<");
for (Object ff : $number){
showResult.showText(ff.toString());
}
showResult.showText(">>>>>>>>>>>>>>>>");
end
rule "Numbers of CashFlow Line"
when
$c : Account( $acc : accountno )
$p : AccountingPeriod ($sDate : startDate ,$eDate : endDate )
$number : ArrayList( )
from collect( CashFlow( mvtDate >= $sDate && mvtDate <= $eDate,accountNo == $acc ) )
then
showResult.showText("Found "+$number+" more than 2 CashFlow Lines");
end
You may need to add constructors in the CashFlow and AccountingPeriod classes :
public AccountingPeriod() {
}
public AccountingPeriod(Date startDate, Date endDate) {
super();
this.startDate = startDate;
this.endDate = endDate;
}
public CashFlow() {
super();
}
public CashFlow(Date mvtDate, double amount, int type, long accountNo) {
super();
this.mvtDate = mvtDate;
this.amount = amount;
this.type = type;
this.accountNo = accountNo;
}
Here is our test case :
@Test
public void testCollecting() throws Exception {
sessionStatefull = KnowledgeSessionHelper.getStatefulKnowledgeSessionWithCallback(kieContainer,
"lesson34-session");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
Account a = new Account();
a.setAccountNo(1);
a.setBalance(0);
sessionStatefull.insert(a);
sessionStatefull.insert(new CashFlow(DateHelper.getDate("2010-01-15"), 1000, CashFlow.CREDIT, 1));
sessionStatefull.insert(new CashFlow(DateHelper.getDate("2010-02-15"), 500, CashFlow.DEBIT, 1));
sessionStatefull.insert(new CashFlow(DateHelper.getDate("2010-04-15"), 1000, CashFlow.CREDIT, 1));
sessionStatefull
.insert(new AccountingPeriod(DateHelper.getDate("2010-01-01"), DateHelper.getDate("2010-31-31")));
sessionStatefull.fireAllRules();
}
Accumulating
In the previous section, we collect data. There is an "from accumulate" that allows us to sum data in one command.
the "from collect" instruction takes 5 parameters :
1) a fact constraint expression
2) an init condition
3) the instruction when the rule applies to the fact constraint expression
4) the reverse action when the fact constraint expression is not true anymore
5) The result of the accumulate
Here is our example :
rule "Credit and Debit Rule"
when
$c : Account( $acc : accountno )
$p : AccountingPeriod ($sDate : startDate ,$eDate : endDate )
$totalCredit : Number( doubleValue > 100 )
from accumulate( CashFlow( type ==CashFlow.CREDIT,$value : amount, mvtDate >= $sDate && mvtDate <= $eDate,accountNo == $acc ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
$totalDebit : Number( doubleValue > 100 )
from accumulate( CashFlow( type ==CashFlow.DEBIT,$value : amount, mvtDate >= $sDate && mvtDate <= $eDate,accountNo == $acc ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
then
showResult.showText(" Found "+$totalCredit+" as a credit");
showResult.showText(" Found "+$totalDebit+" as a debit");
end
The constraint here is on a fact type CashFlow with the constraints that we already used before (good account number and the good date period and it should be a credit or a debit)
Then the initial condition, we initialize a double value we call total. Then in the action/reverse, we add to the total the amount in the CashFlow that we get by using an attribute binding. In the result action we put the total we calculated.
@Test
public void testAccumulate() throws Exception {
sessionStatefull = KnowledgeSessionHelper.getStatefulKnowledgeSessionWithCallback(kieContainer, "lesson35-session");
OutputDisplay display = new OutputDisplay();
sessionStatefull.setGlobal("showResult", display);
sessionStatefull.insert(new Account(1,0));
FactHandle fa = sessionStatefull.insert(new CashFlow(DateHelper.getDate("2010-01-15"), 1000, CashFlow.CREDIT, 1));
sessionStatefull.insert(new CashFlow(DateHelper.getDate("2010-02-15"),500,CashFlow.DEBIT,1));
sessionStatefull.insert(new CashFlow(DateHelper.getDate("2010-04-15"),1000,CashFlow.CREDIT,1));
sessionStatefull.insert(new AccountingPeriod(DateHelper.getDate("2010-01-01"),DateHelper.getDate("2010-12-31")));
sessionStatefull.fireAllRules();
sessionStatefull.delete(fa);
sessionStatefull.fireAllRules();
}
Summary
This lesson was an introduction to the main drools language syntax that are needed for starting a drools project.
In the next lesson, we will start learning the ruleflow concept that we encourage to use in drools projects.