This example is implemented in three different versions to demonstrate different ways of implementing the same basic behavior: forward chaining, i.e., the ability the engine has to evaluate, activate and fire rules in sequence, based on changes on the facts in the Working Memory.
Name: State Example Main class: org.drools.examples.StateExampleUsingSalience Type: Java application Rules file: StateExampleUsingSalience.drl Objective: Demonstrates basic rule use and Conflict Resolution for rule firing priority.
Each State
class has fields for its name and its
current state (see the class org.drools.examples.State
).
The two possible states for each objects are:
NOTRUN
FINISHED
Example 8.9. State Class
public class State { public static final int NOTRUN = 0; public static final int FINISHED = 1; private final PropertyChangeSupport changes = new PropertyChangeSupport( this ); private String name; private int state; ... setters and getters go here... }
Ignoring the PropertyChangeSupport
, which will
be explained later, we see the creation of four State
objects named A, B, C and D. Initially their states are set to
NOTRUN
, which is default for the used constructor.
Each instance is asserted in turn into the Session and then
fireAllRules()
is called.
Example 8.10. Salience State: Execution
State a = new State( "A" ); State b = new State( "B" ); State c = new State( "C" ); final State d = new State( "D" ); // By setting dynamic to TRUE, Drools will use JavaBean // PropertyChangeListeners so you don't have to call modify or update(). boolean dynamic = true; session.insert( a, dynamic ); session.insert( b, dynamic ); session.insert( c, dynamic ); session.insert( d, dynamic ); session.fireAllRules(); session.dispose(); // Stateful rule session must always be disposed when finished
To execute the application:
Open the class org.drools.examples.StateExampleUsingSalience
in your Eclipse IDE.
Right-click the class and select "Run as..." and then "Java application"
You will see the following output in the Eclipse console window:
There are four rules in total. First, the Bootstrap
rule fires, setting A to state FINISHED
, which then
causes B to change its state to FINISHED
. C and D are
both dependent on B, causing a conflict which is resolved by the
salience values. Let's look at the way this was executed.
The best way to understand what is happening is to use the Audit Logging feature to graphically see the results of each operation. To view the Audit log generated by a run of this example:
If the Audit View is not visible, click on "Window" and then select "Show View", then "Other..." and "Drools" and finally "Audit View".
In the "Audit View" click the "Open Log" button and select the file "<drools-examples-drl-dir>/log/state.log".
After that, the "Audit view" will look like the following screenshot:
Reading the log in the "Audit View", top to bottom, we see every
action and the corresponding changes in the Working Memory. This way
we observe that the assertion of the State object A in the state
NOTRUN
activates the Bootstrap
rule, while
the assertions of the other State
objects have no
immediate effect.
Example 8.12. Salience State: Rule "Bootstrap"
rule Bootstrap when a : State(name == "A", state == State.NOTRUN ) then System.out.println(a.getName() + " finished" ); a.setState( State.FINISHED ); end
The execution of rule Bootstrap changes the state of A to
FINISHED
, which, in turn, activates rule "A to B".
Example 8.13. Salience State: Rule "A to B"
rule "A to B" when State(name == "A", state == State.FINISHED ) b : State(name == "B", state == State.NOTRUN ) then System.out.println(b.getName() + " finished" ); b.setState( State.FINISHED ); end
The execution of rule "A to B" changes the state of B to
FINISHED
, which activates both, rules "B to C" and
"B to D", placing their Activations onto the Agenda. From this moment
on, both rules may fire and, therefore, they are said to be
"in conflict". The conflict resolution strategy allows the engine's
Agenda to decide which rule to fire. As rule "B to C" has the
higher salience value (10 versus
the default salience value of 0), it fires first, modifying object C
to state FINISHED
. The Audit view shown above reflects
the modification of the State
object in the rule "A to B",
which results in two activations being in conflict. The Agenda view
can also be used to investigate the state of the Agenda, with debug
points being placed in the rules themselves and the Agenda view opened.
The screen shot below shows the breakpoint in the rule "A to B" and
the state of the Agenda with the two conflicting rules.
Example 8.14. Salience State: Rule "B to C"
rule "B to C" salience 10 when State(name == "B", state == State.FINISHED ) c : State(name == "C", state == State.NOTRUN ) then System.out.println(c.getName() + " finished" ); c.setState( State.FINISHED ); end
Rule "B to D" fires last, modifying object D to state
FINISHED
.
Example 8.15. Salience State: Rule "B to D"
rule "B to D" when State(name == "B", state == State.FINISHED ) d : State(name == "D", state == State.NOTRUN ) then System.out.println(d.getName() + " finished" ); d.setState( State.FINISHED ); end
There are no more rules to execute and so the engine stops.
Another notable concept in this example is the use of
dynamic facts, based on
PropertyChangeListener
objects. As described in the
documentation, in order for the engine to see and react to changes of
fact properties, the application must tell the engine that changes
occurred. This can be done explicitly in the rules by using the
<kw>modify</kw> statement, or implicitly by letting the engine know
that the facts implement PropertyChangeSupport
as defined
by the JavaBeans specification. This example
demonstrates how to use PropertyChangeSupport
to avoid
the need for explicit <kw>modify</kw> statements in the rules. To
make use of this feature, ensure that your facts implement
PropertyChangeSupport
, the same way the class
org.drools.example.State
does, and use the following
code to insert the facts into the Working Memory:
Example 8.16. Inserting a Dynamic Fact
// By setting dynamic to TRUE, Drools will use JavaBean // PropertyChangeListeners so you don't have to call modify or update(). final boolean dynamic = true; session.insert( fact, dynamic );
When using PropertyChangeListener
objects, each
setter must implement a little extra code for the notification. Here
is the setter for state
in the class
org.drools.examples
:
Example 8.17. Setter Example with PropertyChangeSupport
public void setState(final int newState) { int oldState = this.state; this.state = newState; this.changes.firePropertyChange( "state", oldState, newState ); }
There are two other classes in this example:
StateExampleUsingAgendGroup
and
StateExampleWithDynamicRules
. Both execute from A to B
to C to D, as just shown. The StateExampleUsingAgendGroup
uses agenda-groups to control the rule conflict and which one fires
first. StateExampleWithDynamicRules
shows how an
additional rule can be added to an already running Working Memory
with all the existing data applying to it at runtime.
Agenda groups are a way to partition the Agenda into groups
and to control which groups can execute. By default, all rules are
in the agenda group "MAIN". The "agenda-group" attribute lets
you specify a different agenda group for the rule. Initially,
a Working Memory has its focus on the Agenda group "MAIN". A group's
rules will only fire when the group receives the focus. This can be
achieved either ny using the method by setFocus()
or the
rule attribute <kw>auto-focus</kw>. "auto-focus" means that the rule
automatically sets the focus to its agenda group when the rule is
matched and activated. It is this "auto-focus" that enables rule
"B to C" to fire before "B to D".
Example 8.18. Agenda Group State Example: Rule "B to C"
rule "B to C" agenda-group "B to C" auto-focus true when State(name == "B", state == State.FINISHED ) c : State(name == "C", state == State.NOTRUN ) then System.out.println(c.getName() + " finished" ); c.setState( State.FINISHED ); kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "B to D" ).setFocus(); end
The rule "B to C" calls setFocus()
on the
agenda group "B to D", allowing its active rules
to fire, which allows the rule "B to D" to fire.
Example 8.19. Agenda Group State Example: Rule "B to D"
rule "B to D" agenda-group "B to D" when State(name == "B", state == State.FINISHED ) d : State(name == "D", state == State.NOTRUN ) then System.out.println(d.getName() + " finished" ); d.setState( State.FINISHED ); end
The example StateExampleWithDynamicRules
adds
another rule to the Rule Base after fireAllRules()
.
The added rule is just another state transition.
Example 8.20. Dynamic State Example: Rule "D to E"
rule "D to E" when State(name == "D", state == State.FINISHED ) e : State(name == "E", state == State.NOTRUN ) then System.out.println(e.getName() + " finished" ); e.setState( State.FINISHED ); end
This produces the following expected output: