State Example

This example is actually implemented in three different versions to demonstrate different ways of implementing the same basic behavior: rules 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.

Understanding the State Example

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 org.drools.examples.State class). 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...
}

Ignore the PropertyChangeSupport for now, that will be explained later. In the example we create four State objects with names: A, B, C and D. Initially all are set to state 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 Example 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 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:

  1. Open the class org.drools.examples.StateExampleUsingSalience in your Eclipse IDE

  2. Right-click the class an select "Run as..." -> "Java application"

And you will see the following output in the Eclipse console output:

Example 8.11. Salience State Example Console Output

A finished
B finished
C finished
D finished

There are four rules in total, first a Bootstrap rule fires setting A to state FINISHED which then causes B to change to state FINISHED. C and D are both dependent on B - causing a conflict which is resolved by setting salience values. First lets look at how this was executed

The best way to understand what is happening is to use the "Audit Log" feature to graphically see the results of each operation. The Audit log was generated when the example was previously run. To view the Audit log in Eclipse:

  1. If the "Audit View" is not visible, click on: "Window"->"Show View"->"Other..."->"Drools"->"Audit View"

  2. In the "Audit View" click in 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.

Figure 8.4. Salience State Example Audit View

Salience State Example Audit View

Reading the log in the "Audit View", top to down, we see every action and the corresponding changes in the working memory. This way we see that the assertion of the State "A" object with the "NOTRUN" state activates the "Bootstrap" rule, while the assertions of the other state objects have no immediate effect.

Example 8.12. Salience State Example: 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 "Bootstrap" rule changes the state of "A" to "FINISHED", that in turn activates the "A to B" rule.

Example 8.13. Salience State Example: 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 "A to B" rule changes the state of "B" to "FINISHED", which activates both rules "B to C" and "B to D", placing both Activations onto the Agenda. In this moment the two rules may fire and are said to be in conflict. The conflict resolution strategy allows the engine's Agenda to decide which rule to fire. As the "B to C" rule has a higher salience value (10 versus the default salience value of 0), it fires first, modifying the "C" object to state "FINISHED". The Audit view above shows the modification of the State object in the rule "A to B" which results in two highlighted activations being in conflict. The Agenda view can also be used to investigate the state of the Agenda, debug points can be placed in the rules themselves and the Agenda view opened; the screen shot below shows the break point in the rule "A to B" and the state of the Agenda with the two conflicting rules.

Figure 8.5. State Example Agenda View

State Example Agenda View

Example 8.14. Salience State Example: 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

The "B to D" rule fires last, modifying the "D" object to state "FINISHED".

Example 8.15. Salience State Example: 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, which is the PropertyChangeListener part. As mentioned previously in the documentation, in order for the engine to see and react to fact's properties change, the application must tell the engine that changes occurred. This can be done explicitly in the rules, by calling the update() memory action, 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 update() calls in the rules. To make use of this feature, make sure your facts implement the PropertyChangeSupport as the org.drools.example.State class 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 update().
final boolean dynamic = true;

session.insert( fact,
                dynamic );

When using PropertyChangeListeners each setter must implement a little extra code to do the notification, here is the state setter for thte org.drools.examples.State class:

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 State examples: 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 and StateExampleWithDynamicRules shows how an additional rule can be added to an already running WorkingMemory with all the existing data applying to it at runtime.

Agenda groups are a way to partition the agenda into groups and controlling which groups can execute. All rules by default are in the "MAIN" agenda group, by simply using the "agenda-group" attribute you specify a different agenda group for the rule. A working memory initially only has focus on the "MAIN" agenda group, only when other groups are given the focus can their rules fire; this can be achieved by either using the method setFocus() or the rule attribute "auto-focus". "auto-focus" means that the rule automatically sets the focus to it's agenda group when the rule is matched and activated. It is this "auto-focus" that enables "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 );
      drools.setFocus( "B to D" );
end

The rule "B to C" calls "drools.setFocus( "B to D" );" which gives the agenda group "B to D" focus 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 RuleBase after fireAllRules(), the rule it adds 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

It gives the following expected output:

Example 8.21. Dynamic Sate Example Output

A finished
B finished
C finished
D finished
E finished