JBoss Drools

Drools Rule Engine Tutorial

Drools is a Rule Engine that uses the rule-based approach to decouple logic from the system. The logic is external to the system in form of rules which when applied to data results into the decision making.
A rules engine is a tool for executing business rules. In this article, we will write some business rules for a shopping domain model.

If you want to more know about Drools Introduction or its setup, read here.
 
 
 
 

 
This example uses the following frameworks:
 
 
 

  1. Maven 3.2.3
  2. Java 8
  3. Drools 6.2
  4. Eclipse  as the IDE, version Luna 4.4.1.

In your pom.xml, you need to add the below dependencies:

  1. knowledge-api – this provides the interfaces and factories
  2. drools-core – this is the core engine, runtime component. This is the only runtime dependency if you are pre-compiling rules.
  3. drools-complier – this contains the compiler/builder components to take rule source, and build executable rule bases. You don’t need this during runtime, if your rules are pre-compiled.

1. Dependencies

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.javacodegeeks.drools</groupId>
	<artifactId>droolsHelloWorld</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-compiler</artifactId>
			<version>${drools.version}</version>
		</dependency>
	</dependencies>
	<properties>
		<drools.version>6.2.0.Final</drools.version>
		<jbpm.version>6.2.0.Final</jbpm.version>
	</properties>
</project>

 2. Shopping Cart Domain Model

Customer will add one or more products to the cart. There are certain rules that we want to fire as we process the cart. Rules are:

  1. If a product needs registration, customer need to register else the item will not be processed.
  2. There will be discounts applied to the cart total price. If the customer has just registered to the site, there will be a 2% discount on the first purchase.
  3. If the customer has a coupon, another 5% discount will be applied on the total price. The coupon code and the percentage amounts may vary.
  4. If customer’s requested quantity of product exceeds the available stock, then it will be registered as an issue.
  5. If a product turns out-of-stock, an error will be registered.

3. Our First Drools Rule

Business rules are composed of facts and conditional statements.

Before we get to the structure of the rule, lets see our first rule. We will add more rules to the file as we progress further.

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.Cart;
import com.javacodegeeks.drools.CartItem;
import com.javacodegeeks.drools.CartStatus;
import com.javacodegeeks.drools.Product;
import java.util.List;

global List outOfStockProducts;

function String pendingItemKey(CartItem cartItem) { 
return cartItem.getCart().getCustomer().getId() + "-"+ cartItem.getProduct().getDesc();
}

//Is it out of stock?
rule "Is Out-Of Stock"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
then
System.out.println("\nIs Out-Of Stock Rule");
System.out.println("************************************");
String error = "Can't process as " +  $cartItem.getProduct().getDesc() + " is Out-Of-Stock" ;
System.out.println(error);
$cartItem.setErrors(true);
$cartItem.setError(error);
$cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem);
outOfStockProducts.add($cartItem.getProduct());
end

Let’s examine the different parts of the rule in our next section.

4. Structure of a Rule File

  1. package – The first statement begins with the package name. A package is the name of the folder in which a rule file lives. This is useful for organizing our rules.
    package com.javacodegeeks.drools;
    
  2. imports – We will specify the dependent fully classified java class names used in our rules.
    import com.javacodegeeks.drools.Cart;
    import com.javacodegeeks.drools.CartItem;
    import com.javacodegeeks.drools.Product;
    
  3. globals – Using global we define global variables. We will use a global variable if we want it to be available for all the rules defined. It allow us to pass information into and out of our rules.
    global List<Product> outOfStockProducts;
  4. functions – We will use a function if we want do some kind of processing on the data passed in and we need to do it multiple times in most of our rules. For example, below function manufactures a key using customer ID and product desc.
    function String pendingItemKey(CartItem cartItem) { 
    return cartItem.getCart().getCustomer().getId() + "-"+ cartItem.getProduct().getDesc();
    }
    
  5. rules – This is the ‘when then end’ structure. In the when part we match a condition. If the condition holds true then the ‘then’ part is executed.If the cart item is still not processed, the rule checks whether the product is out of stock. If yes, then it logs an error, and moves the cart item to pending items.
    rule "Is Out-Of Stock"
    when
    $cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
    then
    System.out.println("\nIs Out-Of Stock Rule");
    System.out.println("************************************");
    String error = "Can't process as " +  $cartItem.getProduct().getDesc() + " is Out-Of-Stock" ;
    System.out.println(error);
    $cartItem.setErrors(true);
    $cartItem.setError(error);
    $cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem);
    outOfStockProducts.add($cartItem.getProduct());
    end
    

