The Rule construct is where is clearly the most important construct. Rules are of the form "IF" something "THEN" action (of course we chose the keywords "when" and "then") - in the style of production rules.
A rule must have a name, and be a unique name for a rule package. If a rule name is to have spaces, then it will need to be in double quotes (its best to always use double quotes).
Attributes are optional, and are described below (they are best kept as one per line).
The LHS of the rule follows the "when" keyword (ideally on a new line), similarly the RHS follows the "then" keyword (ideally on a newline). The rule is terminated by the keyword "end". Rules cannot be nested of course.
The Left Hand Side (LHS) is a common name for the conditional part of the rule.
To interpret the following diagram, refer to the sections below for the details.
The Right Hand Side (RHS) is a common name for the consequence or action part of the rule. The purpose of the right hand side is to retract or add facts to working memory, and also invoke arbitary actions specific to your application. In practical terms, the RHS is a block of code that is executed when the rule fires.
There are a few convenience methods you can use to modify working memory:
"modify(obj);" will tell the engine that an object has changed (one that has been bound to something on the LHS) and rules may need to be reconsidered.
"assert(new Something());" will place a new object of your creation in working memory.
"assertLogical(new Something());" is similar to assert, but the object will be automatically retracted when there are no more facts to support the truth of the currently firing rule.
"retract(obj);" removes an object from working memory.
These convenience methods are basically macros that provide short cuts to the KnowldgeHelper instance (refer to the KnowledgeHelper interface for more advanced operations). The KnowledgeHelper interface is made available to the RHS code block as a variable called "drools". If you provide "Property Change Listeners" to your java beans that you are asserting into the engine, you can avoid the need to call "modify" when the object changes.
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 will be ignored.
default value : 0
type : integer
Each rule has a salience attribute that can be assigned an Integer number, defaults to zero, the Integer 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.
default value : MAIN
type : String
Agenda group's allow the user to partition the Agenda providing more execution control. Only rules in the focus group are allowed to fire.
default value false
type : Boolean
When a rule is activated if the
auto-focus value is true
and the Rule's
agenda-group does not have
focus then it is given focus, allowing the rule to potentially
default value N/A
type : String
Rules that belong to the same named activation-group will only fire exclusively. In other words, the first rule in an activation-group to fire will cancel the other rules activations (stop them from firing). The Activtion group attribute is any string, as long as the string is identical for all the rules you need to be in the one group.
NOTE: this used to be called Xor group, but technically its not quite an Xor, but you may hear people mention Xor group, just swap that term in your mind with activation-group.
A Rule consists of Field Constraints on one or more Object Types. Internally each matched Object Type instance is stored in an array. If we match against three objects Person, Person and Pet we have a three element array; in Drools we refer to this list of Facts as a Tuple - each element in the array is a Column. Each Object Type instance is filtered through zero or more Field Constraints - the term Column is used to refer to this list of constraints on the Object type. The first example has no constraints and will match any Cheese instance in the Working Memory, regardless of its field values. The second case refers to two Literal Field Constraints on against instances of a Cheese object - they are seperated by a comma, which implicitly means "and".
This is similar to the previous case, but in this case we are binding a variable to that instance of Cheese that the rule engine will match. This means you can use cheapStilton in another condition, or perhaps in the consequence part of the rule. You can also eat it, but I wouldn't.
Field constraints place constraints on the Fact objects for the rule engine to match/select out of working memory. They work comparing/evaluating "field" values from the fact object instances.
A "field" is not a field in the sense of a public or private member of a class. A field is an accessible method. If your model objects follow the java bean pattern, then fields are exposed using "getXXX" or "isXXX" methods (these are methods that take no arguments, and return something). You can access fields either by using the bean-name convention (so "getType" can be accessed as "type").
For example, refering to our Cheese class, the following : Cheese(type == ...) uses the getType() method on the a cheese instance. You can also access non getter methods, like "toString()" on the Object for instance (in which case, you do Cheese(toString == ..) - you use the full name of the method with correct capitalisation, but not brackets). Do please make sure that you are accessing methods that take no parameters, and are in-fact "accessors" (as in, they don't change the state of the object in a way that may effect the rules - remember that the rule engine effectively caches the results of its matching inbetween invocations to make it faster).
Note that if primitive types are used for a field, Drools will autobox them to their corresponding object types (even if you are using java 1.4) - however on java 1.4 there is currently no auto-unboxing when inside code expressions or blocks. On the whole, it is probably best to use the non primitive types in the model objects you are using in your rules. If you use Java 5, then you get the best of both worlds (you can let the compiler autobox for you - much neater) if you use Java 5, Drools will honor that if you use the JDT semantic compiler (JANINO does not yet support Java 5).
A note on JavaBeans: The JavaBean convention is followed, but as shown above, you can access non getter methods by using the method name. The syntax is case sensitive. In the case of getter methods like "getURI" which uses capitals, the property name is "URI" as there is more then one capital letter after the "get" - this is exactly as per the JavaBean standard (in fact, the Instrospector utility is used).
There are a number of opreators that can be used with the various Field Constraints. Valid operators are dependent on the field type. Generally they are self explanatory based on the type of data: for instance, for date fields, "<" means "before" and so on. "Matches" is only applicable to string fields, "contains" and "excludes" is only applicable to Collection type fields.
A note on nulls: you can do checks against fields that are or maybe null, using == and != as you would expect, and the literal "null" keyword, like: Cheese(type != null). Literal Constraints, specifically the '==' operator, provide for very fast execution as we can use hashing to improve performance.
All standard java numeric primitives are supported
Currently only "dd-mmm-yyyy" date format is supported by default. You can customise this by providing an alternative date format mask as a System property ("drools.dateformat" is the name of the property). If more control is required, use the predicate constraint.
Any valid Java String is allowed.
only true or false can be used. 0 and 1 are not recognised,
Cheese ( smelly ) is not allowed
Example 3.9. Literal Constraints with Collections
CheeseCounter( cheeses contains "stilton" ) CheeseCounter( cheeses excludes "chedder" )
Variables can be bound to Facts and their Fields and then used
in subsequent Field Constraints. A bound variable is called a
Declaration. Declarations cannot be used with
'matches', although it works with
'contains'. Valid operators are determined by the
type of the field being constrained. Bound Variables, specifically the
'==' and '=!' operators, provide for very fast execution as we can use
hashing to improve performance.
Example 3.11. Bound Field using '==' operator
Person( likes : favouriteCheese ) Cheese( type == likes )
'likes' is our variable, our Declaration, that is bound to the favouriteCheese field for any matching Person instance and is used to constrain the type of Cheese in the following Column. Any valid java variable name can be used, including '$'; which you will often see used to help differentiate declarations from fields. The exampe below shows a declaration bound to the Columns Object Type instance itself and used with a 'contains' operator, note the optional use of '$' this time.
Example 3.12. Bound Fact using 'contains' operator
$stilton : Cheese( type == "stilton" ) Cheesery( cheeses contains $stilton )
A Predicate constraint can use any valid Java expression as long as it evaluated to a primitive boolean - avoid using any Drools keywords as Declaration identifiers. Previously bound declarations can be used in the expression. Functions used in a Predicate Constraint must return time constant results. All bound primitive declarations are boxed, there is currently no auto-unboxing (if you use java 5, this is all automatic).
This example will find all pairs of male/femal people where the male is 2 years older than the female.
Example 3.13. Return Value operator
Person( girlAge : age, sex = "F" ) Person( boyAge : age -> ( girlAge.intValue() == boyAge.intValue() + 2 ), sex = 'M' )
A Return Value constraint can use any valid Java expression as long as it returns an object, it cannot return primitives - avoid using any Drools keywords as Declaration identifiers. Functions used in a Return value Constraint must return time constant results. Previously bound declarations can be used in the expression. All bound primitive declarations are boxed, there is currently no auto-unboxing. The returned value must be boxed if its a primitive..
Like the Predicate example this will find all pairs of male/femal people where the male is 2 years older than the female. Notice here we didn't have to bind the boyAge, making it a little simpler to read.
Example 3.14. Return Value operator
Person( girlAge : age, sex == "F" ) Person( age == ( new Integer(girlAge.intValue() + 2) ), sex == 'M' )
Conditional elements work on one or more Columns (which were described above). The most common one is "and" which is implicit when you have multiple Columns in the LHS of a rule that are not connected in anyway. Note that an 'and' cannot have a leading declaration binding like 'or' - this is obvious when you think about it. A declaration can only reference a single Fact, when the 'and' is satisfied it matches more than one fact - which fact would the declaration bind to?
valid children : and, or, not, exists, column
Example 3.15. Column
Cheese( cheeseType : type ) && Person( favouriteCheese == cheeseType ) Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
valid children : and, or, not, exists, column
Example 3.16. or
Person( sex == "f", age > 60 ) || Person( sex == "m", age > 65 ) Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 )
Example 3.17. or with binding
pensioner : ( Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 ) )
The 'or' conditional element results in multipe rule generation, called sub rules, 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 OR conditional element is as a shortcut for generating 2 additional rules. When you think of it that way, its clear that for a single rule there could be multiple activations if both sides of the OR conditional element are true.
valid children : none
Eval is essentially a catch all which allows any semantic code (that returns a primitive primitive boolean) to be executed. This can refer to variables that were bound in the LHS of the rule, and functions in the rule package. An eval should be the last conditional element in the LHS of a rule. You can have multiple evals in a rule. Generally you would combine them with some column constraints.
Evals cannot be indexed and thus are not as optimal as using 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. An Eval will be checked each time if all the other conditions in the rules are met.
For folks who are familiar with Drools 2.x lineage, the old Drools paramater and condition tags are equivalent to binding a variable to an appropriate type, and then using it in an eval node.
Example 3.18. eval
p1 : Parameter() p2 : Parameter() eval( p1.getList().containsKey(p2.getItem()) ) eval( isValid(p1, p2) ) //this is how you call a function in the LHS - a function called "isValid"
valid children: Column
'not' is first order logic's Existential Quantifier and checks for the non existence of something in the Working Memory. Currently only Columns may be nested in a 'not' but future versions of will allow 'and' and 'or' to be nested.
Example 3.20. No red Busses
not Bus(color == "red") not ( Bus(color == "red", number == 42) ) //brackets are optional
valid children: Column
'exists' is first order logic's Existential Quantifier and checks for the existence of something in the Working Memory. Think of exist as meaning "at least one..". It is different from just having the Column on its own. If you had the column on its on, its kind of like saying "for each one of...". if you use exist with a Column, then the rule will only activate once regardless of how much data there is in working memory that matches that condition.
Currently only Columns may be nested in a 'exists' but future versions of will allow 'and' and 'or' to be nested.
Grouping is similar to using parentheses in algebra, it makes the order of operations explicit.
Example 3.23. Example of groups
... ( Message( status == Message.HELLO ) and Message(message != null) or Message(status == null) ) ...
Example 3.24. A rule example
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
Java 5 supports autoboxing and unboxing between primitives of appropriate types. This makes for very convenient and easy to read code. However, as drools runs in J2SE 1.4 as well, we can't rely on this. Thus we have to autobox at times. Fields that are referred to are autoboxed in the corresponding object type automatically (if they are already an object, then there is no change). However, it is important to note that they are not "unboxed" automatically. Thus if you bind to a field that is an "int" in your object model, it will behave like an Object in the rule (ie predicates, return value constraints and the RHS).
As a general rule, if possible, make your fields object types (at least until java 5), or at least think of your fields as object types even if they are not to start with). Another special note, is that for return value constraints, the return value snippet of code must return an Object (not a primitive). Now, I bet you can't wait for Java 5 to be the minimum ! The fact that not quite *everything* in java is an object causes headaches like this (keep those tablets handy).