Red Hat Training

A Red Hat training course is available for JBoss Enterprise SOA Platform

Chapter 4. User Guide

4.1. Stateless Knowledge Sessions

A stateless knowledge session is a session without inference. A stateless session can be called like a function in that you can use it to pass data and then receive the result back.
Stateless knowledge sessions are useful in situations requiring validation, calculation, routing and filtering.

4.2. Configuring Rules in a Stateless Session

Procedure 4.1. Task

  1. Create a data model like the driver's license example below:
    public class Applicant {
        private String name;
        private int age;
        private boolean valid;
        // getter and setter methods here
    }
    
  2. Write the first rule. In this example, a rule is added to disqualify any applicant younger than 18:
    package com.company.license
    
    rule "Is of valid age"
    when
        $a : Applicant( age < 18 )
    then
        $a.setValid( false );
    end
    
  3. When the Applicant object is inserted into the rule engine, each rule's constraints evaluate it and search for a match. (There is always an implied constraint of "object type" after which there can be any number of explicit field constraints.)
    In the Is of valid age rule there are two constraints:
    • The fact being matched must be of type Applicant
    • The value of Age must be less than eighteen.
    $a is a binding variable. It exists to make possible a reference to the matched object in the rule's consequence (from which place the object's properties can be updated).

    Note

    Use of the dollar sign ($) is optional. It helps to differentiate between variable names and field names.

    Note

    If the rules are in the same folder as the classes, the classpath resource loader can be used to build the first knowledge base.
  4. Use the KnowledgeBuilder to to compile the list of rules into a knowledge base as shown:
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    kbuilder.add( ResourceFactory.newClassPathResource( "licenseApplication.drl", 
                  getClass() ), ResourceType.DRL );
    if ( kbuilder.hasErrors() ) {
        System.err.println( kbuilder.getErrors().toString() );
    }
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
    kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
    
    The above code snippet looks on the classpath for the licenseApplication.drl file, using the method newClassPathResource(). (The resource type is DRL, short for "Drools Rule Language".)
  5. Check the KnowledgeBuilder for any errors. If there are none, you can build the session.
  6. Execute the data against the rules. (Since the applicant is under the age of eighteen, their application will be marked as "invalid.")
    StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
    Applicant applicant = new Applicant( "Mr John Smith", 16 );
    assertTrue( applicant.isValid() );
    ksession.execute( applicant );
    assertFalse( applicant.isValid() );
    
Result

The preceding code executes the data against the rules. Since the applicant is under the age of 18, the application is marked as invalid.

4.3. Configuring Rules with Multiple Objects

