Skip to content

Drools6.1 and JavaFX8 with Java8 – Genealogy Rules

Skills421

I wanted to create a simple project that integrated Drools6.0 with JavaFX8 using Java8 and some of its new features.  So, here goes.

Objective

We are going to go back to one of the classic problems of Genealogy.  You’re tracing your family tree and you’ve found hundreds of people who could be related but matching these people up by hand can be quite a challenge – a classing candidate for a rule engine.

Take a look at the GUI below:

001-objective

In the left pane we can see the raw data that we want to process.  In this example I have kept it simple with just four records.

We then need to create a set of rules to identify potential spouses and potential children.

003-objective

With the rules entered, we go back to our data and simply fire the rules:

002-objective

At any time, we can change the criteria for our potential matches…

View original post 994 more words

Drools6.1 and JavaFX8 with Java8 – Genealogy Rules

[object HTMLTextAreaElement]

via Drools6.1 and JavaFX8 with Java8 – Genealogy Rules.

Marriage summed up in one simple rule?

Whoever said marriage was easy? Don’t be fooled into believing that the Marriage Rule is simple. It may be only one rule, but there’s a lot of criteria in that rule.

http://www.skills421.com/tutorials-drools-genealogy-marriage.jsp

Created a Family Tree with just 4 Rules

So we have finally created a simple family tree from Birth and Baptism records with just 4 basic rules in our Rule Engine.

In our next tutorial we will include the Marriage Records, but for now – here’s the latest Drools Tutorial:

http://www.skills421.com/tutorials-drools-genealogy-rules.jsp

Drools Expert Components

With the caveat that there may be a couple of errors, I have added a few notes to the Skills421 Site detailing the components of the Drools 6.x Rule Engine.  Hope it is useful.

Any errors, please let me know.

http://www.skills421.com/tutorials-drools-components.jsp

Geneology Rules – Decoupling the Criteria

This blog is an excerpt from the new Drools Training Course that I am currently writing for Skills421 (www.skills421.com)

::

For anyone interested in following the parts before this check out

The Project So Far

We have now got a working set of rules that will identify potential Father Son relationships in a Genealogy Tree.

The rules compare two people and make a number of checks including

  • are the surnames the same
  • are the surnames both “Walker”
  • is the Father 18 years older than the son
  • is the Father married
  • was the Father married 9 months before the son was born

Whilst this is fine and is generating matches – we may wish to dynamically change some of these conditions, particularly if we have  a GUI front end.  Certainly, we do not want to have to re-write the rules if we want to look at Smiths and not Walkers.

New Facts

For this reason, let’s insert the last name, age difference and married period as facts into to Working Memory so that the rules can consider those facts in their determinations instead of the hard-coded values we have so far provided.

We could create a single Configuration class that has  member variables lastname, agedifference and marriedperiod, but what if we wanted to add a new check later on?  In that case we would need to modify the Configuration class and re-deploy the compiled code.

Drools does provide for Declaring objects to be used later on but I am not a big fan of using this to solve this problem.

Instead let’s create a SimpleFact object that can be inserted into working memory.  This will have a key of type String and a value of type Object.

Let’s also create a new Period object that can store values for days, months, and years.

Finally let’s insert some SimpleFacts into Working Memory as follows:

  • lastName, String – e.g. “Walker”
  • ageDifference, Period – e.g. 0 days, 0 months, 18 years
  • marriedPeriod, Period – e.g. 0 days, 9 months, 0 years

So here we go…

SimpleFact and Period

Let’s start with Period

package com.skills421.training.geneology.model;

public class Period
{
	private int days = 0;
	private int months = 0;
	private int years = 0;

	public Period()
	{

	}

	public Period(int days, int months, int years)
	{
		this.days = days;
		this.months = months;
		this.years = years;
	}

	public int getDays()
	{
		return days;
	}

	public void setDays(int days)
	{
		this.days = days;
	}

	public int getMonths()
	{
		return months;
	}

	public void setMonths(int months)
	{
		this.months = months;
	}

	public int getYears()
	{
		return years;
	}

	public void setYears(int years)
	{
		this.years = years;
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + days;
		result = prime * result + months;
		result = prime * result + years;
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Period other = (Period) obj;
		if (days != other.days)
			return false;
		if (months != other.months)
			return false;
		if (years != other.years)
			return false;
		return true;
	}

}

Period.java

Here you can see that we have three simple members: days, months, and years.

We use Eclipse to generate getters and setters for this class, and finally we use Eclipse to generate hashCode() and equals().