5. Rule Name

The first part of the rule block starts with the rule name. For example:

rule "Is Out-Of Stock"
when
...

Next comes the ‘when’ part where we add conditions on the Fact model

6. When Part of the Rule

Let’s look into the ‘when’ part of the rule. We will go through the conditions that we have used in our rules.

  1. Match on cart. Since there is nothing in the bracket, it will match on whatever cart object is passed. It also assigns the fact model to a variable $cart.
    when
    $cart : Cart()
    
  2. If we don’t want to use the fact model in the then part, we can skip the variable assignment. It becomes too simple.
    when
    Cart()
    
  3. Calls to cartItem.getProduct().getAvailableQty() and verifies if the quantity is 0.
    when
    $cartItem : CartItem(product.getAvailableQty() == 0)
    

    We want to make sure the rule fires only for ‘Not Yet’ processed cart item so we will add an ‘&&amps;’ condition.

    when
    $cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
    
  4. Compares the quantity of the item to the product’s available quantity.
    when
    $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, qty > product.getAvailableQty())
    
  5. Checks whether coupon code is equal to ‘DISC01’
    when
    $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.coupon == 'DISC01')
    
  6. Boolean check – checks whether the customer is new
    when
    $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.isNew())
    
  7. Multiple conditions – checks whether the product requires registration and whether customer has registered for the product.
    when
    $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, product.isRequiresRegisteration(),  !cart.customer.isRegistered(product))
    
  8. Rule fires on a processed cart item.
    when
    $cartItem : CartItem(cartStatus == CartStatus.PROCESSED)
    

7. Then Part of the Rule

The ‘Then’ side of a rule determines what will happen when there is at least one result in the ‘when’ part of the rule.

In the ‘Then’ part of the rule we can use anything that can be written in Java code.

For example:

rule "Is Out-Of Stock"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
then
System.out.println("\nIs Out-Of Stock Rule");
System.out.println("************************************");
String error = "Can't process as " +  $cartItem.getProduct().getDesc() + " is Out-Of-Stock" ;
System.out.println(error);
$cartItem.setErrors(true);
$cartItem.setError(error);
$cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem);
outOfStockProducts.add($cartItem.getProduct());
end

As an alternative, the “then” part of a rule can be used to modify Working Memory. A common practice is to insert or update a fact into Working Memory when a rule is evaluated as true. We will see this in one of our next sections how the rules get re-evaluated.

8. Rule Attributes

Drools provides us with rule attribute to modify the behavior of a rule.

  1. no-loop – A rule may modify a fact in which case the rules will be re-evaluated. If a condition causes the same rule to fire again, it will end up modify the fact again, which will trigger the re-evaluation one more time. This may result into an infinite loop. Using no-loop, we can be assured that the rule cannot trigger itself.
  2. salience – It is used to set the priority of a rule. By default, all rules have a salience of zero, but can be given a positive or negative value. See salience example to know more about it.
    rule "Print Cart Issues" salience -1
    when
    $cart : Cart()
    then
    if ($cart.hasIssues()) {
    System.out.println("\nPrint Cart Issues Rule");
    System.out.println("************************************");
    System.out.println($cart.getCartIssues());
    insert($cart.getPendingItems());
    }
    end
    
  3. dialect – This specifies the syntax used in the rule. Currently, the options available are MVEL and Java.

9. Comments

