Drools6.1 and JavaFX8 with Java8 – Genealogy Rules


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, by editing the rules and re-firing them.  We do not need to recompile the code or rebuild the application.  The whole process is dynamic at runtime.

Designing the Layout

I want to keep the code simple and modular where possible. So let’s take a high level look at what we are going to develop;

004-classes

From the image above we can see that we need only 8 classes as follows:

  • MainApp
    This is the main entry point of the project, it renders the whole GUI
  • DataTab
    Renders everything that is visible when you click the Data tab
  • RuleTab
    Renders everything that is visible when you click the Rule tab
  • DataTableView
    Renders each of the 3 tables of Person data
  • DataManager
    Manages all the Person data for each table
  • MessageManager
    Renders any popups and alerts for displaying messages
  • RuleManager
    Responsible for all rule processing and firing and for extracting
  • Person
    Each person

 Dependencies

The project has a number of dependencies, which are included in the pom.xml

  • drools-compiler (6.1.0.Final)
  • drools-core (6.1.0.Final)
  • controlsfx (8.0.6_20, but you may need to change to 8.0.6 if you are not using JavaFX 8_U20)
  • junit (4.11)

More information about each of these can be found at the following urls:

Source Code

The source code for this example can be found on GitHub at https://github.com/johndunning/JavaFXDrools

IDE

This project was developed using Netbeans 8 (since the JavaFX visual interface is better), but it will also import and run in Eclipse or STS.

Creating the Project

To create the project simply create a new Maven project in your preferred IDE.

If you are using Netbeans8, there is an option to choose a new Maven -> JavaFX application.  I used that and then deleted the code I did not require.

Whichever method you use, you will ultimately create a project with the following structure:

006-projectStructure

note: some of the Project Files shown are specific to Netbeans IDE.

Editing the pom.xml

Your pom.xml should look like this:

<?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.sksills421</groupId>
<artifactId>JavaFX8Drools</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>JavaFX8Drools</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mainClass>com.skills421.javafx8drools.MainApp</mainClass>

<drools.version>6.1.0.Final</drools.version>
<controlsfx.version>8.0.6_20</controlsfx.version>
<junit.version>4.11</junit.version>
<java.version>1.8</java.version>
</properties>

<organization>
<!-- Used as the 'Vendor' for JNLP generation -->
<name>Your Organisation</name>
</organization>

<dependencies>
<dependency>
<!-- Drools -->
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>

<!-- ControlsFX -->
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>${controlsfx.version}</version>
</dependency>

<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<excludeScope>system</excludeScope>
<excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>unpack-dependencies</id>

<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/../bin/javafxpackager</executable>
<arguments>
<argument>-createjar</argument>
<argument>-nocss2bin</argument>
<argument>-appclass</argument>
<argument>${mainClass}</argument>
<argument>-srcdir</argument>
<argument>${project.build.directory}/classes</argument>
<argument>-outdir</argument>
<argument>${project.build.directory}</argument>
<argument>-outfile</argument>
<argument>${project.build.finalName}.jar</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>default-cli</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/bin/java</executable>
<commandlineArgs>${runfx.args}</commandlineArgs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArguments>
<bootclasspath>${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>${java.home}/lib/jfxrt.jar</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
</plugins>
</build>

</project>

The  changes to this pom.xml are as follows:

  • properties
  • dependencies
  • maven-compiler-plugin – java version

MainApp.java

The MainApp is the program entry point (as defined in the pom.xml) and creates the primaryStage and renders all the content.  We also expose the primaryStage as a static variable called mainStage so that this can be referenced by the MessageManager methods.

package com.skills421.javafx8drools;

import com.skills421.javafx8drools.component.RuleTab;
import com.skills421.javafx8drools.component.DataTab;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
*
* @author johndunning
*/
public class MainApp extends Application
{
private static Stage mainStage;

public static Stage mainStage()
{
return mainStage;
}

@Override
public void start(Stage primaryStage)
{
mainStage = primaryStage;

BorderPane root = new BorderPane();
Scene scene = new Scene(root, 650, 500, Color.WHITE);

TabPane tabPane = new TabPane();
root.setCenter(tabPane);

Tab dataTab = new DataTab();
Tab ruleTab = new RuleTab();

tabPane.getTabs().addAll(dataTab, ruleTab);

primaryStage.setTitle("Ancestry");
primaryStage.setScene(scene);
primaryStage.show();
}

/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}

}