Procedure 4.2. Task

  1. To execute rules against any object-implementing iterable (such as a collection), add another class as shown in the example code below:
    public class Applicant {
        private String name;
        private int age;
        // getter and setter methods here
    }
    
    public class Application {
        private Date dateApplied;
        private boolean valid;
        // getter and setter methods here
    }
    
  2. In order to check that the application was made within a legitimate time-frame, add this rule:
    package com.company.license
    
    rule "Is of valid age"
    when
        Applicant( age < 18 )
        $a : Application()     
    then
        $a.setValid( false );
    end
    
    rule "Application was made this year"
    when
        $a : Application( dateApplied > "01-jan-2009" )     
    then
        $a.setValid( false );
    end
    
  3. Use the JDK converter to implement the iterable interface. (This method commences with the line Arrays.asList(...).) The code shown below executes rules against an iterable list. Every collection element is inserted before any matched rules are fired:
    StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
    Applicant applicant = new Applicant( "Mr John Smith", 16 );
    Application application = new Application();
    assertTrue( application.isValid() );
    ksession.execute( Arrays.asList( new Object[] { application, applicant } ) );
    assertFalse( application.isValid() );
    

    Note

    The execute(Object object) and execute(Iterable objects) methods are actually "wrappers" around a further method called execute(Command command) which comes from the BatchExecutor interface.
  4. Use the CommandFactory to create instructions, so that the following is equivalent to execute( Iterable it ):
    ksession.execute( CommandFactory.newInsertIterable( new Object[] { application, applicant } ) );
    
  5. Use the BatchExecutor and CommandFactory when working with many different commands or result output identifiers:
    List<Command> cmds = new ArrayList<Command>();
    cmds.add( CommandFactory.newInsert( new Person( "Mr John Smith" ), "mrSmith" );
    cmds.add( CommandFactory.newInsert( new Person( "Mr John Doe" ), "mrDoe" );
    BatchExecutionResults results = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
    assertEquals( new Person( "Mr John Smith" ), results.getValue( "mrSmith" ) );
    

    Note

    CommandFactory supports many other commands that can be used in the BatchExecutor. Some of these are StartProcess, Query and SetGlobal.

4.4. Stateful Sessions

A stateful session allow you to make iterative changes to facts over time. As with the StatelessKnowledgeSession, the StatefulKnowledgeSession supports the BatchExecutor interface. The only difference is the FireAllRules command is not automatically called at the end.

Warning

Ensure that the dispose() method is called after running a stateful session. This is to ensure that there are no memory leaks. This is due to the fact that knowledge bases will obtain references to stateful knowledge sessions when they are created.

4.5. Common Use Cases for Stateful Sessions

Monitoring
For example, you can monitor a stock market and automate the buying process.
Diagnostics
Stateful sessions can be used to run fault-finding processes. They could also be used for medical diagnostic processes.
Logistical
For example, they could be applied to problems involving parcel tracking and delivery provisioning.
Ensuring compliance
For example, to validate the legality of market trades.

4.6. Stateful Session Monitoring Example

Procedure 4.3. Task

  1. Create a model of what you want to monitor. In this example involving fire alarms, the rooms in a house have been listed. Each has one sprinkler. A fire can start in any of the rooms:
    public class Room 
    {
    	private String name
    		// getter and setter methods here
    }
    
    public class Sprinkler 
    {
    	private Room room;
    	private boolean on;
    	// getter and setter methods here
    }
    
    public class Fire 
    {
    	private Room room;
    	// getter and setter methods here
    }
    
    public class Alarm 
    {
    }
    
  2. The rules must express the relationships between multiple objects (to define things such as the presence of a sprinkler in a certain room). To do this, use a binding variable as a constraint in a pattern. This results in a cross-product.
  3. Create an instance of the Fire class and insert it into the session.
    The rule below adds a binding to Fire object's room field to constrain matches. This so that only the sprinkler for that room is checked. When this rule fires and the consequence executes, the sprinkler activates:
    rule "When there is a fire turn on the sprinkler"
    when
        Fire($room : room)
        $sprinkler : Sprinkler( room == $room, on == false )
    then
        modify( $sprinkler ) { setOn( true ) };
        System.out.println("Turn on the sprinkler for room "+$room.getName());
    end
    
    Whereas the stateless session employed standard Java syntax to modify a field, the rule above uses the modify statement. (It acts much like a "with" statement.)

4.7. First Order Logic

First order logic allows you to look at sets of data instead of individual instances.

4.8. Configuring Rules with First Order Logic

Procedure 4.4. Task

  1. Configure a pattern featuring the keyword Not. First order logic ensures rules will only be matched when no other keywords are present. In this example, the rule turns the sprinkler off when the fire is extinguished:
    rule "When the fire is gone turn off the sprinkler"
    when
        $room : Room( )
        $sprinkler : Sprinkler( room == $room, on == true )
        not Fire( room == $room )
    then
        modify( $sprinkler ) { setOn( false ) };
        System.out.println("Turn off the sprinkler for room "+$room.getName());
    end
    
  2. An Alarm object is created when there is a fire, but only one Alarm is needed for the entire building no matter how many fires there might be. Not's complement, exists can now be introduced. It matches one or more instances of a category:
    rule "Raise the alarm when we have one or more fires"
    when
        exists Fire()
    then
        insert( new Alarm() );
        System.out.println( "Raise the alarm" );
    end
    
  3. If there are no more fires, the alarm must be deactivated. To turn it off, use Not again:
    rule "Cancel the alarm when all the fires have gone"
    when
        not Fire()
        $alarm : Alarm()
    then
        retract( $alarm );
        System.out.println( "Cancel the alarm" );
    end
    
  4. Use this code to print a general health status message when the application first starts and also when the alarm and all of the sprinklers have been deactivated:
    rule "Status output when things are ok"
    when
        not Alarm()
        not Sprinkler( on == true ) 
    then
        System.out.println( "Everything is ok" );
    end
    
  5. Store the rules in a file called fireAlarm.drl. Save this file in a sub-directory on the class-path.
  6. Finally, build a knowledge base, using the new name, fireAlarm.drl:
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    kbuilder.add( ResourceFactory.newClassPathResource( "fireAlarm.drl", 
    										getClass() ), ResourceType.DRL );
    										
    if ( kbuilder.hasErrors() )
    	System.err.println( kbuilder.getErrors().toString() );
    
    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
    

4.9. Rule System Sample Configuration

Procedure 4.5. Task

  1. Insert ksession.fireAllRules(). This grants the matched rules permission to run but, since there is no fire in this example, they will merely produce the health message:
    String[] names = new String[]{"kitchen","bedroom","office","livingroom"};
    Map<String,Room> name2room = new HashMap<String,Room>();
    
    for( String name: names )
    {
    	Room room = new Room( name );
    	name2room.put( name, room );
    	ksession.insert( room );
    	Sprinkler sprinkler = new Sprinkler( room );
    	ksession.insert( sprinkler );
    }
    
    ksession.fireAllRules();
    
    The resulting message reads:
    > Everything is okay
    
  2. Create and insert two fires. (A fact handle will be kept.)
  3. With the fires now in the engine, call fireAllRules(). The alarm will be raised and the respective sprinklers will be turned on:
    Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
    Fire officeFire = new Fire( name2room.get( "office" ) );
    
    FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
    FactHandle officeFireHandle = ksession.insert( officeFire );
    
    ksession.fireAllRules();
    
    The resulting message reads:
    > Raise the alarm
    > Turn on the sprinkler for room kitchen
    > Turn on the sprinkler for room office
    
  4. When the fires are extinguished, the fire objects are retracted and the sprinklers are turned off. At this point in time, the alarm is canceled and the health message displays once more:
    ksession.retract( kitchenFireHandle );
    ksession.retract( officeFireHandle );
    
    ksession.fireAllRules();
    
    The resulting message reads:
    > Turn off the sprinkler for room office
    > Turn off the sprinkler for room kitchen
    > Cancel the alarm
    > Everything is okay       
    
    

4.10. Methods in JBoss Rules

Methods are different to rules. They are called directly and are used to pass specific instances. A single call results in a single execution.

4.11. Method Example

A method looks like this:
public void helloWorld(Person person) {
    if ( person.getName().equals( "Chuck" ) ) {
        System.out.println( "Hello Chuck" );
    }
}

4.12. Rule Example

A rule looks like this:
rule "Hello World"
    when
        Person( name == "Chuck" )
    then
        System.out.println( "Hello Chuck" );
end

4.13. Cross-Products

When two or more sets of data are combined, the result is called a cross-product.

4.14. Cross-Product Constraining

Procedure 4.6. Task

  • To prevent a rule from outputting a huge amount of cross-products, you should constrain the cross-products themselves. Do this using the variable constraint seen below:
    rule
    when
        $room : Room()
        $sprinkler : Sprinkler( room == $room )
    then
        System.out.println( "room:" + $room.getName() +
                            " sprinkler:" + $sprinkler.getRoom().getName() );
    end
    
    The following output will be displayed:
    room:office sprinkler:office
    room:kitchen sprinkler:kitchen
    room:livingroom sprinkler:livingroom
    room:bedroom sprinkler:bedroom
Result

Only four rows are outputted with the correct sprinkler for each room. Without this variable, every row in the Room table would have been joined with every row in the Sprinkler table resulting in many lines of output.

4.15. The Inference Engine

The inference engine is the part of the JBoss Rules engine which matches production facts and data to rules. It will then perform actions based on what it infers from the information. A production rules system's inference engine is stateful and is responsible for truth maintenance.

4.16. Inference Example

In this example, a Person fact with an age field and a rule that provides age policy control is used. Inference is used to determine if a Person is an adult or a minor, then act on the result:
rule "Infer Adult"
when
  $p : Person( age >= 18 )
then
  insert( new IsAdult( $p ) )
end
In the above snippet, every Person who is 18 or over will have an instance of IsAdult inserted for them. This fact is special in that it is known as a relation. We can use this inferred relation in any rule:
$p : Person()
IsAdult( person == $p )

4.17. Logical Assertions

After a standard object insertion, you have to retract facts explicitly. With logical assertions, the fact that was asserted will be automatically retracted when the conditions that asserted it in the first place are no longer true. It will be retracted only if there isn't any single condition that supports the logical assertion.

4.18. Stated Insertions

Normal insertions are said to be stated, as in "stating a fact". Using a HashMap and a counter, you can track how many times a particular equality is stated. This means you can count how many different instances are equal.

4.19. Justified Insertions

When an object is logically inserted, it is said to be justified. It is considered to be justified by the firing rule. For each logical insertion there can only be one equal object, and each subsequent equal logical insertion increases the justification counter for this logical assertion. A justification is removed when the creating rule's LHS becomes untrue, and the counter is decreased accordingly. As soon as there are no more justifications, the logical object is automatically retracted.

4.20. The WM_BEHAVIOR_PRESERVE Setting

If you try to logically insert an object when there is an equal stated object, this will fail and return null. If you state an object that has an existing equal object that is justified, you will override the Fact. How this override works depends on the configuration setting WM_BEHAVIOR_PRESERVE. When the property is set to discard, you can use the existing handle and replace the existing instance with the new Object, which is the default behavior. Otherwise you should override it to stated but create an new FactHandle.

4.21. Stated Insertion Flowchart

4.22. Logical Insertion Flowchart

4.23. The Truth Maintenance System

The JBoss Rules Truth Maintenance System (TMS) is a method of representing beliefs and their related dependencies/justifications in a knowledge base.

Important

For Truth Maintenance (and logical assertions) to work, the Fact objects must override equals and hashCode methods correctly. As the truth maintenance system needs to know when two different physical objects are equal in value, both equals and hashCode must be overridden correctly, as per the Java standard.
Two objects are equal only if their equals methods return true for each other and if their hashCode methods return the same values. You must override both equals and hashCode.

4.24. The insertLogical Fact

The insertLogical fact is part of the JBoss Rules TMS. It "inserts logic" so that rules behave and are modified according to the situation. For example, the insertLogical fact can be added to a set of rules so that when a rule becomes false, the fact is automatically retracted.

4.25. Using Inference and TMS

Procedure 4.7. Task

  1. In this example we will use a bus pass issuing system. See the code snippet below:
    rule "Issue Child Bus Pass" when
      $p : Person( age < 16 )
    then
      insert(new ChildBusPass( $p ) );
    end
     
    rule "Issue Adult Bus Pass" when
      $p : Person( age >= 16 )
    then
      insert(new AdultBusPass( $p ) );
    end
  2. Insert the insertLogical property to provide inference:
    rule "Infer Child" when
        $p : Person( age < 16 )
    then
        insertLogical( new IsChild( $p ) )
    end
    rule "Infer Adult" when
        $p : Person( age >= 16 )
    then
        insertLogical( new IsAdult( $p ) )
    end
  3. Re-enter the code to issue the passes. These two configurations can also be logically inserted, as the TMS supports chaining of logical insertions for a cascading set of retracts:
    rule "Issue Child Bus Pass" when
      $p : Person( )
           IsChild( person == $p )
    then
        insertLogical(new ChildBusPass( $p ) );
    end
     
    rule "Issue Adult Bus Pass" when
      $p : Person( age >= 16 )
           IsAdult( person =$p )
    then
        insertLogical(new AdultBusPass( $p ) );
    end
    Now when the person changes from being 15 to 16, both the IsChild fact and the person's ChildBusPass fact are automatically retracted.
  4. Optionally, insert the not conditional element to handle notifications. (In this example, a request for the returning of the pass.) When the TMS automatically retracts the ChildBusPass object, this rule triggers and sends a request to the person:
    rule "Return ChildBusPass Request "when
      $p : Person( )
           not( ChildBusPass( person == $p ) )
    then
        requestChildBusPass( $p );
    end