These are pieces of text ignored by the rule engine. They can be on a single line (anything after ‘//’ until the end of the line) or split over multiple lines (everything between the /* and */ Comments split over many lines). For example:

//Is it out of stock?
rule "Is Out-Of Stock"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
then
...
end

10. Working Memory

With Drools we have rules on one side and Working Memory on the other side. Application code will be responsible for loading appropriate facts into Working Memory and the rules will query on these facts to figure out whether to fire the rule or not. We don’t have to load all the facts to the working memory and only the facts relevant to rules will be loaded. We can also load new fact or update an existing fact.

            KieServices ks = KieServices.Factory.get();
            KieContainer kContainer = ks.getKieClasspathContainer();
            KieSession kSession = kContainer.newKieSession("ksession-rules");
            Customer newCustomer = Customer.newCustomer("JOHN01");
            newCustomer.addItem(p1, 1);
    	    newCustomer.addItem(p2, 2);
    	    newCustomer.addItem(p4OutOfStock, 1);
    	    newCustomer.addItem(p5, 10);    		
    		
    	    cartItems = newCustomer.getCart().getCartItems();
    	    for (CartItem cartItem: cartItems) {
    		kSession.insert(cartItem);
            }

We will insert a new fact when we have come to a stage where some processing is done, a state change happened and there are rules which now fire on the new state.

11. Setting global variable

If you are using a global variable, you may have to set it on your working memory. It is a best practice to set all global values before asserting any fact to the working memory. For Example:

            kSession.insert(newCustomer.getCart());
    	    kSession.setGlobal("outOfStockProducts", new ArrayList());

Globals are not designed to share data between rules, if you want to pass data from rule to rule, we will have to load new facts into the working memory.

We will see in our next section how we can insert or update a fact.

12. Inserting New Fact

The ‘then’ part of the rule can change the contents of the Working Memory. When this occurs, Drools will reevaluate all rules to see if any rules now evaluate to true. We will see two different ways by which we can change the working memory.
Once the all the cart items are processed, we want to add the pending cart items to the working memory so that the rules on the pending items can fire.

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.Cart;
import com.javacodegeeks.drools.CartItem;
import com.javacodegeeks.drools.CartStatus;

rule "Print Cart Issues" salience -1
when
$cart : Cart()
then
if ($cart.hasIssues()) {
System.out.println("\nPrint Cart Issues Rule");
System.out.println("************************************");
System.out.println($cart.getCartIssues());
insert($cart.getPendingItems());
}
end

In the above rule, we have added pending items to the working memory. Once we have done it, its going to re-evaluate the rules so any rules on ‘PendingItems’ will fire now.

Below rule simply prints the pending items.
rule "Print Pending Items"
when
$pendingItems : PendingItems()
then
System.out.println("\nPrint Pending Items Rule");
System.out.println("************************************");
for (CartItem cartItem : $pendingItems.getCartItems()) {
System.out.println(cartItem);
}
end

13. Updating a Fact

The update() statement is similar to insert, but is used where the fact existed before the rule started.

For example:

rule "Mark the items processed" salience -2
when
$cart : Cart()
then
System.out.println("\nMark the items processed Rule");
System.out.println("************************************");
for (CartItem cartItem : $cart.getCartItems()) {
if (cartItem.getCartStatus() != CartStatus.NEW || cartItem.getCartStatus() != CartStatus.PENDING) {
cartItem.updateAsProcessed();
System.out.println(cartItem + " is processed");
update(cartItem);
}
}
end

14. More than on Rule

A rule file can have more than one rules. For example:

cartRules.drl:

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.Cart;
import com.javacodegeeks.drools.CartItem;
import com.javacodegeeks.drools.CartStatus;

rule "Print Cart Issues" salience -1
when
$cart : Cart()
then
...
end

rule "Print Pending Items"
when
$pendingItems : PendingItems()
then
...
end

rule "Mark the items processed" salience -2
when
$cart : Cart()
then
...
end

cartItems.drl:

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.Cart;
import com.javacodegeeks.drools.CartItem;
import com.javacodegeeks.drools.CartStatus;
import com.javacodegeeks.drools.Product;
import java.util.List;

global List<Product> outOfStockProducts;

function String pendingItemKey(CartItem cartItem) { 
return cartItem.getCart().getCustomer().getId() + "-"+ cartItem.getProduct().getDesc();
}
//Is it out of stock?
rule "Is Out-Of Stock"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
then
...
end

rule "Verify Qty"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, qty > product.getAvailableQty())
then
...
end