MessageManager.java

This class is used to display Message and Error popup windows using the ControlsFX api.

Note how the MessageManager is lazily loaded from a static method.

package com.skills421.javafx8drools.manager;

import com.skills421.javafx8drools.MainApp;
import javafx.application.Platform;
import org.controlsfx.dialog.Dialogs;

public class MessageManager
{

private static MessageManager instance;

private MessageManager()
{

}

public static MessageManager getInstance()
{
if (instance == null)
{
instance = new MessageManager();
}

return instance;
}

public void displayMessage(String title, String message)
{
Platform.runLater(new Runnable()
{
@Override
public void run()
{
Dialogs.create()
.owner(MainApp.mainStage())
.title(title)
.masthead(null)
.message(message)
.showInformation();
}
});

}

public void displayError(String title, String message)
{
Platform.runLater(new Runnable()
{
@Override
public void run()
{
Dialogs.create()
.owner(MainApp.mainStage())
.title(title)
.masthead(null)
.message(message)
.showError();
}
});

}
}

Person.java

The person class attempts to adhere to the JavaFX JavaBean standard.  It defines the properties of a person – name, age and sex and exposes them as bindable properties with property change support..

It also exposes an ObservableList of potential spouses and potential children.

package com.sksills421.javafx8drools.model;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/**
*
* @author johndunning
*/
public class Person
{
public static final String MALE = "male";
public static final String FEMALE = "female";

private StringProperty name;
private IntegerProperty age;
private StringProperty sex;

private ObservableList<Person> spouses = FXCollections.observableArrayList();
private ObservableList<Person> children= FXCollections.observableArrayList();

public Person()
{

}

public Person(String name, int age, String sex)
{
this.name = new SimpleStringProperty(name);
this.age = new SimpleIntegerProperty(age);
this.sex = new SimpleStringProperty(sex);
}

public StringProperty nameProperty()
{
if (name == null)
{
name = new SimpleStringProperty();
}
return name;
}

public IntegerProperty ageProperty()
{
if (age == null)
{
age = new SimpleIntegerProperty();
}
return age;
}

public StringProperty sexProperty()
{
if (sex == null)
{
sex = new SimpleStringProperty();
}
return sex;
}

public ObservableList<Person> spousesProperty()
{
return spouses;
}

public ObservableList<Person> childrenProperty()
{
return children;
}

public String getName()
{
return nameProperty().get();
}

public void setName(String name)
{
nameProperty().set(name);
}

public int getAge()
{
return ageProperty().get();
}

public void setAge(int age)
{
ageProperty().set(age);
}

public String getSex()
{
return sexProperty().get();
}

public void setSex(String sex)
{
sexProperty().set(sex);
}

public void addSpouse(Person spouse)
{
spousesProperty().add(spouse);
}

public void addChild(Person child)
{
childrenProperty().add(child);
}

public String toString()
{
return String.format("Person[name=%s, age=%d, sex=%s]",name,age,sex);
}
}

DataManager.java

The DataManager is used to keep all the data manipulation in one place for this example.  The getAllPeople() method creates the four data records used in the application, and resetPeople() resets the data back to original state.  There’s a bit of code redundancy in there which I will fix some time in the future;

Note that the getPossiblePartners() and getPossibleChildren() methods return data that is populated when we select a person in the “All People” table.

package com.skills421.javafx8drools.manager;

import com.sksills421.javafx8drools.model.Person;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/**
*
* @author johndunning
*/
public class DataManager
{

private static DataManager instance;

private ObservableList<Person> allPeople;
private ObservableList<Person> possiblePartners;
private ObservableList<Person> possibleChildren;

private DataManager()
{

}

public static DataManager getInstance()
{
if (instance == null)
{
instance = new DataManager();
}

return instance;
}

public ObservableList<Person> getAllPeople()
{
if (allPeople == null)
{
Person jonDoe = new Person("Jon Doe", 21, Person.MALE);
Person janeDoe = new Person("Jane Doe", 19, Person.FEMALE);
Person markDoe = new Person("Mark Doe", 2, Person.MALE);
Person rubyDoe = new Person("Ruby Doe", 5, Person.FEMALE);

allPeople = FXCollections.observableArrayList(
jonDoe,
janeDoe,
markDoe,
rubyDoe
);
}

return allPeople;
}

public void resetPeople()
{
this.getAllPeople().clear();

Person jonDoe = new Person("Jon Doe", 21, Person.MALE);
Person janeDoe = new Person("Jane Doe", 19, Person.FEMALE);
Person markDoe = new Person("Mark Doe", 2, Person.MALE);
Person rubyDoe = new Person("Ruby Doe", 5, Person.FEMALE);

allPeople.addAll(jonDoe, janeDoe, markDoe, rubyDoe);
getPossiblePartners().clear();
getPossibleChildren().clear();
}

public ObservableList<Person> getPossiblePartners()
{
if (possiblePartners == null)
{
possiblePartners = FXCollections.observableArrayList();
}

return possiblePartners;
}

public ObservableList<Person> getPossibleChildren()
{
if (possibleChildren == null)
{
possibleChildren = FXCollections.observableArrayList();
}

return possibleChildren;
}
}

