JBoss Drools

Drools Salience Example

In this article we will look into an example of Drools Salience Example.

So what is salience and why do we need it?

Salience means to be most noticeable and important. We will use salience keyword against a rule to set the priority. Before we start with the example, let’s work on our setup.

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.

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>

1. About Salience Feature

Each rule has a condition, if the condition matches, the rule’s action is fired. If we end up with more than one rule, then the all the matched rules will fire arbitrarily. It is a good idea not to count on rules firing in any particular order. But suppose you want the rules to be fired in a certain order you can use the keyword salience. salience has one attribute, which takes any expression that returns a number of type int (positive as well as negative numbers are valid). The higher the value, the more likely a rule will be picked up by the conflict resolution strategy.

2. Shopping Cart Domain Model

Customer will add one or more products to the cart.

Salient keyword sets priority to a rule. By default, all rules have a salience of zero. You can specify a negative or positive salience, so in our case, we’ve used a negative one. Why? Because we want our combo
discount to be in addition to any other discounts added to the order, so we need to give it a lower priority to ensure it’s done last.
It’s used to determine the priority of a rule. By default, all rules have a salience of zero, but can be given a positive or negative value.

If a condition results in more than one rules than we can control the order of execution of the rules using their salience levels.

Let’s go through our shopping cart domain model.

Customer will add items to the cart. If a product needs registration, customer need to register else the item will not be processed. Method registerProduct(product) is used to register the product and we can use isRegistered(product) to know whether the product is registered.

Customer:

package com.javacodegeeks.drools;

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


public class Customer {
	private Cart cart;
	private String coupon;
	private boolean isNew;
	private List registeredProducts = new ArrayList();
	
	public static Customer newCustomer() {
		Customer customer = new Customer();
		customer.isNew = true;
		return customer;
	}
	
	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();
	}	
}

A cart knows about the customer, the items and the total discount.

Cart:

package com.javacodegeeks.drools;

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

public class Cart {
	private Customer customer;
	private List cartItems = new ArrayList();
	private double discount;

	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 getCartItems() {
		return cartItems;
	}

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

	public int getFinalPrice() {
		return getTotalPrice() - (int) getDiscount();
	}

	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();
	}
}

A cart item knows about the cart, the product and quantity. If a product requires registration and the customer has not registered, errors will be set to true.

CartItem:

package com.javacodegeeks.drools;

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

	public Product getProduct() {
		return product;
	}

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

	public Cart getCart() {
		return cart;
	}

	public boolean hasErrors() {
		return errors;
	}

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

Here is the product. If a product requires registration requiresRegistration will be set to true.

Product:

package com.javacodegeeks.drools;

public class Product {
	private int price;
	private String desc;
	private boolean requiresRegistration;

	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 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();
	}
}

3. Shopping Cart Rules

As an Example to understand Drool’s Salience, we will consider an example of shopping cart. Customer will add products to the cart. There will be discounts applied to the cart total price.

  1. If the customer has just registered to the site, there will be a 2% discount on the first purchase.
  2. 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.
  3. A new mobile is launched but to for the shopping site to process the item customer must specifically register for the product

Now we want the last item to be fired first so that we can set a boolean whether to process the item or not.

4. Define Rules

Based on the above points we will define our rules. Initially we will not use salience keyword. We want to process the item only when $cartItem.hasErrors() returns false.

shoppingRules.drl:

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