rule "If has coupon, 5% discount"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.coupon == 'DISC01')
then
...
end

rule "If new, 2% discount"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.isNew())
then
...
end

rule "Has customer registered for the product?" salience 1
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, product.isRequiresRegisteration(), !cart.customer.isRegistered(product))
then
...
end

rule "Add Processed CartItem to Order"
when
$cartItem : CartItem(cartStatus == CartStatus.PROCESSED)
then
...
end

You will see the entire rule contents when we run the example.

15. More than one Rule file

If your application ends up using large number of rules, you should be able to manage them by spreading them across files.
For example, as you can see the section before, we have added rules to two different files cartRules.drl and cartItemRules.drl.

One file consists of cart item based rules and the other cart based rules.

16. Domain Model

Our application is about shopping cart. There are rules related to cart items and then cart. A cart item’s initial status is set to NEW.

Once all cart item rules pass, the item is considered as processed and the status is updated to ‘PROCESSED’. Once an item is processed we update the cart item so that the rules can re-evaluated as we want rules on processed items to fire. If an item has one or more issues, its status is set to PENDING. We also have a rule on pending items.

Let’s go through the POJOs, rules and the code that fires the rules.

Product:

package com.javacodegeeks.drools;

public class Product {
	private int price;
	private String desc;
	private int availableQty = 5;
	private boolean requiresRegistration;
	private boolean isOutOfStock;

	public void setRequiresRegistration(boolean requiresRegistration) {
		this.requiresRegistration = requiresRegistration;
	}

	public boolean isRequiresRegisteration() {
		return requiresRegistration;
	}	
	
	public Product(String desc, int price) {
		this.desc = desc;
		this.price = price;
	}

	public int getPrice() {
		return price;
	}

	public String getDesc() {
		return desc;
	}
	
	public String toString() {
		return "product: " + desc + ", price: " + price;
	}
	
	public int getAvailableQty() {
		return availableQty;
	}

	public void setAvailableQty(int availableQty) {
		this.availableQty = availableQty;
	}

	public boolean isOutOfStock() {
		return isOutOfStock;
	}

	public void setOutOfStock(boolean isOutOfStock) {
		this.isOutOfStock = isOutOfStock;
	}

	public boolean equals(Object o) {
		if (o == null) {
			return false;
		}
		if (!(o instanceof Product)) {
			return false;
		}
		Product p = (Product) o;
		return getDesc().equals(p.getDesc());
	}
	
	public int hashCode() {
		return getDesc().hashCode();
	}
}

Customer:

package com.javacodegeeks.drools;

import java.util.ArrayList;
import java.util.List;


public class Customer {
	private String id;
	private Cart cart;
	private String coupon;
	private boolean isNew;
	private List<Product> registeredProducts = new ArrayList<Product>(); 
	
	public static Customer newCustomer(String id) {
		Customer customer = new Customer(id);
		customer.isNew = true;
		return customer;
	}
	
	private Customer(String id) {
		this.id = id;
	}	
	
	public String getId() {
		return id;
	}

	public boolean isNew() {
		return isNew;
	}

	public void addItem(Product product, int qty) {
		if (cart == null) {
			cart = new Cart(this);			
		}
		cart.addItem(product, qty);
	}

	public String getCoupon() {
		return coupon;
	}

	public void setCoupon(String coupon) {
		this.coupon = coupon;
	}

	public Cart getCart() {
		return cart;
	}
	
	public void registerProduct(Product product) {
		registeredProducts.add(product);
	}
	
