DRL File Structure
Drools has a native language called Drools Rule Language (DRL). A DRL file is a simple text file in which you specify a package name, multiple rules, queries, types, functions, and resource declarations such as imports and globals. The order in which these 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.
Rule Structure
A rule has the following general structure:
rule "name"
attributes
when
LHS
then
RHS
end
Punctuation is generally unnecessary, even the double quotes for "name" and newlines are optional. Attributes are simple optional hints as to how the rule should behave. LHS (Left Hand Side) is the conditional part of the rule, and RHS (Right Hand Side) is basically a block that specifies the dialect-specific semantic (action) code to be executed.
Rule Attributes
Rule attributes are a declarative way to influence the behavior of the rule engine when it activates or fires a.
Name |
Type |
Default |
Description |
no-loop |
Boolean |
false |
When a rule's consequence modifies a fact, it may cause the rule to activate again, thus causing an infinite loop. Setting no-loop to true will skip the creation of another Activation for the rule with the current set of facts. |
ruleflow-group |
String |
N/A |
Rules having the same ruleflow-group identifier fire only when their group is active. |
lock-on-active |
Boolean |
false |
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. |
salience |
Integer |
0 |
Salience is a form of priority where rules with higher salience values are given higher priority when ordered by the conflict resolution strategy. |
agenda-group |
String |
“MAIN” |
It allows partitioning of the Agenda to more execution control; only rules in the agenda group that have acquired the focus are allowed to fire. |
auto-focus |
Boolean |
false |
If auto-focus is true and the rule's agenda group does not have focus yet, when a rule is activated, then it is given focus, thus allowing the rule to potentially fire. |
activation-group |
String |
N/A |
Only one rule in the each activation-group will fire i.e., the first rule in an activation-group to fire cancels the other rules' activations |
dialect |
String |
as specified by the package |
The dialect specifies the language to be used for any code expressions in the LHS or the RHS code block. Currently, two dialects are available: Java and MVEL. |
date-effective |
Date |
N/A |
A rule can only activate after the date and time of the date-effective attribute. |
date-expires |
Date |
N/A |
A rule cannot activate after the date-expires attribute. |
duration |
Long |
N/A |
The duration dictates that the rule will fire after a specified duration if it is still true. |
Timers
Rules supports both interval- and cron-based timers. Interval (indicated by "int:") timers follow the semantics of java.util.Timer objects, with an initial delay and an optional repeat interval:
timer ( int: <initial delay> <repeat interval>? )
Cron (indicated by "cron:") timers follow standard Unix cron expressions. One could write the following rule:
rule "Send SMS every 15 minutes" timer (cron:* 0/15 * * * ?)
when
$a : Alarm( on == true )
then
channels["sms"].insert( new Sms( $a.mobileNumber, "Alarm on" );
end
The Left Hand Side
The Left Hand Side (LHS) is the conditional part of the rule. It consists of zero or more Conditional Elements. If the LHS is empty, it is considered always true and is activated once, when a new session is created. A pattern is the most important Conditional Element. It can potentially match on any fact inserted in the working memory. A pattern contains zero or more constraints and has an optional pattern biding, meaning that the object used in a pattern, or some of its attributes, can be bound to variables that can then be used in the following patterns or in the consequence.
In its simplest form with no constraints, a pattern matches against a fact of the given type:
Person() // match all object of type Person in the WorkingMemory
Patterns may refer to superclasses or even interfaces, thereby potentially matching facts from many different classes:
Object() // match all objectS in the WorkingMemory
A comma-separated list of constraints with an implicit AND connective semantic can be added between the pattern parenthesis:
Person( name == "Mark", age > 30 ) // named Mark AND older then 30
It is possible to bind the matched object, or the value of one of its property, to a variable in order to refer to that variable in other subsequent patterns or in the RHS:
$p : Person( $name : name, $age : age > 30 )
The prefixed dollar symbol ($) is just a convention: it can be useful in complex rules where it helps to easily differentiate between variables and fields, but it is not mandatory.
Patterns can be combined into logical conjunctions (AND) and/or disjunctions (OR). Drools supports both infix:
A() or ( B() and C() )
and prefix form:
or( A() and( B() C() ) )
Patterns can also be modified by prepending them with a first order logic existential (exists) and non-existential (not) quantifier. While the function of the not quantifier is obvious ("there must be none of..."), the existing one could appear redundant, but it is not because it has the meaning of "there is at least one…" while the pattern on its own can be thought as "for each one of . . ."
Advanced Conditional Elements
Any LHS pattern is a conditional element. Drools provides some advanced conditional elements that greatly enrich its semantic.
for all
The conditional element forall completes the first order logic support in Drools: it evaluates to true when all facts that match the first pattern match all the remaining patterns. For example, to check that all the English buses are red, one could write
forall( $bus : Bus( type == "English" )
Bus( this == $bus, color = "red" ) )
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 the results of a method call or a sub-field on a bound variable, for example:
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
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 terms of first order logic, this is the cardinality quantifier. For example, to check if a system has 3 pending alarms or more:
$s : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $s, status == 'pending' ) )
accumulate
The conditional element accumulate is a more flexible and powerful form of collect because it allows a rule to iterate over a collection of objects by executing custom actions for each of the elements, and at the end it returns a result object. accumulate supports both the use of pre-defined accumulate functions and the use of inline custom code. The top level accumulate syntax is:
accumulate ( <source pattern>; <functions> [;<constraints>] )
For instance, a pattern to calculate the minimum, maximum and average temperature reading for a given sensor and to match if the minimum temperature is under 20C degrees and the average is over 70C degrees could be written in the following way:
$s : Sensor()
accumulate( Reading( sensor == $s, $temp : temperature );
$min : min( $temp ),
$max : max( $temp ),
$avg : average( $temp );
$min < 20, $avg > 70 )
eval
In the end, the conditional element eval is essentially a catch-all that allows any semantic code that returns a boolean to be executed. eval can refer to variables that were bound in the LHS, and it functions in the rule package. Still, it has to be used sparingly because it reduces the declarativeness of your rules and can result in a poorly performing engine.
The Right Hand Side
The Right Hand Side (RHS) contains the list of actions to be executed when the rule is fired. It should be kept small and avoid any conditional code, thus keeping it declarative and readable. It allows any Java or mvel code (depending on the chosen dialect), but its main purpose is to insert, retract, or modify working memory data and to interact with the engine. To do that, Drools provides the following convenience methods:
Method |
Description |
update(object) |
Informs the rule engine that an object has changed, and then the LHS potentially interesting it has to be reconsidered |
insert(object) |
Inserts a new object in the Working Memory |
insertLogical(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(object) |
Retracts the object from the Working Memory. |
drools.halt() |
Immediately terminates a rule execution |
drools.setFocus(string) |
Sets the focus to the specified agenda group |
drools.getRule().getName() |
Returns the name of the fired rule |
As an alternative to the update method, Drools provides the modify statement that is a more structured approach to notify the engine that the state of an object is changed and how it is changed. The syntax of this statement is:
modify ( <fact-expression> ) { <expression> [,<expression> ]* }
For example, this example illustrates a simple fact modification;
modify( $person ) { setName( "Mark" ), setAge( 35 ) }
Package
A package represents a namespace that groups a set of rules. The package name itself is the namespace, and it is not related to files or folders in any way. Its declaration, if present, must be the first statement in the DRL file, for example:
package org.mydomain.mypackage
Functions
Functions are a way to put semantic code in a rule source file, alternative and equivalent to static Java methods in a helper class. Indeed, the main advantage of using functions in a rule is that you can keep the logic all in one place. A typical function declaration looks like this:
function String hello(String name) {
return "Hello "+name+"!";
}
Type Declarations
Despite Drools' working out of the box with plain Java objects as facts, it also allows the declaration of new types. To do that, it is enough to use the declare keyword followed by the name of the type and the list of its attributes with the respective types as follows:
declare Person
name : String
age : int
end
Query
A query is a simple way to search the working memory for facts that match the stated conditions and, therefore, it contains only the structure of the LHS of a rule as in the following example:
query "over 30"
person : Person( age > 30 )
end
It is then possible to iterate over the query results, as in:
QueryResults results = ksession.getQueryResults( "over 30" );
for ( QueryResultsRow row : results ) {
Person person = ( Person ) row.get( "person" );
}
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}