Chapter 17. Elements and Variables

17.1. Property Access on Java Beans (POJOs)

Any bean property can be used directly. A bean property is exposed using a standard Java bean getter: a method getMyProperty() (or isMyProperty() for a primitive boolean) which takes no arguments and return something.
JBoss Rules uses the standard JDK Introspector class to do this mapping, so it follows the standard Java bean specification.

Warning

Property accessors must not change the state of the object in a way that may effect the rules. The rule engine effectively caches the results of its matching in between invocations to make it faster.

17.2. POJO Example

This is what the bean property looks like:
Person( age == 50 )

// this is the same as:
Person( getAge() == 50 )
The age property
The age property is written as age in DRL instead of the getter getAge()
Property accessors
You can use property access (age) instead of getters explicitly (getAge()) because of performance enhancements through field indexing.

17.3. Working with POJOs

Procedure 17.1. Task

  1. Observe the example below:
    public int getAge() {
        Date now = DateUtil.now(); // Do NOT do this
        return DateUtil.differenceInYears(now, birthday);
    }
  2. To solve this, insert a fact that wraps the current date into working memory and update that fact between fireAllRules as needed.

17.4. POJO Fallbacks

When working with POJOs, a fallback method is applied. If the getter of a property cannot be found, the compiler will resort to using the property name as a method name and without arguments. Nested properties are also indexed.

17.5. Fallback Example

This is what happens when a fallback is implemented:
Person( age == 50 )

// If Person.getAge() does not exists, this falls back to:
Person( age() == 50 )
This is what it looks like as a nested property:
Person( address.houseNumber == 50 )

// this is the same as:
Person( getAddress().getHouseNumber() == 50 )

Warning

In a stateful session, care should be taken when using nested accessors as the Working Memory is not aware of any of the nested values and does not know when they change. Consider them immutable while any of their parent references are inserted into the Working Memory. If you wish to modify a nested value you should mark all of the outer facts as updated. In the above example, when the houseNumber changes, any Person with that Address must be marked as updated.

17.6. Java Expressions

Table 17.1. Java Expressions
Capability Example
You can use any Java expression that returns a boolean as a constraint inside the parentheses of a pattern. Java expressions can be mixed with other expression enhancements, such as property access.
Person( age == 50 )
You can change the evaluation priority by using parentheses, as in any logic or mathematical expression.
Person( age > 100 && ( age % 10 == 0 ) )
You can reuse Java methods.
Person( Math.round( weight / ( height * height ) ) < 25.0 )
Type coercion is always attempted if the field and the value are of different types; exceptions will be thrown if a bad coercion is attempted.
Person( age == "10" ) // "10" is coerced to 10

Warning

Methods must not change the state of the object in a way that may affect the rules. Any method executed on a fact in the LHS should be a read only method.

Warning

The state of a fact should not change between rule invocations (unless those facts are marked as updated to the working memory on every change):
Person( System.currentTimeMillis() % 1000 == 0 ) // Do NOT do this

Important

All operators have normal Java semantics except for == and !=.
The == operator has null-safe equals() semantics:
// Similar to: java.util.Objects.equals(person.getFirstName(), "John")
// so (because "John" is not null) similar to:
// "John".equals(person.getFirstName())
Person( firstName == "John" )
The != operator has null-safe !equals() semantics:
// Similar to: !java.util.Objects.equals(person.getFirstName(), "John")
Person( firstName != "John" )

17.7. Comma-Separated Operators

The comma character (',') is used to separate constraint groups. It has implicit and connective semantics.
The comma operator is used at the top level constraint as it makes them easier to read and the engine will be able to optimize them.

17.8. Comma-Separated Operator Example

The following illustrates comma-separated scenarios in implicit and connective semantics:
// Person is at least 50 and weighs at least 80 kg
Person( age > 50, weight > 80 )
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter.
Person( age > 50, weight > 80, height > 2 )

Note

The comma (,) operator cannot be embedded in a composite constraint expression, such as parentheses.

17.9. Binding Variables

