Getting started with Drools 5.2

When I was attempting to learn Drools, I found it difficult to find a good tutorial. I also had a hard time figuring out how to use the rules engine in a stateless, thread-save manner. This tutorial discusses how to set up Drools in Eclipse, how to write the rules, how to execute the rules, how to call methods outside the rules engine, how to use the rules-flow, how to use Drools with Maven2 and how to configure the Drools knowledge base using Spring.

Setting up Drools in Eclipse:

After downloading and setting up Eclipse.

  • Start up eclipse and go to Help->Install New Software.
  • In the “Work with” box, enter “http://download.jboss.org/jbosstools/updates/stable/”.
  • Expand the “All Jboss Tools” and mark “JBoss Drools Core”, “JBoss Drools Guvnor”, and “JBoss Drools Task” for installation.
  • Finish the installation and allow Eclipse to restart.
  • Open up the Eclipse preferences and go to Drools -> Installed Drools Runtime.
  • Select the “Add” button.
  • Select the “Create a new Drools 5 Runtime”.
  • Choose a location for the Drools 5 runtime and select “OK”.
  • Select the newly created runtime and select “OK” and restart Eclipse.

First Drools project in Eclipse:

To create a drools project in Eclipse:

  • Select File -> New -> Other.
  • Under Drools, select “Drools Project”.
  • Select “next” and enter a project name.
  • Select “next”. Leave the default boxes checked and select “next”.
  • Under “Generate code compatible with” make sure the “Drools 5.1 or above” is selected. Select “Finish”

This will give you a sample “Hello World” application. Let’s look at the pieces:

1. In src/main/rules, open Sample.drl. Here’s the contents of Sample.drl:

    package com.sample

    import com.sample.DroolsTest.Message;

    rule "Hello World"
        when
            m : Message( status == Message.HELLO, myMessage : message )
        then
            System.out.println( myMessage );
            m.setMessage( "Goodbye cruel world" );
            m.setStatus( Message.GOODBYE );
            update( m );
    end

    rule "GoodBye"
        when
            Message( status == Message.GOODBYE, myMessage : message )
        then
            System.out.println( myMessage );
    end

This file contains two rules: “Hello World” and “GoodBye”.

  • The “Hello World” rule says that for every Message in the knowledge base session (don’t worry about this yet, I’ll explain this below) that contains a status of Message.HELLO, assign that object to variable m and assign the message to the variable myMessage. If the system successfully assigns one or more m variables, then print the message to system out, set a new message, change the status to Message.GOODBYE and update the object in the session.
  • The “GoodBye” rule says that for every Message in the knowledge base that contains a status of Message.GOODBYE, assign the message to the variable myMessage then print the message to system out.
  • These rules are evaluated when the objects are inserted or updated. In this case, inserting the initial message will trigger the “Hello World” rule since the status is Message.HELLO. The “GoodBye” rule conditions will be false, so it will be skipped. After the “Hello World” rule updates the Message in the session, both the “Hello World” rule and the “GoodBye” rules will be re-evaluated. In this case, the “Hello World” rule conditions will be false so it will be skipped and the “GoodBye” conditions will be true so it will be executed.

2. In src/main/java, open com.sample.DroolsTest. Here’s the steps:

a. Create the Knowledge Base:
The Knowledge Base holds all the rules. From the knowledge base, you create a session and insert all the objects (also called facts). The facts are what the rules are executed against.
Here’s how this class generates the Knowledge Base:
        private static KnowledgeBase readKnowledgeBase() throws Exception {
            KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
            kbuilder.add(ResourceFactory.newClassPathResource("Sample.drl"), ResourceType.DRL);
            KnowledgeBuilderErrors errors = kbuilder.getErrors();
            if (errors.size() > 0) {
                for (KnowledgeBuilderError error: errors) {
        	    System.err.println(error);
                }
                throw new IllegalArgumentException("Could not parse knowledge.");
            }
            KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
            kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
            return kbase;
        }

In this method we load our drools files (in this case, we only have one: Sample.drl), and build our knowledge base.

