Drools tutorial -- A non trivial example with step by step instructions
Drools allow you to externalize business rules to a database, excel spreadsheet, etc. In this tutorial let's look at an OrderItem scenario where each OrderItem needs to be put through a couple of rules to determine its discounted price.
Step 0: Dependency jars an structure
Step 1: OrderItem.java.
package com.mytutorial;
import java.math.BigDecimal;
public class OrderItem {
public enum PROD_TYPE {
BASIC, LUXURY
}
private PROD_TYPE type;
private int units;
private BigDecimal unitPrice;
private BigDecimal effectiveDiscountRate;
private BigDecimal discountedUnitPrice;
public PROD_TYPE getType() {
return type;
}
public void setType(PROD_TYPE type) {
this.type = type;
}
public int getUnits() {
return units;
}
public void setUnits(int units) {
this.units = units;
}
public BigDecimal getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(BigDecimal unitPrice) {
this.unitPrice = unitPrice;
}
public BigDecimal getEffectiveDiscountRate() {
return effectiveDiscountRate;
}
public void setEffectiveDiscountRate(BigDecimal effectiveDiscountRate) {
this.effectiveDiscountRate = effectiveDiscountRate;
}
public BigDecimal getDiscountedUnitPrice() {
return discountedUnitPrice;
}
public void setDiscountedUnitPrice(BigDecimal discountedUnitPrice) {
this.discountedUnitPrice = discountedUnitPrice;
}
@Override
public String toString() {
return "OrderItem [type=" + type + ", units=" + units + ", unitPrice=" + unitPrice + ", effectiveDiscountRate="
+ effectiveDiscountRate + ", discountedUnitPrice=" + discountedUnitPrice + "]";
}
}
Step 2: Define a map that stores the rule attributes as name/value pairs. Generally handy to load rules from a table say rule_attributes, which stores the rules as name/value pairs.
package com.mytutorial;
import java.util.HashMap;
public class RuleMap<K, V> extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
@Override
public Object put(String key, Object value) {
return super.put(key, value);
}
}
For example,
RuleMap<String, Object> ruleSet1 = new RuleMap<String, Object>(); ruleSet1.put(KEY_ID, 1); ruleSet1.put(KEY_PRODUCT_TYPE, OrderItem.PROD_TYPE.BASIC); ruleSet1.put(KEY_DISCOUNT_RATE, 0.1); ruleSet1.put(KEY_BULK_DISCOUNT_QTY, 5); ruleSet1.put(KEY_BULK_DISCOUNT_RATE, 0.2);
Each rule has a unique id, and bulk discount is applied when number of items in OrderItem is >= than the KEY_BULK_DISCOUNT_QTY. The KEY_DISCOUNT_RATE is applied to all the product types. This will be covered later in the main class.
Step 3: The drools template file product.drl. This is defined under com.mytutorial.
template header
id
normalDiscountRate
productType
bulkDiscountQty
bulkDiscountRate
package com.mytutorial
import com.mytutorial.*;
import java.math.*;
import java.text.*;
import java.util.*;
template "product-discount"
rule "@{id}"
when $item : OrderItem(type == OrderItem.PROD_TYPE.@{productType});
then
BigDecimal disc = new BigDecimal("@{normalDiscountRate}");
if($item.getUnits() >= @{bulkDiscountQty}){
disc = disc.add(BigDecimal.valueOf(@{bulkDiscountRate}));
}
$item.setEffectiveDiscountRate(disc);
$item.setDiscountedUnitPrice($item.getUnitPrice().multiply(BigDecimal.ONE.subtract(disc)));
end
end template
Step 4: The TemplateExpander.java that compiles the com/mytutorial/product.drl with rule attributes defined via the RuleMap.
package com.mytutorial;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collection;
import org.drools.io.Resource;
import org.drools.io.ResourceFactory;
import org.drools.template.ObjectDataCompiler;
public final class TemplateExpander {
private TemplateExpander() {
}
public static Resource expand(Resource template, Collection<?> rules) throws IOException {
ObjectDataCompiler converter = new ObjectDataCompiler();
String drl = converter.compile(rules, template.getInputStream());
Reader rdr = new StringReader(drl);
return ResourceFactory.newReaderResource(rdr);
}
}
Step 5: The "KnowledgeBaseBuilder.java" that build the knowledge base. Depends on the TemplateExpander as well.
package com.mytutorial;
import java.io.IOException;
import java.util.List;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderError;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.Resource;
import org.drools.io.ResourceFactory;
public class KnowledgeBaseBuilder {
public static KnowledgeBase build(List<RuleMap<String, Object>> ruleAttributes) throws IOException {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
Resource rules = loadRuleFile("com/mytutorial/product.drl");
rules = TemplateExpander.expand(rules, ruleAttributes);
kbuilder.add(rules, ResourceType.DRL);
//handle errors
if (kbuilder.hasErrors()) {
for (KnowledgeBuilderError err : kbuilder.getErrors()) {
System.out.println("Errors: " + err);
}
throw new IllegalArgumentException("Could not parse knowledge.");
}
kbase = KnowledgeBaseFactory.newKnowledgeBase();
//add packages to the knowledgebase
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
return kbase;
}
protected static Resource loadRuleFile(String ruleFile) {
if (ruleFile.contains("://")) {
return ResourceFactory.newUrlResource(ruleFile);
} else {
return ResourceFactory.newClassPathResource(ruleFile, KnowledgeBaseBuilder.class);
}
}
}
Step 6: Finally, the runnable main class DroolsMain.java. Constructs dummy rule attributes, dummy OrderItems, and then fire the rules and displays the discountedUnitPrice and the effectiveDiscountRate.
package com.mytutorial;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.drools.KnowledgeBase;
import org.drools.logger.KnowledgeRuntimeLogger;
import org.drools.logger.KnowledgeRuntimeLoggerFactory;
import org.drools.runtime.StatefulKnowledgeSession;
public class DroolsMain {
private static final String KEY_ID = "id";
private static final String KEY_PRODUCT_TYPE = "productType";
private static final String KEY_DISCOUNT_RATE = "normalDiscountRate";
private static final String KEY_BULK_DISCOUNT_QTY = "bulkDiscountQty";
private static final String KEY_BULK_DISCOUNT_RATE = "bulkDiscountRate";
public static void main(String[] args) throws IOException {
List<RuleMap<String, Object>> ruleAttributes = createDummyRuleMap();
KnowledgeBase kbase = KnowledgeBaseBuilder.build(ruleAttributes);
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test");
List<OrderItem> orderItems = getOrderItems();
for (OrderItem orderItem : orderItems) {
System.out.println("Before firing the rules:" + orderItem);
ksession.insert(orderItem);
ksession.fireAllRules();
System.out.println("After firing the rules:" + orderItem);
}
ksession.dispose();
}
private static List<RuleMap<String, Object>> createDummyRuleMap(){
RuleMap<String, Object> ruleSet1 = new RuleMap<String, Object>();
ruleSet1.put(KEY_ID, 1);
ruleSet1.put(KEY_PRODUCT_TYPE, OrderItem.PROD_TYPE.BASIC);
ruleSet1.put(KEY_DISCOUNT_RATE, 0.1);
ruleSet1.put(KEY_BULK_DISCOUNT_QTY, 5);
ruleSet1.put(KEY_BULK_DISCOUNT_RATE, 0.2);
RuleMap<String, Object> ruleSet2 = new RuleMap<String, Object>();
ruleSet2.put(KEY_ID, 2);
ruleSet2.put(KEY_PRODUCT_TYPE, OrderItem.PROD_TYPE.LUXURY);
ruleSet2.put(KEY_DISCOUNT_RATE, 0.2);
ruleSet2.put(KEY_BULK_DISCOUNT_QTY, 10);
ruleSet2.put(KEY_BULK_DISCOUNT_RATE, 0.3);
List<RuleMap<String, Object>> rules = new ArrayList<RuleMap<String,Object>>();
rules.add(ruleSet1);
rules.add(ruleSet2);
return rules;
}
private static List<OrderItem> getOrderItems(){
OrderItem orderItem1 = new OrderItem();
orderItem1.setType(OrderItem.PROD_TYPE.BASIC);
orderItem1.setUnits(3);
orderItem1.setUnitPrice(BigDecimal.valueOf(2.50));
OrderItem orderItem2 = new OrderItem();
orderItem2.setType(OrderItem.PROD_TYPE.LUXURY);
orderItem2.setUnits(12);
orderItem2.setUnitPrice(BigDecimal.valueOf(5.00));
List<OrderItem> listOfItems = new ArrayList<OrderItem>();
listOfItems.add(orderItem1);
listOfItems.add(orderItem2);
return listOfItems;
}
}
Step 7: Output
Before firing the rules:OrderItem [type=BASIC, units=3, unitPrice=2.5, effectiveDiscountRate=null, discountedUnitPrice=null] After firing the rules:OrderItem [type=BASIC, units=3, unitPrice=2.5, effectiveDiscountRate=0.1, discountedUnitPrice=2.25] Before firing the rules:OrderItem [type=LUXURY, units=12, unitPrice=5.0, effectiveDiscountRate=null, discountedUnitPrice=null] After firing the rules:OrderItem [type=LUXURY, units=12, unitPrice=5.0, effectiveDiscountRate=0.5, discountedUnitPrice=2.50]
Labels: Drools tutorial

0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home