You can bind properties to variables in JBoss Rules. It allows for faster execution and performance.

17.10. Binding Variable Examples

This is an example of a property bound to a variable:
// 2 persons of the same age
Person( $firstAge : age ) // binding
Person( age == $firstAge ) // constraint expression

Note

For backwards compatibility reasons, it's allowed (but not recommended) to mix a constraint binding and constraint expressions as such:
// Not recommended
Person( $age : age * 2 < 100 )
// Recommended (separates bindings and constraint expressions)
Person( age * 2 < 100, $age : age )

17.11. Unification

You can unify arguments across several properties. While positional arguments are always processed with unification, the unification symbol, ':=', exists for named arguments.

17.12. Unification Example

This is what unifying two arguments looks like:
Person( $age := age ) 
Person( $age := age)

17.13. Options and Operators in JBoss Rules

Table 17.2. Options and Operators in JBoss Rules
Option Description Example
Date literal
The date format dd-mmm-yyyy is supported by default. You can customize this by providing an alternative date format mask as the System property named drools.dateformat. If more control is required, use a restriction.
Cheese( bestBefore < "27-Oct-2009" )
List and Map access
You can directly access a List value by index.
// Same as childList(0).getAge() == 18
Person( childList[0].age == 18 )
Value key
You can directly access a Map value by key.
// Same as credentialMap.get("jsmith").isValid()
Person( credentialMap["jsmith"].valid )
Abbreviated combined relation condition
This allows you to place more than one restriction on a field using the restriction connectives && or ||. Grouping via parentheses is permitted, resulting in a recursive syntax pattern.
// Simple abbreviated combined relation condition using a single &&
Person( age > 30 && < 40 )
// Complex abbreviated combined relation using groupings
Person( age ( (> 30 && < 40) ||
              (> 20 && < 25) ) )
// Mixing abbreviated combined relation with constraint connectives
Person( age > 30 && < 40 || location == "london" )
Operators
Operators can be used on properties with natural ordering. For example, for Date fields, < means before, for String fields, it means alphabetically lower.
Person( firstName < $otherFirstName )
Person( birthDate < $otherBirthDate )
Operator matches
Matches a field against any valid Java regular expression. Typically that regexp is a string literal, but variables that resolve to a valid regexp are also allowed. It only applies on String properties. Using matches against a null value always evaluates to false.
Cheese( type matches "(Buffalo)?\\S*Mozarella" )
Operator not matches
The operator returns true if the String does not match the regular expression. The same rules apply as for the matches operator. It only applies on String properties.
Cheese( type not matches "(Buffulo)?\\S*Mozarella" )
The operator contains
The operator contains is used to check whether a field that is a Collection or array contains the specified value. It only applies on Collection properties.
CheeseCounter( cheeses contains "stilton" ) // contains with a String literal
CheeseCounter( cheeses contains $var ) // contains with a variable
The operator not contains
The operator not contains is used to check whether a field that is a Collection or array does not contain the specified value. It only applies on Collection properties.
CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal
CheeseCounter( cheeses not contains $var ) // not contains with a variable
The operator memberOf
The operator memberOf is used to check whether a field is a member of a collection or array; that collection must be a variable.
CheeseCounter( cheese memberOf $matureCheeses )
The operator not memberOf
The operator not memberOf is used to check whether a field is not a member of a collection or array. That collection must be a variable.
CheeseCounter( cheese not memberOf $matureCheeses )
The operator soundslike
This operator is similar to matches, but it checks whether a word has almost the same sound (using English pronunciation) as the given value.
// match cheese "fubar" or "foobar"
Cheese( name soundslike 'foobar' )
The operator str
The operator str is used to check whether a field that is a String starts with or ends with a certain value. It can also be used to check the length of the String.
Message( routingValue str[startsWith] "R1" )
Message( routingValue str[endsWith] "R2" )
Message( routingValue str[length] 17 )
Compound Value Restriction
Compound value restriction is used where there is more than one possible value to match. Currently only the in and not in evaluators support this. The second operand of this operator must be a comma-separated list of values, enclosed in parentheses. Values may be given as variables, literals, return values or qualified identifiers. Both evaluators are actually syntactic sugar, internally rewritten as a list of multiple restrictions using the operators != and ==.
Person( $cheese : favouriteCheese )
Cheese( type in ( "stilton", "cheddar", $cheese ) )