Our Period class is now ready to use.

Now let’s move on to SimpleFact.

package com.skills421.training.geneology.model;

import java.util.Date;

public class SimpleFact
{
	private String key;
	private Object value;

	public SimpleFact()
	{

	}

	public SimpleFact(String key, Object value)
	{
		this.key = key;
		this.value = value;
	}

	public void setKey(String key)
	{
		this.key = key;
	}

	public String getKey()
	{
		return this.key;
	}

	public void setValue(Object value)
	{
		this.value = value;
	}

	public Object getValue()
	{
		return this.value;
	}

	public Number getNumberValue()
	{
		if(value instanceof Number)
		{
			return (Number) value;
		}

		return null;
	}

	public Date getDateValue()
	{
		if(value instanceof Date)
		{
			return (Date) value;
		}

		return null;
	}

	public String getStringValue()
	{
		if(value instanceof String)
		{
			return (String) value;
		}

		return null;
	}

	public Period getPeriodValue()
	{
		if(value instanceof Period)
		{
			return (Period) value;
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	public <T> T getValueOfType(Class<T> type)
	{
		if(value.getClass().equals(type))
		{
			return (T) value;
		}

		return null;
	}
}

SimpleFact.java

SimpleFact has two members: key of type String and value of type object.

To get values from SimpleFact I have created 5 methods as follows:

  • getValue which returns an Object
  • getNumberValue which returns a Number or null
  • getDateValue which returns a Date or null
  • getStringValue which returns a String or null
  • getPeriodValue which returns a Period or null
  • getValueOfType which returns a value of specified type, or null

Whilst we’re at it, let’s add a few more constructors to our Person class to make life a little easier:

package com.skills421.training.geneology.model;

import java.io.Serializable;
import java.text.ParseException;

import com.skills421.training.extend.util.ExtendedDate;

public class Person implements Serializable
{
	/**
	 *
	 */
	private static final long serialVersionUID = 8214659086096344769L;

	private ExtendedDate birthdate;
	private ExtendedDate christeningdate;
	private ExtendedDate marriagedate;
	private ExtendedDate deathdate;

	private String firstname;
	private String lastname;
	private String fatherFirstname;
	private String motherFirstname;

	private String birthLocation;

	public Person()
	{

	}

	public Person(String firstname, String lastname)
	{
		this.firstname = firstname;
		this.lastname = lastname;
	}

	public Person(String firstname, String lastname, String born, String christened, String married, String died)
	throws ParseException
	{
		this(firstname,lastname);

		this.birthdate = born!=null ? new ExtendedDate(born) : null;
		this.christeningdate = christened!=null ? new ExtendedDate(christened) : null;
		this.marriagedate = married!=null ? new ExtendedDate(married) : null;
		this.deathdate = died!=null ? new ExtendedDate(died) : null;
	}

	public ExtendedDate getBirthdate()
	{
		return birthdate;
	}

	public void setBirthdate(ExtendedDate birthdate)
	{
		this.birthdate = birthdate;
	}

	public ExtendedDate getChristeningdate()
	{
		return christeningdate;
	}

	public void setChristeningdate(ExtendedDate christeningdate)
	{
		this.christeningdate = christeningdate;
	}

	public ExtendedDate getMarriagedate()
	{
		return marriagedate;
	}

	public void setMarriagedate(ExtendedDate marriagedate)
	{
		this.marriagedate = marriagedate;
	}

	public ExtendedDate getDeathdate()
	{
		return deathdate;
	}

	public void setDeathdate(ExtendedDate deathdate)
	{
		this.deathdate = deathdate;
	}

	public String getFirstname()
	{
		return firstname;
	}

	public void setFirstname(String firstname)
	{
		this.firstname = firstname;
	}

	public String getLastname()
	{
		return lastname;
	}

	public void setLastname(String lastname)
	{
		this.lastname = lastname;
	}

	public String getFatherFirstname()
	{
		return fatherFirstname;
	}

	public void setFatherFirstname(String fatherFirstname)
	{
		this.fatherFirstname = fatherFirstname;
	}

	public String getMotherFirstname()
	{
		return motherFirstname;
	}

	public void setMotherFirstname(String motherFirstname)
	{
		this.motherFirstname = motherFirstname;
	}

	public String getBirthLocation()
	{
		return birthLocation;
	}

	public void setBirthLocation(String birthLocation)
	{
		this.birthLocation = birthLocation;
	}
}

Person.java

These changes provide the following constructors:

  • Person()
  • Person(firstName, lastName)
  • Person(firstname, lastname, born, christened, married, died)
    all the dates are passed as Strings and converted within the constructor

Changing the Rules

Now we have finished modifying our fact objects, let’s move on to changing the rules.

We want our rules to handle 3 SimpleFacts:

  • lastName, String – e.g. “Walker”
  • ageDifference, Period – e.g. 0 days, 0 months, 18 years
  • marriedPeriod, Period – e.g. 0 days, 9 months, 0 years

Starting with Ancestry1.drl we change the rule as follows:

package com.skills421.training.geneology

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

dialect "mvel"

rule "Person Found"
    when
    	SimpleFact(key=="lastName", $lastName : stringValue);
        $person : Person(lastname==$lastName);
    then
        System.out.println( "Found Person: "+$person.firstname+" "+$person.lastname+" b:"+$person.birthdate);
end

Ancestry1.drl

Looking at the rule, we can see that the Matcher in the Rule Engine first looks for a SimpleFact with a key of ‘lastName’.  If it finds this it then obtains the value from the SimpleFact as a String.

Next, the Matcher looks for  a Person with a lastname that matches the value in the SimpleFact.

Where a match is found, the rule prints the details of the matched person.

::

Moving on to our next rule, let’s make the changes to Ancestry1.1

package com.skills421.training.geneology

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

dialect "mvel"

rule "Order People by Date of Birth"
    when
    	SimpleFact(key=="lastName", $lastName : stringValue);
        $person : Person(lastname==$lastName,  $dob : birthdate);
        not Person(lastname==$lastName, birthdate < $dob)
    then
        System.out.println( "Found Person: "+$person.firstname+" "+$person.lastname+" b:"+$person.birthdate);
        retract($person);
end

Ancestry1.1.drl

This rule is the same as Ancestry1.drl with one additional line in the Criteria to ensure that there is not a Person in Working Memory with a last name that matches the person we have already found, and who was born before this person.

Remember that for this rule to re-evaluate until all the Facts have been processed, we much retract the first born fact that we identify after we have printed it.

::

Moving on to our next rule, Ancestry2 starts looking for a possible Father – Son match by comparing the dates of birth.

package com.skills421.training.geneology

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

dialect "mvel"

rule "Could be Father"
	when
    	SimpleFact(key=="lastName", $name : stringValue);
    	SimpleFact(key=="ageDifference", $agedif : periodValue);

        $father : Person(lastname==$name, $fborn : birthdate);
        $son : Person(lastname==$name, $sborn : birthdate > ((ExtendedDate) $fborn).add($agedif.days,$agedif.months,$agedif.years))
    then
        System.out.println( $father.firstname+" (b: "+$fborn+") could be father of "+$son.firstname+" (b: "+$sborn+")");
end

Ancestry2.drl

In this example we load 2 SimpleFacts, the lastName and the ageDifference between the father and the son.

The rest of the Criteria section remains the same with the one change that we use the ageDifference Period values when adding them to the Father’s birth date.

::

Finally, let’s change Ancestry3 which also stipulates that the father must have married before the son was born.

java training, it training, objective-c training, ios training, drools training, spring training, java, spring, spring mvc, spring webflow, core spring, java enterprise, ejb, jsf, java server faces, primefaces, jpa, java persistence architecture, drools, rule engines

This is a great training company with excellent trainers. Highly recommended. IT Training Courses throughout the UK.

com.skills421.training.geneology.rules

com.skills421.training.geneology.rules

package com.skills421.training.geneology

import com.skills421.training.geneology.model.Person;
import com.skills421.training.extend.util.ExtendedDate;
import com.skills421.training.geneology.model.SimpleFact;
import com.skills421.training.geneology.model.Period;

dialect "mvel"

rule "Could be Married Father"
when
    SimpleFact(key=="lastName", $name : stringValue);
    SimpleFact(key=="ageDifference", $ageDif : periodValue != null);
    SimpleFact(key=="marriedPeriod", $marriedPeriod : periodValue != null);

        $father : Person(lastname=="Walker", $fborn : birthdate, $married : marriagedate != null);
        $son : Person(lastname=="Walker",
          $sborn : birthdate > ((ExtendedDate) $fborn).add($ageDif.days,$ageDif.months,$ageDif.years),
                      birthdate > ((ExtendedDate) $married).add(0,$marriedPeriod.months,$marriedPeriod.years) );
    then
        System.out.println("MARRIED "+$father.firstname+" (b: "+$fborn+", m:"+$married+") could be father of "+$son.firstname+" ("+$sborn+")");
end

Ancestry3.drl

In this rule we load the additional SimpleFact called marriedPeriod and use this to ensure that our father was married before the son was born.

Modifying the Test Case

Now, with our Facts and our Rules modified all that remains is for us to modify the tests and re-run them.

package com.skills421.training.geneology.rules;

import org.junit.BeforeClass;
import org.junit.Test;

import com.skills421.training.geneology.model.Period;
import com.skills421.training.geneology.model.Person;
import com.skills421.training.geneology.model.SimpleFact;

public class TestDroolsRunner
{
	private static RuleRunner ruleRunner;

	private static Person p1;
	private static Person p2;
	private static Person p3;
	private static Person p4;
	private static Person p5;
	private static Person p6;
	private static Person p7;

	@BeforeClass
	public static void setupFacts()
	{
		try
		{
			ruleRunner = new RuleRunner();

			p1 = new Person("John", "Walker", "18/02/1803", null, null, null);
			p2 = new Person("John", "Walker", "14/11/1825", null, null, null);
			p3 = new Person("John", "Walker", "28/12/1810", null, null, null);
			p4 = new Person("John", "Walker", "07/01/1800", null, "14/02/1824", null);

			p5 = new Person("Betty", "Smith", "21/06/1804", null, null, null);
			p6 = new Person("John", "Smith", "19/04/1806", null, null, null);
			p7 = new Person("Jean", "Smith", "14/02/1801", null, "14/02/1824", null);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	@Test
	public void testFactsInserted()
	{
		System.out.println("*** testFactsInserted ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry1.drl");

		System.out.println("*** Looking for Walker ***");
		SimpleFact lastname = new SimpleFact("lastName","Walker");

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname);
		ruleRunner.dispose();

		System.out.println("*** Looking for Smith ***");
		lastname.setValue("Smith");

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname);

		ruleRunner.dispose();
	}

	@Test
	public void testFactsInsertedPrintInOrder()
	{
		System.out.println("*** testFactsInsertedPrintInOrder ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry1.1.drl");

		System.out.println("*** Looking for Walker ***");
		SimpleFact lastname = new SimpleFact("lastName","Walker");

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname);
		ruleRunner.dispose();

		System.out.println("*** Looking for Smith ***");
		lastname.setValue("Smith");

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname);

		ruleRunner.dispose();
	}

