Google

Jul 10, 2014

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:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home