b. Create a Stateful Knowledge Session (Drools offers both a stateful and a stateless session. From what I can tell, the difference between the two is that the stateless session has a different memory manager, but still uses a stateful session under the hood. I have not seen any benefit from the stateless session, and it appears the stateful is better supported than the stateless):
        KnowledgeBase kbase = readKnowledgeBase();
        StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
c. Insert the facts into the session:
            Message message = new Message();
            message.setMessage("Hello World");
            message.setStatus(Message.HELLO);
            ksession.insert(message);
d. Execute the rules:
           ksession.fireAllRules();

Sample System
For the purposes Mersoft has needed a Drools engine, the engine must be thread-safe and stateless. The system needs to insert the facts, execute the rules, process the results and repeat without any knowledge of the previous facts. To demonstrate this, we are going to build a system to figure the state’s sales tax for Kansas and Missouri.

Let’s start with a model to hold each item being purchased:

        package com.sample.model;

        import java.math.BigDecimal;

        public class SaleItem {
        	public enum State {
        		KANSAS, MISSOURI
        	}

        	public enum Type {
        		FOOD, PRESCRIPTION_MEDICATION, NON_PRESCRIPTION_MEDICATION, NON_FOOD_NON_MEDICATION_ITEM
        	}

        	private State purchaseState;
	        private BigDecimal salesPrice;
        	private Type itemType;
	        private BigDecimal stateTax;

        	public State getPurchaseState() {
        		return purchaseState;
        	}
        	public void setPurchaseState(State purchaseState) {
        		this.purchaseState = purchaseState;
        	}
        	public BigDecimal getSalesPrice() {
        		return salesPrice;
        	}
        	public void setSalesPrice(BigDecimal salesPrice) {
        		this.salesPrice = salesPrice;
        	}
        	public Type getItemType() {
        		return itemType;
        	}
        	public void setItemType(Type itemType) {
        		this.itemType = itemType;
        	}
        	public BigDecimal getStateTax() {
        		return stateTax;
        	}
        	public void setStateTax(BigDecimal stateTax) {
        		this.stateTax = stateTax;
        	}

        }

Since the state sales tax is being determined by the state and the purchase type, I have created these as enumerations. For our rules, we will create our rules with the understanding that the purchaseState, salesPrice, and itemType will be populated and the rules will populate the stateTax.

Now for our rules. Let’s start with the sales tax from Kansas. Kansas state sales tax is calculated as 6.3% of the purchase with an exemption for prescription medication.

Let’s create a new rules file specifically for calculating the Kansas state sales tax.

  • In Eclipse, right-click on the src/main/rules folder and select New -> Other. Select Drools -> Rule Resource.
  • After selecting “Next” enter “KansasSalesTax” as the file name and “com.sample” as the package name and select “Finish”.

Things to note:,

  • Any Java objects used, must be declared as an import (unfortunately, Eclipse will not manage these for you).
  • Code used in the when clause, must use the Drools language.
  • Every condition in the when clause must evaluate to true to execute the then clause.
  • Code in the then clause is pure Java code.
  • The colon (:) in the when clause is for assignment.
  • If an assignment fails, the when clause evaluates to false.
  • Use the eval method to determine if something is equal or is true or false via Java.

First, let’s create a rule for calculating the sales tax for the state of Kansas for non-prescription medication items:

	package com.sample

	import com.sample.model.SaleItem;
	import java.math.BigDecimal;

	rule "Kansas non-prescription medication item"
	    when
		item : SaleItem(purchaseState == SaleItem.State.KANSAS, itemType != SaleItem.Type.PRESCRIPTION_MEDICATION)
	    then
		BigDecimal tax = new BigDecimal(.063);
		item.setStateTax(tax.multiply(item.getSalesPrice()));
	end

Notice our when clause has two items it’s using to evaluate the item. To trigger this rule, both these conditions must be met.

Here’s the rule for calculating the sales tax for the prescription medication:

	rule "Kansas prescription medication item"
	    when
		item : SaleItem(purchaseState == SaleItem.State.KANSAS, itemType == SaleItem.Type.PRESCRIPTION_MEDICATION)
	    then
		item.setStateTax(new BigDecimal(0));
	end

