In my previous post Drools 5 – Complex Event Processing, I gave introduction to Drools Fusion module. In the current post, I would like to demonstrate Drools Fusion capabilities.
As an example chosen for the current article, I decided to use a scenario when an insurance firm rewards its policy members. The members are rewarded when they keep a low average of claimed money under the threshold set on their account over a period of time.
In other words, if the policy member had last five claims average and average of claims during last ninety (90) days lower than the threshold limit – he/she will be eligible for a reward.
Drools allows us to test for these conditions by applying the sliding window concept. So to obtain a claim average for a particular period in time, I will use time based sliding window. To obtain an average of last five claims, I will use length based sliding window.
Before I begin, I would like to say that the source code for this article is available as attachment, the download link is at the bottom.
Ok, lets begin. Below is my POJO Account:
[java]
public class Account {
private String number = "";
private double threshold = 0.00;
public Account() {
super();
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public double getThreshold() {
return threshold;
}
public void setThreshold(double threshold) {
this.threshold = threshold;
}
}
[/java]
The following is my implementation of the claim event POJO:
[java]
public class ClaimApprovedEvent implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber = "";
private double amount = 0.00;
public ClaimApprovedEvent(String accountNumber, double amount) {
this.accountNumber = accountNumber;
this.amount = amount;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
}
[/java]
This event represents a claim event when a policy member has claimed some money. Event contains policy member account number and the claim amount. These events, I will be “feeding” in to the KnowledgeSession as facts. Yes, they are play a role knowledge facts in Drools.
Having said that I want to point out that all events are facts, but not all facts are events. I know it maybe a little bit confusing, but if you read Drools 5.0 doco it will become more clearer to you.
To give some information about it – events almost never change state in Drools (almost means it is still possible to change the state of event), therefore they are immutable.
Events simply hold information about something that has already happened and because you cannot change what has already happened – events are immutable. Events also allow the use of sliding windows.
The following is implementation of a POJO, that plays a role of a common data structure for rules to use at run time. It is one of the new features that available in Drools 5. Keep in mind – that this is not a knowledge fact, but just a data structure.
This POJO is an internal type, therefore there is no need to create new instance of this class. The object will only be created at run time, when the knowledge package is compiled (In my tester class I show how to do it). For now, we just have to add POJOs declaration to DRL, and to have the source file present. Later on, you will see how the declaration in DRL is done:
[java]
public class AccountInfo {
private String accountNumber = "";
private double aveOfLastClaims = 0.00;
private double aveForPeriod = 0.00;
private boolean eligibleForBonusClaims = false;
private boolean eligibleForPeriodicBonus = false;
public AccountInfo() {
super();
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public double getAveOfLastClaims() {
return aveOfLastClaims;
}
public void setAveOfLastClaims(double aveOfLastClaims) {
this.aveOfLastClaims = aveOfLastClaims;
}
public double getAveForPeriod() {
return aveForPeriod;
}
public void setAveForPeriod(double aveForPeriod) {
this.aveForPeriod = aveForPeriod;
}
public boolean isEligibleForBonusClaims() {
return eligibleForBonusClaims;
}
public void setEligibleForBonusClaims(oolean eligibleForBonusClaims) {
this.eligibleForBonusClaims = eligibleForBonusClaims;
}
public boolean isEligibleForPeriodicBonus() {
return eligibleForPeriodicBonus;
}
public void setEligibleForPeriodicBonus(boolean eligibleForPeriodicBonus) {
this.eligibleForPeriodicBonus = eligibleForPeriodicBonus;
}
}
[/java]
…and the following is the DRL:
[java]
package net.javabeansdotasia.casestudy.pojo;
import net.javabeansdotasia.casestudy.pojo.ClaimApprovedEven
import net.javabeansdotasia.casestudy.pojo.Account;
declare ClaimApprovedEvent
// declare a fact type as an event, default is ‘fact’
@role( event )
end
// Decalare common data structure for all rules to use
declare AccountInfo
accountNumber : String
aveOfLastClaims : Double
aveForPeriod : Double
eligibleForBonusClaims : Boolean
eligibleForPeriodicBonus : Boolean
end
rule "FiveLastClaims"
dialect "mvel"
no-loop true
salience 100
when
$account : Account()
//check if the average of last five claims is less
//than the account threshold
$aveOfLastClaims : Number($account.threshold > doubleValue )
from accumulate(
ClaimApprovedEvent(
accountNumber == $account.number,
$amount : amount
)
over window:length( 5 ) from entry-point ClaimStream,
average($amount)
)
//the member must not have received eligibility
//for last claim bonus yet
$accountInfo : AccountInfo(accountNumber == $account.number, eligibleForBonusClaims == false )
then modify($accountInfo) {
setAveOfLastClaims($aveOfLastClaims);
setEligibleForBonusClaims(true);
};
end
rule "NinetyDaysPeriod"
dialect "mvel"
no-loop true
salience 90
when
$account : Account()
//check if the average of claims for period is less
//than the account threshold
$aveForPeriod : Number($account.threshold > doubleValue )
from accumulate(
ClaimApprovedEvent(
accountNumber == $account.number,
$amount : amount
)
over window:time( 90d ) from entry-point ClaimStream,
average($amount))
//the member must have eligibility for last claim bonus and
//the member must not have received eligibility for
//periodic bonus yet
$accountInfo : AccountInfo(accountNumber == $account.number, eligibleForBonusClaims == true, eligibleForPeriodicBonus == false)
then modify($accountInfo) {
setAveForPeriod($aveForPeriod);
setEligibleForPeriodicBonus(true);
};
end
rule "EligibleForVoucher"
dialect "mvel"
no-loop true
salience 80
when
$account : Account()
//the member must have eligibility for last claim bonus and
//the member must have eligibility for periodic bonus
$accountInfo : AccountInfo(
accountNumber == $account.number, eligibleForBonusClaims == true, eligibleForPeriodicBonus == true)
then
System.out.println("Notifying policy member…");
System.out.println("Dear member, you have claimed " + "in average $" +
$accountInfo.aveOfLastClaims + " during last five claims, which is " +
"under the account threshold of $" + $account.threshold);
System.out.println("Dear member, you have claimed " + "in average $" +
$accountInfo.aveForPeriod + " during last 90 days, which is "+
"under the account threshold of $" + $account.threshold);
System.out.println("You are eligible for a holiday!");
end
[/java]
Lets have a look what does this DRL do:
1. Modifying existing type by specifying that ClaimApprovedEvent is an event (line 6). To allow Drools to process events, we have to set that the fact is of type event
2. Declaring new type that plays the role of a common data structure (line 12). This is the declaration that I was talking about previously.
3. rule “FiveLastClaims” obtains an average of last five event claims and compares it to the account threshold. If the rule return true, the AccountInfo is updated with new data for other rules to use.
4. rule “NinetyDaysPeriod” obtains an average claim totals over a period of ninety (90) days and compares it to the account threshold. If the rule return true, the AccountInfo is updated with new data for other rules to use
5. rule “EligibleForVoucher” checks if the account owner has both eligibility for a periodic bonus and last five claim bonus, if true – the policy member will be eligible for a reward
The following is the snippet code from my Tester class where I show how to I load claim events over a period of time into the Knowledge session entry point (once again to remind: please refer to the source code attached for more information):
[java]
SessionPseudoClock clock = session.getSessionClock();
WorkingMemoryEntryPoint claimStream = session.getWorkingMemoryEntryPoint("ClaimStream");
claimStream.insert(new ClaimApprovedEvent(account.getNumber(), 12.00));
clock.advanceTime(80, TimeUnit.DAYS);
claimStream.insert(new ClaimApprovedEvent(account.getNumber(), 46.00));
clock.advanceTime(15, TimeUnit.DAYS);
claimStream.insert(new ClaimApprovedEvent(account.getNumber(), 60.00));
clock.advanceTime(45, TimeUnit.DAYS);
claimStream.insert(new ClaimApprovedEvent(account.getNumber(), 110.00));
clock.advanceTime(60, TimeUnit.DAYS);
claimStream.insert(new ClaimApprovedEvent(account.getNumber(), 20.00));
session.insert(account);
session.insert(accountInfo);
session.fireAllRules();
[/java]
Please note an entry point “ClaimStream”. Entry point plays a role of a partition in KnowledgeSession in Drools. Partitioning is also a new concept in Drools that makes KnowledgeSession multithreaded. You can have multiple entry points and choose where to insert the fact.
In the current example, I use only one partition (or entry point). If I would to use more than one, than I would have to enable multi-partitioning with the following code:
[java]
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption(MultithreadEvaluationOption.YES);
[/java]
Have a read Drools Fusion manual, the team really gives a good cover of this concept.
I use SessionPseudoClock that allows me to test the rules by feeding claim events into the KnowledgeSession over a period of time. As you can see, after each event I move the clock forward, simulation submission of each claim event in different time.
The following is the program output:
[java]
Notifying policy member…
Dear member, you have claimed in average $49.6 during last five claims, which is under the account threshold of $70.0
Dear member, you have claimed in average $65.0 during last 90 days, which is under the account threshold of $70.0
You are eligible for a holiday!
[/java]
That’s it. In this example I showed how it is possible to process complex events over a period of time by applying sliding window concept.
Please note that the above example was tested by me and its working fine. I also included source files for the above example as Eclipse project. You can simply create a new Java project from this existing Ant build.xml file.
Also, I wanted to point out that in my Eclipse project setup my Drools binaries located under JAVA_HOME/lib/drools-5.0-bin/ (have a look at the build.xml)
I hope this was clear
Cheers
drools-case-study-2-CEP
Resources:
Drools JBoss Rules 5.0 Developer’s Guide July 2009