	public boolean isRegistered(Product p) {
		return registeredProducts.contains(p);
	}		

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Customer new? ")
		   .append(isNew)
		   .append("\nCoupon: ")
		   .append(coupon)
		   .append("\n")
		   .append(cart);
		return sb.toString();
	}	
}

Cart:

package com.javacodegeeks.drools;

import java.util.ArrayList;
import java.util.List;

public class Cart {
	private Customer customer;
	private List<CartItem> cartItems = new ArrayList<CartItem>();
	private double discount;
	private CartIssues cartIssues = new CartIssues();
	private PendingItems pendingItems = new PendingItems(customer);

	public Cart(Customer customer) {
		this.customer = customer;
	}

	public void addItem(Product p, int qty) {
		CartItem cartItem = new CartItem(this, p, qty);
		cartItems.add(cartItem);
	}

	public double getDiscount() {
		return discount;
	}

	public void addDiscount(double discount) {
		this.discount += discount;
	}

	public int getTotalPrice() {
		int total = 0;
		for (CartItem item : cartItems) {
			if (item.hasErrors()) {
				continue;
			}
			total += item.getProduct().getPrice() * item.getQty();
		}
		return total;
	}

	public Customer getCustomer() {
		return customer;
	}

	public List<CartItem> getCartItems() {
		return cartItems;
	}

	public void setCustomer(Customer customer) {
		this.customer = customer;
	}

	public int getFinalPrice() {
		return getTotalPrice() - (int) getDiscount();
	}
	
	public void logItemError(String key, CartItem cartItem) {
		cartIssues.logItemError(key, cartItem);
		pendingItems.addItem(cartItem);
		cartItem.setCartStatus(CartStatus.PENDING);
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (CartItem cartItem : cartItems) {
			sb.append(cartItem)
			  .append("\n");
		}
		sb.append("Discount: ")
		  .append(getDiscount())
		  .append("\nTotal: ")
		  .append(getTotalPrice())
		  .append("\nTotal After Discount: ")
		  .append(getFinalPrice());
		return sb.toString();
	}
	
	public PendingItems getPendingItems() {
		return pendingItems;
	}
	
	public CartIssues getCartIssues() {
		return cartIssues;
	}

	public boolean hasIssues() {
		return cartIssues.hasIssues();
	}
}

CartItem:

package com.javacodegeeks.drools;

public class CartItem {
	private Cart cart;
	private Product product;
	private int qty;
	private boolean errors;
	private String error;
	private CartStatus cartStatus;
	
	public CartItem(Cart cart, Product product, int qty) {
		this.cart = cart;
		this.product = product;
		this.qty = qty;
		cartStatus = CartStatus.NEW;
	}

	public Product getProduct() {
		return product;
	}

	public int getQty() {
		return qty;
	}
	
	public String toString() {
		return "Product: " + product + ", qty: " + qty + ", processed: " + hasErrors() + (hasErrors() ? ", Issue: " + getError() : "");
	}

	public Cart getCart() {
		return cart;
	}

	public boolean hasErrors() {
		return errors;
	}

	public void setErrors(boolean errors) {
		this.errors = errors;
	}

	public String getError() {
		return error;
	}

	public void setError(String error) {
		this.error = error;
	}		
		
	public void updateAsProcessed() {
		cartStatus = CartStatus.PROCESSED;
	}

	public CartStatus getCartStatus() {
		return cartStatus;
	}

	public void setCartStatus(CartStatus cartStatus) {
		this.cartStatus = cartStatus;
	}			
}

CartStatus:

package com.javacodegeeks.drools;

public enum CartStatus {
NEW,
PROCESSED,
PENDING
}

CartIssues:

package com.javacodegeeks.drools;

import java.util.HashMap;
import java.util.Map;

public class CartIssues {
	private Map<String, CartItem> cartErrors = new HashMap<String, CartItem>();
	