The sales tax in Missouri is 4.225% of the purchase with an exemption for prescription medication. Also, if the item is a food item, the sales tax is 1.225%. Here’s our rules for Missouri (MissouriSalesTax.drl):

	package com.sample

	import com.sample.model.SaleItem;
	import java.math.BigDecimal;

	rule "Missouri non-prescription medication, non-food item"
	    when
		item : SaleItem(purchaseState == SaleItem.State.MISSOURI, itemType != SaleItem.Type.PRESCRIPTION_MEDICATION, itemType != SaleItem.Type.FOOD)
	    then
		BigDecimal tax = new BigDecimal(0.04225);
		item.setStateTax(tax.multiply(item.getSalesPrice()));

	end

	rule "Missouri prescription medication item"
	    when
		item : SaleItem(purchaseState == SaleItem.State.MISSOURI, itemType == SaleItem.Type.PRESCRIPTION_MEDICATION)
	    then
		item.setStateTax(new BigDecimal(0));
	end

	rule "Missouri food item"
	    when
		item : SaleItem(purchaseState == SaleItem.State.MISSOURI, itemType == SaleItem.Type.FOOD)
	    then
		BigDecimal tax = new BigDecimal(0.01225);
		item.setStateTax(tax.multiply(item.getSalesPrice()));
	end

