Skip to content

Simple Genealogy Rules – part 4

August 28, 2013

This tutorial is an excerpt from the new Skills421 (www.skills421.com) training course that I am writing for JBOSS Drools.

In part1 we covered the Problem Domain, in part2 we set up our project to work with Drools 5.4.0.Final and JUnit, and in part3 we will create the Classes for our Facts that we are going to pass to the Rule Engine.

Now in part4 we will create the Person class which is the type of Fact that we will insert into the Rule Engine, we will create the Rule Runner that will build the Knowledge Base for our Rule Engine and insert our Facts into the Rule Engine, and we will create and run our first rule.

Creating the Rule Runner

The Rule Runner is responsible for parsing the rules that we pass to it and packaging them into a Knowledge Base.

It is also responsible for evaluating the Facts that we supply against the Rules that we have written.

Once a Knowledge Base is created it is possible to create multiple Knowledge Sessions with which to process the Facts we supply – meaning that we do not need to rebuild the Knowledge Base every time we process a different set of facts.  Indeed, we only need to rebuild the Knowledge Base if we change the rules.

In the src/main/java let’s create the package com.skills421.training.geneology.rules and in that package we will create the RuleRunner.java class as follows:

package com.skills421.training.geneology.rules;

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

public class RuleRunner
{
	private KnowledgeBase knowledgeBase;
	private StatefulKnowledgeSession session;

	public KnowledgeBase buildKnowledgeBaseWithRuleFiles(String...ruleFiles)
	{
		KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();

		for(String ruleFile : ruleFiles)
		{
			if(ruleFile.toUpperCase().endsWith(".DRL"))
			{
				builder.add(ResourceFactory.newClassPathResource(ruleFile), ResourceType.DRL);
			}
		}

		if (builder.hasErrors())
		{
			throw new RuntimeException(builder.getErrors().toString());
		}

		this.knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
		this.knowledgeBase.addKnowledgePackages(builder.getKnowledgePackages());
		
		return this.knowledgeBase;
	}

	public StatefulKnowledgeSession runWithFacts(Object... facts)
	{
		this.session = this.knowledgeBase.newStatefulKnowledgeSession();

		for (Object fact : facts)
		{
			this.session.insert(fact);
		}

		this.session.fireAllRules();
		
		return this.session;
	}

	public void dispose()
	{
		this.session.dispose();
	}
}

RuleRunner.java

Reviewing the Code

Let’s take a quick look at the code:

The RuleRunner class comprises three methods:

  • buildKnowledgeBaseWithRuleFiles(String…ruleFiles)
  • runWithFacts(Object… facts)
  • dispose()

The buildKnowledgeBase method is responsible for parsing the rules and creating a Knowledge Base in the Rule Engine.  We store this in a member variable so that we can reuse this Knowledge Base with future calls and we also return a reference to the Knowledge Base in the event that we wish to access that Knowledge Base from our own code.

Each time we supply a set of facts we will use the same Knowledge Base.

The runWithFacts methods is responsible for processing the facts that we pass.  It firsts creates a  Knowledge Session from the Knowledge Base and then inserts the facts into the Knowledge Session.  It then fires all the rules.

A reference to the Knowledge Session is returned to the calling code in case we can to access the Knowledge Sesssion from our own code.

Finally, the dispose method is responsible for disposing with the Knowledge Session and freeing up memory and resources.

Creating our First Rule

So far all we have done is provide the foundations for our Geneology Project.

In simple terms, all that we want to do is the following:

  • create a set of Rules
  • create a set of Facts
  • pass the Rules and the Facts to the Rule Engine
  • process the Facts with the Rules
  • print out the result

So let’s get on with creating a very simple rule to put together everything that we have done and test it with a JUnit Test.

The first thing we need to do, is create a Source Folder in which to store our Rules so that we can find the Rules on our ClassPath.

So let’s create a new Source Folder called src/main/rules by doing the following:

Right click on the Project in Project Explorer panel and from the popup menu select
-> New -> Source Folder

025-RuleFolder

In the Folder Name: field enter: src/main/rules and click Finish

Now create a New File in the rules folder by right clicking on the Folder and selecting
-> New -> Other -> File

Name the file Ancestry1.drl and click Finish.

Now let’s type our first rule into Ancestry1.drl as follows:

package com.skills421.training.geneology
 
import com.skills421.training.geneology.model.Person;

dialect "mvel"
 
rule "Person Found"
    when
        $person : Person(lastname=="Walker");
    then
        System.out.println( "Found Person: "+$person.firstname+" "+$person.lastname+" b:"+$person.birthdate);
end

Ancestry1.drl

Understanding our First Rule

For now we will take a cursory look at the Rule we have written, looking at the most important parts.

