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:
In your pom.xml
, you need to add the below dependencies:
knowledge-api
– this provides the interfaces and factoriesdrools-core
– this is the core engine, runtime component. This is the only runtime dependency if you are pre-compiling rules.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:
- If a product needs registration, customer need to register else the item will not be processed.
- 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.
- 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.
- If customer’s requested quantity of product exceeds the available stock, then it will be registered as an issue.
- 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
- 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;
- 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;
- 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;
- 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(); }
- 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.
- 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()
- 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()
- 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 ‘&&s;’ condition.
when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED && product.getAvailableQty() == 0)
- Compares the quantity of the item to the product’s available quantity.
when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, qty > product.getAvailableQty())
- Checks whether coupon code is equal to ‘DISC01’
when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.coupon == 'DISC01')
- Boolean check – checks whether the customer is new
when $cartItem : CartItem(cartStatus != CartStatus.PROCESSED, cart.customer.isNew())
- 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))
- 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.
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. Usingno-loop
, we can be assured that the rule cannot trigger itself.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
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.
You can download the full source code of this example here: DroolsRuleEngineExamples.zip
Very Interesting article!
Thanks for the clear explanation.
I am happy to explore now on Drools
May I know what actually the terminology FACT mean
I have downloaded the project and end up with lot of maven issues. Can you please help with maven entries?
Should the status property be placed on the cartItem rather than the cart? I think it makes more sense semantically, cheers!