RuleManager.java

This class is responsible for all actions involving the Rule Engine and the Rules.  readRules() and SaveRules() facilitate reading from and writing to the single rule file that we use.

fireRules() is invoked to fire the rules with the data contained in the allPeopleTableView.

These methods contain a number of design flaws as they should not be passed UI components at all and certainly should not be updating components – something I will fix later.

Ideally, the methods should be simply passed the raw data with which they are working and Custom EventHandlers that can be invoked to update the UI on completion – once again, something I will fix later.

Note that the KieContainer / KnowledgeBase is only re-built if the rule file has been saved.  If the rule file has not been saved, then the existing KnowledgeBase is used from which to extract a KieSession / Knowledge Session.

package com.skills421.javafx8drools.manager;

import com.sksills421.javafx8drools.model.Person;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;

import javafx.concurrent.Task;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message.Level;
import org.kie.api.io.KieResources;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;

import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

/**
*
* @author johndunning
*/
public class RuleManager
{
private static final String RULEPATH = "src/main/resources/com/skills421/examples/rules/test1.drl";

private static RuleManager instance;
private KieContainer kContainer = null;

private RuleManager()
{

}

public static RuleManager getInstance()
{
if(instance==null)
{
instance = new RuleManager();
}

return instance;
}

public void fireRules(final TableView<Person> allPeopleTableView)
{
final int idx = allPeopleTableView.getSelectionModel().getFocusedIndex();
DataManager.getInstance().resetPeople();

Task ruleTask = new Task()
{
@Override
protected Object call() throws Exception
{
List<Path> rulePaths = Arrays.asList(new Path[]{Paths.get(RULEPATH)});

if(kContainer==null) buildKieContainer(rulePaths);

KieSession kSession = kContainer.newKieSession();

DataManager.getInstance().getAllPeople().forEach(p -> kSession.insert(p));

kSession.fireAllRules();

kSession.dispose();

return true;
}

@Override
protected void succeeded()
{
allPeopleTableView.requestFocus();
allPeopleTableView.getSelectionModel().select(idx==-1?0:idx);
}

};

new Thread(ruleTask).start();
}

public void readRules(final TextArea ruleTA)
{
StringBuilder ruleContentSB = new StringBuilder();

Task readTask = new Task()
{
@Override
protected Object call() throws Exception
{
Path rulePath = Paths.get(RULEPATH);

try (BufferedReader br = Files.newBufferedReader(rulePath))
{
br.lines()
.forEach(s -> {
ruleContentSB.append(s);
ruleContentSB.append("\n");
});
}
catch (IOException e)
{
System.out.println(e.getMessage());
}

return true;
}

@Override
protected void succeeded()
{
ruleTA.setText(ruleContentSB.toString());
}

};

new Thread(readTask).start();
}

public void saveRules(final TextArea ruleTA)
{
kContainer = null;

Task readTask = new Task()
{
@Override
protected Object call() throws Exception
{
Path rulePath = Paths.get(RULEPATH);

String content = ruleTA.getText();
Files.write(rulePath, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);

return true;
}

@Override
protected void succeeded()
{
MessageManager.getInstance().displayMessage("Rule Message", "Rule file saved.");
}

};

new Thread(readTask).start();
}

private KieContainer buildKieContainer(List<Path> rulePaths)
{
KieServices ks = KieServices.Factory.get();
KieResources kr = ks.getResources();
KieFileSystem kfs = ks.newKieFileSystem();

rulePaths.forEach(rulePath ->
{
try
{
Resource resource = kr.newInputStreamResource(Files.newInputStream(rulePath, StandardOpenOption.READ));
resource.setResourceType(ResourceType.DRL);

kfs.write(rulePath.toString(),resource);
}
catch (IOException e)
{
MessageManager.getInstance().displayError("Rule Build Error", e.getMessage());
}
});

KieBuilder kb = ks.newKieBuilder(kfs);

kb.buildAll();

if (kb.getResults().hasMessages(Level.ERROR))
{
MessageManager.getInstance().displayError("Rule Build Error", kb.getResults().toString());
}

this.kContainer = ks.newKieContainer(ks.getRepository().getDefaultReleaseId());

return this.kContainer;
}
}