	@Test
	public void testFatherOlderThanSon()
	{
		System.out.println("*** testFatherOlderThanSon ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry2.drl");

		System.out.println("*** Looking for Walker 18 years diff ***");
		SimpleFact lastname = new SimpleFact("lastName","Walker");
		Period ageDifference = new Period(0,0,18);
		SimpleFact ageDifferenceSF = new SimpleFact("ageDifference",ageDifference);

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname,ageDifferenceSF);
		ruleRunner.dispose();

		//

		System.out.println("*** Looking for Walker 25 years diff ***");
		ageDifference.setYears(25);

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname,ageDifferenceSF);
		ruleRunner.dispose();
	}

	@Test
	public void testFatherMarriedAndOlderThanSon()
	{
		System.out.println("*** testFatherMarriedAndOlderThanSon ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry3.drl");

		System.out.println("*** Looking for Walker 18 years diff, married 9 months ***");
		SimpleFact lastname = new SimpleFact("lastName","Walker");
		Period ageDifference = new Period(0,0,18);
		SimpleFact ageDifferenceSF = new SimpleFact("ageDifference",ageDifference);
		Period marriedPeriod = new Period(0,9,0);
		SimpleFact marriedPeriodSF = new SimpleFact("marriedPeriod",marriedPeriod);

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname,ageDifferenceSF,marriedPeriodSF);
		ruleRunner.dispose();

		System.out.println("*** Looking for Walker 18 years diff, married 1 year ***");
		marriedPeriod.setMonths(0);
		marriedPeriod.setYears(1);

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname,ageDifferenceSF,marriedPeriodSF);
		ruleRunner.dispose();

		System.out.println("*** Looking for Walker 18 years diff, married 2 years ***");
		marriedPeriod.setMonths(0);
		marriedPeriod.setYears(2);

		ruleRunner.runWithFacts(p1,p2,p3,p4,p5,p6,p7,lastname,ageDifferenceSF,marriedPeriodSF);
		ruleRunner.dispose();

	}

}

