JBoss.orgCommunity Documentation
The overall structure of a rule file is:
The order in which the elements are declared is not important, except for the package name that, if declared, must be the first element in the rules file. All elements are optional, so you will use only those you need. We will discuss each of them in the following sections.
Drools 5 introduces the concept of hard and soft keywords.
Here is the list of hard keywords that must be avoided as identifiers when writing rules:
Holiday( `when` == "july" )
rule "validate holiday by eval" dialect "mvel" when h1 : Holiday( ) eval( h1.when == "july" ) then System.out.println(h1.name + ":" + h1.when); end
rule "validate holiday" dialect "mvel" when h1 : Holiday( `when` == "july" ) then System.out.println(h1.name + ":" + h1.when); end
The above example generates this message:
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
At first glance this seems to be valid syntax, but it is not (exits != exists). Let's take a look at next example:
Example 5.3.
1: package org.drools; 2: rule 3: when 4: Object() 5: then 6: System.out.println("A RHS"); 7: end
Now the above code generates this message:
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
This message means that the parser encountered the token WHEN, actually a hard keyword, but it's in the wrong place since the the rule name is missing.
The error "no viable alternative" also occurs when you make a simple lexical mistake. Here is a sample of a lexical problem:
The above code misses to close the quotes and because of this the parser generates this error message:
[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule simple_rule in pattern Student
Usually the Line and Column information are accurate, but in some cases (like unclosed quotes), the parser generates a 0:-1 position. In this case you should check whether you didn't forget to close quotes, apostrophes or parentheses.
The above example generates this message:
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern Bar
To fix this problem, it is necessary to complete the rule statement.
Usually when you get a 0:-1 position, it means that parser reached the end of source.
The following code generates more than one error message:
Example 5.6.
1: package org.drools; 2: 3: rule "Avoid NPE on wrong syntax" 4: when 5: not( Cheese( ( type == "stilton", price == 10 ) || ( type == "brie", price == 15 ) ) from $cheeseList ) 6: then 7: System.out.println("OK"); 8: end
These are the errors associated with this source:
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule "Avoid NPE on wrong syntax" in pattern Cheese
[ERR 101] Line 5:57 no viable alternative at input 'type' in rule "Avoid NPE on wrong syntax"
[ERR 102] Line 5:106 mismatched input ')' expecting 'then' in rule "Avoid NPE on wrong syntax"
Note that the second problem is related to the first. To fix it, just replace the commas (',') by AND operator ('&&').
In some situations you can get more than one error message. Try to fix one by one, starting at the first one. Some error messages are generated merely as consequences of other errors.
Notice that any rule atttribute (as described the section Rule Attributes) may also be written at package level, superseding the attribute's default value. The modified default may still be replaced by an attribute setting within a rule.
Import statements work like import statements in Java. You need to
specify the fully qualified paths and type names for any objects you want
to use in the rules. Drools automatically imports classes from the
Java package of the same name, and also from the package
java.lang
.
Type declarations have two main goals in the rules engine: to allow the declaration of new types, and to allow the declaration of metadata for types.
Declaring new types: Drools works out of the box with plain Java objects as facts. Sometimes, however, users may want to define the model directly to the rules engine, without worrying about creating models in a lower level language like Java. At other times, there is a domain model already built, but eventually the user wants or needs to complement this model with additional entities that are used mainly during the reasoning process.
Declaring metadata: facts may have meta information associated to them. Examples of meta information include any kind of data that is not represented by the fact attributes and is consistent among all instances of that fact type. This meta information may be queried at runtime by the engine and used in the reasoning process.
The previous example declares a new fact type called
Address
. This fact type will have three attributes:
number
, streetName
and city
.
Each attribute has a type that can be any valid
Java type, including any other class created by the user or even other
fact types previously declared.
For instance, we may want to declare another fact type
Person
:
Example 5.11. declaring a new fact type: Person
declare Person name : String dateOfBirth : java.util.Date address : Address end
As we can see on the previous example,
dateOfBirth
is of type java.util.Date
,
from the Java API, while address
is of the previously
defined fact type Address.
You may avoid having to write the fully qualified name of a class
every time you write it by using the import
clause, as previously
discussed.
Example 5.12. Avoiding the need to use fully qualified class names by using import
import java.util.Date declare Person name : String dateOfBirth : Date address : Address end
When you declare a new fact type, Drools will, at compile time, generate bytecode that implements a Java class representing the fact type. The generated Java class will be a one-to-one Java Bean mapping of the type definition. So, for the previous example, the generated Java class would be:
Example 5.13. generated Java class for the previous Person fact type declaration
public class Person implements Serializable {
private String name;
private java.util.Date dateOfBirth;
private Address address;
// getters and setters
// equals/hashCode
// toString
}
Since the generated class is a simple Java class, it can be used transparently in the rules, like any other fact.
Example 5.14. Using the declared types in rules
rule "Using a declared Type" when $p : Person( name == "Bob" ) then // Insert Mark, who is Bob's mate. Person mark = new Person(); mark.setName("Mark"); insert( mark ); end
@metadata_key( metadata_value )
The parenthesized metadata_value is optional.
Drools allows the declaration of any arbitrary metadata attribute, but some will have special meaning to the engine, while others are simply available for querying at runtime. Drools allows the declaration of metadata both for fact types and for fact attributes. Any metadata that is declared before the fields of a fact type are assigned to the fact type, while metadata declared after an attribute are assigned to that particular attribute.
Example 5.16. Declaring metadata attributes for fact types and attributes
import java.util.Date declare Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) name : String @key @maxLength( 30 ) dateOfBirth : Date address : Address end
In the previous example, there are two metadata items declared for the
fact type (@author
and @dateOfCreation
) and two
more defined for the name attribute (@key
and
@maxLength
). Please note that the @key
metadata
has no value, and so the parentheses and the value were omitted.
Instead of using the import, it is also possible to reference the class by its fully qualified name, but since the class will also be referenced in the rules, it is usually shorter to add the import and use the short class name everywhere.
Example 5.18. Declaring metadata using the fully qualified class name
declare org.drools.examples.Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) end
Declared types, as discussed previously, are generated at knowledge base compilation time, i.e., the application will only have access to them at application run time. Therefore, these classes are not available for direct reference from the application.
Drools then provides an interface through which users can handle
declared types from the application code:
org.drools.definition.type.FactType
. Through this interface, the user can
instantiate, read and write fields in the declared fact types.
Example 5.20. Handling declared fact types through the API
// get a reference to a knowledge base with a declared type: KnowledgeBase kbase = ... // get the declared FactType FactType personType = kbase.getFactType( "org.drools.examples", "Person" ); // handle the type as necessary: // create instances: Object bob = personType.newInstance(); // set attributes values personType.set( bob, "name", "Bob" ); personType.set( bob, "age", 42 ); // insert fact into a session StatefulKnowledgeSession ksession = ... ksession.insert( bob ); ksession.fireAllRules(); // read attributes String name = personType.get( bob, "name" ); int age = personType.get( bob, "age" );
The API also includes other helpful methods, like setting all the attributes at once, reading values from a Map, or reading all attributes at once, into a Map.
Although the API is similar to Java reflection (yet much simpler to use), it does not use reflection underneath, relying on much more performant accessors implemented in generated bytecode.
A rule specifies that when a particular set of conditions occur, specified in the Left Hand Side (LHS), then do what is specified as a list of actions in the Right Hand Side (RHS). A common question from users is "Why use when instead of if?" "When" was chosen over "if" because "if" is normally part of a procedural execution flow, where, at a specific point in time, a condition is to be checked. In contrast, "when" indicates that the condition evaluation is not tied to a specific evaluation sequence or point in time, but that it happens continually, at any time during the life time of the engine; whenever the condition is met, the actions are executed.
A rule must have a name, unique within its rule package. If you define a rule twice in the same DRL it produces an error while loading. If you add a DRL that includes a rule name already in the package, it replaces the previous rule. If a rule name is to have spaces, then it will need to be enclosd in double quotes (it is best to always use double quotes).
Attributes - described below - are optional. They are best written one per line.
The LHS of the rule follows the when
keyword
(ideally on a new line), similarly the RHS follows the
then
keyword (again, ideally on a newline). The rule is
terminated by the keyword end
. Rules cannot be
nested.
Example 5.21. Rule Syntax Overview
rule "<name>" <attribute>* when <conditional element>* then <action>* end
Example 5.22. A simple rule
rule "Approve if not rejected" salience -100 agenda-group "approval" when not Rejection() p : Policy(approved == false, policyState:status) exists Driver(age > 25) Process(status == policyState) then log("APPROVED: due to no objections."); p.setApproved(true); end
no-loop
default value: false
type: Boolean
When the rule's consequence modifies a fact it may cause the Rule to activate again, causing recursion. Setting no-loop to true means the attempt to create the Activation for the current set of data will be ignored.
ruleflow-group
default value: N/A
type: String
Ruleflow is a Drools feature that lets you exercise control over the firing of rules. Rules that are assembled by the same ruleflow-group identifier fire only when their group is active.
lock-on-active
default value: false
type: Boolean
Whenever a ruleflow-group becomes active or an agenda-group receives the focus, any rule within that group that has lock-on-active set to true will not be activated any more; irrespective of the origin of the update, the activation of a matching rule is discarded. This is a stronger version of no-loop, because the change could now be caused not only by the rule itself. It's ideal for calculation rules where you have a number of rules that modify a fact and you don't want any rule re-matching and firing again. Only when the ruleflow-group is no longer active or the agenda-group loses the focus those rules with lock-on-active set to true become eligible again for their activations to be placed onto the agenda.
salience
default value: 0
type: integer
Each rule has a salience attribute that can be assigned an integer number, which defaults to zero and can be negative or positive. Salience is a form of priority where rules with higher salience values are given higher priority when ordered in the Activation queue.
Drools also supports dynamic salience where you can use an expression involving bound variables.
Example 5.23. Dynamic Salience
rule "Fire in rank order 1,2,.." salience( -$rank ) when Element( $rank : rank,... ) then ... end
agenda-group
auto-focus
activation-group
dialect
default value: as specified by the package
date-effective
type: String, containing a date and time definition
A rule can only activate if the current date and time is after date-effective attribute.
date-expires
type: String, containing a date and time definition
A rule cannot activate if the current date and time is after the date-expires attribute.
duration
default value: no default value
The duration dictates that the rule will fire after a specified duration, if it is still true.
Interval "int:" timers follow the JDK semantics for initial delay optionally followed by a repeat interval. Cron "cron:" timers follow standard cron expressions:
Example 5.26. A Cron Example
rule "Send SMS every 15 minutes"
timer (cron:* 0/15 * * * ?)
when
$a : Alarm( on == true )
then
channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on" );
end
Calendars may now be used to control when rules can fire. The Calendar API is modelled on Quartz http://www.quartz-scheduler.org/ :
Calendars are registered with the StatefulKnowledgeSession:
They can be used in conjunction with normal rules and rules including timers. The rule calendar attribute can have one or more comma calendar names.
Example 5.29. Using Calendars and Timers together
rule "weekdays are high priority"
calendars "weekday"
timer (int:0 1h)
when
Alarm()
then
send( "priority high - we have an alarm );
end
rule "weekend are low priority"
calendars "weekend"
timer (int:0 4h)
when
Alarm()
then
send( "priority low - we have an alarm );
end
Example 5.30. Rule without a Conditional Element
rule "no CEs" when then <action>* end # The above rule is internally rewritten as: rule "eval(true)" when eval( true ) then <action>* end
Conditional elements work on one or more
patterns (which are described below). The most common
one is and
, which is implicit when you have multiple
patterns in the LHS of a rule that are not connected in any way. Note that
an and
cannot have a leading declaration binding like
or
. This is obvious, since a declaration can only
reference a single fact, and when the and
is satisfied
it matches more than one fact - so which fact would the declaration bind
to?
At the top of the ER diagram you can see that the pattern consists of zero or more constraints and has an optional pattern binding. The railroad diagram below shows the syntax for this.
In its simplest form, with no constraints, a pattern matches
against a fact of the given type. In the following case the type is
Cheese
, which means that the pattern will match against all
Cheese
objects in the Working Memory.
Notice that the type need not be the actual class of some fact object. Patterns may refer to superclasses or even interfaces, thereby potentially matching facts from many different classes.
For referring to the matched object, use a pattern binding
variable such as $c
. The prefixed dollar symbol ('$') is
optional; it can be useful in complex rules where it helps to more
easily differentiate between variables and fields.
Inside of the pattern parenthesis is where all the action happens. A constraint can be either a Field Constraint, Inline Eval, or a Constraint Group. Constraints can be separated by the following symbols: ',', '&&' or '||'.
The comma character (',') is used to separate constraint groups. It has implicit and connective semantics.
Example 5.33. Constraint Group connective ','
# Cheese type is stilton and price < 10 and age is mature. Cheese( type == "stilton", price < 10, age == "mature" )
The above example has three constraint groups, each with a
single constraint:
The above example has two constraint groups. The first has two constraints and the second has one constraint.
The connectives are evaluated in the following order, from first to last:
&&
||
,
It is possible to change the evaluation priority by using parentheses, as in any logic or mathematical expression. Example:
Example 5.35. Using parentheses to change evaluation priority
# Cheese type is stilton and ( price is less than 20 or age is mature ). Cheese( type == "stilton" && ( price < 20 || age == "mature" ) )
In the above example, the use of parentheses evaluates the connective '||' before the connective '&&'.
Also, it is important to note that besides having the same semantics, the connectives '&&' and ',' are resolved with different priorities, and ',' cannot be embedded in a composite constraint expression.
Example 5.36. Not Equivalent connectives
// invalid as ',' cannot be embedded in an expression: Cheese( ( type == "stilton", price < 10 ) || age == "mature" ) // valid as '&&' can be embedded in an expression: Cheese( ( type == "stilton" && price < 10 ) || age == "mature")
There are three types of restrictions: Single Value Restriction, Compound Value Restriction, and Multi Restriction.
You can do checks against fields that are or may be null,
using '==' and '!=' as you would expect, and the literal
null
keyword, as in Cheese(type !=
null)
, where the evaluator will not throw an exception and
return true if the value is null. 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. For instance, if "ten" is
provided as a string in a numeric evaluator, an exception is thrown,
whereas "10" would coerce to a numeric 10. Coercion is always in
favor of the field type and not the value type.
A Single Value Restriction is a binary relation, applying a binary operator to the field value and another value, which may be a literal, a variable, a parenthesized expression ("return value"), or a qualified identifier, i.e., an enum constant.
The operators '==' and '!=' are valid for all types. Other
relational operatory may be used whenever the type values are
ordered; for date fields, '<' means "before". The pair
matches
and not matches
is
only applicable to string fields, contains
and
not contains
require the field to be of some
Collection type. Coercion to the correct value for the evaluator
and the field will be attempted, as mentioned in the "Values"
section.
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 is important to note that, different from Java, within regular expressions written as string literals you don't need to escape '\'. Example:
The operator contains
is used to check
whether a field that is a Collection or array contains the specified
value.
The operator not contains
is used to
check whether a field that is a Collection or array does not
contain the specified value.
Note
For backward compatibility, the
excludes
operator is supported as a synonym fornot contains
.
Example 5.42. Literal Constraint with Collections
CheeseCounter( cheese not memberOf $matureCheeses )
Literal Restrictions using the operator '==' provide for faster execution as we can index using hashing to improve performance.
Variables can be bound to facts and their fields and then used in subsequent Field Constraints. A bound variable is called a Declaration. Valid operators are determined by the type of the field being constrained; coercion will be attempted where possible. Bound Variable Restrictions using the operator '==' provide for very fast execution as we can use hashing to improve performance.
Here, likes
is the variable that is bound in
its declaration to the field favouriteCheese
of any
matching Person instance. It is then used to constrain the type of
Cheese in the following pattern. Any valid Java variable name can
be used, and it may be prefixed with a '$', which you will often
see used to help differentiate declarations from fields. The
example below shows a declaration for $stilton
, bound
to the object matching the first pattern and used with a
contains
operator. - Note the optional use of
'$'.
Example 5.51. Bound Fact using 'contains' operator
$stilton : Cheese( type == "stilton" ) Cheesery( cheeses contains $stilton )
Example 5.53. Compound Restriction using "in"
Person( $cheese : favouriteCheese ) Cheese( type in ( "stilton", "cheddar", $cheese )
Example 5.54. Multi Restriction
// Simple multi restriction using a single && Person( age > 30 && < 40 ) // Complex multi restriction using groupings of multi restrictions Person( age ( (> 30 && < 40) || (> 20 && < 25) ) ) // Mixing muti restrictions with constraint connectives Person( age > 30 && < 40 || location == "london" )
An inline eval constraint can use any valid dialect expression as long as it results to a primitive boolean. The expression must be constant over time. Any previously bound variable, from the current or previous pattern, can be used; autovivification is also used to auto-create field binding variables. When an identifier is found that is not a current variable, the builder looks to see if the identifier is a field on the current object type, if it is, the field binding is auto-created as a variable of the same name. This is called autovivification of field variables inside of inline evals.
Example 5.58. implicit root prefixAnd
when Cheese( cheeseType : type ) Person( favouriteCheese == cheeseType )
Infix and
is supported along with explicit
grouping with parentheses, should it be needed. The symbol '&&',
as an alternative to and
, is deprecated although it
is still supported in the syntax for legacy support reasons.
Example 5.59. infixAnd
//infixAnd Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType ) //infixAnd with grouping ( Cheese( cheeseType : type ) and ( Person( favouriteCheese == cheeseType ) or Person( favouriteCheese == cheeseType ) )
Infix or
is supported along with explicit
grouping with parentheses, should it be needed. The symbol '||', as an
alternative to or
, is deprecated although it is still
supported in the syntax for legacy support reasons.
Example 5.61. infixOr
//infixOr Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType ) //infixOr with grouping ( Cheese( cheeseType : type ) or ( Person( favouriteCheese == cheeseType ) and Person( favouriteCheese == cheeseType ) )
The Conditional Element or
also allows for
optional pattern binding. This means that each resulting subrule will
bind its pattern to the pattern binding. Each pattern must be bound
separately, using eponymous variables:
Example 5.62. or with binding
(or pensioner : Person( sex == "f", age > 60 ) pensioner : Person( sex == "m", age > 65 ) )
Since the conditional element or
results in
multiple subrule generation, one for each possible logically outcome,
the example above would result in the internal generation of two rules.
These two rules work independently within the Working Memory, which
means both can match, activate and fire - there is no
shortcutting.
The best way to think of the conditional element
or
is as a shortcut for generating two or more
similar rules. When you think of it that way, it's clear that for a
single rule there could be multiple activations if two or more terms of
the disjunction are true.
The CE 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.
For folks who are familiar with Drools 2.x lineage, the old Drools parameter and condition tags are equivalent to binding a variable to an appropriate type, and then using it in an eval node.
Example 5.63. eval
p1 : Parameter() p2 : Parameter() eval( p1.getList().containsKey(p2.getItem()) ) // call function isValid in the LHS eval( isValid(p1, p2) )
The CE not
is first order logic's
non-existential quantifier and checks for the non-existence of something
in the Working Memory. Think of "not" as meaning "there must be none
of...".
The keyword not
be followed by parentheses
around the CEs that it applies to. In the simplest case of a single
pattern (like below) you may optionally omit the parentheses.
Example 5.65. No red Busses
// 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") )
The CE exists
is first order logic's
existential quantifier and checks for the existence of something in the
Working Memory. Think of "exists" as meaning "there is at least one..".
It is different from just having the pattern on its own, which is more
like saying "for each one of...". If you use exists
with a pattern, the rule will only activate at most once, regardless of
how much data there is in working memory that matches the condition
inside of the exists
pattern. Since only the
existence matters, no bindings will be established.
The keyword exists
must be followed by
parentheses around the CEs that it applies to. In the simplest case of a
single pattern (like below) you may omit the parentheses.
Example 5.67. At least one red Bus
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") )
The Conditional Element forall
completes the
First Order Logic support in Drools. The Conditional Element
forall
evaluates to true when all facts that match
the first pattern match all the remaining patterns. Example:
rule "All English buses are red" when forall( $bus : Bus( type == 'english') Bus( this == $bus, color = 'red' ) ) then # all english buses are red end
In the above rule, we "select" all Bus objects whose type is "english". Then, for each fact that matches this pattern we evaluate the following patterns and if they match, the forall CE will evaluate to true.
To state that all facts of a given type in the working memory must
match a set of constraints, forall
can be written
with a single pattern for simplicity. Example:
Example 5.68. Single Pattern Forall
rule "All Buses are Red" when forall( Bus( color == 'red' ) ) then # all asserted Bus facts are red end
Another example shows multiple patterns inside the
forall
:
Example 5.69. 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
Forall can be nested inside other CEs.
For instance, forall
can be used inside a
not
CE. Note that only single patterns have optional
parentheses, so that with a nested forall
parentheses must be used:
Example 5.70. Combining Forall with Not CE
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
As a side note, forall( p1 p2 p3...)
is
equivalent to writing:
not(p1 and not(and p2 p3...))
Also, it is important to note that forall
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.
The Conditional Element accumulate
is a more
flexible and powerful form of collect
, the sense that
it can be used to do what collect
does and also
achieve things that the CE collect
is not capable of
doing. Basically, what it does is that it allows a rule to iterate over
a collection of objects, executing custom actions for each of the
elements, and at the end it returns a result object.
The general syntax of the accumulate
CE
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.
It is easier to understand if we look at an example:
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.
The example used Java as the semantic dialect, and as such, note that the usage of the semicolon as statement delimiter is mandatory in the init, action and reverse code blocks. The result is an expression and, as such, it does not admit ';'. If the user uses any other dialect, he must comply to that dialect's specific syntax.
As mentioned before, the reverse code is optional, but it is strongly recommended that the user writes it in order to benefit from the improved performance on update and retract.
The accumulate
CE can be used to execute any
action on source objects. The following example instantiates and
populates 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
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
Drools ships with the following built-in accumulate functions:
rule "Average profit" when $order : Order() $profit : Number() from accumulate( OrderItem( order == $order, $cost : cost, $price : price ) average( 1 - $cost / $price ) ) then # average profit for $order is $profit end
/*
* Copyright 2007 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Created on Jun 21, 2007
*/
package org.drools.base.accumulators;
/**
* An implementation of an accumulator capable of calculating average values
*/
public class AverageAccumulateFunction implements AccumulateFunction {
protected static class AverageData {
public int count = 0;
public double total = 0;
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#createContext()
*/
public Object createContext() {
return new AverageData();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)
*/
public void init(Object 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(Object 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(Object 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(Object 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;
}
}
drools.accumulate.function.average = org.drools.base.accumulators.AverageAccumulateFunction
insert(new
Something());
will place a new
object of your creation into the Working Memory.
retract(
handle);
removes an object from Working Memory.
The call kcontext.getKnowledgeRuntime().halt()
terminates rule execution immediately.
// 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, as explained
in section “Query”.
A set of methods dealing with event management lets you, among other things, 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
which works much like what you know as your
operating system's environment.
A query is a simple way to search the working memory for facts that match the stated conditions. Therefore, it contains only the structure of the LHS of a rule, so that you specify neither "when" nor "then". A query has an optional set of parameters, each of which can be optionally typed. If the type is not given, the type Object is assumed. The engine will attempt to coerce the values as needed. Query names are global to the KnowledgeBase; so do not add queries of the same name to different packages for the same RuleBase.
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.
The first example presents a simple query for all the people over the age of 30. The second one, using parameters, combines the age limit with a location.
Example 5.72. Query People over the age of 30
query "people over the age of 30" person : Person( age > 30 ) end
Example 5.73. Query 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
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.
Example 5.74. Query People over the age of 30
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" ); }
In the preceding example, [when]
indicates the scope of
the expression, i.e., whether it is valid for the LHS or the RHS of a rule. The
part after the bracketed keyword is the expression that you use in the rule;
typically a natural language expression, but it doesn't have to be. The
part to the right of the equal sign ("=") is the mapping of the expression into
the rule language. The form of this string depends on its destination, RHS or
LHS. If it is for the LHS, then it ought to be a term according to the regular
LHS syntax; if it is for the RHS then it might be a Java statement.
Whenever the DSL parser matches a line from the rule file written in the
DSL with an expression in the DSL definition, it performs three steps of
string manipulation. First, it extracts the string values appearing where the
expression contains variable names in braces (here: {colour}
). Then,
the values obtained from these captures are then interpolated wherever that name,
again enclosed in braces, occurs on the right hand side of the mapping. Finally, the
interpolated string replaces whatever was matched by the entire expression
in the line of the DSL rule file.
Note that the expressions (i.e., the strings on the left hand side of the equal sign) are used as regular expressions in a pattern matching operation against a line of the DSL rule file, matching all or part of a line. This means you can use (for instance) a '?' to indicate that the preceding character is optional. One good reason to use this is to overcome variations in natural language phrases of your DSL. But, given that these expressions are regular expression patterns, this also means that all "magic" characters of Java's pattern syntax have to be escaped with a preceding backslash ('\').
It is important to note that the compiler transforms DSL rule files line by line. In the above example, all the text after "Something is " to the end of the line is captured as the replacement value for "{colour}", and this is used for interpolating the target string. This may not be exactly what you want. For instance, when you intend to merge different DSL expressions to generate a composite DRL pattern, you need to transform a DSLR line in several independent operations. The best way to achieve this is to ensure that the captures are surrounded by characteristic text - words or even single characters. As a result, the matching operation done by the parser plucks out a substring from somewhere within the line. In the example below, quotes are used as distinctive characters. Note that the characters that surround the capture are not included during interpolation, just the contents between them.
As a rule of thumb, use quotes for textual data that a rule editor
may want to enter. You can also enclose the capture with words to ensure
that the text is correctly matched. Both is illustrated by the following
example. Note that a single line such as Something is "green" and
another solid thing
is now correctly expanded.
Example 5.76. Example with quotes
[when]something is "{colour}"=Something(colour=="{colour}") [when]another {state} thing=OtherThing(state=="{state}"
It is a good idea to avoid punctuation (other than quotes or apostrophes) in your DSL expressions as much as possible. The main reason is that punctuation is easy to forget for rule authors using your DSL. Another reason is that parentheses, the period and the question mark are magic characters, requiring escaping in the DSL definition.
In a DSL mapping, the braces "{" and "}" should only be used to enclose a variable definition or reference, resulting in a capture. If they should occur literally, either in the expression or within the replacement text on the right hand side, they must be escaped with a preceding backslash ("\"):
[then]do something= if (foo) \{ doSomething(); \}
If braces "{" and "}" should appear in the replacement string of a DSL definition, escape them with a backslash ('\').
Example 5.77. Examples of DSL mapping entries
# This is a comment to be ignored. [when]There is a person with name of "{name}"=Person(name=="{name}") [when]Person is at least {age} years old and lives in "{location}"= Person(age >= {age}, location=="{location}") [then]Log "{message}"=System.out.println("{message}"); [when]And = and
Given the above DSL examples, the following examples show the expansion of various DSLR snippets:
Example 5.78. Examples of DSL expansions
There is a person with name of "Kitty" ==> Person(name="Kitty") Person is at least 42 years old and lives in "Atlanta" ==> Person(age >= 42, location="Atlanta") Log "boo" ==> System.out.println("boo"); There is a person with name of "Bob" and Person is at least 30 years old and lives in "Utah" ==> Person(name="Bob") and Person(age >= 30, location="Utah")
Don't forget that if you are capturing plain text from a DSL rule line and want to use it as a string literal in the expansion, you must provide the quotes on the right hand side of the mapping.
You can chain DSL expressions together on one line, as long as it is clear to the parser where one ends and the next one begins and where the text representing a parameter ends. (Otherwise you risk getting all the text until the end of the line as a parameter value.) The DSL expressions are tried, one after the other, according to their order in the DSL definition file. After any match, all remaining DSL expressions are investigated, too.
The resulting DRL text may consist of more than one line. Line ends
are in the replacement text are written as \n
.
Cheese(age < 5, price == 20, type=="stilton", country=="ch")
[when]There is a Cheese with=Cheese() [when]- age is less than {age}=age<{age} [when]- type is '{type}'=type=='{type}' [when]- country equal to '{country}'=country=='{country}'
You can then write rules with conditions like the following:
There is a Cheese with - age is less than 42 - type is 'stilton'
Cheese(age<42, type=='stilton')
[when][]is less than or equal to=<= [when][]is less than=< [when][]is greater than or equal to=>= [when][]is greater than=> [when][]is equal to=== [when][]equals=== [when][]There is a Cheese with=Cheese()[when][]- {field:\w*} {operator} {value:\d*}={field} {operator} {value}
Given these DSL definitions, you can write rules with conditions such as:
There is a Cheese with - age is less than 42 - rating is greater than 50 - type equals 'stilton'
In this specific case, a phrase such as "is less than" is replaced by
<
, and then the line matches the last DSL entry. This
removes the hyphen, but the final result is still added as a constraint
to the preceding pattern. After processing all of the lines, the resulting
DRL text is:
Cheese(age<42, rating > 50, type=='stilton')
The order of the entries in the DSL is important if separate DSL expressions are intended to match the same line, one after the other.
A DSL entry consists of the following four parts:
Below are some sample DSL definitions, with comments describing the language features they illustrate.
# Comment: DSL examples #/ debug: display result and usage # keyword definition: replaces "regula" by "rule" [keyword][]regula=rule # conditional element: "T" or "t", "a" or "an", convert matched word [when][][Tt]here is an? {entity:\w+}= ${entity!lc}: ${entity!ucfirst} () # consequence statement: convert matched word, literal braces [then][]update {entity:\w+}=modify( ${entity!lc} )\{ \}
The transformation of a DSLR file proceeds as follows:
The text is read into memory.
Each of the "keyword" entries is applied to the entire text. First, the regular expression from the keyword definition is modified by replacing white space sequences with a pattern matching any number of white space characters, and by replacing variable definitions with a capture made from the regular expression provided with the definition, or with the default (".*?"). Then, the DSLR text is searched exhaustively for occurrences of strings matching the modified regular expression. Substrings of a matching string corresponding to variable captures are extracted and replace variable references in the corresponding replacement text, and this text replaces the matching string in the DSLR text.
Sections of the DSLR text between "when" and "then", and "then" and "end", respectively, are located and processed in a uniform manner, line by line, as described below.
For a line, each DSL entry pertaining to the line's section is taken in turn, in the order it appears in the DSL file. Its regular expression part is modified: white space is replaced by a pattern matching any number of white space characters; variable definitions with a regular expression are replaced by a capture with this regular expression, its default being ".*?". If the resulting regular expression matches all or part of the line, the matched part is replaced by the suitably modified replacement text.
Modification of the replacement text is done by replacing variable references with the text corresponding to the regular expression capture. This text may be modified according to the string transformation function given in the variable reference; see below for details.
If there is a variable reference naming a variable that is not defined in the same entry, the expander substitutes a value bound to a variable of that name, provided it was defined in one of the preceding lines of the current rule.
If a DSLR line in a condition is written with a leading hyphen, the expanded result is inserted into the last line, which should contain a pattern CE, i.e., a type name followed by a pair of parentheses. if this pair is empty, the expanded line (which should contain a valid constraint) is simply inserted, otherwise a comma (",") is inserted beforehand.
If a DSLR line in a consequence is written with a leading hyphen, the expanded result is inserted into the last line, which should contain a "modify" statement, ending in a pair of braces ("{" and "}"). If this pair is empty, the expanded line (which should contain a valid method call) is simply inserted, otherwise a comma (",") is inserted beforehand.
It is currently not possible to use a line with a leading hyphen to insert text into other conditional element forms (e.g., "accumulate") or it may only work for the first insertion (e.g., "eval").
All string transformation functions are described in the following table.
Table 5.2. String transformation functions
Name | Description |
---|---|
uc | Converts all letters to upper case. |
lc | Converts all letters to lower case. |
ucfirst | Converts the first letter to upper case, and all other letters to lower case. |
num | Extracts all digits and "-" from the string. If the last two digits in the original string are preceded by "." or ",", a decimal period is inserted in the corresponding position. |
a?b/c | Compares the string with string a, and if they are equal, replaces it with b, otherwise with c. But c can be another triplet a, b, c, so that the entire structure is, in fact, a translation table. |
The following DSL examples show how to use string transformation functions.
# definitions for conditions [when][]There is an? {entity}=${entity!lc}: {entity!ucfirst}() [when][]- with an? {attr} greater than {amount}={attr} <= {amount!num} [when][]- with a {what} {attr}={attr} {what!positive?>0/negative?%lt;0/zero?==0/ERROR}
A file containing a DSL definition is customarily given the extension
.dsl
. It is passed to the Knowledge Builder with
ResourceType.DSL
. For a file using DSL definition, the extension
.dslr
should be used. The Knowledge Builder expects
ResourceType.DSLR
. The IDE, however, relies on file extensions
to correctly recognize and work with your rules file.
The DSL must be passed to the Knowledge Builder ahead of any rules file using the DSL.
KnowledgeBuilder kBuilder = new KnowledgeBuilder(); Resource dsl = ResourceFactory.newClassPathResource( dslPath, getClass() ); kBuilder.add( dsl, ResourceType.DSL ); Resource dslr = ResourceFactory.newClassPathResource( dslrPath, getClass() ); kBuilder.add( dslr, ResourceType.DSLR );
For parsing and expanding a DSLR file the DSL configuration is read and supplied to the parser. Thus, the parser can "recognize" the DSL expressions and transform them into native rule language expressions.
As an option, Drools also supports a "native" rule language as an alternative to DRL. This allows you to capture and manage your rules as XML data. Just like the non-XML DRL format, the XML format is parsed into the internal "AST" representation - as fast as possible (using a SAX parser). There is no external transformation step required. All the features are available with XML that are available to DRL.
In the preceding XML text you will see the typical XML element, the package declaration, imports, globals, functions, and the rule itself. Most of the elements are self explanatory if you have some understanding of the Drools features.
The import
elements import the types you wish to
use in the rule.
The global
elements define global objects that can
be referred to in the rules.
The function
contains a function declaration, for
a function to be used in the rules. You have to specify a return type,
a unique name and parameters, in the body goes a snippet of code.
The rule is discussed below.
Example 5.80. Detail of rule element
<rule name="simple_rule">
<rule-attribute name="salience" value="10" />
<rule-attribute name="no-loop" value="true" />
<rule-attribute name="agenda-group" value="agenda-group" />
<rule-attribute name="activation-group" value="activation-group" />
<lhs>
<pattern identifier="cheese" object-type="Cheese">
<from>
<accumulate>
<pattern object-type="Person"></pattern>
<init>
int total = 0;
</init>
<action>
total += $cheese.getPrice();
</action>
<result>
new Integer( total ) );
</result>
</accumulate>
</from>
</pattern>
<pattern identifier="max" object-type="Number">
<from>
<accumulate>
<pattern identifier="cheese" object-type="Cheese"></pattern>
<external-function evaluator="max" expression="$price"/>
</accumulate>
</from>
</pattern>
</lhs>
<rhs>
list1.add( $cheese );
</rhs>
</rule>
In the above detail of the rule we see that the rule has LHS and RHS (conditions and consequence) sections. The RHS is simple, it is just a block of semantic code that will be executed when the rule is activated. The LHS is slightly more complicated as it contains nested elements for conditional elements, constraints and restrictions.
A key element of the LHS is the Pattern element. This allows you to specify a type (class) and perhaps bind a variable to an instance of that class. Nested under the pattern object are constraints and restrictions that have to be met. The Predicate and Return Value constraints allow Java expressions to be embedded.
That leaves the conditional elements, not, exists, and, or etc. They work like their DRL counterparts. Elements that are nested under and an "and" element are logically "anded" together. Likewise with "or" (and you can nest things further). "Exists" and "Not" work around patterns, to check for the existence or nonexistence of a fact meeting the pattern's constraints.
The Eval element allows the execution of a valid snippet of Java code - as long as it evaluates to a boolean (do not end it with a semi-colon, as it is just a fragment) - this can include calling a function. The Eval is less efficient than the columns, as the rule engine has to evaluate it each time, but it is a "catch all" feature for when you can express what you need to do with Column constraints.