	public void logItemError(String key, CartItem cartItem) {
		cartErrors.put(key,  cartItem);
	}
	
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (String key : cartErrors.keySet()) {
			sb.append(key).append(cartErrors.get(key)).append("\n");
		}
		return sb.toString();
	}
	
	public boolean hasIssues() {
		return !cartErrors.isEmpty();
	}
}

PendingItems:

package com.javacodegeeks.drools;

import java.util.ArrayList;
import java.util.List;

public class PendingItems {
	private Customer customer;
	private List<CartItem> cartItems = new ArrayList<CartItem>();
	
	public PendingItems(Customer customer) {
		this.customer = customer;
	}
	
	public Customer getCustomer() {
		return customer;
	}	
	
	public List>CartItem< getCartItems() {
		return cartItems;
	}

	public void addItem(CartItem cartItem) {
		cartItems.add(cartItem);
	}
}

17. Rules

Most of the cart Item based rules fire for ‘Not yet’ PROCESSED items. Some rules are there to calculate the discount of any. The other rules check whether the quantity is valid and whether the product is available.

If there are any issues, the items are moves to pending zone to be dealt later. You can think of a scenario where the customer is being notified as soon as the issues are resolved.

The rules are grouped by cart and cart items.

Once all the items are processed, the rules related to PROCESSED items will fire. Also, rules related to PENDING items will fire.

cartItems.drl:

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.Cart;
import com.javacodegeeks.drools.CartItem;
import com.javacodegeeks.drools.CartStatus;
import com.javacodegeeks.drools.Product;
import java.util.List;

global List<Product> outOfStockProducts;

function String pendingItemKey(CartItem cartItem) { 
return cartItem.getCart().getCustomer().getId() + "-"+ cartItem.getProduct().getDesc();
}
//Is it out of stock?
rule "Is Out-Of Stock"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
then
System.out.println("\nIs Out-Of Stock Rule");
System.out.println("************************************");
String error = "Can't process as " +  $cartItem.getProduct().getDesc() + " is Out-Of-Stock" ;
System.out.println(error);
$cartItem.setErrors(true);
$cartItem.setError(error);
$cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem);
outOfStockProducts.add($cartItem.getProduct());
end

rule "Verify Qty"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, qty > product.getAvailableQty())
then
System.out.println("\nVerify Qty Rule");
System.out.println("************************************");
String error = "Can't process as only " +  $cartItem.getProduct().getAvailableQty() + " of " 
+ $cartItem.getProduct().getDesc() + " are left whereas qty requested is " + $cartItem.getQty();
System.out.println(error);
 $cartItem.setErrors(true);
$cartItem.setError(error);
$cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem);
end

rule "If has coupon, 5% discount"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.coupon == 'DISC01')
then
if (!$cartItem.hasErrors()) {
    System.out.println("\nIf has coupon, 5% discount Rule");
    System.out.println("************************************");
    double discount = ((double)$cartItem.getCart().getTotalPrice())*0.05d;
    System.out.println("Coupon Rule: Process " + $cartItem.getProduct() + ", qty " + $cartItem.getQty() + ", apply discount " + discount);
    $cartItem.getCart().addDiscount(discount);
}
end

rule "If new, 2% discount"
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.isNew())
then
if (!$cartItem.hasErrors()) {
    System.out.println("\nIf new, 2% discount Rule");
    System.out.println("************************************");
    double discount = ((double)$cartItem.getCart().getTotalPrice())*0.2d;
    System.out.println("New Customer Rule: Process " + $cartItem.getProduct() + ", qty " + $cartItem.getQty() + ", apply discount " + discount);
    $cartItem.getCart().addDiscount(discount);
}
end

rule "Has customer registered for the product?" salience 1
when
$cartItem : CartItem(cartStatus != CartStatus.PROCESSED, product.isRequiresRegisteration(), !cart.customer.isRegistered(product))
then
System.out.println("\nHas customer registered for the product? Rule");
System.out.println("************************************");
String error = "Can't process " + $cartItem.getProduct() + ", as requires registration. Customer not registered for the product!";
System.out.println(error);
$cartItem.setErrors(true);
$cartItem.setError(error);
$cartItem.getCart().logItemError(pendingItemKey($cartItem), $cartItem);
end