TestRulesRunner.java

A few things to note here about the modified TestRulesRunner:

  • I’ve added 3 new facts for the Smith family.
  • The Test Cases now insert our SimpleFact objects for comparison

Running the Rules

So, now we have everything in place, let’s give the TestRulesRunner  a whirl and check the Console for output.

Here’s what you should get:

*** testFactsInserted ***

*** Looking for Walker ***
Found Person: John Walker b:18/02/1803
Found Person: John Walker b:14/11/1825
Found Person: John Walker b:28/12/1810
Found Person: John Walker b:07/01/1800

*** Looking for Smith ***
Found Person: Betty Smith b:21/06/1804
Found Person: John Smith b:19/04/1806
Found Person: Jean Smith b:14/02/1801

*** testFactsInsertedPrintInOrder ***

*** Looking for Walker ***
Found Person: John Walker b:07/01/1800
Found Person: John Walker b:18/02/1803
Found Person: John Walker b:28/12/1810
Found Person: John Walker b:14/11/1825

*** Looking for Smith ***
Found Person: Jean Smith b:14/02/1801
Found Person: Betty Smith b:21/06/1804
Found Person: John Smith b:19/04/1806

*** testFatherOlderThanSon ***

*** Looking for Walker 18 years diff ***
John (b: 07/01/1800) could be father of John (b: 14/11/1825)
John (b: 18/02/1803) could be father of John (b: 14/11/1825)