DataTableView.java

This code is quite simple and hopefully self explanatory.

package com.skills421.javafx8drools.component;

import com.sksills421.javafx8drools.model.Person;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

/**
*
* @author johndunning
*/
public class DataTableView extends TableView<Person>
{
public DataTableView(ObservableList<Person> data)
{
setPrefWidth(200);

setItems(data);

TableColumn<Person, String> nameCol = new TableColumn<>("Name");
TableColumn<Person, Integer> ageCol = new TableColumn<>("Age");

nameCol.setPrefWidth(getPrefWidth() * 2 / 3);
ageCol.setPrefWidth(getPrefWidth() / 3);

nameCol.setStyle("-fx-alignment: CENTER-LEFT;");
ageCol.setStyle("-fx-alignment: CENTER-RIGHT;");

nameCol.setCellValueFactory(new PropertyValueFactory("name"));
ageCol.setCellValueFactory(new PropertyValueFactory("age"));

getColumns().setAll(nameCol, ageCol);
}
}

RuleTab.java

This tab renders the content of the “Rules” TabPane, and uses a lambda expression to invoke the RuleManager to save the rule file when the saveButton is clicked.

package com.skills421.javafx8drools.component;

import com.skills421.javafx8drools.manager.RuleManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;

/**
*
* @author johndunning
*/
public class RuleTab extends Tab
{
private TextArea ruleTA;

public RuleTab()
{
this.setText("Rules");

BorderPane rulePane = new BorderPane();
rulePane.setPadding(new Insets(5));
setContent(rulePane);

// top
FlowPane topPane = new FlowPane(10, 10);
topPane.setAlignment(Pos.CENTER);
Label ruleLbl = new Label("Rules");
topPane.getChildren().add(ruleLbl);

// middle
final TextArea ruleTA = new TextArea();
RuleManager.getInstance().readRules(ruleTA);

// bottom
FlowPane bottomPane = new FlowPane(10, 10);
bottomPane.setPadding(new Insets(5));
bottomPane.setAlignment(Pos.CENTER);

Button saveButton = new Button("Save");
bottomPane.getChildren().addAll(saveButton);

rulePane.setTop(topPane);
rulePane.setCenter(ruleTA);
rulePane.setBottom(bottomPane);

//
saveButton.setOnAction(event -> RuleManager.getInstance().saveRules(ruleTA));
}
}

DataTab.java

This code renders the “Data” TabPane.

It creates each of the three TableViews for “All People”, “Possible Partners”, and “Possible Children”.  An ActionListener added to the selectedItemProperty() of the allPeopleTableView extracts the list of possiblePartners() and possibleChildren() from the selected Person object and configures the DataManager properties with that data.

As the DataManager possiblePartners() and possibleChildren() are bound to their requisite TableViews, then the act of updating the DataManager data will automatically update these tables.

The resetDataButton invokes the resetPeople() method in DataManager to reset the allPeople data.  With binding this will have a cascading effect of updating all the tables.

The fireRulesButton invoked the fireRules method of the RuleManager.  This in turns either uses the existing knowledgeBase or re-creates it if the rule file has been saved recently.

The successful firing of the rules will update the allPeopleTableView and, as with resetPeople(), this will have a cascading effect of updating all the tables.

package com.skills421.javafx8drools.component;

import com.skills421.javafx8drools.manager.DataManager;
import com.skills421.javafx8drools.manager.RuleManager;
import com.sksills421.javafx8drools.model.Person;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;

/**
*
* @author johndunning
*/
public class DataTab extends Tab
{
public DataTab()
{
this.setText("Data");

// add BorderPane to the tab
BorderPane dataTabPane = new BorderPane();
setContent(dataTabPane);

// now add the content to the BorderPane
GridPane gridpane = new GridPane();
gridpane.setHgap(10);
gridpane.setVgap(10);
gridpane.setPadding(new Insets(5));

TableView<Person> allPeopleTableView = new DataTableView(DataManager.getInstance().getAllPeople());
TableView<Person> spousesTableView = new DataTableView(DataManager.getInstance().getPossiblePartners());
TableView<Person> childrenTableView = new DataTableView(DataManager.getInstance().getPossibleChildren());

// selection listening
allPeopleTableView.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends Person> observable, Person oldValue, Person newValue) ->
{
if (observable != null && observable.getValue() != null)
{
DataManager.getInstance().getPossiblePartners().clear();
DataManager.getInstance().getPossiblePartners().addAll(observable.getValue().spousesProperty());

DataManager.getInstance().getPossibleChildren().clear();
DataManager.getInstance().getPossibleChildren().addAll(observable.getValue().childrenProperty());

}
});

// column 1
Label allPeopleLbl = new Label("All People");
GridPane.setHalignment(allPeopleLbl, HPos.CENTER);
gridpane.add(allPeopleLbl, 0, 0);
gridpane.add(allPeopleTableView, 0, 1);

// column 2
Label spouseLbl = new Label("Possible Partners");
GridPane.setHalignment(spouseLbl, HPos.CENTER);
gridpane.add(spouseLbl, 1, 0);
gridpane.add(spousesTableView, 1, 1);

// column 3
Label childLbl = new Label("Possible Children");
GridPane.setHalignment(childLbl, HPos.CENTER);
gridpane.add(childLbl, 2, 0);
gridpane.add(childrenTableView, 2, 1);

//
FlowPane bottomPane = new FlowPane(10, 10);
bottomPane.setPadding(new Insets(5));
bottomPane.setAlignment(Pos.CENTER);

Button resetDataButton = new Button("Reset Data");
Button fireRulesButton = new Button("Fire Rules");
bottomPane.getChildren().addAll(resetDataButton,fireRulesButton);

resetDataButton.setOnAction(event -> DataManager.getInstance().resetPeople());
fireRulesButton.setOnAction(event -> RuleManager.getInstance().fireRules(allPeopleTableView));

//
dataTabPane.setCenter(gridpane);
dataTabPane.setBottom(bottomPane);
}
}

test1.drl

Finally, we have the test rule file that we have created.  Note that you can change this file when you run the app, so another file also exists called test1dflt.drl if you need to repair your rule file.

This file contains just two rules – “Match Children” and “Match Spouses”.  It is very rudimentary but works with the given data.

package com.skills421.examples.rules

import com.sksills421.javafx8drools.model.Person

dialect "mvel"

/*
* This rule checks for potential children
*/
rule "Match Children"
when
$parent : Person($parentAge:age>15)
$child : Person(age<$parentAge-15)
then
$parent.addChild($child);
end

/*
* this rule checks for potential spouses (could not be old enough to be a parent
*/
rule "Match Spouses"
when
$partner1 : Person($partnerAge:age>15,$partnerName:name)
$partner2 : Person(this!=$partner1,$age:age>15,$age>$partnerAge-15)
then
$partner1.addSpouse($partner2);
end

Running the Application

A quick demonstration of the application is as follows:

Step 1: Launch the Application

001-runProject

Step 2: Select Jane Doe

002-runProject

Step 3: Fire Rules

003-runProject

Step 4: Edit the Rules (click the Rules Tab)

004-runProject

Step 5: Edit the “Match Children” rule

change the criteria for parent from:

$parent : Person($parentAge:age>15)

to:

$parent : Person($parentAge:age>19)
005-runProject

click Save

Step 6: Click the Data tab and click Fire Rules

006-runProject

Note how Jane Doe no longer has any possible Children as she would be too young to parent either child using the edited rules.

Source Code

The source code for this example can be found on GitHub here: https://github.com/johndunning/JavaFXDrools

4 comments

  1. This type of approach would be very useful for any project that uses drools; i.e. having the ability to change rules without requiring a deployment. The turnaround time for drools development would reduce massively (it would literally take seconds to change the rules and test).

    Looking at your code; it would’nt take too much effort to drop JavaFx and replace with any UI (web UI’s seem to be more favourable in a lot of systems nowadays).

    Good work John!

Leave a comment