rule "Add Processed CartItem to Order"
when
$cartItem : CartItem(cartStatus == CartStatus.PROCESSED)
then
System.out.println("\nAdd Processed CartItem to Order Rule");
System.out.println("************************************");
System.out.println("Add to order " + $cartItem);
end

cart.drl:

package com.javacodegeeks.drools;
import com.javacodegeeks.drools.Cart;
import com.javacodegeeks.drools.CartItem;
import com.javacodegeeks.drools.CartStatus;

rule "Print Cart Issues" salience -1
when
$cart : Cart()
then
if ($cart.hasIssues()) {
System.out.println("\nPrint Cart Issues Rule");
System.out.println("************************************");
System.out.println($cart.getCartIssues());
insert($cart.getPendingItems());
}
end

rule "Print Pending Items"
when
$pendingItems : PendingItems()
then
System.out.println("\nPrint Pending Items Rule");
System.out.println("************************************");
for (CartItem cartItem : $pendingItems.getCartItems()) {
System.out.println(cartItem);
}
end

rule "Mark the items processed" salience -2
when
$cart : Cart()
then
System.out.println("\nMark the items processed Rule");
System.out.println("************************************");
for (CartItem cartItem : $cart.getCartItems()) {
if (cartItem.getCartStatus() != CartStatus.NEW || cartItem.getCartStatus() != CartStatus.PENDING) {
cartItem.updateAsProcessed();
System.out.println(cartItem + " is processed");
update(cartItem);
}
}
end

18. Let’s fire the rules

We will create the session first. The rules are automatically read from the classpath and added to the session. Next, we will build the customer’s cart and insert each cart item into the session. We will also insert cart as we do have rules that fire on cart fact.

DroolsRuleEngineExample:

package com.javacodegeeks.drools;

import java.util.ArrayList;
import java.util.List;

import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;


/**
 * This is a sample class to launch a rule.
 */
public class DroolsRuleEngineExample {