17.14. Operator Precedence

Table 17.3. Operator precedence
Operator type Operators Notes
(nested) property access . Not normal Java semantics
List/Map access [ ] Not normal Java semantics
constraint binding : Not normal Java semantics
multiplicative * /%
additive + -
shift << >>>>>
relational < ><= >=instanceof
equality == != Does not use normal Java (not) same semantics: uses (not) equals semantics instead.
non-short circuiting AND &
non-short circuiting exclusive OR ^
non-short circuiting inclusive OR |
logical AND &&
logical OR ||
ternary ? :
Comma separated AND , Not normal Java semantics

17.15. Fine Grained Property Change Listeners

This feature allows the pattern matching to only react to modification of properties actually constrained or bound inside of a given pattern. This helps with performance and recursion and avoid artificial object splitting.

Note

By default this feature is off in order to make the behavior of the rule engine backward compatible with the former releases. When you want to activate it on a specific bean you have to annotate it with @propertyReactive.

17.16. Fine Grained Property Change Listener Example

DRL example
declare Person
          @propertyReactive
          firstName : String
          lastName : String
          end
Java class example
@PropertyReactive
          public static class Person {
          private String firstName;
          private String lastName;
          }

17.17. Working with Fine Grained Property Change Listeners

Using these listeners means you do not need to implement the no-loop attribute to avoid an infinite recursion. The engine recognizes that the pattern matching is done on the property while the RHS of the rule modifies other the properties. On Java classes, you can also annotate any method to say that its invocation actually modifies other properties.

17.18. Using Patterns with @watch

Annotating a pattern with @watch allows you to modify the inferred set of properties for which that pattern will react. The properties named in the @watch annotation are added to the ones automatically inferred. You can explicitly exclude one or more of them by beginning their name with a ! and to make the pattern to listen for all or none of the properties of the type used in the pattern respectively with the wildcards * and !*.

17.19. @watch Example

This is the @watch annotation in a rule's LHS:
// listens for changes on both firstName (inferred) and lastName
          Person( firstName == $expectedFirstName ) @watch( lastName )

          // listens for all the properties of the Person bean
          Person( firstName == $expectedFirstName ) @watch( * )

          // listens for changes on lastName and explicitly exclude firstName
          Person( firstName == $expectedFirstName ) @watch( lastName, !firstName )

          // listens for changes on all the properties except the age one
          Person( firstName == $expectedFirstName ) @watch( *, !age )

Note

Since doesn't make sense to use this annotation on a pattern using a type not annotated with @PropertyReactive the rule compiler will raise a compilation error if you try to do so. Also the duplicated usage of the same property in @watch (for example like in: @watch( firstName, ! firstName ) ) will end up in a compilation error.

17.20. Using @PropertySpecificOption

You can enable @watch by default or completely disallow it using the on option of the KnowledgeBuilderConfiguration. This new PropertySpecificOption can have one of the following 3 values:
- DISABLED => the feature is turned off and all the other related annotations are just ignored
          - ALLOWED => this is the default behavior: types are not property reactive unless they are not annotated with @PropertySpecific
          - ALWAYS => all types are property reactive by default

17.21. Basic Conditional Elements

Table 17.4. Basic Conditional Elements
Name Description Example Additional options
and
The Conditional Element and is used to group other Conditional Elements into a logical conjunction. JBoss Rules supports both prefix and and infix and. It supports explicit grouping with parentheses. You can also use traditional infix and prefix and.
//infixAnd
Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
//infixAnd with grouping
( Cheese( cheeseType : type ) and
  ( Person( favouriteCheese == cheeseType ) or 
    Person( favouriteCheese == cheeseType ) )
Prefix and is also supported:
(and Cheese( cheeseType : type )
     Person( favouriteCheese == cheeseType ) )
The root element of the LHS is an implicit prefix and and doesn't need to be specified:
when
    Cheese( cheeseType : type )
    Person( favouriteCheese == cheeseType )
then
    ...
or
This is a shortcut for generating two or more similar rules. JBoss Rules supports both prefix or and infix or. You can use traditional infix, prefix and explicit grouping parentheses.
//infixOr
Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType )
//infixOr with grouping
( Cheese( cheeseType : type ) or
  ( Person( favouriteCheese == cheeseType ) and
    Person( favouriteCheese == cheeseType ) )
(or Person( sex == "f", age > 60 )
    Person( sex == "m", age > 65 )
Allows for optional pattern binding. Each pattern must be bound separately, using eponymous variables:
pensioner : ( Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 ) )
(or pensioner : Person( sex == "f", age > 60 ) 
    pensioner : Person( sex == "m", age > 65 ) )
not
This checks to ensure an object specified as absent is not included in the Working Memory. It may be followed by parentheses around the condition elements it applies to. (In a single pattern you can omit the parentheses.)
// Brackets are optional:
not Bus(color == "red")
// Brackets are optional:
not ( Bus(color == "red", number == 42) )
// "not" with nested infix and - two patterns,
// brackets are requires:
not ( Bus(color == "red") and
      Bus(color == "blue") )
exists
This checks the working memory to see if a specified item exists. The keyword exists must be followed by parentheses around the CEs that it applies to. (In a single pattern you can omit the parentheses.)
exists Bus(color == "red")
// brackets are optional:
exists ( Bus(color == "red", number == 42) )
// "exists" with nested infix and,
// brackets are required:
exists ( Bus(color == "red") and
         Bus(color == "blue") )

Note

The behavior of the Conditional Element or is different from the connective || for constraints and restrictions in field constraints. The engine cannot interpret the Conditional Element or. Instead, a rule with or is rewritten as a number of subrules. This process ultimately results in a rule that has a single or as the root node and one subrule for each of its CEs. Each subrule can activate and fire like any normal rule; there is no special behavior or interaction between these subrules.

17.22. The Conditional Element Forall

This element evaluates to true when all facts that match the first pattern match all the remaining patterns. It is a scope delimiter. Therefore, it can use any previously bound variable, but no variable bound inside it will be available for use outside of it.
Forall can be nested inside other CEs. For instance, forall can be used inside a not CE. Only single patterns have optional parentheses, so with a nested forall parentheses must be used.

17.23. Forall Examples

Evaluating to true
rule "All English buses are red"
when
    forall( $bus : Bus( type == 'english') 
                   Bus( this == $bus, color = 'red' ) )
then
    // all English buses are red
end
Single pattern forall
rule "All Buses are Red"
when
    forall( Bus( color == 'red' ) )
then
    // all Bus facts are red
end
Multi-pattern forall
rule "all employees have health and dental care programs"
when
    forall( $emp : Employee()
            HealthCare( employee == $emp )
            DentalCare( employee == $emp )
          )
then
    // all employees have health and dental care
end
Nested forall
rule "not all employees have health and dental care"
when 
    not ( forall( $emp : Employee()
                  HealthCare( employee == $emp )
                  DentalCare( employee == $emp ) ) 
        )
then
    // not all employees have health and dental care
end

17.24. The Conditional Element From

The Conditional Element from enables users to specify an arbitrary source for data to be matched by LHS patterns. This allows the engine to reason over data not in the Working Memory. The data source could be a sub-field on a bound variable or the results of a method call. It is a powerful construction that allows out of the box integration with other application components and frameworks. One common example is the integration with data retrieved on-demand from databases using hibernate named queries.
The expression used to define the object source is any expression that follows regular MVEL syntax. Therefore, it allows you to easily use object property navigation, execute method calls and access maps and collections elements.

Important

Using from with lock-on-active rule attribute can result in rules not being fired.
There are several ways to address this issue:
  • Avoid the use of from when you can assert all facts into working memory or use nested object references in your constraint expressions (shown below).
  • Place the variable assigned used in the modify block as the last sentence in your condition (LHS).
  • Avoid the use of lock-on-active when you can explicitly manage how rules within the same rule-flow group place activations on one another.

17.25. From Examples

Reasoning and binding on patterns
rule "validate zipcode"
when
    Person( $personAddress : address ) 
    Address( zipcode == "23920W") from $personAddress 
then
    // zip code is ok
end
Using a graph notation
rule "validate zipcode"
when
    $p : Person( ) 
    $a : Address( zipcode == "23920W") from $p.address 
then
    // zip code is ok
end
Iterating over all objects
rule "apply 10% discount to all items over US$ 100,00 in an order"
when
    $order : Order()
    $item  : OrderItem( value > 100 ) from $order.items
then
    // apply discount to $item
end
Use with lock-on-active
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
    $p : Person(address.state == "NC" )  
then
    modify ($p) {} // Assign person to sales region 1 in a modify block
end

rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
    $p : Person(address.city == "Raleigh" )  
then
    modify ($p) {} //Apply discount to person in a modify block
end

17.26. The Conditional Element Collect

The Conditional Element collect allows rules to reason over a collection of objects obtained from the given source or from the working memory. In First Oder Logic terms this is the cardinality quantifier.
The result pattern of collect can be any concrete class that implements the java.util.Collection interface and provides a default no-arg public constructor. You can use Java collections like ArrayList, LinkedList and HashSet or your own class, as long as it implements the java.util.Collection interface and provide a default no-arg public constructor.
Variables bound before the collect CE are in the scope of both source and result patterns and therefore you can use them to constrain both your source and result patterns. Any binding made inside collect is not available for use outside of it.

17.27. The Conditional Element Accumulate

The Conditional Element accumulate is a more flexible and powerful form of collect, in the sense that it can be used to do what collect does and also achieve results that the CE collect is not capable of doing. It allows a rule to iterate over a collection of objects, executing custom actions for each of the elements. At the end it returns a result object.
Accumulate supports both the use of pre-defined accumulate functions, or the use of inline custom code. Inline custom code should be avoided though, as it is harder for rule authors to maintain, and frequently leads to code duplication. Accumulate functions are easier to test and reuse.
The Accumulate CE also supports multiple different syntaxes. The preferred syntax is the top level accumulate, as noted bellow, but all other syntaxes are supported for backward compatibility.

17.28. Syntax for the Conditional Element Accumulate

Top level accumulate syntax
accumulate( <source pattern>; <functions> [;<constraints>] )
Syntax example
rule "Raise alarm"
when
    $s : Sensor()
    accumulate( Reading( sensor == $s, $temp : temperature );
                $min : min( $temp ),
                $max : max( $temp ),
                $avg : average( $temp );
                $min < 20, $avg > 70 )
then
    // raise the alarm
end
In the above example, min, max and average are Accumulate Functions and will calculate the minimum, maximum and average temperature values over all the readings for each sensor.

17.29. Functions of the Conditional Element Accumulate

  • average
  • min
  • max
  • count
  • sum
  • collectList
  • collectSet
These common functions accept any expression as input. For instance, if someone wants to calculate the average profit on all items of an order, a rule could be written using the average function:
rule "Average profit"
when
    $order : Order()
    accumulate( OrderItem( order == $order, $cost : cost, $price : price );
                $avgProfit : average( 1 - $cost / $price ) )
then
    // average profit for $order is $avgProfit
end

17.30. The Conditional Element accumulate and Pluggability

Accumulate functions are all pluggable. That means that if needed, custom, domain specific functions can easily be added to the engine and rules can start to use them without any restrictions. To implement a new Accumulate Function all one needs to do is to create a Java class that implements the org.drools.runtime.rule.TypedAccumulateFunction interface and add a line to the configuration file or set a system property to let the engine know about the new function.

17.31. The Conditional Element accumulate and Pluggability Example

As an example of an Accumulate Function implementation, the following is the implementation of the average function:
/**
 * An implementation of an accumulator capable of calculating average values
 */
public class AverageAccumulateFunction implements org.drools.runtime.rule.TypedAccumulateFunction {

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }

    public void writeExternal(ObjectOutput out) throws IOException {

    }

    public static class AverageData implements Externalizable {
        public int    count = 0;
        public double total = 0;
        
        public AverageData() {}

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            count   = in.readInt();
            total   = in.readDouble();
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(count);
            out.writeDouble(total);
        }

    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#createContext()
     */
    public Serializable createContext() {
        return new AverageData();
    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)
     */
    public void init(Serializable context) throws Exception {
        AverageData data = (AverageData) context;
        data.count = 0;
        data.total = 0;
    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object, java.lang.Object)
     */
    public void accumulate(Serializable context,
                           Object value) {
        AverageData data = (AverageData) context;
        data.count++;
        data.total += ((Number) value).doubleValue();
    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object, java.lang.Object)
     */
    public void reverse(Serializable context,
                        Object value) throws Exception {
        AverageData data = (AverageData) context;
        data.count--;
        data.total -= ((Number) value).doubleValue();
    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object)
     */
    public Object getResult(Serializable context) throws Exception {
        AverageData data = (AverageData) context;
        return new Double( data.count == 0 ? 0 : data.total / data.count );
    }

    /* (non-Javadoc)
     * @see org.drools.base.accumulators.AccumulateFunction#supportsReverse()
     */
    public boolean supportsReverse() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public Class< ? > getResultType() {
        return Number.class;
    }

}

17.32. Code for the Conditional Element Accumulate's Functions

Code for plugging in functions (to be entered into the config file)
jbossrules.accumulate.function.average =
   org.jbossrules.base.accumulators.AverageAccumulateFunction
Alternate Syntax: single function with return type
rule "Apply 10% discount to orders over US$ 100,00"
when
    $order : Order()
    $total : Number( doubleValue > 100 ) 
             from accumulate( OrderItem( order == $order, $value : value ),
                              sum( $value ) )
then
    # apply discount to $order
end
** item name **
** item description **

17.33. Accumulate with Inline Custom Code

Warning

The use of accumulate with inline custom code is not a good practice for several reasons, including difficulties on maintaining and testing rules that use them, as well as the inability of reusing that code. Implementing your own accumulate functions allows for simpler testing. This form of accumulate is supported for backward compatibility only.
The general syntax of the accumulate CE with inline custom code is:
<result pattern> from accumulate( <source pattern>,
                                  init( <init code> ),
                                  action( <action code> ),
                                  reverse( <reverse code> ),
                                  result( <result expression> ) )
The meaning of each of the elements is the following:
  • <source pattern>: the source pattern is a regular pattern that the engine will try to match against each of the source objects.
  • <init code>: this is a semantic block of code in the selected dialect that will be executed once for each tuple, before iterating over the source objects.
  • <action code>: this is a semantic block of code in the selected dialect that will be executed for each of the source objects.
  • <reverse code>: this is an optional semantic block of code in the selected dialect that if present will be executed for each source object that no longer matches the source pattern. The objective of this code block is to undo any calculation done in the <action code> block, so that the engine can do decremental calculation when a source object is modified or retracted, hugely improving performance of these operations.
  • <result expression>: this is a semantic expression in the selected dialect that is executed after all source objects are iterated.
  • <result pattern>: this is a regular pattern that the engine tries to match against the object returned from the <result expression>. If it matches, the accumulate conditional element evaluates to true and the engine proceeds with the evaluation of the next CE in the rule. If it does not matches, the accumulate CE evaluates to false and the engine stops evaluating CEs for that rule.

17.34. Accumulate with Inline Custom Code Examples

Inline custom code
rule "Apply 10% discount to orders over US$ 100,00"
when
    $order : Order()
    $total : Number( doubleValue > 100 ) 
             from accumulate( OrderItem( order == $order, $value : value ),
                              init( double total = 0; ),
                              action( total += $value; ),
                              reverse( total -= $value; ),
                              result( total ) )
then
    # apply discount to $order
end
In the above example, for each Order in the Working Memory, the engine will execute the init code initializing the total variable to zero. Then it will iterate over all OrderItem objects for that order, executing the action for each one (in the example, it will sum the value of all items into the total variable). After iterating over all OrderItem objects, it will return the value corresponding to the result expression (in the above example, the value of variable total). Finally, the engine will try to match the result with the Number pattern, and if the double value is greater than 100, the rule will fire.
Instantiating and populating a custom object
rule "Accumulate using custom objects"
when
    $person   : Person( $likes : likes )
    $cheesery : Cheesery( totalAmount > 100 )
                from accumulate( $cheese : Cheese( type == $likes ),
                                 init( Cheesery cheesery = new Cheesery(); ),
                                 action( cheesery.addCheese( $cheese ); ),
                                 reverse( cheesery.removeCheese( $cheese ); ),
                                 result( cheesery ) );
then
    // do something
end

17.35. Conditional Element Eval

The conditional element eval is essentially a catch-all which allows any semantic code (that returns a primitive boolean) to be executed. This code can refer to variables that were bound in the LHS of the rule, and functions in the rule package. Overuse of eval reduces the declarativeness of your rules and can result in a poorly performing engine. While eval can be used anywhere in the patterns, the best practice is to add it as the last conditional element in the LHS of a rule.
Evals cannot be indexed and thus are not as efficient as Field Constraints. However this makes them ideal for being used when functions return values that change over time, which is not allowed within Field Constraints.

17.36. Conditional Element Eval Examples

This is what eval looks like in use:
p1 : Parameter()
p2 : Parameter()
eval( p1.getList().containsKey( p2.getItem() ) )
p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval( isValid( p1, p2 ) )

17.37. The Right Hand Side

The Right Hand Side (RHS) is a common name for the consequence or action part of the rule. The main purpose of the RHS is to insert, retractor modify working memory data. It should contain a list of actions to be executed. The RHS part of a rule should also be kept small, thus keeping it declarative and readable.

Note

If you find you need imperative and/or conditional code in the RHS, break the rule down into multiple rules.

17.38. RHS Convenience Methods

Table 17.5. RHS Convenience Methods
Name Description
update(object, handle);
Tells the engine that an object has changed (one that has been bound to something on the LHS) and rules that need to be reconsidered.
update(object);
Using update(), the Knowledge Helper will look up the facthandle via an identity check for the passed object. (If you provide Property Change Listeners to your Java beans that you are inserting into the engine, you can avoid the need to call update() when the object changes.). After a fact's field values have changed you must call update before changing another fact, or you will cause problems with the indexing within the rule engine. The modify keyword avoids this problem.
insert(newobject());
Places a new object of your creation into the Working Memory.
insertLogical(newobject());
Similar to insert, but the object will be automatically retracted when there are no more facts to support the truth of the currently firing rule.
retract(handle);
Removes an object from Working Memory.

17.39. Convenience Methods using the Drools Variable

  • The call drools.halt() terminates rule execution immediately. This is required for returning control to the point whence the current session was put to work with fireUntilHalt().
  • Methods insert(Object o), update(Object o) and retract(Object o) can be called on drools as well, but due to their frequent use they can be called without the object reference.
  • drools.getWorkingMemory() returns the WorkingMemory object.
  • drools.setFocus( String s) sets the focus to the specified agenda group.
  • drools.getRule().getName(), called from a rule's RHS, returns the name of the rule.
  • drools.getTuple() returns the Tuple that matches the currently executing rule, and drools.getActivation() delivers the corresponding Activation. (These calls are useful for logging and debugging purposes.)

17.40. Convenience Methods using the Kcontext Variable

  • The call kcontext.getKnowledgeRuntime().halt() terminates rule execution immediately.
  • The accessor getAgenda() returns a reference to the session's Agenda, which in turn provides access to the various rule groups: activation groups, agenda groups, and rule flow groups. A fairly common paradigm is the activation of some agenda group, which could be done with the lengthy call:
    // give focus to the agenda group CleanUp
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
    (You can achieve the same using drools.setFocus( "CleanUp" ).)
  • To run a query, you call getQueryResults(String query), whereupon you may process the results.
  • A set of methods dealing with event management lets you add and remove event listeners for the Working Memory and the Agenda.
  • MethodgetKnowledgeBase() returns the KnowledgeBase object, the backbone of all the Knowledge in your system, and the originator of the current session.
  • You can manage globals with setGlobal(...), getGlobal(...) and getGlobals().
  • Method getEnvironment() returns the runtime's Environment.

17.41. The Modify Statement

Table 17.6. The Modify Statement
Name Description Syntax Example
modify
This provides a structured approach to fact updates. It combines the update operation with a number of setter calls to change the object's fields.
modify ( <fact-expression> ) {
    <expression> [ , <expression> ]*
}
The parenthesized <fact-expression> must yield a fact object reference. The expression list in the block should consist of setter calls for the given object, to be written without the usual object reference, which is automatically prepended by the compiler.
rule "modify stilton"
when
    $stilton : Cheese(type == "stilton")
then
    modify( $stilton ){
        setPrice( 20 ),
        setAge( "overripe" )
    }
end

17.42. Query Examples

Note

To return the results use ksession.getQueryResults("name"), where "name" is the query's name. This returns a list of query results, which allow you to retrieve the objects that matched the query.
Query for people over the age of 30
query "people over the age of 30" 
    person : Person( age > 30 )
end
Query for people over the age of X, and who live in Y
query "people over the age of x"  (int x, String y)
    person : Person( age > x, location == y )
end

17.43. QueryResults Example

We iterate over the returned QueryResults using a standard "for" loop. Each element is a QueryResultsRow which we can use to access each of the columns in the tuple. These columns can be accessed by bound declaration name or index position:
QueryResults results = ksession.getQueryResults( "people over the age of 30" );
System.out.println( "we have " + results.size() + " people over the age  of 30" );

System.out.println( "These people are are over 30:" );

for ( QueryResultsRow row : results ) {
    Person person = ( Person ) row.get( "person" );
    System.out.println( person.getName() + "\n" );
}

17.44. Queries Calling Other Queries

Queries can call other queries. This combined with optional query arguments provides derivation query style backward chaining. Positional and named syntax is supported for arguments. It is also possible to mix both positional and named, but positional must come first, separated by a semi colon. Literal expressions can be passed as query arguments, but you cannot mix expressions with variables.

Note

Using the '?' symbol in this process means the query is pull only and once the results are returned you will not receive further results as the underlying data changes.

17.45. Queries Calling Other Queries Example

Query calling another query
declare Location
    thing : String 
    location : String 
end

query isContainedIn( String x, String y ) 
    Location(x, y;)
    or 
    ( Location(z, y;) and ?isContainedIn(x, z;) )
end
Using live queries to reactively receive changes over time from query results
query isContainedIn( String x, String y ) 
    Location(x, y;)
    or 
    ( Location(z, y;) and isContainedIn(x, z;) )
end

rule look when 
    Person( $l : likes ) 
    isContainedIn( $l, 'office'; )
then
   insertLogical( $l 'is in the office' );
end

17.46. Unification for Derivation Queries

JBoss Rules supports unification for derivation queries. This means that arguments are optional. It is possible to call queries from Java leaving arguments unspecified using the static field org.drools.runtime.rule.Variable.v. (You must use 'v' and not an alternative instance of Variable.) These are referred to as 'out' arguments.

Note

The query itself does not declare at compile time whether an argument is in or an out. This can be defined purely at runtime on each use.
Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat Documentation

We help Red Hat users innovate and achieve their goals with our products and services with content they can trust.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

© 2024 Red Hat, Inc.