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