*** Looking for Walker 25 years diff ***
John (b: 07/01/1800) could be father of John (b: 14/11/1825)

*** testFatherMarriedAndOlderThanSon ***

*** Looking for Walker 18 years diff, married 9 months ***
MARRIED John (b: 07/01/1800, m:14/02/1824) could be father of John (14/11/1825)

*** Looking for Walker 18 years diff, married 1 year ***
MARRIED John (b: 07/01/1800, m:14/02/1824) could be father of John (14/11/1825)

*** Looking for Walker 18 years diff, married 2 years ***

Console Output

Moving Geneology to Drools 6.0

This blog is an excerpt from the new Drools Training Course that I am currently writing for Skills421 (www.skills421.com)

::

I set myself the challenge today of moving the Geneology Project to Drools 6.0.

For anyone interested in following the parts before this check out

The Project So Far

The Project so far comprises the following key files:

  • pom.xml
  • RuleRunner
  • TestRuleRunner
  • Person
  • ExtendedDate
  • Ancestry1.drl
  • Ancestry1.1.drl
  • Ancestry2.drl
  • Ancestry3.drl

Changing the Code

Of the files already coded we will need to change the following files to work with Drools 6.0.

  • pom.xml
    Change the Drools Version
  • RuleRunner
    Change to use the new org.kie api
  • Ancestry2.drl
    Our ExtendedDate needs to be Cast Explicitly
  • Ancestry3.drl
    Our ExtendedDate needs to be Cast Explicitly

With these changes implemented the System should work.

Changing the Pom

Let’s start first with the pom.xml.  The changes to this file are very simple.  Simply change the Drools Version to 6.0.0.CR1 as follows:

<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.skills421.training.drools</groupId>
<artifactId>Geneology</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>6.0.0.CR1</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>6.0.0.CR1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
</project>

pom.xml

Immediately we save this we will see code error markers against the RuleRunner and TestRuleRunner classes.

Changing the RuleRunner

Changing the Rule Runner is quite a comprehensive change to use the new kie api.

Open the RuleRunner and change it to the following:

package com.skills421.training.geneology.rules;

import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.Message.Level;
import org.kie.api.io.KieResources;
import org.kie.api.io.Resource;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class RuleRunner
{
	private KieContainer kContainer;
	private KieSession kSession;

	public KieContainer buildKnowledgeBaseWithRuleFiles(String... ruleFiles)
	{
		KieServices kieServices = KieServices.Factory.get();
		KieResources kieResources = kieServices.getResources();
		KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
		KieRepository kieRepository = kieServices.getRepository();

		for(String ruleFile : ruleFiles)
		{
			Resource resource = kieResources.newClassPathResource(ruleFile);

			// path has to start with src/main/resources
			// append it with the package from the rule
			kieFileSystem.write("src/main/resources/com/skills421/training/geneology/"+ruleFile, resource);
		}

		KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);

        kb.buildAll();

        if (kb.getResults().hasMessages(Level.ERROR))
        {
            throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
        }

        kContainer = kieServices.newKieContainer(kieRepository.getDefaultReleaseId());

        return kContainer;
	}

	public KieSession runWithFacts(Object... facts)
	{
		this.kSession = this.kContainer.newKieSession();

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

		this.kSession.fireAllRules();

		return this.kSession;
	}

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

