The last two articles focused on some of the current projects that I was working on. So after kick starting Drools(4.0.7), I lost track and wrote some posts on JBoss clustering. Interesting as they were, I want to get back on defining business rules using JBoss Drools. I am just taking up from where I left off in my last Drools post. In my last post, I covered how Drools can use MS Excel spreadsheets to implement decision tables. Now I am going to talk about implementing business rules using a DRL. You could treat this as a tutorial.
Please find below a sample DRL file, I have implemented. The idea here is to understand the various elements and operators provided by Drools and how to utilize them in an actual business scenario.
package IssueRules
#list any import classes here.
import com.ins.Policy;
import com.ins.PolicyProduct;
import com.ins.Coverage;
import java.util.ArrayList;
#declare any global variables here
global com.ins.result.PolicyRuleResult result;
#global com.ins.PolicyProduct policyProd;
rule "Determine if any one of coverages are eligible for Issue"
salience 100
when
$policy : Policy()
exists ($coverage : Coverage(eligible == true) from $policy.coverages)
then
result.setEligible(Boolean.TRUE);
end
rule "Determine if all of coverages are eligible for Issue"
salience 99
when
$policy : Policy()
not (exists(Coverage(eligible == false) from $policy.coverages))
then
result.setAllCoveragesAreEligible(Boolean.TRUE);
end
rule "Determine if agent information captured is valid"
salience 98
when
$policy : Policy( agents contains "Bill Smith")
then
result.setAgentInformationValid(Boolean.TRUE);
end
rule "Determine if product can be sold in the specific state"
salience 97
when
$policyProd : PolicyProduct()
Policy(issueState memberOf $policyProd.states)
then
result.setApprovedState(Boolean.TRUE);
end
rule "Check out the soundex function of Drools"
# check if any insured name matches a particular pattern
salience 96
when
$policy : Policy()
exists ($coverage : Coverage(insured.firstName soundslike 'Elizabeth') from $policy.coverages)
then
result.addSoundMessage("Sound Ex Works!");
end
rule "Check out the soundex function of Drools - minor change"
# check if any insured name matches a particular pattern with a minor variation
salience 95
when
$policy : Policy()
$coverage : Coverage(insured.firstName soundslike 'Elizabeth') from $policy.coverages
then
result.addSoundMessage("Sound Ex Works!");
end
rule "Check out the collect function of Drools and collect functionality diff from function"
salience 94
when
$policy : Policy()
$coverages : ArrayList(size == 2)
from collect($coverage : Coverage(insured.firstName soundslike 'Elizabeth') from $policy.coverages)
then
result.setSoundexWorksCollect(Boolean.TRUE);
end
rule "Check if the coverages are valid and satisfy gender, age, faceamount validation rules"
salience 93
#no-loop true
when
$policy : Policy()
$coverage : Coverage( (faceAmount < 50000 || faceAmount > 250000) ||
( (insured.gender == "M" && (insured.age < 25 || insured.age > 65))
||
(insured.gender == "F" && (insured.age < 18 || insured.age > 65))
)
)
from $policy.coverages
then
$coverage.setEligible(false);
//update($policy); #Point to be noted Infinite Loop Refer: http://www.mail-archive.com/rules-users@lists.jboss.org/msg07671.html
end
rule "Return value identifier sample"
salience 92
#no-loop true
when
$policy : Policy()
$coverage1: Coverage (faceAmount == 250000, insured.gender == "F", $age : insured.age) from $policy.coverages
$coverage2: Coverage (insured.gender == "F", insured.age == ($age+15)) from $policy.coverages
then
result.setRetIdentifier(Boolean.TRUE);
//update($policy); #Point to be noted Infinite Loop Refer: http://www.mail-archive.com/rules-users@lists.jboss.org/msg07671.html
end
rule "Compute the total and average faceamount per coverage"
salience 91
when
$policy : Policy()
$total : Number()
from accumulate ( $coverage: Coverage($amt : faceAmount) from $policy.coverages,
init (double total =0;),
action (total += $amt;),
reverse(total -= $amt;),
result(total)
)
then
result.setTotalFaceAmount(new Integer($total.intValue()));
end
rule "Compute the total in a simpler fashion"
salience 90
when
$policy : Policy()
$total : Number()
from accumulate ( $coverage: Coverage($amt : faceAmount) from $policy.coverages,
sum($amt)
)
then
result.setTotalFaceAmountSimp(new Integer($total.intValue()));
end
rule "Compute the average faceamount per coverage"
salience 89
when
$policy : Policy()
$total : Number()
from accumulate ( $coverage: Coverage($amt : faceAmount) from $policy.coverages,
average($amt)
)
then
result.setAverageFaceAmount(new Double($total.doubleValue()));
end
query "Get Insured details for the policy"
$policy : Policy()
$coverages : Coverage($insured: insured ) from $policy.coverages
end
To explain the implementation, I have created some sample Java files, Policy, PolicyProduct, Coverage and PolicyRuleResult.
##Policy.java
package com.ins;
import java.util.ArrayList;
import java.util.List;
public class Policy {
private List<Coverage> coverages = new ArrayList<Coverage>();
private List<String> agents = new ArrayList<String>();
private String issueState = null;
private List<String> sample = new ArrayList<String>();
public List<Coverage> getCoverages() {
return coverages;
}
public void setCoverages(List<Coverage> coverages) {
this.coverages = coverages;
}
public void addCoverage(Coverage coverage) {
this.coverages.add(coverage);
}
public List<String> getAgents() {
return agents;
}
public void setAgents(List<String> agents) {
this.agents = agents;
}
public void addAgent(String agentName) {
this.agents.add(agentName);
}
public List<String> getSample() {
return sample;
}
public void setSample(List<String> sample) {
this.sample = sample;
}
public String getIssueState() {
return issueState;
}
public void setIssueState(String issueState) {
this.issueState = issueState;
}
}
##Policy.java
##PolicyProduct.java
package com.ins;
import java.util.ArrayList;
import java.util.List;
public class PolicyProduct {
private List<String> states = new ArrayList<String>();
{
states.add("VA");
states.add("WI");
states.add("NY");
}
public List<String> getStates() {
return states;
}
public void setStates(List<String> states) {
this.states = states;
}
}
##PolicyProduct.java
##Coverage.java
package com.ins;
public class Coverage {
private boolean eligible = false;
private Insured insured = null;
private int faceAmount = 0;
public boolean isEligible() {
return eligible;
}
public void setEligible(boolean eligible) {
this.eligible = eligible;
}
public Insured getInsured() {
return insured;
}
public void setInsured(Insured insured) {
this.insured = insured;
}
public int getFaceAmount() {
return faceAmount;
}
public void setFaceAmount(int faceAmount) {
this.faceAmount = faceAmount;
}
}
##Coverage.java
##Insured.java
package com.ins;
public class Insured {
private String firstName = null;
private String lastName = null;
private int age = 0;
private String gender = null;
public static final String GENDER_MALE = "M";
public static final String GENDER_FEMALE = "F";
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
##Insured.java
##PolicyRuleResult.java
package com.ins.result;
import java.util.ArrayList;
import java.util.List;
public class PolicyRuleResult {
private boolean eligible = false;
private boolean allCoveragesAreEligible = false;
private boolean agentInformationValid = false;
private boolean approvedState = false;
private List<String> soundMessages = new ArrayList<String>();
private boolean soundexWorksCollect = false;
private boolean retIdentifier = false;
private Integer totalFaceAmount = null;
private Integer totalFaceAmountSimp = null;
private Double averageFaceAmount = null;
public boolean isAllCoveragesAreEligible() {
return allCoveragesAreEligible;
}
public void setAllCoveragesAreEligible(boolean allCoveragesAreEligible) {
this.allCoveragesAreEligible = allCoveragesAreEligible;
}
public boolean isEligible() {
return eligible;
}
public void setEligible(boolean eligible) {
this.eligible = eligible;
}
public boolean isAgentInformationValid() {
return agentInformationValid;
}
public void setAgentInformationValid(boolean agentInformationValid) {
this.agentInformationValid = agentInformationValid;
}
public boolean isApprovedState() {
return approvedState;
}
public void setApprovedState(boolean approvedState) {
this.approvedState = approvedState;
}
public void addSoundMessage(String msg) {
this.soundMessages.add(msg);
}
public List<String> getSoundMessages() {
return this.soundMessages;
}
public boolean isSoundexWorksCollect() {
return soundexWorksCollect;
}
public void setSoundexWorksCollect(boolean soundexWorksCollect) {
this.soundexWorksCollect = soundexWorksCollect;
}
public boolean isRetIdentifier() {
return retIdentifier;
}
public void setRetIdentifier(boolean retIdentifier) {
this.retIdentifier = retIdentifier;
}
public Integer getTotalFaceAmount() {
return totalFaceAmount;
}
public void setTotalFaceAmount(Integer totalFaceAmount) {
this.totalFaceAmount = totalFaceAmount;
}
public Integer getTotalFaceAmountSimp() {
return totalFaceAmountSimp;
}
public void setTotalFaceAmountSimp(Integer totalFaceAmountSimp) {
this.totalFaceAmountSimp = totalFaceAmountSimp;
}
public Double getAverageFaceAmount() {
return averageFaceAmount;
}
public void setAverageFaceAmount(Double averageFaceAmount) {
this.averageFaceAmount = averageFaceAmount;
}
}
##PolicyRuleResult.java
rule salience <integer value> when <CONDITION> then <ACTION>
1: rule "Determine if any one of coverages are eligible for Issue" 2: salience 100 3: when 4: $policy : Policy() 5: exists ($coverage : Coverage(eligible == true) from $policy.coverages) 6: then 7: result.setEligible(Boolean.TRUE); 8: end
from $policy.coverages
$coverage : Coverage(eligible == true) from $policy.coverages
This clause helps retrieve all coverages where eligible attribute is set to true. To confirm via a boolean condition we add the exists conditional element. So the complete clause becomes
exists($coverage : Coverage(eligible == true) from $policy.coverages). Note all java classes referenced in the rule are added in the drl file import statements. Now that we have defined the condition let’s define the action. Apparently only global variables can be accessed in the action block. We have defined a global variable acroynmed ‘result’. As a an action we set its eligible attribute to true via line 7.
Great we are done with our first rule. Let’s write some test code to validate if it runs successfully.
package com.ins.test;
import java.io.FileReader;
import java.net.URL;
import java.util.Iterator;
import org.drools.QueryResult;
import org.drools.QueryResults;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.audit.WorkingMemoryFileLogger;
import org.drools.compiler.PackageBuilder;
import org.drools.rule.Package;
import com.ins.Coverage;
import com.ins.Insured;
import static com.ins.Insured.*;
import com.ins.Policy;
import com.ins.PolicyProduct;
import com.ins.result.PolicyRuleResult;
public class IssueTest {
public static void main(String args[]) {
try {
//load up the rulebase
RuleBase ruleBase = readDRL();
WorkingMemory workingMemory = ruleBase.newStatefulSession();
WorkingMemoryFileLogger logger = new WorkingMemoryFileLogger(workingMemory);
logger.setFileName("C:/drools-audit");
Policy policy = new Policy();
policy.addAgent("Bill Smith");
Coverage coverage1 = new Coverage();
coverage1.setEligible(true);
coverage1.setFaceAmount(250000);
Coverage coverage2 = new Coverage();
coverage2.setEligible(true);
coverage2.setFaceAmount(1500000);
policy.addCoverage(coverage1);
policy.addCoverage(coverage2);
policy.setIssueState("CO");
Insured insured1 = new Insured();
insured1.setFirstName("Elizabeth");
insured1.setAge(30);
insured1.setGender(GENDER_FEMALE);
coverage1.setInsured(insured1);
Insured insured2 = new Insured();
insured2.setFirstName("Elizabeth");
insured2.setAge(45);
insured2.setGender(GENDER_FEMALE);
coverage2.setInsured(insured2);
PolicyRuleResult result = new PolicyRuleResult();
PolicyProduct policyProd = new PolicyProduct();
workingMemory.insert(policy);
workingMemory.insert(policyProd);
workingMemory.setGlobal("result", result);
workingMemory.fireAllRules();
logger.writeToDisk();
System.out.println(result.isEligible());
System.out.println(result.isAllCoveragesAreEligible());
System.out.println(result.isAgentInformationValid());
System.out.println(result.isApprovedState());
System.out.println(result.getSoundMessages());
System.out.println(result.isSoundexWorksCollect());
System.out.println("Cov1: " + coverage1.isEligible());
System.out.println("Cov2: " + coverage2.isEligible());
System.out.println("Ret Id: " + result.isRetIdentifier());
System.out.println("Total Face Amount: " + result.getTotalFaceAmount());
System.out.println("Total Face Amount Simp: " + result.getTotalFaceAmountSimp());
System.out.println("Ave. Face Amount: " + result.getAverageFaceAmount());
//Query Results
QueryResults results = workingMemory.getQueryResults("Get Insured details for the policy");
System.out.println( "we have " + results.size() + " insured(s)" );
for ( Iterator it = results.iterator(); it.hasNext(); ) {
QueryResult qResult = (QueryResult)it.next();
Insured insured = (Insured) qResult.get( "$insured" );
System.out.println( insured.getFirstName() + "\n" );
}
} catch (Throwable t) {
t.printStackTrace();
}
}
private static RuleBase readDRL() throws Exception {
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl( new FileReader(getDRLFilePath()));
Package pkg = builder.getPackage();
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage( pkg );
return ruleBase;
}
private static String getDRLFilePath() {
ClassLoader loader = Policy.class.getClassLoader();
URL url = loader.getResource("IssueRules.drl");
return url.getPath();
}
}
System.out.println(result.isEligible());
1:rule "Determine if all of coverages are eligible for Issue" 2: salience 99 3: when 4: $policy : Policy() 5: not (exists(Coverage(eligible == false) from $policy.coverages)) 6: 7: then 8: result.setAllCoveragesAreEligible(Boolean.TRUE); 9: 10:end
rule "Determine if agent information captured is valid" salience 98 when $policy : Policy( agents contains "Bill Smith") then result.setAgentInformationValid(Boolean.TRUE); end rule "Determine if product can be sold in the specific state" salience 97 when $policyProd : PolicyProduct() Policy(issueState memberOf $policyProd.states) then result.setApprovedState(Boolean.TRUE); end
rule "Check out the soundex function of Drools"
# check if any insured name matches a particular pattern
salience 96
when
$policy : Policy()
exists ($coverage : Coverage(insured.firstName soundslike 'Elizabeth') from $policy.coverages)
then
result.addSoundMessage("Sound Ex Works!");
end
rule "Check out the soundex function of Drools - minor change"
# check if any insured name matches a particular pattern with a minor variation
salience 95
when
$policy : Policy()
$coverage : Coverage(insured.firstName soundslike 'Elizabeth') from $policy.coverages
then
result.addSoundMessage("Sound Ex Works!");
end
rule "Check out the collect function of Drools and collect functionality diff from function" salience 94 when $policy : Policy() $coverages : ArrayList(size == 2) from collect($coverage : Coverage(eligibility == false) from $policy.coverages) then result.setSoundexWorksCollect(Boolean.TRUE); end
rule "Check if the coverages are valid and satisfy gender, age, faceamount validation rules" salience 93 #no-loop true when $policy : Policy() $coverage : Coverage( (faceAmount < 50000 || faceAmount > 250000) || ( (insured.gender == "M" && (insured.age < 25 || insured.age > 65)) || (insured.gender == "F" && (insured.age < 18 || insured.age > 65)) ) ) from $policy.coverages then $coverage.setEligible(false); end
rule "Return value identifier sample" salience 92 #no-loop true when $policy : Policy() $coverage1: Coverage (faceAmount == 250000, insured.gender == "F", $age : insured.age) from $policy.coverages $coverage2: Coverage (insured.gender == "M", insured.age == ($age+15)) from $policy.coverages then result.setRetIdentifier(Boolean.TRUE); end
The condition been evaluated is that a female insured has coverage face amount == 250K and age is 15 years lesser than the male insured’s age, then the RETIdentifier is set to true.Some business rules need value computations to be done as a part of the business rules condition. Drools supports such requirements too. Here are some samples of implementations using the accumulate conditional element.
rule "Compute the total and average faceamount per coverage" salience 91 when $policy : Policy() $total : Number() from accumulate ( $coverage: Coverage($amt : faceAmount) from $policy.coverages, init (double total =0;), action (total += $amt;), reverse(total -= $amt;), result(total) ) then result.setTotalFaceAmount(new Integer($total.intValue())); end rule "Compute the total in a simpler fashion" salience 90 when $policy : Policy() $total : Number() from accumulate ( $coverage: Coverage($amt : faceAmount) from $policy.coverages, sum($amt) ) then result.setTotalFaceAmountSimp(new Integer($total.intValue())); end rule "Compute the average faceamount per coverage" salience 89 when $policy : Policy() $total : Number() from accumulate ( $coverage: Coverage($amt : faceAmount) from $policy.coverages, average($amt) ) then result.setAverageFaceAmount(new Double($total.doubleValue())); end
The final feature of Drools I am going to cover is query. Drools also provides you with the ability to query the input objects. Please find below a sample code for the same.
query "Get Insured details for the policy"
$policy : Policy()
$coverages : Coverage($insured: insured ) from $policy.coverages
end
Please find below the snippet of Java code to retrieve the results.
//Query Results
QueryResults results = workingMemory.getQueryResults("Get Insured details for the policy");
System.out.println( "we have " + results.size() + " insured(s)" );
for ( Iterator it = results.iterator(); it.hasNext(); ) {
QueryResult qResult = (QueryResult)it.next();
Insured insured = (Insured) qResult.get( "$insured" );
System.out.println( insured.getFirstName() + "\n" );
}
2 responses so far ↓
Bill Tarr // August 18, 2009 at 8:50 pm |
Thanks for posting your code. I came across it while searching for some Drools examples, and your code helped get me pointed in the direction I needed to go.
Keep up the good blogging!
Novice // September 16, 2009 at 2:55 pm |
Hi,
Nice article!!
It can surely be treated as a tutorial for Novices like me
Thanks,
Novice