    public static final void main(String[] args) {
        try {
            // load up the knowledge base
            KieServices ks = KieServices.Factory.get();
            KieContainer kContainer = ks.getKieClasspathContainer();
            KieSession kSession = kContainer.newKieSession("ksession-rules");

            Customer customer = Customer.newCustomer("RS");
    		Product p1 = new Product("Laptop", 15000);
    		Product p2 = new Product("Mobile", 5000);
    		p2.setRequiresRegistration(true);
    		Product p3 = new Product("Books", 2000);
    		
    		Product p4OutOfStock = new Product("TV", 2000);
    		p4OutOfStock.setAvailableQty(0);
    		
    		Product p5 = new Product("Tab", 10000);
    		p5.setAvailableQty(2);
    		
    		customer.addItem(p1, 1);
    		customer.addItem(p2, 2);
    		customer.addItem(p3, 5);
    		customer.setCoupon("DISC01");

    		List<CartItem> cartItems = customer.getCart().getCartItems();
    		for (CartItem cartItem: cartItems) {
    			kSession.insert(cartItem);
    		}
    		System.out.println("************* Fire Rules **************");
            kSession.fireAllRules(); 
            System.out.println("************************************");
            System.out.println("Customer cart\n" + customer);
            
            Customer newCustomer = Customer.newCustomer("JOHN01");
    		newCustomer.addItem(p1, 1);
    		newCustomer.addItem(p2, 2);
    		newCustomer.addItem(p4OutOfStock, 1);
    		newCustomer.addItem(p5, 10);    		
    		
    		cartItems = newCustomer.getCart().getCartItems();
    		for (CartItem cartItem: cartItems) {
    			kSession.insert(cartItem);
    		}
    		kSession.insert(newCustomer.getCart());
    		kSession.setGlobal("outOfStockProducts", new ArrayList<Product>());
    		System.out.println("************* Fire Rules **************");
            kSession.fireAllRules(); 
            System.out.println("************************************");
            System.out.println("Customer cart\n" + customer);
                        
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

}

Output:

************* Fire Rules **************

Has customer registered for the product? Rule
************************************
Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!

If has coupon, 5% discount Rule
************************************
Coupon Rule: Process product: Books, price: 2000, qty 5, apply discount 1250.0

If has coupon, 5% discount Rule
************************************
Coupon Rule: Process product: Laptop, price: 15000, qty 1, apply discount 1250.0

If new, 2% discount Rule
************************************
New Customer Rule: Process product: Books, price: 2000, qty 5, apply discount 5000.0

If new, 2% discount Rule
************************************
New Customer Rule: Process product: Laptop, price: 15000, qty 1, apply discount 5000.0
************************************
Customer cart
Customer new? true
Coupon: DISC01
Product: product: Laptop, price: 15000, qty: 1, processed: false
Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
Product: product: Books, price: 2000, qty: 5, processed: false
Discount: 12500.0
Total: 25000
Total After Discount: 12500
************* Fire Rules **************

Has customer registered for the product? Rule
************************************
Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!

Is Out-Of Stock Rule
************************************
Can't process as TV is Out-Of-Stock

Verify Qty Rule
************************************
Can't process as only 2 of Tab are left whereas qty requested is 10

Verify Qty Rule
************************************
Can't process as only 0 of TV are left whereas qty requested is 1

If new, 2% discount Rule
************************************
New Customer Rule: Process product: Laptop, price: 15000, qty 1, apply discount 3000.0

Print Cart Issues Rule
************************************
JOHN01-TabProduct: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10
JOHN01-MobileProduct: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
JOHN01-TVProduct: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1


Print Pending Items Rule
************************************
Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1
Product: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10
Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1

Mark the items processed Rule
************************************
Product: product: Laptop, price: 15000, qty: 1, processed: false is processed
Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product! is processed
Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1 is processed
Product: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10 is processed

Add Processed CartItem to Order Rule
************************************
Add to order Product: product: Tab, price: 10000, qty: 10, processed: true, Issue: Can't process as only 2 of Tab are left whereas qty requested is 10

Add Processed CartItem to Order Rule
************************************
Add to order Product: product: TV, price: 2000, qty: 1, processed: true, Issue: Can't process as only 0 of TV are left whereas qty requested is 1

Add Processed CartItem to Order Rule
************************************
Add to order Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!

Add Processed CartItem to Order Rule
************************************
Add to order Product: product: Laptop, price: 15000, qty: 1, processed: false
************************************
Customer cart
Customer new? true
Coupon: DISC01
Product: product: Laptop, price: 15000, qty: 1, processed: false
Product: product: Mobile, price: 5000, qty: 2, processed: true, Issue: Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
Product: product: Books, price: 2000, qty: 5, processed: false
Discount: 12500.0
Total: 25000
Total After Discount: 12500

19. Download the Eclipse Project

This was a tutorial about JBoss Drools Rule Engine.

Download
You can download the full source code of this example here: DroolsRuleEngineExamples.zip

Ram Mokkapaty

Ram holds a master's degree in Machine Design from IT B.H.U. His expertise lies in test driven development and re-factoring. He is passionate about open source technologies and actively blogs on various java and open-source technologies like spring. He works as a principal Engineer in the logistics domain.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
SriRamaChandra Macharayya Gangumalla
SriRamaChandra Macharayya Gangumalla
6 years ago

Very Interesting article!
Thanks for the clear explanation.
I am happy to explore now on Drools

SriRamaChandra Macharayya Gangumalla
SriRamaChandra Macharayya Gangumalla
6 years ago

May I know what actually the terminology FACT mean

Somasekhar
Somasekhar
5 years ago

I have downloaded the project and end up with lot of maven issues. Can you please help with maven entries?

Dan
Dan
3 years ago

Should the status property be placed on the cartItem rather than the cart? I think it makes more sense semantically, cheers!

Last edited 3 years ago by Dan
Back to top button