RuleRunner.java

As soon as you save this all the error indicators will disappear.

This looks quite hopeful, so let’s forget for now that I explained some of the rules need changing and let’s try running the rules so far.

Running the Rules

Right click on the TestRulesRunner and let’s run this as a JUnit Test.

Look to the console for the output:

I get the following

*** testFatherMarriedAndOlderThanSon ***
*** testFactsInsertedPrintInOrder ***
Found Person: Jack Walker b:07/01/1800
Found Person: John Walker b:18/02/1803
Found Person: Sam Walker b:28/12/1810
Found Person: James Walker b:14/11/1825
*** testFatherOlderThanSon ***
*** 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

Console Output (ignoring SL4J errors)

There is nothing meaningful in this other than we know that testFatherMarriedAndOlderThanSon and testFatherOlderThanSon produced no results.

However, we ran a JUnit Test so let’s click on the JUnit Tab to see if the Test Case failed.

Here we can see the following error:

java.lang.RuntimeException: Build Errors:
Error Messages:
Message [id=1, level=ERROR, path=com/skills421/training/geneology/Ancestry3.drl, line=10, column=0
text=Unable to Analyse Expression birthdate > $fborn.add(0,0,18):
[Error: unable to resolve method using strict-mode: java.util.Date.add(java.lang.Integer, java.lang.Integer, java.lang.Integer)]
[Near : {… birthdate > $fborn.add(0,0,18) ….}]
^
[Line: 10, Column: 8]]
Message [id=2, level=ERROR, path=com/skills421/training/geneology/Ancestry3.drl, line=10, column=0
text=Unable to Analyse Expression birthdate > $married.add(0,9,0):
[Error: unable to resolve method using strict-mode: java.util.Date.add(java.lang.Integer, java.lang.Integer, java.lang.Integer)]
[Near : {… birthdate > $married.add(0,9,0) ….}]
^
[Line: 10, Column: 8]]

Warning Messages:

Info Messages:

at com.skills421.training.geneology.rules.RuleRunner.buildKnowledgeBaseWithRuleFiles(RuleRunner.java:40)

Changing the Rule Files

If we look carefully at the error we can see that the Rule Engine is treating out birthdate and marriagedate fields as simple Dates and not as ExtendedDates.

So let’s supply a quick fix by providing a cast in the Rule Files Ancestry2.drl and Ancestry3.drl where the add() method of the ExtendedDate class is used:

package com.skills421.training.geneology

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

dialect "mvel"

rule "Could be Father"
	when
        $father : Person(lastname=="Walker", $fborn : birthdate);
        $son : Person(lastname=="Walker", $sborn : birthdate > ((ExtendedDate) $fborn).add(0,0,18))
    then
        System.out.println( $father.firstname+" (b: "+$fborn+") could be father of "+$son.firstname+" (b: "+$sborn+")");
end

Ancestry2.drl

package com.skills421.training.geneology

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

dialect "mvel"

rule "Could be Married Father"
	when
        $father : Person(lastname=="Walker", $fborn : birthdate, $married : marriagedate !=null);
        $son : Person(lastname=="Walker", $sborn : birthdate > ((ExtendedDate) $fborn).add(0,0,18), birthdate > ((ExtendedDate) $married).add(0,9,0) );
    then
        System.out.println("MARRIED "+$father.firstname+" (b: "+$fborn+", m:"+$married+") could be father of "+$son.firstname+" ("+$sborn+")");
end

Ancestry3.drl

Running the Rules

Right click on the TestRulesRunner and let’s run this as a JUnit Test.

Now you should see that all the rules ran successfully.

Look to the console for the output:

*** testFatherMarriedAndOlderThanSon ***
MARRIED Jack (b: 07/01/1800, m:14/02/1824) could be father of James (14/11/1825)
*** testFactsInsertedPrintInOrder ***
Found Person: Jack Walker b:07/01/1800
Found Person: John Walker b:18/02/1803
Found Person: Sam Walker b:28/12/1810
Found Person: James Walker b:14/11/1825
*** testFatherOlderThanSon ***
John (b: 18/02/1803) could be father of James (b: 14/11/1825)
Jack (b: 07/01/1800) could be father of James (b: 14/11/1825)
*** 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

Console Output (ignoring SL4J errors)

So there you have it. The project now runs on Drools 6.0.

%d bloggers like this: