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 gettergetAge()
- 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
- Observe the example below:
public int getAge() { Date now = DateUtil.now(); // Do NOT do this return DateUtil.differenceInYears(now, birthday); }
- 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
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
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
|
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
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
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
| |
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
| |
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, theaccumulate
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 eachOrder
in the Working Memory, the engine will execute the init code initializing the total variable to zero. Then it will iterate over allOrderItem
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 allOrderItem
objects, it will return the value corresponding to the result expression (in the above example, the value of variabletotal
). Finally, the engine will try to match the result with theNumber
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
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(new object());
|
Places a new object of your creation into the Working Memory.
|
insertLogical(new object());
|
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 withfireUntilHalt()
. - Methods
insert(Object o)
,update(Object o)
andretract(Object o)
can be called ondrools
as well, but due to their frequent use they can be called without the object reference. drools.getWorkingMemory()
returns theWorkingMemory
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, anddrools.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'sAgenda
, 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 usingdrools.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.
- Method
getKnowledgeBase()
returns theKnowledgeBase
object, the backbone of all the Knowledge in your system, and the originator of the current session. - You can manage globals with
setGlobal(...)
,getGlobal(...)
andgetGlobals()
. - Method
getEnvironment()
returns the runtime'sEnvironment
.
17.41. 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.
|
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.