Let’s run a test and verify we received the correct amounts:

        package com.sample;

        import java.math.BigDecimal;

        import org.drools.KnowledgeBase;
        import org.drools.KnowledgeBaseFactory;
        import org.drools.builder.KnowledgeBuilder;
        import org.drools.builder.KnowledgeBuilderError;
        import org.drools.builder.KnowledgeBuilderErrors;
        import org.drools.builder.KnowledgeBuilderFactory;
        import org.drools.builder.ResourceType;
        import org.drools.io.ResourceFactory;
        import org.drools.runtime.StatefulKnowledgeSession;

        import com.sample.model.SaleItem;
        import com.sample.model.SaleItem.State;
        import com.sample.model.SaleItem.Type;

        public class SalesTaxTest {

            public static final void main(String[] args) {
                try {
                    // load up the knowledge base
                    KnowledgeBase kbase = readKnowledgeBase();
                    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

                    //Non-prescription item
                    //Expect $1.89 for the tax
                    SaleItem item1 = new SaleItem();
                    item1.setPurchaseState(State.KANSAS);
                    item1.setItemType(Type.FOOD);
                    item1.setSalesPrice(new BigDecimal(29.95));
                    ksession.insert(item1);

                    //Prescription item
                    //Expect $0 for the tax
                    SaleItem item2 = new SaleItem();
                    item2.setPurchaseState(State.KANSAS);
                    item2.setItemType(Type.PRESCRIPTION_MEDICATION);
                    item2.setSalesPrice(new BigDecimal(29.95));
                    ksession.insert(item2);

                    //Non-prescription, non-food item
                    //Expect $1.27 for the tax
                    SaleItem item3 = new SaleItem();
                    item3.setPurchaseState(State.MISSOURI);
                    item3.setItemType(Type.NON_FOOD_NON_MEDICATION_ITEM);
                    item3.setSalesPrice(new BigDecimal(29.95));
                    ksession.insert(item3);

                    //Prescription item
                    //Expect $0 for the tax
                    SaleItem item4 = new SaleItem();
                    item4.setPurchaseState(State.MISSOURI);
                    item4.setItemType(Type.PRESCRIPTION_MEDICATION);
                    item4.setSalesPrice(new BigDecimal(29.95));
                    ksession.insert(item4);

                    //Food item
                    //Expect $0.37 for the tax
                    SaleItem item5 = new SaleItem();
                    item5.setPurchaseState(State.MISSOURI);
                    item5.setItemType(Type.FOOD);
                    item5.setSalesPrice(new BigDecimal(29.95));
                    ksession.insert(item5);

                    ksession.fireAllRules();

                    System.out.println(item1.getPurchaseState().toString() + " " + item1.getItemType().toString() + ": " + item1.getStateTax().toString());
                    System.out.println(item2.getPurchaseState().toString() + " " + item2.getItemType().toString() + ": " + item2.getStateTax().toString());
                    System.out.println(item3.getPurchaseState().toString() + " " + item3.getItemType().toString() + ": " + item3.getStateTax().toString());
                    System.out.println(item4.getPurchaseState().toString() + " " + item4.getItemType().toString() + ": " + item4.getStateTax().toString());
                    System.out.println(item5.getPurchaseState().toString() + " " + item5.getItemType().toString() + ": " + item5.getStateTax().toString());

                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }

            private static KnowledgeBase readKnowledgeBase() throws Exception {
                KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
                kbuilder.add(ResourceFactory.newClassPathResource("KansasSalesTax.drl"), ResourceType.DRL);
                kbuilder.add(ResourceFactory.newClassPathResource("MissouriSalesTax.drl"), ResourceType.DRL);
                KnowledgeBuilderErrors errors = kbuilder.getErrors();
                if (errors.size() > 0) {
                    for (KnowledgeBuilderError error: errors) {
                        System.err.println(error);
                    }
                    throw new IllegalArgumentException("Could not parse knowledge.");
                }
                KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
                kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
                return kbase;
            }

        }

Executing methods outside of Drools

When using the Drools engine, I have found it necessary to be able to access methods accessing a database. I have also found it convenient to access static methods that are commonly used, such as an error handler.

To use a static method, simply declare the class in the imports and use it in a static manner.

Here’s an example:

        package com.sample;

        public class HelloSayer {

        	public static void sayHello(String name) {
        		System.out.println("HELLO " + name + "!!!");
        	}
        }

To use this within Drools, add this import:

        import com.sample.HelloSayer;

Update the rule:

        rule "Kansas non-prescription medication item"
            when
                item : SaleItem(purchaseState == SaleItem.State.KANSAS, itemType != SaleItem.Type.PRESCRIPTION_MEDICATION)
            then
    	        BigDecimal tax = new BigDecimal(.063);
    	        item.setStateTax(tax.multiply(item.getSalesPrice()));
            	HelloSayer.sayHello(item.getPurchaseState().toString());
        end

Another way of accessing methods outside drools is to declare a global variable. Here’s the method I want to access:

        package com.sample;

        public class AnotherHello {
	        public String getHelloLine(String name) {
		        return "Hello " + name + "!!!!";
	        }
        }

To access this in the rule, I need to import the class and declare the global object:

        import com.sample.AnotherHello;

        global AnotherHello anotherHello;

Here’s the updated rule:

        rule "Kansas prescription medication item"
            when
                item : SaleItem(purchaseState == SaleItem.State.KANSAS, itemType == SaleItem.Type.PRESCRIPTION_MEDICATION)
            then
    	        item.setStateTax(new BigDecimal(0));
    	        System.out.println(anotherHello.getHelloLine(item.getPurchaseState().toString()));
        end

Lastly, you need to set the global object into the knowledge base session:

            ksession.setGlobal("anotherHello", new AnotherHello());

Using Drools rules flow

The Drools rules are executed in no particular order. There are two ways to order rules: salience, and rules flow.

  • For salience, you declare salience above the rule’s where clause and give a value. The higher the value the earlier the rule will be executed.
  • For the rules flow, you declare a rule-flow group for each rule and then you provide a rules flow file to indicate the flow of the rules.

For this example, we’ll use both salience and the rules flow.

For our sales tax, let’s adjust the rules so that we can print out the total state tax after all the rules have executed. Let’s also create an object to store the total tax in case we would like to retrieve this for use outside the rules engine.

Here’s our Tax item:

        package com.sample.model;

        import java.math.BigDecimal;

        public class Tax {

        	private BigDecimal totalTax = new BigDecimal(0);

	        public BigDecimal getTotalTax() {
		        return totalTax;
	        }

        	public void setTotalTax(BigDecimal totalTax) {
	        	this.totalTax = totalTax;
	        }

	        public void addTax(BigDecimal tax) {
		        totalTax = totalTax.add(tax);
	        }
        }

Let’s make the Tax item available to all the rules so we can easily add the tax as it is being calculated. Here’s the new rules file (SalesTax.drl):

        package com.sample

        import com.sample.model.Tax;
        import java.math.BigDecimal;

        rule "Add Sales Tax Calculation"
	        ruleflow-group "taxItem"
            when
    	        not (Tax())
            then
    	        insert(new Tax());
        end

        rule "Print final sales tax"
	        ruleflow-group "finished"
	        when
		        tax : Tax()
	        then
		        System.out.println("Total taxes: $" + tax.getTotalTax().setScale(2).toString());
        end
  • Here the ruleflow-group determines when these rules are executing.
  • The “Add Sales Tax Calculation” will be executed when the taxItem rules are executed and the “Print final sales tax” will be executed when the finished rules are executed. We’ll tell drools when to execute each group below.

Let’s update the other rules to also have a rule-flow group of calculation. Also, let’s say that for some reason the “Kansas non-prescription medication item” rule needs to run before the other rules. We’ll accomplish this by using salience. Here’s the updated KansasSalesTax.drl:

        package com.sample

        import com.sample.model.SaleItem;
        import com.sample.model.Tax;
        import java.math.BigDecimal;
        import com.sample.HelloSayer;
        import com.sample.AnotherHello;

        global AnotherHello anotherHello;

        rule "Kansas non-prescription medication item"
	        ruleflow-group "calculation"
        	salience 5
            when
                item : SaleItem(purchaseState == SaleItem.State.KANSAS, itemType != SaleItem.Type.PRESCRIPTION_MEDICATION)
                totalTax : Tax()
            then
    	        BigDecimal tax = new BigDecimal(.063);
            	item.setStateTax(tax.multiply(item.getSalesPrice()));
    	        HelloSayer.sayHello(item.getPurchaseState().toString());
            	totalTax.addTax(item.getStateTax());

        end

        rule "Kansas prescription medication item"
	        ruleflow-group "calculation"
            when
                item : SaleItem(purchaseState == SaleItem.State.KANSAS, itemType == SaleItem.Type.PRESCRIPTION_MEDICATION)
                totalTax : Tax()
            then
    	        item.setStateTax(new BigDecimal(0));
            	System.out.println(anotherHello.getHelloLine(item.getPurchaseState().toString()));
    	        totalTax.addTax(item.getStateTax());
        end

Here’s the updated MissouriSalesTax.drl:

        package com.sample

        import com.sample.model.SaleItem;
        import com.sample.model.Tax;
        import java.math.BigDecimal;

        rule "Missouri non-prescription medication, non-food item"
	        ruleflow-group "calculation"
            when
                item : SaleItem(purchaseState == SaleItem.State.MISSOURI, itemType != SaleItem.Type.PRESCRIPTION_MEDICATION, itemType != SaleItem.Type.FOOD)
                totalTax : Tax()
            then
    	        BigDecimal tax = new BigDecimal(0.04225);
            	item.setStateTax(tax.multiply(item.getSalesPrice()));
            	totalTax.addTax(item.getStateTax());

        end

        rule "Missouri prescription medication item"
	        ruleflow-group "calculation"
            when
                item : SaleItem(purchaseState == SaleItem.State.MISSOURI, itemType == SaleItem.Type.PRESCRIPTION_MEDICATION)
                totalTax : Tax()
            then
    	        item.setStateTax(new BigDecimal(0));
            	totalTax.addTax(item.getStateTax());
        end

        rule "Missouri food item"
	        ruleflow-group "calculation"
            when
                item : SaleItem(purchaseState == SaleItem.State.MISSOURI, itemType == SaleItem.Type.FOOD)
                totalTax : Tax()
            then
    	        BigDecimal tax = new BigDecimal(0.01225);
            	item.setStateTax(tax.multiply(item.getSalesPrice()));
    	        totalTax.addTax(item.getStateTax());
        end

Now we need to create our Rules Flow.

  • In Eclipse, right-click on src/main/rules, select New -> Other.
  • Select Drools -> Flow File and hit “Next”.
  • Enter rulesFlow as the file name and hit “Next”.
  • Change the “Generate code compatible with” to be “5.1.x” and select “Finished”. Note: when I upgraded from 5.1 to 5.2.0.Final, I noticed I could not use the 5.0.x rules flow without the knowledge base erring out. The rules flow for the 5.0 and 5.1 have changed drastically, and I believe they have removed the 5.0 compatibility from the 5.2.0.Final libraries.

Notes:

  • Every rules flow has a start event and an end event.
  • The Rule Task component tells the rules flow which group to execute.
  • The Gateway diverge component will take in the output from one rule task and split into multiple paths.
  • The Gateway converge component will take in multiple paths and combine them to one path.
  • For this example, I will not be using the Gateway diverge or Gateway converge.

The rules flow should start out looking like this:

Screen shot 2011-09-06 at 2.10.29 PM

  • Click on “Rule Task” on the left hand side and click again under the “Start Event” indicator on the right hand side.
  • This is for the “taxItem” group.
  • The editing of these items is not very intuitive. In Eclipse, you need to have the “Properties” window open.
  • With the Rule task selected, you can edit the “RuleFlowGroup” (this must be the ruleflow-group name we used above: taxItem) and the Name (I used TaxItem).
  • Add the remaining rule tasks (next calculation, then finished).
  • Add the end event.
  • Select “Sequence Flow” and click from one item to the next until the arrows connect all the items.

When you’re finished, it should look like this:

Screen shot 2011-09-06 at 2.17.23 PM

The last thing to do with the rules flow is to give it an id and a package.

  • Select the “Select” on the left hand side and click on a blank portion of the right hand side of the rules flow.
  • In the properties view, you should be able to edit these properties for the rules flow.
  • Use taxflow for the id and com.sample for the package. Be sure to save the file.

We return to our SalesTaxTest.java and update the “readKnowledgeBase” method as follows:

            private static KnowledgeBase readKnowledgeBase() throws Exception {
                KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
                kbuilder.add(ResourceFactory.newClassPathResource("rulesFlow.bpmn"), ResourceType.BPMN2);
                kbuilder.add(ResourceFactory.newClassPathResource("KansasSalesTax.drl"), ResourceType.DRL);
                kbuilder.add(ResourceFactory.newClassPathResource("MissouriSalesTax.drl"), ResourceType.DRL);
                kbuilder.add(ResourceFactory.newClassPathResource("SalesTax.drl"), ResourceType.DRL);
                KnowledgeBuilderErrors errors = kbuilder.getErrors();
                if (errors.size() > 0) {
                    for (KnowledgeBuilderError error: errors) {
                        System.err.println(error);
                    }
                    throw new IllegalArgumentException("Could not parse knowledge.");
                }
                KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
                kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
                return kbase;
            }

Also, we need to tell Drools which process to start, so in the main method, before firing all the rules, add the following line to the main method:

        ksession.startProcess("taxflow");

Execute the rules. Here’s the output I received:

        HELLO KANSAS!!!
        Hello KANSAS!!!!
        Total taxes: $3.52
        KANSAS FOOD: 1.886849999999999968536279482123063328849916437901430179058573886885508130717425956390798091888427734375
        KANSAS PRESCRIPTION_MEDICATION: 0
        MISSOURI NON_FOOD_NON_MEDICATION_ITEM: 1.265387500000000054770077362320533018853500952510289266498408528895114333323590471991337835788726806640625
        MISSOURI PRESCRIPTION_MEDICATION: 0
        MISSOURI FOOD: 0.3668875000000000054276028116362337017301445984489805027497347548158523888872650786652229726314544677734375

Using Drools with Maven

Since the Drools libraries are a collection of jars, it can easily be used with Maven. Here’s the pom.xml for the system we’ve been discussing:

<?xml version="1.0" encoding="UTF-8"?>
<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.sample</groupId>
    <artifactId>HelloDrools</artifactId>
    <packaging>jar</packaging>
    <version>0.0.1-SNAPSHOT</version>

    <name>Sample Drools Project</name>

    <dependencies>
        <dependency>
            <groupId>org.jbpm</groupId>
            <artifactId>jbpm-bpmn2</artifactId>
            <version>5.1.0.Final</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/rules</directory>
            </resource>
        </resources>
    </build>
</project>

Note: Although there are lots of dependencies needed for this project, I have discovered that if you pull in this one dependency, it will pull in the other dependencies you need.

In the Eclipse environment, we use the m2eclipse plugin. Once we convert the project to a maven project, we simply remove the “Drools Library” from the build path and that ensures the maven dependencies are being used properly and prevents library conflicts. To use the Drools tools that Eclipse provides, you must keep the “Drools Project” configuration.

Using Spring to generate the Knowledge base.

Starting with Drools 5.1, JBoss has provided to ability to configure the Knowledge base via Spring. To do this, add the drools-spring library along with the Spring libraries. Here’s the updated pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<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.sample</groupId>
    <artifactId>HelloDrools</artifactId>
    <packaging>jar</packaging>
    <version>0.0.1-SNAPSHOT</version>

    <name>Sample Drools Project</name>

    <dependencies>
        <dependency>
            <groupId>org.jbpm</groupId>
            <artifactId>jbpm-bpmn2</artifactId>
            <version>5.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-spring</artifactId>
            <version>5.2.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/rules</directory>
            </resource>
        </resources>
    </build>
</project>

Now we need to add the Spring configuration. Under src/main/resources, I created droolsContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:drools="http://drools.org/schema/drools-spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://drools.org/schema/drools-spring http://drools.org/schema/drools-spring-1.3.0.xsd">

  <drools:grid-node id="node1"/>

  <drools:kbase id="kbase1" node="node1">
      <drools:resources>
      <drools:resource type="BPMN2" source="classpath:rulesFlow.bpmn"/>
      <drools:resource type="DRL" source="classpath:KansasSalesTax.drl"/>
      <drools:resource type="DRL" source="classpath:MissouriSalesTax.drl"/>
      <drools:resource type="DRL" source="classpath:SalesTax.drl"/>
    </drools:resources>
  </drools:kbase>

</beans>

I have not used the grid-node in any other form than above. I’m not sure it’s purpose, but it is required to generate the knowledge base. Notice that we’re including all the files we included when we created the knowledge base in code.

Now in our SalesTaxTest class, we can update readKnowledgeBase to be as follows:

        private static KnowledgeBase readKnowledgeBase() throws Exception {
    	        ClassPathXmlApplicationContext serviceContext = new ClassPathXmlApplicationContext( "droolsContext.xml" );
		return (KnowledgeBase) serviceContext.getBean("kbase1");
        }

Note: I have used Drools spring to create the knowledge base session, however, Spring creates a singleton by default and the drools-spring plugin does not allow you to override that. So, use this is you want all your threads to share the same session and you want the session to always hold on to all the objects.

Using Drools in a stateless, thread-safe manner

The context I’ve needed to use Drools at Mersoft has been a thread-safe, stateless manner. In other words, each thread should have no knowledge of the objects or the rules the other threads are executing. I attempted to use this using the Stateless Session from the Knowledge Base, however, I had problems getting this to run in a stateless manner and I discovered that the Drools framework tends to have more bugs in the stateless implementation than in the stateful implementation. I have also discovered that in the stateless session, you cannot release the objects unless the class using the stateless session is completely disposed of. You can call “dispose” on a stateful session once you’re finished with it.

To do the stateless, thread safe, I generate a new stateful session for each request and dispose of the session when I’m through. Here’s an example:

        package com.sample;

        import java.math.BigDecimal;

        import org.drools.KnowledgeBase;
        import org.drools.runtime.StatefulKnowledgeSession;
        import org.springframework.context.support.ClassPathXmlApplicationContext;

        import com.sample.model.SaleItem;
        import com.sample.model.SaleItem.State;
        import com.sample.model.SaleItem.Type;

        public class SalesTaxSpring {

            public static final void main(String[] args) {
                try {
                    // load up the knowledge base
                    final KnowledgeBase kbase = readKnowledgeBase();

                    Runnable shoppingCart1 = new Runnable() {

				public void run() {
                                    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
		                    //Non-prescription item
        		            //Expect $1.89 for the tax
	        	            SaleItem item1 = new SaleItem();
        		            item1.setPurchaseState(State.KANSAS);
	        	            item1.setItemType(Type.FOOD);
		                    item1.setSalesPrice(new BigDecimal(29.95));
		                    ksession.insert(item1);

		                    //Prescription item
        		            //Expect $0 for the tax
	        	            SaleItem item2 = new SaleItem();
		                    item2.setPurchaseState(State.KANSAS);
		                    item2.setItemType(Type.PRESCRIPTION_MEDICATION);
        		            item2.setSalesPrice(new BigDecimal(29.95));
	        	            ksession.insert(item2);

		                    //Non-prescription, non-food item
		                    //Expect $1.27 for the tax
        		            SaleItem item3 = new SaleItem();
	        	            item3.setPurchaseState(State.MISSOURI);
		                    item3.setItemType(Type.NON_FOOD_NON_MEDICATION_ITEM);
		                    item3.setSalesPrice(new BigDecimal(29.95));
        		            ksession.insert(item3);

	        	            //Prescription item
		                    //Expect $0 for the tax
		                    SaleItem item4 = new SaleItem();
        		            item4.setPurchaseState(State.MISSOURI);
	        	            item4.setItemType(Type.PRESCRIPTION_MEDICATION);
		                    item4.setSalesPrice(new BigDecimal(29.95));
		                    ksession.insert(item4);

        		            //Food item
	        	            //Expect $0.37 for the tax
		                    SaleItem item5 = new SaleItem();
		                    item5.setPurchaseState(State.MISSOURI);
        		            item5.setItemType(Type.FOOD);
	        	            item5.setSalesPrice(new BigDecimal(29.95));
		                    ksession.insert(item5);

		                    ksession.setGlobal("anotherHello", new AnotherHello());
				    ksession.startProcess("taxflow");
        		            ksession.fireAllRules();     

	        	            ksession.dispose();

				}
                    };

                    Runnable shoppingCart2 = new Runnable() {

				public void run() {
                                    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

        		            SaleItem item = new SaleItem();
	        	            item.setPurchaseState(State.KANSAS);
		                    item.setItemType(Type.FOOD);
		                    item.setSalesPrice(new BigDecimal(32.45));
        		            ksession.insert(item);

	        	            item = new SaleItem();
		                    item.setPurchaseState(State.KANSAS);
		                    item.setItemType(Type.NON_FOOD_NON_MEDICATION_ITEM);
        		            item.setSalesPrice(new BigDecimal(255.05));
	        	            ksession.insert(item);

		                    item = new SaleItem();
		                    item.setPurchaseState(State.KANSAS);
        		            item.setItemType(Type.NON_PRESCRIPTION_MEDICATION);
	        	            item.setSalesPrice(new BigDecimal(14.70));
		                    ksession.insert(item);

		                    ksession.setGlobal("anotherHello", new AnotherHello());
				    ksession.startProcess("taxflow");
        		            ksession.fireAllRules();     

	        	            ksession.dispose();
				}
                      };

    		    Thread t1 = new Thread(shoppingCart1);
    		    Thread t2 = new Thread(shoppingCart2);
    		    Thread t3 = new Thread(shoppingCart2);

    		    t1.start();
    		    t2.start();
    		    t3.start();

                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }

            private static KnowledgeBase readKnowledgeBase() throws Exception {
            	ClassPathXmlApplicationContext serviceContext = new ClassPathXmlApplicationContext( "droolsContext.xml" );
        	return (KnowledgeBase) serviceContext.getBean("kbase1");
            }

        }

In this example, I’m using threading to demonstrate multiple threads running this process. I am calculating the shopping cart 2 twice.

NOTE: If this is a system that runs the rules based on the events coming into the system, be sure to call the dispose. Without the dispose, the system will continue to use memory until it runs out of memory and starts throwing exceptions.

Here’s the output I received:

        HELLO KANSAS!!!
        HELLO KANSAS!!!
        HELLO KANSAS!!!
        Total taxes: $19.04
        HELLO KANSAS!!!
        Hello KANSAS!!!!
        Total taxes: $3.52
        HELLO KANSAS!!!
        HELLO KANSAS!!!
        HELLO KANSAS!!!
        Total taxes: $19.04

Although this isn’t an exhaustive list of what you can do with Drools, I feel it is enough to get someone started with using Drools. This is what I’ve picked up during my Drools quest and I hope this will be beneficial to someone else.

  • Share/Bookmark

3 Comments

  1. jasonhendry says:

    Awesome article and a great introduction. Thanks for putting in the time and effort to bring this to novices.

  2. Randy says:

    Finally a simple and really Awesome article on Drools. Thanks a lot.

  3. CloakedMirror says:

    Great article. I could have used it about a year ago, as I had to learn many of the same lessons the hard way. BTW, the element is useful when you are using Drools in a clustered mode/environment.

Leave a Reply

You must be logged in to post a comment.