rule "If has coupon, 5% discount"
when
$cartItem : CartItem(cart.customer.coupon == 'DISC01')
then
if (!$cartItem.hasErrors()) {
    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(cart.customer.isNew())
then
if (!$cartItem.hasErrors()) {
    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?"
when
$cartItem : CartItem(product.isRequiresRegisteration(),  !cart.customer.isRegistered(product))
then
System.out.println("Can't process " + $cartItem.getProduct() + ", as requires registration. Customer not registered for the product!");
$cartItem.setErrors(true);
end

Let’s run the example and see how the rules are processed. We will create couple of customers. add items and then fire the rules. Product mobile requires registration.

DroolsSalienceExample:

package com.javacodegeeks.drools;

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 DroolsSalienceExample {
    private String topic;
    
    public DroolsSalienceExample(String topic) {
        this.topic = topic;
    }

    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 = new Customer();
    		Product p1 = new Product("Laptop", 15000);
    		Product p2 = new Product("Mobile", 5000);
    		p2.setRequiresRegistration(true);
    		Product p3 = new Product("Books", 2000);
    		customer.addItem(p1, 1);
    		customer.addItem(p2, 2);
    		customer.addItem(p3, 5);
    		customer.setCoupon("DISC01");

    		List 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();
    		newCustomer.addItem(p1, 1);
    		newCustomer.addItem(p2, 2);
    		
    		cartItems = newCustomer.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);
                        
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    public String getTopic() {
        return topic;
    }
        
    public String introduceYourself() {
        return "Drools 6.2.0.Final";
    }
}

As you can see without the use of salience, it has processed even the mobile item even though the customer was not registered.

Output:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
************* Fire Rules **************
Coupon Rule: Process product: Books, price: 2000, qty 5, apply discount 1750.0
Coupon Rule: Process product: Mobile, price: 5000, qty 2, apply discount 1750.0
Coupon Rule: Process product: Laptop, price: 15000, qty 1, apply discount 1750.0
Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
************************************
Customer cart
Customer new? false
Coupon: DISC01
Is processed? true product: Laptop, price: 15000, qty: 1
Is processed? false
Is processed? true product: Books, price: 2000, qty: 5
Discount: 5250.0
Total: 25000
Total After Discount: 19750
************* Fire Rules **************
New Customer Rule: Process product: Mobile, price: 5000, qty 2, apply discount 5000.0
New Customer Rule: Process product: Laptop, price: 15000, qty 1, apply discount 5000.0
Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
************************************
Customer cart
Customer new? false
Coupon: DISC01
Is processed? true product: Laptop, price: 15000, qty: 1
Is processed? false
Is processed? true product: Books, price: 2000, qty: 5
Discount: 5250.0
Total: 25000
Total After Discount: 19750

5. Using Salience to set priority

We will add salience attribute and set to value 1 so that the rule fires first. Since we haven’t used salience in other rules, their value will be treated as 0.

shoppingRules.drl:

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

rule "If has coupon, 5% discount"
when
$cartItem : CartItem(cart.customer.coupon == 'DISC01')
then
if (!$cartItem.hasErrors()) {
    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(cart.customer.isNew())
then
if (!$cartItem.hasErrors()) {
    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(product.isRequiresRegisteration(),  !cart.customer.isRegistered(product))
then
System.out.println("Can't process " + $cartItem.getProduct() + ", as requires registration. Customer not registered for the product!");
$cartItem.setErrors(true);
end

Now from the output, we can clearly see, mobile is skipped from processing.

Output:

************* Fire Rules **************
Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
Coupon Rule: Process product: Books, price: 2000, qty 5, apply discount 1250.0
Coupon Rule: Process product: Laptop, price: 15000, qty 1, apply discount 1250.0
************************************
Customer cart
Customer new? false
Coupon: DISC01
Is processed? true product: Laptop, price: 15000, qty: 1
Is processed? false
Is processed? true product: Books, price: 2000, qty: 5
Discount: 2500.0
Total: 25000
Total After Discount: 22500
************* Fire Rules **************
Can't process product: Mobile, price: 5000, as requires registration. Customer not registered for the product!
New Customer Rule: Process product: Laptop, price: 15000, qty 1, apply discount 3000.0
************************************
Customer cart
Customer new? false
Coupon: DISC01
Is processed? true product: Laptop, price: 15000, qty: 1
Is processed? false
Is processed? true product: Books, price: 2000, qty: 5
Discount: 2500.0
Total: 25000
Total After Discount: 22500

6. Download the Eclipse Project

This was an example about Drools Salience.

Download
You can download the full source code of this example here: DroolsSalienceExample.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.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jeff Bragle
5 years ago

Correction to code; it should be as follows otherwise the discount to the whole cart is added multiple times: double discount = ((double)$cartItem.getTotalPrice())*0.05d;

Back to top button