First we have imported the Person class so that the Rule knows how to communicate with the Person facts that are passed to it.

The Rule itself is then broken down into two section:

  • The Criteria (aka the Where Clause, or the Condition) is the code preceded by the when statement.
  • The Consequence is the code preceded by the then statement.

When the facts are inserted into the Rule Engine they are inserted into Working Memory where the are stored.

When the Rules are Fired each Fact is matched against each rule and where a match occurs, the Fact is processed by that Rule.

The Criteria specifies the Matching Criteria.

In our rule above we have specified:

$person : Person(lastname=="Walker")

This means that every Person object that the Rule Engine is holding will be evaluated to see if the lastname is equal to “Walker”.  Where a match is found, the Person object will be referenced by the variable $person so that we can access that object in the Consequence.

In the Consequence, we simply print the details of our referenced $person object.

Putting it all Together – JUnit Test

Ok, so we now have our Rule Runner and our first simple rule.

Let’s now write the JUnit Test Case to put it all together.

In the  /src/test/java Source Folder, create the package com.skills421.training.geneology.rules

In our new package, create the JUnit Test Case TestDroolsRunner.java as follows:

package com.skills421.training.geneology.rules;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.skills421.training.extend.util.ExtendedDate;
import com.skills421.training.geneology.model.Person;
import com.skills421.training.geneology.rules.RuleRunner;

public class TestDroolsRunner
{
	private static RuleRunner ruleRunner;
	
	private static Person p1;
	private static Person p2;
	private static Person p3;
	private static Person p4;

	@BeforeClass
	public static void setupFacts()
	{
		try
		{
			ruleRunner = new RuleRunner();
			
			p1 = new Person();
			p1.setFirstname("John");
			p1.setLastname("Walker");
			p1.setBirthdate(new ExtendedDate("18/02/1803"));
			
			p2 = new Person();
			p2.setFirstname("James");
			p2.setLastname("Walker");
			p2.setBirthdate(new ExtendedDate("14/11/1825"));
			
			p3 = new Person();
			p3.setFirstname("Sam");
			p3.setLastname("Walker");
			p3.setBirthdate(new ExtendedDate("28/12/1810"));
			
			p4 = new Person();
			p4.setFirstname("Jack");
			p4.setLastname("Walker");
			p4.setBirthdate(new ExtendedDate("07/01/1800"));
			p4.setMarriagedate(new ExtendedDate("14/02/1824"));
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	@Test
	public void testFactsInserted()
	{
		System.out.println("*** testFactsInserted ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry1.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}
	
	@Test
	public void testFactsInsertedPrintInOrder()
	{
		System.out.println("*** testFactsInsertedPrintInOrder ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry1.1.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}
	
	@Test
	public void testFatherOlderThanSon()
	{
		System.out.println("*** testFatherOlderThanSon ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry2.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}
	
	@Test
	public void testFatherMarriedAndOlderThanSon()
	{
		System.out.println("*** testFatherMarriedAndOlderThanSon ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry3.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}

}

TestDroolsRunner.java

Reviewing the Code

In our Test class we first create a static reference to the RuleRunner and to four Facts.

@BeforeClass setupFacts()

Then, one time only for the whole class (regardless of the number of tests in the class) we run the static method setupFacts().  This method instantiates the RuleRunner object and 4 Person objects which are the facts that we will insert into each Knowledge Session as we run each individual Test.

@Test testFactsInserted()

Finally we have the testFactsInserted() method which is out Test.

In this method, we first:

  • Print a message so we know which Test Case is being executed.
  • Then we build a Knowledge Base with the rule that we have written.
  • Then we obtain a Knowledge Session and insert into it the 4 facts that we have.
  • Then we Fire all the Rules.
  • Finally we dispose of the Knowledge Session.

Our expected result is that the Rule Engine will print out the details of each Person with a last name of Walker.

Running the Test

So at last, let’s give our test a whirl.

Run the TestDroolsRunner as a JUnit Test and look to the Console for the Output.

*** testFactsInserted ***
Found Person: Jack Walker b:07/01/1800
Found Person: Sam Walker b:28/12/1810
Found Person: James Walker b:14/11/1825
Found Person: John Walker b:18/02/1803

Sample Console Output

Note that the order in which each Person is printed may vary.  This is because the Rule Engine does not process the Rules or the Facts in any particular order unless we tell it to.

Moving Forward

In the next part we will modify our rules to identify possible Father – Son relationships amongst the People for whom Facts have been created.

Advertisements

From → Tutorials

2 Comments
  1. Reblogged this on Skills421.

Trackbacks & Pingbacks

  1. Moving Geneology to Drools 6.0 | Drools Rules

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: