JBoss.orgCommunity Documentation
Drools has a "native" rule language.
This format is very light in terms of punctuation, and supports
natural and domain specific languages via "expanders" that allow the
language to morph to your problem domain. This chapter is mostly concerted
with this native rule format. The diagrams used to present the syntax are
known as "railroad" diagrams, and they are basically flow charts for the
language terms. The
technically very keen may also refer to DRL.g which is
the Antlr3
grammar for the rule language. If you use the Rule Workbench, a lot of the
rule structure is done for you with content assistance, for example, type
"ru" and press ctrl+space, and it will build the rule structure for
you.
A rule file is typically a file with a .drl extension. In a DRL file you can have multiple rules, queries and functions, as well as some resource declarations like imports, globals and attributes that are assigned and used by your rules and queries. However, you are also able to spread your rules across multiple rule files (in that case, the extension .rule is suggested, but not required) - spreading rules across files can help with managing large numbers of rules. A DRL file is simply a text file.
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.
For the inpatients, just as an early view, a rule has the following rough structure:
rule"name"attributeswhenLHSthenRHSend
It's really that simple. Mostly punctuation is not needed, even the double quotes for "name" are optional, as are newlines. Attributes are simple (always optional) hints to how the rule should behave. LHS is the conditional parts of the rule, which follows a certain syntax which is covered below. RHS is basically a block that allows dialect specific semantic code to be executed.
It is important to note that white space is not important, except in the case of domain specific languages, where lines are processed one by one and spaces may be significant to the domain language.
(updated to Drools 5.0)
Drools 5 introduces the concept of hard and soft keywords.
Hard keywords are reserved, you cannot use any hard keyword when naming your domain objects, properties, methods, functions and other elements that are used in the rule text.
Here is the list of hard keywords that must be avoided as identifiers when writing rules:
<kw>true</kw>
<kw>false</kw>
<kw>accumulate</kw>
<kw>collect</kw>
<kw>from</kw>
<kw>null</kw>
<kw>over</kw>
<kw>then</kw>
<kw>when</kw>
Soft keywords are just recognized in their context, enabling you to use these words in any other place you wish. Here is a list of the soft keywords:
<kw>lock-on-active</kw>
<kw>date-effective</kw>
<kw>date-expires</kw>
<kw>no-loop</kw>
<kw>auto-focus</kw>
<kw>activation-group</kw>
<kw>agenda-group</kw>
<kw>ruleflow-group</kw>
<kw>entry-point</kw>
<kw>duration</kw>
<kw>package</kw>
<kw>import</kw>
<kw>dialect</kw>
<kw>salience</kw>
<kw>enabled</kw>
<kw>attributes</kw>
<kw>rule</kw>
<kw>extend</kw>
<kw>template</kw>
<kw>query</kw>
<kw>declare</kw>
<kw>function</kw>
<kw>global</kw>
<kw>eval</kw>
<kw>not</kw>
<kw>in</kw>
<kw>or</kw>
<kw>and</kw>
<kw>exists</kw>
<kw>forall</kw>
<kw>action</kw>
<kw>reverse</kw>
<kw>result</kw>
<kw>end</kw>
<kw>init</kw>
Of course, you can have these (hard and soft) words as part of a method name in camel case, like notSomething() or accumulateSomething() - there are no issues with that scenario.
Another improvement of the DRL language is the ability to escape hard keywords on rule text. This feature enables you to use your existing domain objects without worrying about keyword collision. To escape a word, simply enclose it in grave accents, like this:
Holiday( `when` == "july" )
The escape should be used everywehere in rule text, except within code expressions in the LHS or RHS code block. Here are examples of proper usage:
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
Comments are sections of text that are ignored by the rule engine. They are stripped out when they are encountered, except inside semantic code blocks, like the RHS of a rule.
To create single line comments, you can use either '#' or '//'. The parser will ignore anything in the line after the comment symbol. Example:
rule "Testing Comments"
when
# this is a single line comment
// this is also a single line comment
eval( true ) # this is a comment in the same line of a pattern
then
// this is a comment inside a semantic code block
# this is another comment in a semantic code block
end
Multi-line comments are used to comment blocks of text, both in and outside semantic code blocks. Example:
rule "Test Multi-line Comments"
when
/* this is a multi-line comment
in the left hand side of a rule */
eval( true )
then
/* and this is a multi-line comment
in the right hand side of a rule */
end (updated to Drools 5.0)
Drools 5 introduces standardized error messages. This standardization aims to help users to find and resolve problems in a easier and faster way. In this section you will learn how to identify and interpret those error messages, and you will also receive some tips on how to solve the problems associated with them.
The standardization includes the error message format and to better explain this format, let's use the following example:
1st Block: This area identifies the error code.
2nd Block: Line and column information.
3rd Block: Some text describing the problem.
4th Block: This is the first context. Usually indicates the rule, function, template or query where the error occurred. This block is not mandatory.
5th Block: Identifies the pattern where the error occurred. This block is not mandatory.
Indicates the most common errors, where the parser came to a decision point but couldn't identify an alternative. Here are some examples:
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 4.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.
This error indicates that the parser was looking for a particular symbol that it didn’t find at the current input position. Here are some samples:
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 4.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.
A validating semantic predicate evaluated to false. Usually these semantic predicates are used to identify soft keywords. This sample shows exactly this situation:
Example 4.7.
1: package nesting;
2: dialect "mvel"
3:
4: import org.drools.Person
5: import org.drools.Address
6:
7: fdsfdsfds
8:
9: rule "test something"
10: when
11: p: Person( name=="Michael" )
12: then
13: p.name = "other";
14: System.out.println(p.name);
15: end
With this sample, we get this error message:
[ERR 103] Line 7:0 rule 'rule_key' failed predicate: {(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rule
The fdsfdsfds text is invalid and the parser couldn’t identify it as the soft keyword <kw>rule</kw>.
This error is very similar to 102: Mismatched input, but usually involves soft keywords.
This error is associated with the <kw>eval</kw> clause, where its expression may not be terminated with a semicolon. Check this example:
Due to the trailing semicolon within eval, we get this error message:
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule simple_rule
This problem is simple to fix: just remove the semi-colon.
The recognizer came to a subrule in the grammar that must match an alternative at least once, but the subrule did not match anything. Simply put: the parser has entered a branch from where there is no way out. This example illustrates it:
This is the message associated to the above sample:
[ERR 105] Line 2:2 required (...)+ loop did not match anything at input 'aa' in template test_error
To fix this problem it is necessary to remove the numeric value as it is neither a valid data type which might begin a new template slot nor a possible start for any other rule file construct.
A package is a collection of rules and other related constructs, such as imports and globals. The package members are typically related to each other - perhaps HR rules, for instance. A package represents a namespace, which ideally is kept unique for a given grouping of rules. The package name itself is the namespace, and is not related to files or folders in any way.
It is possible to assemble rules from multiple rule sources, and have one top level package configuration that all the rules are kept under (when the rules are assembled). Although, it is not possible to merge into the same package resources declared under different names. A single Rulebase may, however, contain multiple packages built on it. A common structure is to have all the rules for a package in the same file as the package declaration (so that is it entirely self-contained).
The following railroad diagram shows all the components that may make up a package. Note that a package must have a namespace and be declared using standard Java conventions for package names; i.e., no spaces, unlike rule names which allow spaces. In terms of the order of elements, they can appear in any order in the rule file, with the exception of the <kw>package</kw> statement, which must be at the top of the file. In all cases, the semicolons are optional.
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.
With <kw>global</kw> you define global variables. They are used to make application objects available to the rules. Typically, they are used to provide data or services that the rules use, especially application services used in rule consequences, and to return data from the rules, like logs or values added in rule consequences, or for the rules to interact with the application, doing callbacks. Globals are not inserted into the Working Memory, and therefore a global should never be used to establish conditions in rules except when it has a constant immutable value. The engine cannot be notified about value changes of globals and does not track their changes. Incorrect use of globals in constraints may yield surprising results - surprising in a bad way.
If multiple packages declare globals with the same identifier they must be of the same type and all of them will reference the same global value.
In order to use globals you must:
Declare your global variable in your rules file and use it in rules. Example:
global java.util.List myGlobalList;
rule "Using a global"
when
eval( true )
then
myGlobalList.add( "Hello World" );
end
Set the global value on your working memory. It is a best practice to set all global values before asserting any fact to the working memory. Example:
List list = new ArrayList(); WorkingMemory wm = rulebase.newStatefulSession(); wm.setGlobal( "myGlobalList", list );
Note that these are just named instances of objects that you pass in from your application to the working memory. This means you can pass in any object you want: you could pass in a service locator, or perhaps a service itself. With the new <kw>from</kw> element it is now common to pass a Hibernate session as a global, to allow <kw>from</kw> to pull data from a named Hibernate query.
One example may be an instance of a Email service. In your integration code that is calling the rule engine, you obtain your emailService object, and then set it in the working memory. In the DRL, you declare that you have a global of type EmailService, and give it the name "email". Then in your rule consequences, you can use things like email.sendSMS(number, message).
Globals are not designed to share data between rules and they should never be used for that purpose. Rules always reason and react to the working memory state, so if you want to pass data from rule to rule, assert the data as facts into the working memory.
It is strongly discouraged to set or change a global value from inside your rules. We recommend to you always set the value from your application using the working memory interface.
Functions are a way to put semantic code in your rule source file, as opposed to in normal Java classes. They can't do anything more than what you can do with helper classes. (In fact, the compiler generates the helper class for you behind the scenes.) The main advantage of using functions in a rule is that you can keep the logic all in one place, and you can change the functions as needed (which can be a good or a bad thing). Functions are most useful for invoking actions on the consequence (<kw>then</kw>) part of a rule, especially if that particular action is used over and over again, perhaps with only differing parameters for each rule.
A typical function declaration looks like:
function String hello(String name) {
return "Hello "+name+"!";
}
Note that the <kw>function</kw> keyword is used, even though its not really part of Java. Parameters to the function are defined as for a method, and you don't have to have parameters if they are not needed. The return type is defined just like in a regular method.
Alternatively, you could use a static method in a helper class,
e.g., Foo.hello(). Drools supports the use of
function imports, so all you would need to do is:
import function my.package.Foo.hello
Irrespective of the way the function is defined or imported, you use a function by calling it by its name, in the consequence or inside a semantic code block. Example:
rule "using a static function"
when
eval( true )
then
System.out.println( hello( "Bob" ) );
end
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.
To declare a new type, all you need to do is use the keyword <kw>declare</kw>, followed by the list of fields, and the keyword <kw>end</kw>.
Example 4.10. Declaring a new fact type: Address
declare Address number : int streetName : String city : String end
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 4.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 <kw>import</kw> clause, as previously discussed.
Example 4.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 4.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 4.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 may be assigned to several different constructions in Drools: fact types, fact attributes and rules. Drools uses the at sign ('@') to introduce metadata, and it always uses the form:
@metadata_key( metadata_value )
The parenthesized metadata_value is optional.
For instance, if you want to declare a metadata attribute like
author, whose value is Bob, you
could simply write:
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 4.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.
Drools allows the declaration of metadata attributes for existing types in the same way as when declaring metadata attributes for new fact types. The only difference is that there are no fields in that declaration.
For instance, if there is a class org.drools.examples.Person, and one wants to declare metadata for it, it's possible to write the following code:
Example 4.17. Declaring metadata for an existing type
import org.drools.examples.Person declare Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) end
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 4.18. Declaring metadata using the fully qualified class name
declare org.drools.examples.Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) end
Declared types are usually used inside rules files, while Java models are used when sharing the model between rules and applications. Although, sometimes, the application may need to access and handle facts from the declared types, especially when the application is wrapping the rules engine and providing higher level, domain specific user interfaces for rules management.
In such cases, the generated classes can be handled as usual with the Java Reflection API, but, as we know, that usually requires a lot of work for small results. Therefore, Drools provides a simplified API for the most common fact handling the application may want to do.
The first important thing to realize is that a declared fact will
belong to the package where it was declared. So, for instance, in the
example below, Person will belong to the
org.drools.examples package, and so the
fully qualified name of the generated class will be
org.drools.examples.Person.
Example 4.19. Declaring a type in the org.drools.examples package
package org.drools.examples import java.util.Date declare Person name : String dateOfBirth : Date address : Address 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 4.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 <kw>when</kw> keyword (ideally on a new line), similarly the RHS follows the <kw>then</kw> keyword (again, ideally on a newline). The rule is terminated by the keyword <kw>end</kw>. Rules cannot be nested.
Example 4.21. Rule Syntax Overview
rule "<name>"
<attribute>*
when
<conditional element>*
then
<action>*
endExample 4.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);
endRule attributes provide a declarative way to influence the behavior of the rule. Some are quite simple, while others are part of complex subsystems such as ruleflow. To get the most from Drools you should make sure you have a proper understanding of each attribute.
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.
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.
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.
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.
default value: MAIN
type: String
Agenda groups allow the user to partition the Agenda providing more execution control. Only rules in the agenda group that has acquired the focus are allowed to fire.
default value: false
type: Boolean
When a rule is activated where the auto-focus
value is true and the rule's agenda group does
not have focus yet, then it is given focus, allowing the rule to
potentially fire.
default value: N/A
type: String
Rules that belong to the same activation-group, identified by this attribute's string value, will only fire exclusively. In other words, the first rule in an activation-group to fire will cancel the other rules' activations, i.e., stop them from firing.
Note: This used to be called Xor group, but technically it's not quite an Xor. You may still hear people mention Xor group; just swap that term in your mind with activation-group.
default value: as specified by the package
type: String
possible values: "java" or "mvel"
The dialect species 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. While the dialect can be specified at the package level, this attribute allows the package definition to be overridden for a rule.
default value: N/A
type: String, containing a date and time definition
A rule can only activate if the current date and time is after date-effective attribute.
default value: N/A
type: String, containing a date and time definition
A rule cannot activate if the current date and time is after the date-expires attribute.
default value: no default value
type: long
The duration dictates that the rule will fire after a specified duration, if it is still true.
The Left Hand Side (LHS) is a common name for the conditional part
of the rule. It consists of zero or more Conditional Elements. If the LHS
is left empty, it is re-written as eval(true), which means
that the rule's condition is always true. It will be activated, once,
when a new Working Memory session is created.
Example 4.24. 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>*
endConditional elements work on one or more patterns (which are described below). The most common one is <kw>and</kw>, which is implicit when you have multiple patterns in the LHS of a rule that are not connected in any way. Note that an <kw>and</kw> cannot have a leading declaration binding like <kw>or</kw>. This is obvious, since a declaration can only reference a single fact, and when the <kw>and</kw> is satisfied it matches more than one fact - so which fact would the declaration bind to?
The pattern element is the most important Conditional Element. The entity relationship diagram below provides an overview of the various parts that make up the pattern's constraints and how they work together; each is then covered in more detail with railroad diagrams and examples.
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 4.27. 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:
Group 1 - type == "stilton" requires that the
type is stilton.
Group 2 - price < 10 demands a price less
than 10.
Group 3 - age == "mature" accepts only mature
cheese.
The '&&' (and) and '||' (or) constraint connectives allow constraint groups to have multiple constraints. Example:
Example 4.28. && and || Constraint Connectives
// Cheese type is "stilton" and price < 10, and age is mature Cheese( type == "stilton" && price < 10, age == "mature" ) // Cheese type is "stilton" or price < 10, and age is mature Cheese( type == "stilton" || price < 10, age == "mature" )
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 4.29. 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 4.30. 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")
A Field constraint specifies a restriction to be used on a named field; the field name can have an optional variable binding.
There are three types of restrictions: Single Value Restriction, Compound Value Restriction, and Multi Restriction.
A field is derived from an accessible method of the object. If your model objects follow the Java Bean pattern, then fields are exposed using "getXXX" or "isXXX" methods, where these methods take no arguments, and return something. Within patterns, fields can be accessed using the bean naming convention, so that "getType" would be accessed as "type". Drools uses the standard JDK Introspector class to do this mapping.
For example, referring to our Cheese class, the pattern
Cheese(type == "brie") applies the getType() method
to a Cheese instance. If a field name cannot be found, the
compiler will resort to using the name as a method without
arguments. Thus, the method toString() is called due to a
constraint Cheese(toString == "cheddar"). In this
case, you use the full name of the method with correct
capitalization, but still without parentheses. Do
please make sure that you are accessing methods that take no
parameters, and that are in fact accessors
which 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 in between invocations to make it faster.
The field constraints can take a number of values; including literal, qualifiedIdentifier (enum), variable and returnValue.
You can do checks against fields that are or may be null, using
'==' and '!=' as you would expect, and the literal <kw>null</kw> 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 <kw>matches</kw> and <kw>not matches</kw> is only applicable to string fields, <kw>contains</kw> and <kw>not contains</kw> 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 returns true if the string does not match the regular expression. The same rules apply as for the <kw>matches</kw> operator. Example:
The operator <kw>contains</kw> is used to check whether a field that is a Collection or array contains the specified value.
Example 4.33. Contains with Collections
CheeseCounter( cheeses contains "stilton" ) // contains with a String literal CheeseCounter( cheeses contains $var ) // contains with a variable
The operator <kw>not contains</kw> is used to check whether a field that is a Collection or array does not contain the specified value.
Example 4.34. Literal Constraint with Collections
CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal CheeseCounter( cheeses not contains $var ) // not contains with a variable
Note
For backward compatibility, the <kw>excludes</kw> operator is supported as a synonym for <kw>not contains</kw>.
The operator <kw>memberOf</kw> is used to check whether a field is a member of a collection or array; that collection must be a variable.
The operator <kw>not memberOf</kw> is used to check whether a field is not a member of a collection or array; that collection must be a variable.
Example 4.36. Literal Constraint with Collections
CheeseCounter( cheese not memberOf $matureCheeses )
This operator is similar to <kw>matches</kw>, but it checks
whether a word has almost the same sound (using English pronounciation)
as the given value. This is based on the Soundex algorithm
(see http://en.wikipedia.org/wiki/Soundex).
Example 4.37. Test with soundslike
// match cheese "fubar" or "foobar" Cheese( name soundslike 'foobar' )
Literal restrictions are the simplest form of restrictions and evaluate a field against a specified literal, which may be numeric or a date, a string or a boolean.
Literal Restrictions using the operator '==' provide for faster execution as we can index using hashing to improve performance.
All standard Java numeric primitives are supported.
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 the inline-eval constraint.
Any valid Java String is allowed.
Only true or false can be used; 0 and 1 are not
acceptable. A boolean field alone (as in Cheese( smelly ) is
not permitted; you must compare this to a boolean literal.
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.
Example 4.43. Bound Field using the operator '=='
Person( likes : favouriteCheese ) Cheese( type == likes )
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 4.44. Bound Fact using 'contains' operator
$stilton : Cheese( type == "stilton" ) Cheesery( cheeses contains $stilton )
A Return Value restriction is a parenthesized expression composed from literals, any valid Java primitive or object, previously bound variables, function calls, and operators. Functions used in a Return Value must return results that do not depend on time.
Example 4.45. Return Value Restriction
Person( girlAge : age, sex == "F" ) Person( age == ( girlAge + 2) ), sex == 'M' )
The compound value restriction is used where there is more than one possible value to match. Currently only the <kw>in</kw> and <kw>not in</kw> 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 '=='.
Example 4.46. Compound Restriction using "in"
Person( $cheese : favouriteCheese ) Cheese( type in ( "stilton", "cheddar", $cheese )
Multi restriction 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.
Example 4.47. 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.
This example will find all male-female pairs where the
male is 2 years older than the female; the variable age
is auto-created in the second pattern by the autovivification process.
Example 4.48. Return Value operator
Person( girlAge : age, sex = "F" ) Person( eval( age == girlAge + 2 ), sex = 'M' )
Drools permits nested accessors in in the field constraints using the MVEL accessor graph notation. Field constraints involving nested accessors are actually re-written as an MVEL dialect inline-eval. 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; they should be considered immutable while any of their parent references are inserted into the Working Memory. If you wish to modify a nested value you should remove the parent objects first and re-assert afterwards. If you only have a single parent at the root of the graph, when in the MVEL dialect, you can use the <kw>modify</kw> construct and its block setters to write the nested accessor assignments while retracting and inserting the the root parent object as required. Nested accessors can be used on either side of the operator symbol.
Example 4.49. Nested Accessors
// Find a pet older than its owners first-born child $p : Person( ) Pet( owner == $p, age > $p.children[0].age )
This is internally rewriten as an MVEL inline eval:
// Find a pet older than its owners first-born child $p : Person( ) Pet( owner == $p, eval( age > $p.children[0].age ) )
Note: Nested accessors have a much greater performance cost than direct field accesses, so use them carefully.
The Conditional Element <kw>and</kw> is used to group other Conditional Elements into a logical conjunction. The root element of the LHS is an implicit prefix <kw>and</kw> and doesn't need to be specified. Drools supports both prefix <kw>and</kw> and infix <kw>and</kw>, but prefix is the preferred option as its implicit grouping avoids confusion.
Example 4.51. implicit root prefixAnd
when
Cheese( cheeseType : type )
Person( favouriteCheese == cheeseType )Infix <kw>and</kw> is supported along with explicit grouping with parentheses, should it be needed. The symbol '&&', as an alternative to <kw>and</kw>, is deprecated although it is still supported in the syntax for legacy support reasons.
Example 4.52. infixAnd
//infixAnd
Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
//infixAnd with grouping
( Cheese( cheeseType : type ) and
( Person( favouriteCheese == cheeseType ) or
Person( favouriteCheese == cheeseType ) )The Conditional Element <kw>or</kw> is used to group other Conditional Elements into a logical disjunction. Drools supports both prefix <kw>or</kw> and infix <kw>or</kw>, but prefix is the preferred option as its implicit grouping avoids confusion. The behavior of the Conditional Element <kw>or</kw> is different from the connective '||' for constraints and restrictions in field constraints. The engine actually has no understanding of the Conditional Element <kw>or</kw>; instead, via a number of different logic transformations, a rule with <kw>or</kw> is rewritten as a number of subrules. This process ultimately results in a rule that has a single <kw>or</kw> 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. - This can be most confusing to new rule authors.
Infix <kw>or</kw> is supported along with explicit grouping with parentheses, should it be needed. The symbol '||', as an alternative to <kw>or</kw>, is deprecated although it is still supported in the syntax for legacy support reasons.
Example 4.54. 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 <kw>or</kw> 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 4.55. or with binding
(or pensioner : Person( sex == "f", age > 60 )
pensioner : Person( sex == "m", age > 65 ) )Since the conditional element <kw>or</kw> 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 <kw>or</kw> 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 <kw>eval</kw> 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 <kw>eval</kw> 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 4.56. eval
p1 : Parameter() p2 : Parameter() eval( p1.getList().containsKey(p2.getItem()) ) // call function isValid in the LHS eval( isValid(p1, p2) )
The CE <kw>not</kw> 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 <kw>not</kw> 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 4.58. No red Busses
// Brackets are optional:
not Bus(color == "red")
// Brackets are optional:
not ( Bus(color == "red", number == 42) )
// "not" with nested infix <kw>and</kw> - two patterns,
// brackets are requires:
not ( Bus(color == "red") and
Bus(color == "blue") )The CE <kw>exists</kw> 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 <kw>exists</kw> 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 <kw>exists</kw> pattern. Since only the existence matters, no bindings will be established.
The keyword <kw>exists</kw> must 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 4.60. At least one red Bus
exists Bus(color == "red")
// brackets are optional:
exists ( Bus(color == "red", number == 42) )
// "exists" with nested infix <kw>and</kw>,
// brackets are required:
exists ( Bus(color == "red") and
Bus(color == "blue") )The Conditional Element <kw>forall</kw> completes the First Order Logic support in Drools. The Conditional Element <kw>forall</kw> 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, <kw>forall</kw> can be written with a single pattern for simplicity. Example:
Example 4.61. 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 <kw>forall</kw>:
Example 4.62. 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 complete expressiveness. For instance, <kw>forall</kw> can be used inside a <kw>not</kw> CE. Note that only single patterns have optional parentheses, so that with a nested forall parentheses must be used :
Example 4.63. 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, not( forall( p1 p2 p3...)) is
equivalent to writing:
not(p1 and not(and p2 p3...))
Also, it is important to note that <kw>forall</kw> 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 <kw>from</kw> 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.
Here is a simple example of reasoning and binding on another pattern sub-field:
rule "validate zipcode"
when
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
then
# zip code is ok
end
With all the flexibility from the new expressiveness in the Drools engine you can slice and dice this problem many ways. This is the same but shows how you can use a graph notation with the 'from':
rule "validate zipcode"
when
$p : Person( )
$a : Address( zipcode == "23920W") from $p.address
then
# zip code is ok
end
Previous examples were evaluations using a single pattern. The CE <kw>from</kw> also support object sources that return a collection of objects. In that case, <kw>from</kw> will iterate over all objects in the collection and try to match each of them individually. For instance, if we want a rule that applies 10% discount to each item in an order, we could do:
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
The above example will cause the rule to fire once for each item whose value is greater than 100 for each given order.
You must take caution, however, when using <kw>from</kw>, especially in conjunction with the <kw>lock-on-active</kw> rule attribute as it may produce unexpected results. Consider the example provided earlier, but now slightly modified as follows:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( state == "NC") from $p.address
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( )
$a : Address( city == "Raleigh") from $p.address
then
modify ($p) {} #Apply discount to person in a modify block
end
In the above example, persons in Raleigh, NC should be assigned to sales region 1 and receive a discount; i.e., you would expect both rules to activate and fire. Instead you will find that only the second rule fires.
If you were to turn on the audit log, you would also see that when the second rule fires, it deactivates the first rule. Since the rule attribute <kw>lock-on-active</kw> prevents a rule from creating new activations when a set of facts change, the first rule fails to reactivate. Though the set of facts have not changed, the use of <kw>from</kw> returns a new fact for all intents and purposes each time it is evaluated.
First, it's important to review why you would use the above pattern. You may have many rules across different rule-flow groups. When rules modify working memory and other rules downstream of your RuleFlow (in different rule-flow groups) need to be reevaluated, the use of <kw>modify</kw> is critical. You don't, however, want other rules in the same rule-flow group to place activations on one another recursively. In this case, the <kw>no-loop</kw> attribute is ineffective, as it would only prevent a rule from activating itself recursively. Hence, you resort to <kw>lock-on-active</kw>.
There are several ways to address this issue:
Avoid the use of <kw>from</kw> 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 <kw>lock-on-active</kw> when you can explicitly manage how rules within the same rule-flow group place activations on one another (explained below).
The preferred solution is to minimize use of <kw>from</kw> when you can assert all your facts into working memory directly. In the example above, both the Person and Address instance can be asserted into working memory. In this case, because the graph is fairly simple, an even easier solution is to modify your rules as follows:
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
Now, you will find that both rules fire as expected. However, it is not always possible to access nested facts as above. Consider an example where a Person holds one or more Addresses and you wish to use an existential quantifier to match people with at least one address that meets certain conditions. In this case, you would have to resort to the use of <kw>from</kw> to reason over the collection.
There are several ways to use <kw>from</kw> to achieve this and not all of them exhibit an issue with the use of <kw>lock-on-active</kw>. For example, the following use of <kw>from</kw> causes both rules to fire as expected:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person($addresses : addresses)
exists (Address(state == "NC") from $addresses)
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($addresses : addresses)
exists (Address(city == "Raleigh") from $addresses)
then
modify ($p) {} #Apply discount to person in a modify block
end
However, the following slightly different approach does exhibit the problem:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$assessment : Assessment()
$p : Person()
$addresses : List() from $p.addresses
exists (Address( state == "NC") from $addresses)
then
modify ($assessment) {} #Modify assessment in a modify block
end
rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
$assessment : Assessment()
$p : Person()
$addresses : List() from $p.addresses
exists (Address( city == "Raleigh") from $addresses)
then
modify ($assessment) {} #Modify assessment in a modify block
endIn the above example, the $addresses variable is returned from the use of <kw>from</kw>. The example also introduces a new object, assessment, to highlight one possible solution in this case. If the $assessment variable assigned in the condition (LHS) is moved to the last condition in each rule, both rules fire as expected.
Though the above examples demonstrate how to combine the use of <kw>from</kw> with <kw>lock-on-active</kw> where no loss of rule activations occurs, they carry the drawback of placing a dependency on the order of conditions on the LHS. In addition, the solutions present greater complexity for the rule author in terms of keeping track of which conditions may create issues.
A better alternative is to assert more facts into working memory. In this case, a person's addresses may be asserted into working memory and the use of <kw>from</kw> would not be necessary.
There are cases, however, where asserting all data into working memory is not practical and we need to find other solutions. Another option is to reevaluate the need for <kw>lock-on-active</kw>. An alternative to <kw>lock-on-active</kw> is to directly manage how rules within the same rule-flow group activate one another by including conditions in each rule that prevent rules from activating each other recursively when working memory is modified. For example, in the case above where a discount is applied to citizens of Raleigh, a condition may be added to the rule that checks whether the discount has already been applied. If so, the rule does not activate.
The Conditional Element <kw>collect</kw> 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. A simple example:
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
$system : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $system, status == 'pending' ) )
then
# Raise priority, because system $system has
# 3 or more alarms pending. The pending alarms
# are $alarms.
end
In the above example, the rule will look for all pending alarms in the working memory for each given system and group them in ArrayLists. If 3 or more alarms are found for a given system, the rule will fire.
The result pattern of <kw>collect</kw> can
be any concrete class that implements the java.util.Collection
interface
and provides a default no-arg public constructor. This means that you can use
Java collections like ArrayList, LinkedList, HashSet, etc., or
your own class, as long as it implements the java.util.Collection
interface and provide a default no-arg public constructor.
Both source and result patterns can be constrained as any other pattern.
Variables bound before the <kw>collect</kw> 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. But note that <kw>collect</kw> is a scope delimiter for bindings, so that any binding made inside of it is not available for use outside of it.
Collect accepts nested <kw>from</kw> CEs. The following example is a valid use of "collect":
import java.util.LinkedList;
rule "Send a message to all mothers"
when
$town : Town( name == 'Paris' )
$mothers : LinkedList()
from collect( Person( gender == 'F', children > 0 )
from $town.getPeople()
)
then
# send a message to all mothers
end
The Conditional Element <kw>accumulate</kw> is a more flexible and powerful form of <kw>collect</kw>, the sense that it can be used to do what <kw>collect</kw> does and also achieve things that the CE <kw>collect</kw> 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 <kw>accumulate</kw> CE is:
<result pattern><kw>from accumulate(</kw><source pattern><kw>,</kw> <kw>init(</kw><init code><kw>),</kw> <kw>action(</kw><action code><kw>),</kw> <kw>reverse(</kw><reverse code><kw>),</kw> <kw>result(</kw><result expression><kw>) )</kw>
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 <kw>accumulate</kw> 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 <kw>accumulate</kw> 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 <kw>accumulate</kw> 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
endThe accumulate CE is a very powerful CE, but it gets real declarative and easy to use when using predefined functions that are known as Accumulate Functions. They work exactly like accumulate, but instead of explicitly writing custom code in every accumulate CE, the user can use predefined code for common operations.
For instance, the rule to apply discount on orders written in the previous section, could be written in the following way, using Accumulate Functions:
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
In the above example, sum is an Accumulate Function and will sum the $value of all OrderItems and return the result.
Drools ships with the following built-in accumulate functions:
average
min
max
count
sum
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()
$profit : Number()
from accumulate( OrderItem( order == $order, $cost : cost, $price : price )
average( 1 - $cost / $price ) )
then
# average profit for $order is $profit
end
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 Functions all one needs to do is to create
a Java class that implements the
org.drools.base.acumulators.AccumulateFunction interface and add a
line to the configuration file or set a system property to let the
engine know about the new function. As an example of an Accumulate
Function implementation, the following is the implementation of the
<kw>average</kw> function:
/*
* 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
*
* @author etirelli
*
*/
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;
}
}
The code for the function is very simple, as we could expect, as all the "dirty" integration work is done by the engine. Finally, to plug the function into the engine, we added it to the configuration file:
drools.accumulate.function.average = org.drools.base.accumulators.AverageAccumulateFunction
Here, "drools.accumulate.function." is a prefix that must always be used, "average" is how the function will be used in the rule file, and "org.drools.base.accumulators.AverageAccumulateFunction" is the fully qualified name of the class that implements the function behavior.
The Right Hand Side (RHS) is a common name for the consequence or action part of the rule; this part should contain a list of actions to be executed. It is bad practice to use imperative or conditional code in the RHS of a rule; as a rule should be atomic in nature - "when this, then do this", not "when this, maybe do this". The RHS part of a rule should also be kept small, thus keeping it declarative and readable. If you find you need imperative and/or conditional code in the RHS, then maybe you should be breaking that rule down into multiple rules. The main purpose of the RHS is to insert, retractor modify working memory data. To assist with that there are a few convenience methods you can use to modify working memory; without having to first reference a working memory instance.
<kw>update(</kw>object, handle<kw>);</kw> 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.
<kw>update(</kw>object<kw>);</kw> can
also be used; here the Knowledge Helper will look up the facthandle for you,
via an identity check, for the passed object. (Note that 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.)
<kw>insert(new</kw> Something<kw>());</kw> will place a new object of your creation into the Working Memory.
<kw>insertLogical(new</kw> Something<kw>());</kw> is 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.
<kw>retract(</kw>handle<kw>);</kw> removes an object from Working Memory.
These convenience methods are basically macros that provide short
cuts to the KnowledgeHelper instance that lets you access your
Working Memory from rules files. The predefined variable drools
of type KnowledgeHelper lets you call several other useful
methods. (Refer to the KnowledgeHelper interface documentation for
more advanced operations).
The call drools.halt() terminates rule execution
immediately. This is required for returning control to the point
whence the current session was put to work with
fireUntilHalt().
Methods insert(Object o),
update(Object o) and retract(Object o)
can be called on drools as well, but due to their
frequent use they can be called without the object reference.
drools.getWorkingMemory() returns the
WorkingMemory object.
drools.setFocus( String s) sets the focus
to the specified agenda group.
drools.getRule().getName(), called from a
rule's RHS, returns the name of the rule.
drools.getTuple() returns the Tuple that
matches the currently executing rule, and
drools.getActivation() delivers the corresponding
Activation. (These calls are useful for logging and debugging
purposes.)
The full Knowlege Runtime API is exposed through another predefined
variable, kcontext, of type KnowledgeContext. Its
method getKnowledgeRuntime() delivers an object of type
KnowledgeRuntime, which, in turn, provides access to a wealth
of methods, many of which are quite useful for coding RHS logic.
The call kcontext.getKnowledgeRuntime().halt()
terminates rule execution immediately.
The accessor getAgenda() returns a reference
to this session's Agenda, which in turn provides access
to the various rule groups: activation groups, agenda groups, and
rule flow groups. A fairly common paradigm is the activation of some
agenda group, which could be done with the lengthy call:
// give focus to the agenda group CleanUp kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
(You can achieve the same using drools.setFocus( "CleanUp" ).)
To run a query, you call getQueryResults(String query),
whereupon you may process the results, 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.
This language extension provides a structured approach to fact updates. It combines the update operation with a number of setter calls to change the object's fields. This is the syntax schema for the <kw>modify</kw> statement:
<kw>modify ( </kw><fact-expression><kw> ) {</kw><expression>[ <kw>,</kw><expression>]* <kw>}</kw>
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.
The example illustrates a simple fact modification.
Example 4.64. A modify statement
rule "modify stilton"
when
$stilton : Cheese(type == "stilton")
then
modify( $stilton ){
setPrice( 20 ),
setAge( "overripe" )
}
endDrools attempts to preserve numbers in their primitive or object wrapper form, so a variable bound to an int primitive when used in a code block or expression will no longer need manual unboxing; unlike Drools 3.0 where all primitives were autoboxed, requiring manual unboxing. A variable bound to an object wrapper will remain as an object; the existing JDK 1.5 and JDK 5 rules to handle auto-boxing and unboxing apply in this case. When evaluating field constraints, the system attempts to coerce one of the values into a comparable format; so a primitive is comparable to an object wrapper.
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 4.65. Query People over the age of 30
query "people over the age of 30"
person : Person( age > 30 )
endExample 4.66. 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 )
endWe 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 4.67. 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" );
}As mentioned previously, (or DSLs) are a way of extending the rule language to your problem domain. They are wired in to the rule language for you, and can make use of all the underlying rule language and engine features.
DSLs are used both in the IDE, as well as the web based BRMS. Of course as rules are text, you can use them even without this tooling.
DSLs can serve as a layer of separation between rule authoring (and rule authors) and the domain objects that the engine operates on. DSLs can also act as "templates" of conditions or actions that are used over and over in your rules, perhaps only with parameters changing each time. If your rules need to be read and validated by less technical folk, (such as Business Analysts) the DSLs are definitely for you. If the conditions or consequences of your rules follow similar patterns which you can express in a template. You wish to hide away your implementation details, and focus on the business rule. You want to provide a controlled means of editing rules based on pre-defined templates.
DSLs have no impact on the rules at runtime, they are just a parse/compile time feature.
Note that Drools 4 DSLs are quite different from Drools 2 XML based DSLs. It is still possible to do Drools 2 style XML languages - if you require this, then take a look at the Drools 4 XML rule language, and consider using XSLT to map from your XML language to the Drools 4 XML language.
A DSL's configuration like most things is stored in plain text. If you use the IDE, you get a nice graphical editor (with some validation), but the format of the file is quite simple, and is basically a properties file.
Note that since Drools 4.0, DSLs have become more powerful in allowing you to customise almost any part of the language, including keywords. Regular expressions can also be used to match words/sentences if needed (this is provided for enhanced localisation). However, not all features are supported by all the tools (although you can use them, the content assistance just may not be 100% accurate in certain cases).
Referring to the above example, the [when] refers to the scope of the expression: ie does it belong on the LHS or the RHS of a rule. The part after the [scope] is the expression that you use in the rule (typically a natural language expression, but it doesn't have to be). The part on the right of the "=" is the mapping into the rule language (of course the form of this depends on if you are talking about the RHS or the LHS - if its the LHS, then its the normal LHS syntax, if its the RHS then its fragments of Java code for instance).
The parser will take the expression you specify, and extract the values that match where the {something} (named Tokens) appear in the input. The values that match the tokens are then interpolated with the corresponding {something} (named Tokens) on the right hand side of the mapping (the target expression that the rule engine actually uses).
Note also that the "sentences" above can be regular expressions. This means the parser will match the sentence fragements that match the expressions. This means you can use (for instance) the '?' to indicate the character before it is optional (think of each sentence as a regular expression pattern - this means if you want to use regex characters - you will need to escape them with a '\' of course.
It is important to note that the DSL expressions are processed one line at a time. This means that in the above example, all the text after "This is " to the end of the line will be included as the value for "{something}" when it is interpolated into the target string. This may not be exactly what you want, as you may want to "chain" together different DSL expressions to generate a target expression. The best way around this is to make sure that the {tokens} are enclosed with characters or words. This means that the parser will scan along the sentence, and pluck out the value BETWEEN the characters (in the example below they are double-quotes). Note that the characters that surround the token are not included in when interpolating, just the contents between them (rather then all the way to the end of the line, as would otherwise be the case).
As a rule of thumb, use quotes for textual data that a rule editor may want to enter. You can also wrap words around the {tokens} to make sure you enclose the data you want to capture (see other example).
Example 4.69. Example with quotes
[when]This is "{something}" and "{another}"=Something(something=="{something}", another=="{another}")
[when]This is {also} valid=Another(something=="{also}")It is a good idea to try and avoid punctuation in your DSL expressions where possible, other then quotes and the like - keep it simple and things will be easier. Using a DSL can make debugging slightly harder when you are first building rules, but it can make the maintenance easier (and of course the readability of the rules).
The "{" and "}" characters should only be used on the left hand side of the mapping (the expression) to mark tokens. On the right hand side you can use "{" and "}" on their own if needed - such as
if (foo) \{
doSomething();\ }as well as with the token names as shown above.
PLEASE NOTE that if you want curly braces to appear literally as curly braces, then escape them with a backslack (\). Otherwise it may think it is a token to be replaced.
Don't forget that if you are capturing strings from users, you will also need the quotes on the right hand side of the mapping, just like a normal rule, as the result of the mapping must be a valid expression in the rule language.
Example 4.70. Some more examples
#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 = andReferring to the above examples, this would render the following input as shown below:
Example 4.71. Some examples as processed
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 "atlanta"
---> Person(name="kitty") and Person(age > 30, location="atlanta")A good way to get started if you are new to Rules (and DSLs) is just write the rules as you normally would against your object model. You can unit test as you go (like a good agile citizen!). Once you feel comfortable, you can look at extracting a domain language to express what you are doing in the rules. Note that once you have started using the "expander" keyword, you will get errors if the parser does not recognize expressions you have in there - you need to move everything to the DSL. As a way around this, you can prefix each line with ">" and it will tell the parser to take that line literally, and not try and expand it (this is handy also if you are debugging why something isn't working).
Also, it is better to rename the extension of your rules file from ".drl" to ".dslr" when you start using DSLs, as that will allow the IDE to correctly recognize and work with your rules file.
As you work through building up your DSL, you will find that the DSL configuration stabilizes pretty quickly, and that as you add new rules and edit rules you are reusing the same DSL expressions over and over. The aim is to make things as fluent as possible.
To use the DSL when you want to compile and run the rules, you will need to pass the DSL configuration source along with the rule source.
PackageBuilder builder = new PackageBuilder(); builder.addPackageFromDrl( source, dsl ); //source is a reader for the rule source, dsl is a reader for the DSL configuration
You will also need to specify the expander by name in the rule source file:
expander your-expander.dsl
Typically you keep the DSL in the same directory as the rule, but this is not required if you are using the above API (you only need to pass a reader). Otherwise everything is just the same.
You can chain DSL expressions together on one line, as long as it is clear to the parser what the {tokens} are (otherwise you risk reading in too much text until the end of the line). The DSL expressions are processed according to the mapping file, top to bottom in order. You can also have the resulting rule expressions span lines - this means that you can do things like:
Example 4.72. Chaining DSL Expressions
There is a person called Bob who is happy Or There is a person called Mike who is sad
Of course this assumes that "Or" is mapped to the "or" conditional element (which is a sensible thing to do).
A common requirement when writing rule conditions is to be able to add many constraints to fact declarations. A fact may have many (dozens) of fields, all of which could be used or not used at various times. To come up with every combination as separate DSL statements would in many cases not be feasible.
The DSL facility allows you to achieve this however, with a simple convention. If your DSL expression starts with a "-", then it will be assumed to be a field constraint, which will be added to the declaration that is above it (one per line).
This is easier to explain with an example. Lets take look at Cheese class, with the following fields: type, price, age, country. We can express some LHS condition in normal DRL like the following
Cheese(age < 5, price == 20, type=="stilton", country=="ch")
If you know ahead of time that you will use all the fields, all the time, it is easy to do a mapping using the above techniques. However, chances are that you will have many fields, and many combinations. If this is the case, you can setup your mappings like so:
[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}'IMPORTANT: It is NOT possible to use the "-" feature after an accumulate statement to add constraints to the accumulate pattern. This limitation will be removed in the future.
You can then write rules with conditions like the following:
There is a Cheese with
- age is less than 42
- type is 'stilton'The parser will pick up the "-" lines (they have to be on their own line) and add them as constraints to the declaration above. So in this specific case, using the above mappings, is the equivalent to doing (in DRL):
Cheese(age<42, type=='stilton')
The parser will do all the work for you, meaning you just define mappings for individual constraints, and can combine them how you like (if you are using context assistant, if you press "-" followed by CTRL+space it will conveniently provide you with a filtered list of field constraints to choose from.
To take this further, after alter the DSL to have [when][org.drools.Cheese]- age is less than {age} ... (and similar to all the items in the example above).
The extra [org.drools.Cheese] indicates that the sentence only applies for the main constraint sentence above it (in this case "There is a Cheese with"). For example, if you have a class called "Cheese" - then if you are adding contraints to the rule (by typing "-" and waiting for content assistance) then it will know that only items marked as having an object-scope of "com.yourcompany.Something" are valid, and suggest only them. This is entirely optional (you can leave out that section if needed - OR it can be left blank).
DSLs kick in when the rule is parsed. The DSL configuration is read and supplied to the parser, so the parser can "expand" the DSL expressions into the real rule language expressions.
When the parser is processing the rules, it will check if an "expander" representing a DSL is enabled, if it is, it will try to expand the expression based on the context of where it is the rule. If an expression can not be expanded, then an error will be added to the results, and the line number recorded (this insures against typos when editing the rules with a DSL). At present, the DSL expander is fairly space sensitive, but this will be made more tolerant in future releases (including tolerance for a wide range of punctuation).
The expansion itself works by trying to match a line against the expression in the DSL configuration. The values that correspond to the token place holders are stored in a map based on the name of the token, and then interpolated to the target mapping. The values that match the token placeholders are extracted by either searching until the end of the line, or until a character or word after the token place holder is matched. The "{" and "}" are not included in the values that are extracted, they are only used to demarcate the tokens - you should not use these characters in the DSL expression (but you can in the target).
DSLs can be aid with capturing rules if the rules are well known, just not in any technically usable format (ie. sitting around in people brains). Until we are able to have those little sockets in our necks like in the Matrix, our means of getting stuff into computers is still the old fashioned way.
Rules engines require an object or a data model to operate on - in many cases you may know this up front. In other cases the model will be discovered with the rules. In any case, rules generally work better with simpler flatter object models. In some cases, this may mean having a rule object model which is a subset of the main applications model (perhaps mapped from it). Object models can often have complex relationships and hierarchies in them - for rules you will want to simplify and flatten the model where possible, and let the rule engine infer relationships (as it provides future flexibility). As stated previously, DSLs can have an advantage of providing some insulation between the object model and the rule language.
Coming up with a DSL is a collaborative approach for both technical and domain experts. Historically there was a role called "knowledge engineer" which is someone skilled in both the rule technology, and in capturing rules. Over a short period of time, your DSL should stabilize, which means that changes to rules are done entirely using the DSL. A suggested approach if you are starting from scratch is the following workflow:
Capture rules as loose "if then" statements - this is really to get an idea of size and complexity (possibly in a text document).
Look for recurring statements in the rules captured. Also look for the rule objects/fields (and match them up with what may already be known of the object model).
Create a new DSL, and start adding statements from the above steps. Provide the "holes" for data to be edited (as many statements will be similar, with only some data changing).
Use the above DSL, and try to write the rules just like that appear in the "if then" statements from the first and second steps. Iterate this process until patterns appear and things stabilize. At this stage, you are not so worried about the rule language underneath, just the DSL.
At this stage you will need to look at the Objects, and the Fields that are needed for the rules, reconcile this with the datamodel so far.
Map the DSL statements to the rule language, based on the object model. Then repeat the process. Obviously this is best done in small steps, to make sure that things are on the right track.
If you are editing the DSL with the GUI, or as text, you will notice there is a [scope] item at the start of each mapping line. This indicates if the sentence/word applies to the LHS, RHS or is a keyword. Valid values for this are [condition], [consequence] and [keyword] (with [when] and [then] being the same as [condition] and [consequence] respectively). When [keyword] is used, it means you can map any keyword of the language like "rule" or "end" to something else. Generally this is only used when you want to have a non English rule language (and you would ideally map it to a single word).
You can use DSLs in the BRMS in both guided editor rules, and textual rules that use a dsl. (In fact, the same applies to the IDE).
In the guided editor - the DSLs generally have to be simpler - what you are doing is defining little "forms" to capture data from users in text fields (ie as you pick a DSL expression - it will add an item to the GUI which only allows you enter data in the {token} parts of a DSL expression). You can not use sophisticated regular expressions to match text. However, in textual rules (which have a .dslr extension in the IDE) you are free to use the full power as needed.
In the BRMS - when you build a package the DSLs are already included and all the work is done for you. In the IDE (or in any IDE) - you will either need to use the drools-ant task, or otherwise use the code shown in sections above.
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.
There are several scenarios that XML is desirable. However, we recommend that it is not a default choice, as XML is not readily human readable (unless you like headaches) and can create visually bloated rules.
If you do want to edit XML by hand, use a good schema aware editor that provides nice hierarchical views of the XML, ideally visually (commercial tools like XMLSpy, Oxygen etc are good, but cost money, but then so do headache tablets).
Other scenarios where you may want to use the XML format are if you have a tool that generates rules from some input (programmatically generated rules), or perhaps interchange from another rule language, or from another tool that emits XML (using XSLT you can easily transform between XML formats). Note you can always generate normal DRL as well.
Alternatively you may be embedding Drools in a product that already uses XML for configuration, so you would like the rules to be in an XML format. You may be creating your own rule language on XML - note that you can always use the AST objects directly to create your own rule language as well (the options are many, due to the open architecture).
A full W3C standards (XMLSchema) compliant XSD is provided that describes the XML language, which will not be repeated here verbatim. A summary of the language follows.
<?xml version="1.0" encoding="UTF-8"?>
<package name="com.sample"
xmlns="http://drools.org/drools-4.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/drools-4.0 drools-4.0.xsd">
<import name="java.util.HashMap" />
<import name="org.drools.*" />
<global identifier="x" type="com.sample.X" />
<global identifier="yada" type="com.sample.Yada" />
<function return-type="void" name="myFunc">
<parameter identifier="foo" type="Bar" />
<parameter identifier="bada" type="Bing" />
<body>
System.out.println("hello world");
</body>
</function>
<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="foo2" object-type="Bar" >
<or-constraint-connective>
<and-constraint-connective>
<field-constraint field-name="a">
<or-restriction-connective>
<and-restriction-connective>
<literal-restriction evaluator=">" value="60" />
<literal-restriction evaluator="<" value="70" />
</and-restriction-connective>
<and-restriction-connective>
<literal-restriction evaluator="<" value="50" />
<literal-restriction evaluator=">" value="55" />
</and-restriction-connective>
</or-restriction-connective>
</field-constraint>
<field-constraint field-name="a3">
<literal-restriction evaluator="==" value="black" />
</field-constraint>
</and-constraint-connective>
<and-constraint-connective>
<field-constraint field-name="a">
<literal-restriction evaluator="==" value="40" />
</field-constraint>
<field-constraint field-name="a3">
<literal-restriction evaluator="==" value="pink" />
</field-constraint>
</and-constraint-connective>
<and-constraint-connective>
<field-constraint field-name="a">
<literal-restriction evaluator="==" value="12"/>
</field-constraint>
<field-constraint field-name="a3">
<or-restriction-connective>
<literal-restriction evaluator="==" value="yellow"/>
<literal-restriction evaluator="==" value="blue" />
</or-restriction-connective>
</field-constraint>
</and-constraint-connective>
</or-constraint-connective>
</pattern>
<not>
<pattern object-type="Person">
<field-constraint field-name="likes">
<variable-restriction evaluator="==" identifier="type"/>
</field-constraint>
</pattern>
<exists>
<pattern object-type="Person">
<field-constraint field-name="likes">
<variable-restriction evaluator="==" identifier="type"/>
</field-constraint>
</pattern>
</exists>
</not>
<or-conditional-element>
<pattern identifier="foo3" object-type="Bar" >
<field-constraint field-name="a">
<or-restriction-connective>
<literal-restriction evaluator="==" value="3" />
<literal-restriction evaluator="==" value="4" />
</or-restriction-connective>
</field-constraint>
<field-constraint field-name="a3">
<literal-restriction evaluator="==" value="hello" />
</field-constraint>
<field-constraint field-name="a4">
<literal-restriction evaluator="==" value="null" />
</field-constraint>
</pattern>
<pattern identifier="foo4" object-type="Bar" >
<field-binding field-name="a" identifier="a4" />
<field-constraint field-name="a">
<literal-restriction evaluator="!=" value="4" />
<literal-restriction evaluator="!=" value="5" />
</field-constraint>
</pattern>
</or-conditional-element>
<pattern identifier="foo5" object-type="Bar" >
<field-constraint field-name="b">
<or-restriction-connective>
<return-value-restriction evaluator="==" >a4 + 1</return-value-restriction>
<variable-restriction evaluator=">" identifier="a4" />
<qualified-identifier-restriction evaluator="==">
org.drools.Bar.BAR_ENUM_VALUE
</qualified-identifier-restriction>
</or-restriction-connective>
</field-constraint>
</pattern>
<pattern identifier="foo6" object-type="Bar" >
<field-binding field-name="a" identifier="a4" />
<field-constraint field-name="b">
<literal-restriction evaluator="==" value="6" />
</field-constraint>
</pattern>
</lhs>
<rhs>
if ( a == b ) {
assert( foo3 );
} else {
retract( foo4 );
}
System.out.println( a4 );
</rhs>
</rule>
</package>
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 4.74. 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.
The Drools 2.x legacy XML format is no longer supported by Drools XML parser
Drools comes with some utility classes to transform between formats. This works by parsing the rules from the source format into the AST, and then "dumping" out to the appropriate target format. This allows you, for example, to write rules in DRL, and when needed, export to XML if necessary at some point in the future.
The classes to look at if you need to do this are:
XmlDumper - for exporting XML. DrlDumper - for exporting DRL. DrlParser - reading DRL. XmlPackageReader - reading XML.
Using combinations of the above, you can convert between any format (including round trip). Note that DSLs will not be preserved (from DRLs that are using a DSL) - but they will be able to be converted.
Feel free to make use of XSLT to provide all sorts of possibilities for XML, XSLT and its ilk are what make XML powerful.