Chapter 8. Working With Rules
8.1. About Rule Files Copy linkLink copied to clipboard!
8.1.1. Rule File Copy linkLink copied to clipboard!
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.
8.1.2. Structure of Rule Files Copy linkLink copied to clipboard!
The overall structure of a rule file is the following:
Example 8.1. Rule File
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.
8.2. Operating on Facts Copy linkLink copied to clipboard!
Facts are domain model objects that BRMS uses to evaluate conditions and execute consequences. A rule specifies that when a particular set of conditions occur, then the specified list of actions must be executed. The inference engine matches facts against rules, and when matches are found, rule actions are placed on the agenda. The agenda is the place where rules are queued ready to have their actions fired. The rule engine then determines which eligible rules on the agenda must fire.
8.2.1. Accessing Working Memory Copy linkLink copied to clipboard!
The working memory is a stateful object that provides temporary storage and enables manipulation of facts. The working memory includes an API that contains methods which enable access to the working memory from rule files. The available methods are:
update(OBJECT, HANDLE)Used to inform the engine that an object has changed and rules can need to be reconsidered.
update(OBJECT)This method causes
KieSessionto search for a fact handle of the passed object using an identity check. You do not have to call this method when the object changes if property change listeners are provided. For more infomartion, see Section 8.12.15, “Fine Grained Property Change Listeners”.If field values of a fact have changed, call this method or use the
modifykeyword before changing another fact to avoid issues with indexing within the engine.insert(OBJECT)Used to place a new object into the working memory.
insertLogical(OBJECT)This method is similar to the
insertmethod. The newly inserted object is automatically retracted from the working memory if there are no more facts supporting the truth of the rule that inserted the fact.retract(HANDLE)Used to remove an object from the working memory. This method is mapped to the
deletemethod inKieSession.halt()Used to terminate a rule execution immediately. Calling
fireUntilHalt()causes continuous firing of the rules. To stop the firing, callhalt().getKieRuntime()The whole KIE API is exposed through a predefined
kcontextvariable of typeRuleContext. The inheritedgetKieRuntime()method returns aKieRuntimeobject that provides access to various methods, many of which are useful for coding the rule logic.For example, calling
kcontext.getKieRuntime().halt()terminates a rule execution immediately.
8.3. Using Rule Keywords Copy linkLink copied to clipboard!
8.3.1. Hard Keywords Copy linkLink copied to clipboard!
Hard keywords are words which you cannot use when naming your domain objects, properties, methods, functions, and other elements that are used in the rule text. The hard keywords are true, false, and null.
8.3.2. Soft Keywords Copy linkLink copied to clipboard!
Soft keywords can be used for naming domain objects, properties, methods, functions, and other elements. The rules engine recognizes their context and processes them accordingly.
8.3.3. List of Soft Keywords Copy linkLink copied to clipboard!
Rule attributes can be both simple and complex properties that provide a way to influence the behavior of the rule. They are usually written as one attribute per line and can be optional to the rule. Listed below are various rule attributes:
Figure 8.1. Rule Attributes
- no-loop BOOLEAN
When a rule’s consequence modifies a fact, it may cause the rule to activate again, causing an infinite loop. Setting
no-looptotruewill skip the creation of another activation for the rule with the current set of facts.Default value:
false.- lock-on-active BOOLEAN
Whenever a
ruleflow-groupbecomes active or anagenda-groupreceives the focus, any rule within that group that haslock-on-activeset totruewill not be activated any more. Regardless of the origin of the update, the activation of a matching rule is discarded. This is a stronger version ofno-loopbecause the change is not only caused by the rule itself. It is ideal for calculation rules where you have a number of rules that modify a fact, and you do not want any rule re-matching and firing again. Only when theruleflow-groupis no longer active or theagenda-grouploses the focus, those rules withlock-on-activeset totruebecome eligible again for their activations to be placed onto the agenda.Default value:
false.- salience INTEGER
Each rule has an integer salience attribute 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:
0.Red Hat JBoss BRMS also supports dynamic salience where you can use an expression involving bound variables like the following:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - ruleflow-group STRING
-
Ruleflow is a BRMS feature that lets you exercise control over the firing of rules. Rules that are assembled by the same
ruleflow-groupidentifier fire only when their group is active. This attribute has been merged withagenda-groupand the behaviours are basically the same. - agenda-group STRING
Agenda groups enable you to partition the agenda, which provides more execution control. Only rules in the agenda group that have acquired the focus are allowed to fire. This attribute has been merged with
ruleflow-groupand the behaviours are basically the same.Default value:
MAIN.- auto-focus BOOLEAN
When a rule is activated where the
auto-focusvalue istrueand the rule’s agenda group does not have focus yet, it is automatically given focus, allowing the rule to potentially fire.Default value:
false.- activation-group STRING
-
Rules that belong to the same
activation-groupidentified by this attribute’s String value, will only fire exclusively. More precisely, the first rule in anactivation-groupto fire will cancel all pending activations of all rules in the group, for example stop them from firing. - dialect STRING
Java and MVEL are the possible values of the
dialectattribute. This attribute specifies the language to be used for any code expressions in the LHS or the RHS code block. While thedialectcan be specified at the package level, this attribute allows the package definition to be overridden for a rule.Default value: specified by the package.
- date-effective STRING
A rule can only activate if the current date and time is after the
date-effectiveattribute. Note that STRING is a date and time definition. An exampledate-effectiveattribute is displayed below:Copy to Clipboard Copied! Toggle word wrap Toggle overflow - date-expires STRING
A rule cannot activate if the current date and time is after the
date-expiresattribute. Note that STRING is a date and time definition. An exampledate-expiresattribute is displayed below:Copy to Clipboard Copied! Toggle word wrap Toggle overflow - duration LONG
-
If a rule is still
true, thedurationattribute will dictate that the rule will fire after a specified duration.
The attributes ruleflow-group and agenda-group have been merged and now behave the same. The GET methods have been left the same, for deprecations reasons, but both attributes return the same underlying data.
8.4. Adding Comments to Rule File Copy linkLink copied to clipboard!
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 a rule’s RHS).
8.4.1. Single Line Comment Example Copy linkLink copied to clipboard!
This is what a single line comment looks like. To create single line comments, you can use //. The parser will ignore anything in the line after the comment symbol:
8.4.2. Multi-Line Comment Example Copy linkLink copied to clipboard!
This is what a multi-line comment looks like. This configuration comments out blocks of text, both in and outside semantic code blocks:
8.5. Error Messages in Rules Copy linkLink copied to clipboard!
Red Hat JBoss BRMS provides standardized error messages. This standardization aims to help users to find and resolve problems in a easier and faster way.
8.5.1. Error Message Format Copy linkLink copied to clipboard!
This is the standard error message format.
Figure 8.2. Error Message Format 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.
8.5.2. Error Message Description Copy linkLink copied to clipboard!
- [ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
Indicates when the parser came to a decision point but couldn’t identify an alternative. For example:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 101] Line 3:2 no viable alternative at input 'WHEN
This message means the parser has encountered the token
WHEN(a hard keyword) which is in the wrong place, since the rule name is missing. For example:Copy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule simple_rule in pattern [name]
Indicates an open quote, apostrophe or parentheses. For example:
1: rule simple_rule 2: when 3: Student(name == "Andy) 4: then 5: end
1: rule simple_rule 2: when 3: Student(name == "Andy) 4: then 5: endCopy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern Bar
Indicates that the parser was looking for a particular symbol that it didn’t end at the current input position.
1: rule simple_rule 2: when 3: foo3 : Bar(
1: rule simple_rule 2: when 3: foo3 : Bar(Copy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern [name]
This error is the result of an incomplete rule statement. Usually when you get a 0:-1 position, it means that parser reached the end of source. To fix this problem, it is necessary to complete the rule statement.
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 103] Line 7:0 rule 'rule_key' failed predicate: {(validateIdentifierKey( DroolsSoftKeywords.RULE ))}? in rule
A validating semantic predicate evaluated to false. Usually these semantic predicates are used to identify soft keywords.
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 104] Line 3:4 trailing semi-colon not allowed in rule simple_rule
This error is associated with the
evalclause, where its expression may not be terminated with a semicolon. This problem is simple to fix: just remove the semi-colon.1: rule simple_rule 2: when 3: eval(abc();) 4: then 5: end
1: rule simple_rule 2: when 3: eval(abc();) 4: then 5: endCopy to Clipboard Copied! Toggle word wrap Toggle overflow - [ERR 105] Line 2:2 required (…)+ loop did not match anything at input 'aa' in template test_error
The recognizer came to a subrule in the grammar that must match an alternative at least once, but the subrule did not match anything. 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.
1: template test_error 2: aa s 11; 3: end
1: template test_error 2: aa s 11; 3: endCopy to Clipboard Copied! Toggle word wrap Toggle overflow
8.6. Packaging Copy linkLink copied to clipboard!
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, such as HR rules. 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). 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).
8.6.1. Import Statements Copy linkLink copied to clipboard!
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. Red Hat JBoss BRMS automatically imports classes from the Java package of the same name, and also from the package java.lang.
8.6.2. Using Globals Copy linkLink copied to clipboard!
In DRL files, globals represent global variables. To use globals in rules:
Declare the global variable:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow Set the global value in the working memory. The best practice is to set all global values before asserting any fact into the working memory. For example:
List list = new ArrayList(); KieSession kieSession = kieBase.newKieSession(); kieSession.setGlobal("myGlobalList", list);List list = new ArrayList(); KieSession kieSession = kieBase.newKieSession(); kieSession.setGlobal("myGlobalList", list);Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.6.3. From Element Copy linkLink copied to clipboard!
The from element allows you to pass a Hibernate session as a global. It also lets you pull data from a named Hibernate query.
8.6.4. Using Globals with E-Mail Service Copy linkLink copied to clipboard!
Procedure: Task
- Open the integration code that is calling the rule engine.
- Obtain your emailService object and then set it in the working memory.
-
In the DRL, declare that you have a global of type emailService and give it the name
email. In your rule consequences, you can use things like
email.sendSMS(number, message).WarningGlobals 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.
ImportantDo not set or change a global value from inside the rules. We recommend to you always set the value from your application using the working memory interface.
8.7. Functions in Rules Copy linkLink copied to clipboard!
Functions are a way to put semantic code in a rule source file, as opposed to in normal Java classes. The main advantage of using functions in a rule is that you can keep the logic all in one place. You can change the functions as needed.
Functions are most useful for invoking actions on the consequence (then) part of a rule, especially if that particular action is used repeatedly.
A typical function declaration looks like the following:
function String hello(String name) {
return "Hello " + name + "!";
}
function String hello(String name) {
return "Hello " + name + "!";
}
Note that the function keyword is used, even though it is not technically part of Java. Parameters to the function are defined as for a method. You do not have to have parameters if they are not needed. The return type is defined just like in a regular method.
8.7.1. Importing Static Method Example Copy linkLink copied to clipboard!
In the following example, a static method Foo.hello() from a helper class is imported as a function. To import a method, enter the following into your DRL file:
import function my.package.Foo.hello
import function my.package.Foo.hello
8.7.2. Calling Function Declaration Example Copy linkLink copied to clipboard!
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. This is shown below:
8.7.3. Type Declarations Copy linkLink copied to clipboard!
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.
| Role | Description |
|---|---|
| Declaring new types | Red Hat JBoss BRMS uses plain Java objects as facts out of the box. However, if you wish to define the model directly to the rules engine, you can do so by declaring a new type. You can also declare a new type when there is a domain model already built and you want 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. |
8.7.4. Declaring New Types Copy linkLink copied to clipboard!
To declare a new type, the keyword declare is used, followed by the list of fields and the keyword end. A new fact must have a list of fields, otherwise the engine will look for an existing fact class in the classpath and raise an error if not found.
8.7.5. Declaring New Fact Type Example Copy linkLink copied to clipboard!
In this example, a new fact type called Address is used. 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 other fact types previously declared:
declare Address number : int streetName : String city : String end
declare Address
number : int
streetName : String
city : String
end
8.7.6. Declaring New Fact Type Additional Example Copy linkLink copied to clipboard!
This fact type declaration uses a Person example. dateOfBirth is of the type java.util.Date (from the Java API) and address is of the fact type Address.
declare Person name : String dateOfBirth : java.util.Date address : Address end
declare Person
name : String
dateOfBirth : java.util.Date
address : Address
end
8.7.7. Using Import Example Copy linkLink copied to clipboard!
To avoid using fully qualified class names, use the import statement:
8.7.8. Generated Java Classes Copy linkLink copied to clipboard!
When you declare a new fact type, Red Hat JBoss BRMS generates bytecode that implements a Java class representing the fact type. The generated Java class is a one-to-one Java Bean mapping of the type definition.
8.7.9. Generated Java Class Example Copy linkLink copied to clipboard!
This is an example of a generated Java class using the Person fact type:
8.7.10. Using Declared Types in Rules Example Copy linkLink copied to clipboard!
Since the generated class is a simple Java class, it can be used transparently in the rules like any other fact:
8.7.11. Declaring Metadata Copy linkLink copied to clipboard!
Metadata may be assigned to several different constructions in Red Hat JBoss BRMS, such as fact types, fact attributes and rules. Red Hat JBoss BRMS uses the at sign (@) to introduce metadata and it always uses the form:
@metadata_key(metadata_value)
@metadata_key(metadata_value)
The parenthesized metadata_value is optional.
8.7.12. Working with Metadata Attributes Copy linkLink copied to clipboard!
Red Hat JBoss BRMS allows the declaration of any arbitrary metadata attribute. Some have special meaning to the engine, while others are available for querying at runtime. Red Hat JBoss BRMS allows the declaration of metadata both for fact types and for fact attributes. Any metadata that is declared before the attributes of a fact type are assigned to the fact type, while metadata declared after an attribute are assigned to that particular attribute.
8.7.13. Declaring Metadata Attribute with Fact Types Example Copy linkLink copied to clipboard!
This is an example of declaring metadata attributes for fact types and attributes. There are two metadata items declared for the fact type (@author and @dateOfCreation) and two more defined for the name attribute (@key and @maxLength). The @key metadata has no required value, and so the parentheses and the value were omitted:
8.7.14. @position Attribute Copy linkLink copied to clipboard!
The @position attribute can be used to declare the position of a field, overriding the default declared order. This is used for positional constraints in patterns.
8.7.15. @position Example Copy linkLink copied to clipboard!
This is what the @position attribute looks like in use:
declare Cheese name : String @position(1) shop : String @position(2) price : int @position(0) end
declare Cheese
name : String @position(1)
shop : String @position(2)
price : int @position(0)
end
8.7.16. Predefined Class Level Annotations Copy linkLink copied to clipboard!
- @role( <fact\|event>)
- This attribute can be used to assign roles to facts and events.
- @typesafe(<boolean>)
-
By default, all type declarations are compiled with type safety enabled.
@typesafe(false)provides a means to override this behavior by permitting a fall-back, to type unsafe evaluation where all constraints are generated as MVEL constraints and executed dynamically. This is useful when dealing with collections that do not have any generics or mixed type collections. - @timestamp(<attribute name>)
- Creates a timestamp.
- @duration(<attribute name>)
- Sets a duration for the implementation of an attribute.
- @expires(<time interval>)
- Allows you to define when the attribute should expire.
- @propertyChangeSupport
- Facts that implement support for property changes as defined in the Javabean spec can now be annotated so that the engine register itself to listen for changes on fact properties.
- @propertyReactive
- Makes the type property reactive.
8.7.17. @key Attribute Functions Copy linkLink copied to clipboard!
Declaring an attribute as a key attribute has two major effects on generated types:
-
The attribute is used as a key identifier for the type, and thus the generated class implements the
equals()andhashCode()methods taking the attribute into account when comparing instances of this type. - Red Hat JBoss BRMS generates a constructor using all the key attributes as parameters.
8.7.18. @key Declaration Example Copy linkLink copied to clipboard!
This is an example of @key declarations for a type. Red Hat JBoss BRMS generates equals() and hashCode() methods that checks the firstName and lastName attributes to determine if two instances of Person are equal to each other. It does not check the age attribute. It also generates a constructor taking firstName and lastName as parameters:
declare Person firstName : String @key lastName : String @key age : int end
declare Person
firstName : String @key
lastName : String @key
age : int
end
8.7.19. Creating Instance with Key Constructor Example Copy linkLink copied to clipboard!
This is what creating an instance using the key constructor looks like:
Person person = new Person("John", "Doe");
Person person = new Person("John", "Doe");
8.7.20. Positional Arguments Copy linkLink copied to clipboard!
Patterns support positional arguments on type declarations and are defined by the @position attribute.
Positional arguments are when you do not need to specify the field name, as the position maps to a known named field. That is, Person(name == "mark") can be rewritten as Person("mark";). The semicolon ; is important so that the engine knows that everything before it is a positional argument. You can mix positional and named arguments on a pattern by using the semicolon ; to separate them. Any variables used in a positional that have not yet been bound will be bound to the field that maps to that position.
8.7.21. Positional Argument Example Copy linkLink copied to clipboard!
Observe the example below:
declare Cheese name : String shop : String price : int end
declare Cheese
name : String
shop : String
price : int
end
The default order is the declared order, but this can be overridden using @position.
declare Cheese name : String @position(1) shop : String @position(2) price : int @position(0) end
declare Cheese
name : String @position(1)
shop : String @position(2)
price : int @position(0)
end
8.7.22. @position Annotation Copy linkLink copied to clipboard!
The @position annotation can be used to annotate original pojos on the classpath. Currently only fields on classes can be annotated. Inheritance of classes is supported, but not interfaces of methods.
8.7.23. Example Patterns Copy linkLink copied to clipboard!
These example patterns have two constraints and a binding. The semicolon ; is used to differentiate the positional section from the named argument section. Variables and literals and expressions using just literals are supported in positional arguments, but not variables:
Cheese("stilton", "Cheese Shop", p;)
Cheese("stilton", "Cheese Shop"; p : price)
Cheese("stilton"; shop == "Cheese Shop", p : price)
Cheese(name == "stilton"; shop == "Cheese Shop", p : price)
Cheese("stilton", "Cheese Shop", p;)
Cheese("stilton", "Cheese Shop"; p : price)
Cheese("stilton"; shop == "Cheese Shop", p : price)
Cheese(name == "stilton"; shop == "Cheese Shop", p : price)
8.8. Backward-Chaining Copy linkLink copied to clipboard!
8.8.1. Backward-Chaining Systems Copy linkLink copied to clipboard!
Backward-Chaining is a feature recently added to the BRMS Engine. This process is often referred to as derivation queries, and it is not as common compared to reactive systems since BRMS is primarily reactive forward chaining. That is, it responds to changes in your data. The backward-chaining added to the engine is for product-like derivations.
8.8.2. Cloning Transitive Closures Copy linkLink copied to clipboard!
Figure 8.3. Reasoning Graph
The previous chart demonstrates a House example of transitive items. A similar reasoning chart can be created by implementing the following rules:
Configuring Transitive Closures
- First, create some java rules to develop reasoning for transitive items. It inserts each of the locations.
-
Next, create the
Locationclass; it has the item and where it is located. Type the rules for the House example as depicted below:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow A transitive design is created in which the item is in its designated location such as a "desk" located in an "office."
Figure 8.4. Transitive Reasoning Graph of House
Notice compared to the previous graph, there is no "key" item in a "drawer" location. This will become evident in a later topic.
8.8.3. Defining Query Copy linkLink copied to clipboard!
Create a query to search for data inserted into the rule engine:
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) end
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow Note that the query in the example above is recursive, calling
isContainedIn.To see implementation details, create a rule similar to the following for printing each string inserted into the system:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow Create a rule that uses the
isContainedInquery from the first step.Copy to Clipboard Copied! Toggle word wrap Toggle overflow The rule checks whether the item
officeis in the locationhouse. The query created in the first step is triggered when the stringgo1is inserted.Insert a fact into the engine and call
fireAllRules().ksession.insert("go1"); ksession.fireAllRules();ksession.insert("go1"); ksession.fireAllRules();Copy to Clipboard Copied! Toggle word wrap Toggle overflow The output of the engine should look like the following:
go1 office is in the house
go1 office is in the houseCopy to Clipboard Copied! Toggle word wrap Toggle overflow The following holds:
-
The salience ensures that the
gorule is fired first and the message output is printed. -
The
go1rule matches the query andoffice is in the houseis printed.
-
The salience ensures that the
8.8.4. Transitive Closure Example Copy linkLink copied to clipboard!
Creating Transitive Closure
Create a transitive closure by implementing the following rule:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow Recall from the cloning transitive closure topic, there was no instance of "drawer" in "house." "Drawer" was located in "desk."
Figure 8.5. Transitive Reasoning Graph of a Drawer.
Use the previous query for this recursive information.
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) end
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow Create the
go2, insert it into the engine, and call thefireAllRules.ksession.insert( "go2" ); ksession.fireAllRules(); --- go2 Drawer in the House
ksession.insert( "go2" ); ksession.fireAllRules(); --- go2 Drawer in the HouseCopy to Clipboard Copied! Toggle word wrap Toggle overflow When the rule is fired, it correctly tells you
go2has been inserted and that the "drawer" is in the "house."Check how the engine determined this outcome.
- The query has to recurse down several levels to determine this.
-
Instead of using
Location(x, y;), the query uses the value of(z, y;)since "drawer" is not in "house." -
The
zis currently unbound which means it has no value and will return everything that is in the argument. -
yis currently bound to "house," sozwill return "office" and "kitchen." Information is gathered from "office" and checks recursively if the "drawer" is in the "office." The following query line is being called for these parameters:
isContainedIn(x ,z;)There is no instance of "drawer" in "office"; therefore, it does not match. With
zbeing unbound, it will return data that is within the "office", and it will gather thatz == desk.isContainedIn(x==drawer, z==desk)
isContainedIn(x==drawer, z==desk)Copy to Clipboard Copied! Toggle word wrap Toggle overflow isContainedInrecurses three times. On the final recurse, an instance triggers of "drawer" in the "desk".Location(x==drawer, y==desk)
Location(x==drawer, y==desk)Copy to Clipboard Copied! Toggle word wrap Toggle overflow This matches on the first location and recurses back up, so we know that "drawer" is in the "desk", the "desk" is in the "office", and the "office" is in the "house"; therefore, the "drawer" is in the "house" and returns
true.
8.8.5. Reactive Transitive Queries Copy linkLink copied to clipboard!
Creating a Reactive Transitive Query
Create a reactive transitive query by implementing the following rule:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow Reactive transitive queries can ask a question even if the answer can not be satisfied. Later, if it is satisfied, it will return an answer.
NoteRecall from the cloning transitive closures example that there was no
keyitem in the system.Use the same query for this reactive information.
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) end
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow Create the
go3, insert it into the engine, and call thefireAllRules.ksession.insert("go3"); ksession.fireAllRules(); --- go3ksession.insert("go3"); ksession.fireAllRules(); --- go3Copy to Clipboard Copied! Toggle word wrap Toggle overflow -
go3is inserted -
fireAllRules();is called
The first rule that matches any String returns
go3but nothing else is returned because there is no answer; however, whilego3is inserted in the system, it will continuously wait until it is satisfied.-
Insert a new location of "key" in the "drawer":
ksession.insert( new Location("key", "drawer")); ksession.fireAllRules(); --- Key in the Officeksession.insert( new Location("key", "drawer")); ksession.fireAllRules(); --- Key in the OfficeCopy to Clipboard Copied! Toggle word wrap Toggle overflow This new location satisfies the transitive closure because it is monitoring the entire graph. In addition, this process now has four recursive levels in which it goes through to match and fire the rule.
8.8.6. Queries with Unbound Arguments Copy linkLink copied to clipboard!
Creating Unbound Argument Query
Create a query with unbound arguments by implementing the following rule:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow This rule is asking for everything in the "office", and it will tell everything in all the rows below. The unbound argument (out variable
thing) in this example will return every possible value; accordingly, it is very similar to thezvalue used in the reactive transitive query example.Use the query for the unbound arguments.
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) end
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow Create the
go4, insert it into the engine, and call thefireAllRules.Copy to Clipboard Copied! Toggle word wrap Toggle overflow When
go4is inserted, it returns all the previous information that is transitively below "office."
8.8.7. Multiple Unbound Arguments Copy linkLink copied to clipboard!
Creating Multiple Unbound Arguments
Create a query with multiple unbound arguments by implementing the following rule:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow Both
thingandlocationare unbound out variables, and without bound arguments, everything is called upon.Use the query for multiple unbound arguments.
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) end
query isContainedIn(String x, String y) Location(x, y;) or (Location(z, y;) and isContainedIn(x, z;)) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow Create the
go5, insert it into the engine, and call thefireAllRules.Copy to Clipboard Copied! Toggle word wrap Toggle overflow When
go5is called, it returns everything within everything.
8.9. Type Declaration Copy linkLink copied to clipboard!
8.9.1. Declaring Metadata for Existing Types Copy linkLink copied to clipboard!
Red Hat JBoss BRMS 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.
8.9.2. Declaring Metadata for Existing Types Example Copy linkLink copied to clipboard!
This example shows how to declare metadata for an existing type:
8.9.3. Declaring Metadata Using Fully Qualified Class Name Example Copy linkLink copied to clipboard!
This example shows how you can declare metadata using the fully qualified class name instead of using the import annotation:
declare org.drools.examples.Person @author(Bob) @dateOfCreation(01-Feb-2009) end
declare org.drools.examples.Person
@author(Bob)
@dateOfCreation(01-Feb-2009)
end
8.9.4. Parametrized Constructors for Declared Types Example Copy linkLink copied to clipboard!
For a declared type like the following:
declare Person firstName : String @key lastName : String @key age : int end
declare Person
firstName : String @key
lastName : String @key
age : int
end
The compiler will implicitly generate 3 constructors: one without parameters, one with the @key fields and one with all fields.
Person() // parameterless constructor Person(String firstName, String lastName) Person(String firstName, String lastName, int age)
Person() // parameterless constructor
Person(String firstName, String lastName)
Person(String firstName, String lastName, int age)
8.9.5. Non-Typesafe Classes Copy linkLink copied to clipboard!
The @typesafe(BOOLEAN) annotation has been added to type declarations. By default all type declarations are compiled with type safety enabled. @typesafe(false) provides a means to override this behaviour by permitting a fall-back, to type unsafe evaluation where all constraints are generated as MVEL constraints and executed dynamically. This is useful when dealing with collections that do not have any generics or mixed type collections.
8.9.6. Accessing Declared Types from Application Code Copy linkLink copied to clipboard!
Sometimes applications need to access and handle facts from the declared types. In such cases, Red Hat JBoss BRMS provides a simplified API for the most common fact handling the application wishes to do. A declared fact belongs to the package where it is declared.
8.9.7. Declaring Type Copy linkLink copied to clipboard!
This illustrates the process of declaring a type:
8.9.8. Handling Declared Fact Types Through API Example Copy linkLink copied to clipboard!
This example illustrates the handling of declared fact types through the API:
For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies. If you use Red Hat JBoss BRMS, see Embedded Drools Engine Dependencies.
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.
8.9.9. Type Declaration Extends Copy linkLink copied to clipboard!
Type declarations support the extends keyword for inheritance. To extend a type declared in Java by a DRL declared subtype, repeat the supertype in a declare statement without any fields.
8.9.10. Type Declaration Extends Example Copy linkLink copied to clipboard!
This illustrates the use of the extends annotation:
8.9.11. Traits Copy linkLink copied to clipboard!
Traits allow you to model multiple dynamic types which do not fit naturally in a class hierarchy. A trait is an interface that can be applied (and eventually removed) to an individual object at runtime. To create a trait out of an interface, a @format(trait) annotation is added to its declaration in DRL.
8.9.12. Traits Example Copy linkLink copied to clipboard!
In order to apply a trait to an object, the new don keyword is added:
when $c : Customer() then GoldenCustomer gc = don($c, Customer.class); end
when
$c : Customer()
then
GoldenCustomer gc = don($c, Customer.class);
end
8.9.13. Core Objects and Traits Copy linkLink copied to clipboard!
When a core object dons a trait, a proxy class is created on the fly (one such class will be generated lazily for each core/trait class combination). The proxy instance, which wraps the core object and implements the trait interface, is inserted automatically and will possibly activate other rules. An immediate advantage of declaring and using interfaces, getting the implementation proxy for free from the engine, is that multiple inheritance hierarchies can be exploited when writing rules. The core classes, however, need not implement any of those interfaces statically, also facilitating the use of legacy classes as cores. Any object can don a trait. For efficiency reasons, however, you can add the @traitable annotation to a declared bean class to reduce the amount of glue code that the compiler will have to generate. This is optional and will not change the behavior of the engine.
8.9.14. @traitable Example Copy linkLink copied to clipboard!
This illustrates the use of the @traitable annotation:
declare Customer @traitable code : String balance : long end
declare Customer
@traitable
code : String
balance : long
end
8.9.15. Writing Rules with Traits Copy linkLink copied to clipboard!
The only connection between core classes and trait interfaces is at the proxy level. (That is, a trait is not specifically tied to a core class.) This means that the same trait can be applied to totally different objects. For this reason, the trait does not transparently expose the fields of its core object. When writing a rule using a trait interface, only the fields of the interface will be available, as usual. However, any field in the interface that corresponds to a core object field, will be mapped by the proxy class.
8.9.16. Rules with Traits Example Copy linkLink copied to clipboard!
This example illustrates the trait interface being mapped to a field:
8.9.18. Two-Part Proxy Copy linkLink copied to clipboard!
The two-part proxy has been developed to deal with soft and hidden fields which are not processed intuitively. Internally, proxies are formed by a proper proxy and a wrapper. The former implements the interface, while the latter manages the core object fields, implementing a name/value map to supports soft fields. The proxy uses both the core object and the map wrapper to implement the interface, as needed.
8.9.19. Wrappers Copy linkLink copied to clipboard!
The wrapper provides a looser form of typing when writing rules. However, it has also other uses. The wrapper is specific to the object it wraps, regardless of how many traits have been attached to an object. All the proxies on the same object will share the same wrapper. Additionally, the wrapper contains a back-reference to all proxies attached to the wrapped object, effectively allowing traits to see each other.
8.9.20. Wrapper Example Copy linkLink copied to clipboard!
This is an example of using the wrapper:
8.9.21. Wrapper with isA Annotation Example Copy linkLink copied to clipboard!
This illustrates a wrapper in use with the isA annotation:
$sc : GoldenCustomer($maxExpense : maxExpense > 1000, this isA "SeniorCustomer")
$sc : GoldenCustomer($maxExpense : maxExpense > 1000, this isA "SeniorCustomer")
8.9.22. Removing Traits Copy linkLink copied to clipboard!
The business logic may require that a trait is removed from a wrapped object. There are two ways to do so:
- Logical don
Results in a logical insertion of the proxy resulting from the traiting operation.
then don($x, // core object Customer.class, // trait class true // optional flag for logical insertion)then don($x, // core object Customer.class, // trait class true // optional flag for logical insertion)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - The shed keyword
The shed keyword causes the retraction of the proxy corresponding to the given argument type.
then Thing t = shed($x, GoldenCustomer.class)
then Thing t = shed($x, GoldenCustomer.class)Copy to Clipboard Copied! Toggle word wrap Toggle overflow This operation returns another proxy implementing the
org.drools.factmodel.traits.Thinginterface, where thegetFields()andgetCore()methods are defined. Internally, all declared traits are generated to extend this interface (in addition to any others specified). This allows to preserve the wrapper with the soft fields which would otherwise be lost.
8.10. Rule Attributes Copy linkLink copied to clipboard!
For the list of all rule attributes and their description, see Section 8.3.2, “Soft Keywords”.
See an example of rule attributes below:
rule "my rule" salience 42 agenda-group "number-1" when ...
rule "my rule"
salience 42
agenda-group "number-1"
when
...
8.10.1. Timer Attribute Example Copy linkLink copied to clipboard!
This is what the timer attribute looks like:
8.10.2. Timers Copy linkLink copied to clipboard!
The following timers are available in Red Hat JBoss BRMS:
- Interval
-
Interval (indicated by
int:) timers follow the semantics ofjava.util.Timerobjects, with an initial delay and an optional repeat interval. - Cron
-
Cron (indicated by
cron:) timers follow standard Unix cron expressions.
A rule controlled by a timer becomes active when it matches, and once for each individual match. Its consequence is executed repeatedly, according to the timer’s settings. This stops as soon as the condition doesn’t match any more.
Consequences are executed even after control returns from a call to fireUntilHalt. Moreover, the Engine remains reactive to any changes made to the Working Memory. For instance, removing a fact that was involved in triggering the timer rule’s execution causes the repeated execution to terminate, or inserting a fact so that some rule matches will cause that rule to fire. But the Engine is not continually active, only after a rule fires, for whatever reason. Thus, reactions to an insertion done asynchronously will not happen until the next execution of a timer-controlled rule.
Disposing a session puts an end to all timer activity.
8.10.3. Cron Timer Example Copy linkLink copied to clipboard!
This is what the Cron timer looks like:
8.10.4. Calendars Copy linkLink copied to clipboard!
Calendars are used to control when rules can fire. Red Hat JBoss BRMS uses the Quartz calendar.
8.10.5. Quartz Calendar Example Copy linkLink copied to clipboard!
This is what the Quartz calendar looks like:
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
8.10.6. Registering Calendar Copy linkLink copied to clipboard!
Procedure: Task
-
Start a
StatefulKnowledgeSession. Use the following code to register the calendar:
ksession.getCalendars().set("weekday", weekDayCal);ksession.getCalendars().set("weekday", weekDayCal);Copy to Clipboard Copied! Toggle word wrap Toggle overflow If you wish to utilize the calendar and a timer together, use the following code:
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.10.7. Left Hand Side Copy linkLink copied to clipboard!
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 empty, it will be considered as a condition element that is always true and it will be activated once, when a new WorkingMemory session is created.
8.10.8. Conditional Elements Copy linkLink copied to clipboard!
Conditional elements work on one or more patterns. The most common conditional element is and. It is implicit when you have multiple patterns in the LHS of a rule that is not connected in any way.
8.10.9. Rule Without Conditional Element Example Copy linkLink copied to clipboard!
This is what a rule without a conditional element looks like:
8.11. Patterns Copy linkLink copied to clipboard!
A pattern element is the most important conditional element. It can potentially match on each fact that is inserted in the working memory. A pattern contains constraints and has an optional pattern binding.
8.11.1. Pattern Example Copy linkLink copied to clipboard!
This is what a pattern looks like:
An and cannot have a leading declaration binding. This is because a declaration can only reference a single fact at a time, and when the and is satisfied it matches both facts.
8.11.2. Pattern Matching Copy linkLink copied to clipboard!
A pattern matches against a fact of the given type. 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. The constraints are defined inside parentheses.
8.11.3. Pattern Binding Copy linkLink copied to clipboard!
Patterns can be bound to a matching object. This is accomplished using a pattern binding variable such as $p.
8.11.4. Pattern Binding with Variable Example Copy linkLink copied to clipboard!
This is what pattern binding using a variable looks like:
The prefixed dollar symbol ($) is not mandatory.
8.11.5. Constraints Copy linkLink copied to clipboard!
A constraint is an expression that returns true or false. For example, you can have a constraint that states "five is smaller than six".
8.12. Elements and Variables Copy linkLink copied to clipboard!
8.12.1. Property Access on Java Beans (POJOs) Copy linkLink copied to clipboard!
Any bean property can be used directly. A bean property is exposed using a standard Java bean getter: a method getMyProperty() (or isMyProperty() for a primitive boolean) which takes no arguments and return something.
Red Hat JBoss BRMS uses the standard JDK Introspector class to do this mapping, so it follows the standard Java bean specification.
Property accessors must not change the state of the object in a way that may effect the rules. The rule engine effectively caches the results of its matching in between invocations to make it faster.
8.12.2. POJO Example Copy linkLink copied to clipboard!
This is what the bean property looks like:
Person(age == 50) // this is the same as: Person(getAge() == 50)
Person(age == 50)
// this is the same as:
Person(getAge() == 50)
- The age property
-
The age property is written as
agein DRL instead of the gettergetAge(). - Property accessors
-
You can use property access (
age) instead of getters explicitly (getAge()) because of performance enhancements through field indexing.
8.12.3. Working with POJOs Copy linkLink copied to clipboard!
Procedure: Task
Observe the example below:
public int getAge() { Date now = DateUtil.now(); // Do NOT do this. return DateUtil.differenceInYears(now, birthday); }public int getAge() { Date now = DateUtil.now(); // Do NOT do this. return DateUtil.differenceInYears(now, birthday); }Copy to Clipboard Copied! Toggle word wrap Toggle overflow -
To solve this, insert a fact that wraps the current date into working memory and update that fact between
fireAllRulesas needed.
8.12.4. POJO Fallbacks Copy linkLink copied to clipboard!
When working with POJOs, a fallback method is applied. If the getter of a property cannot be found, the compiler will resort to using the property name as a method name and without arguments. Nested properties are also indexed.
8.12.5. Fallback Example Copy linkLink copied to clipboard!
This is what happens when a fallback is implemented:
Person(age == 50) // If Person.getAge() does not exists, this falls back to: Person(age() == 50)
Person(age == 50)
// If Person.getAge() does not exists, this falls back to:
Person(age() == 50)
This is what it looks like as a nested property:
Person(address.houseNumber == 50) // this is the same as: Person(getAddress().getHouseNumber() == 50)
Person(address.houseNumber == 50)
// this is the same as:
Person(getAddress().getHouseNumber() == 50)
In a stateful session, care should be taken when using nested accessors as the Working Memory is not aware of any of the nested values and does not know when they change. Consider them immutable while any of their parent references are inserted into the Working Memory. If you wish to modify a nested value you should mark all of the outer facts as updated. In the above example, when the houseNumber changes, any Person with that Address must be marked as updated.
8.12.6. Java Expressions Copy linkLink copied to clipboard!
| Capability | Example |
|---|---|
|
You can use any Java expression that returns a |
Person(age == 50)
|
| You can change the evaluation priority by using parentheses, as in any logic or mathematical expression. |
Person(age > 100 && (age % 10 == 0))
|
| You can reuse Java methods. |
Person(Math.round(weight / (height * height)) < 25.0)
|
| Type coercion is always attempted if the field and the value are of different types; exceptions will be thrown if a bad coercion is attempted. |
Person(age == "10") // "10" is coerced to 10
|
Methods must not change the state of the object in a way that may affect the rules. Any method executed on a fact in the LHS should be a read only method.
The state of a fact should not change between rule invocations (unless those facts are marked as updated to the working memory on every change):
Person(System.currentTimeMillis() % 1000 == 0) // Do NOT do this.
Person(System.currentTimeMillis() % 1000 == 0) // Do NOT do this.
All operators have normal Java semantics except for == and !=.
The == operator has null-safe equals() semantics:
// Similar to: java.util.Objects.equals(person.getFirstName(), "John") // so (because "John" is not null) similar to: // "John".equals(person.getFirstName()) Person(firstName == "John")
// Similar to: java.util.Objects.equals(person.getFirstName(), "John")
// so (because "John" is not null) similar to:
// "John".equals(person.getFirstName())
Person(firstName == "John")
The != operator has null-safe !equals() semantics:
// Similar to: !java.util.Objects.equals(person.getFirstName(), "John") Person(firstName != "John")
// Similar to: !java.util.Objects.equals(person.getFirstName(), "John")
Person(firstName != "John")
8.12.7. Comma-Separated Operators Copy linkLink copied to clipboard!
The comma character (,) is used to separate constraint groups. It has implicit and connective semantics.
The comma operator is used at the top-level constraint as it makes them easier to read and the engine will be able to optimize them.
8.12.8. Comma-Separated Operator Example Copy linkLink copied to clipboard!
The following illustrates comma-separated scenarios in implicit and connective semantics:
// Person is at least 50 and weighs at least 80 kg. Person(age > 50, weight > 80)
// Person is at least 50 and weighs at least 80 kg.
Person(age > 50, weight > 80)
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter. Person(age > 50, weight > 80, height > 2)
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter.
Person(age > 50, weight > 80, height > 2)
The comma (,) operator cannot be embedded in a composite constraint expression, such as parentheses.
8.12.9. Binding Variables Copy linkLink copied to clipboard!
You can bind properties to variables in Red Hat JBoss BRMS. It allows for faster execution and performance.
8.12.10. Binding Variable Examples Copy linkLink copied to clipboard!
This is an example of a property bound to a variable:
// Two people of the same age: Person($firstAge : age) // binding Person(age == $firstAge) // constraint expression
// Two people of the same age:
Person($firstAge : age) // binding
Person(age == $firstAge) // constraint expression
For backwards compatibility reasons, it’s allowed (but not recommended) to mix a constraint binding and constraint expressions as such:
// Not recommended: Person($age : age * 2 < 100)
// Not recommended:
Person($age : age * 2 < 100)
// Recommended (separates bindings and constraint expressions): Person(age * 2 < 100, $age : age)
// Recommended (separates bindings and constraint expressions):
Person(age * 2 < 100, $age : age)
8.12.11. Unification Copy linkLink copied to clipboard!
You can unify arguments across several properties. While positional arguments are always processed with unification, the unification symbol, :=, exists for named arguments.
8.12.12. Unification Example Copy linkLink copied to clipboard!
This is what unifying two arguments looks like:
Person($age := age) Person($age := age)
Person($age := age)
Person($age := age)
8.12.13. Options and Operators in Red Hat JBoss BRMS Copy linkLink copied to clipboard!
- Date literal
The date format
dd-mmm-yyyyis supported by default. You can customize this by providing an alternative date format mask as the System property nameddrools.dateformat. If more control is required, use a restriction.Cheese(bestBefore < "27-Oct-2009")
Cheese(bestBefore < "27-Oct-2009")Copy to Clipboard Copied! Toggle word wrap Toggle overflow - List and Map access
You can directly access a List value by index.
// Same as childList(0).getAge() == 18 Person(childList[0].age == 18)
// Same as childList(0).getAge() == 18 Person(childList[0].age == 18)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Value key
You can directly access a Map value by key.
// Same as credentialMap.get("jsmith").isValid() Person(credentialMap["jsmith"].valid)// Same as credentialMap.get("jsmith").isValid() Person(credentialMap["jsmith"].valid)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Abbreviated combined relation condition
This allows you to place more than one restriction on a field using the restriction connectives
&&or\|\|. Grouping via parentheses is permitted, resulting in a recursive syntax pattern.// Simple abbreviated combined relation condition using a single && Person(age > 30 && < 40)
// Simple abbreviated combined relation condition using a single && Person(age > 30 && < 40)Copy to Clipboard Copied! Toggle word wrap Toggle overflow // Complex abbreviated combined relation using groupings Person(age ((> 30 && < 40) \|\| (> 20 && < 25)))
// Complex abbreviated combined relation using groupings Person(age ((> 30 && < 40) \|\| (> 20 && < 25)))Copy to Clipboard Copied! Toggle word wrap Toggle overflow // Mixing abbreviated combined relation with constraint connectives Person(age > 30 && < 40 \|\| location == "london")
// Mixing abbreviated combined relation with constraint connectives Person(age > 30 && < 40 \|\| location == "london")Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Operators
Operators can be used on properties with natural ordering. For example, for Date fields,
<means before, for String fields, it means alphabetically lower.Person(firstName < $otherFirstName)
Person(firstName < $otherFirstName)Copy to Clipboard Copied! Toggle word wrap Toggle overflow Person(birthDate < $otherBirthDate)
Person(birthDate < $otherBirthDate)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Operator matches
Matches a field against any valid Java regular expression. Typically that regexp is a string literal, but variables that resolve to a valid regexp are also allowed. It only applies on String properties. Using
matchesagainst anullvalue always evaluates to false.Cheese(type matches "(Buffalo)?\\S*Mozarella")
Cheese(type matches "(Buffalo)?\\S*Mozarella")Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Operator not matches
The operator returns true if the String does not match the regular expression. The same rules apply as for the
matchesoperator. It only applies on String properties.Cheese(type not matches "(Buffulo)?\\S*Mozarella")
Cheese(type not matches "(Buffulo)?\\S*Mozarella")Copy to Clipboard Copied! Toggle word wrap Toggle overflow - The operator contains
The operator
containsis used to check whether a field that is a Collection or array and contains the specified value. It only applies on Collection properties.CheeseCounter(cheeses contains "stilton") // contains with a String literal CheeseCounter(cheeses contains $var) // contains with a variable
CheeseCounter(cheeses contains "stilton") // contains with a String literal CheeseCounter(cheeses contains $var) // contains with a variableCopy to Clipboard Copied! Toggle word wrap Toggle overflow - The operator not contains
The operator
not containsis used to check whether a field that is a Collection or array and does not contain the specified value. It only applies on Collection properties.CheeseCounter(cheeses not contains "cheddar") // not contains with a String literal CheeseCounter(cheeses not contains $var) // not contains with a variable
CheeseCounter(cheeses not contains "cheddar") // not contains with a String literal CheeseCounter(cheeses not contains $var) // not contains with a variableCopy to Clipboard Copied! Toggle word wrap Toggle overflow - The operator memberOf
The operator
memberOfis used to check whether a field is a member of a collection or array; that collection must be a variable.CheeseCounter(cheese memberOf $matureCheeses)
CheeseCounter(cheese memberOf $matureCheeses)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - The operator not memberOf
The operator
not memberOfis used to check whether a field is not a member of a collection or array. That collection must be a variable.CheeseCounter(cheese not memberOf $matureCheeses)
CheeseCounter(cheese not memberOf $matureCheeses)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - The operator soundslike
This operator is similar to
matches, but it checks whether a word has almost the same sound (using English pronunciation) as the given value.// match cheese "fubar" or "foobar" Cheese(name soundslike 'foobar')
// match cheese "fubar" or "foobar" Cheese(name soundslike 'foobar')Copy to Clipboard Copied! Toggle word wrap Toggle overflow - The operator str
The operator
stris used to check whether a field that is a String starts with or ends with a certain value. It can also be used to check the length of the String.Message(routingValue str[startsWith] "R1")
Message(routingValue str[startsWith] "R1")Copy to Clipboard Copied! Toggle word wrap Toggle overflow Message(routingValue str[endsWith] "R2")
Message(routingValue str[endsWith] "R2")Copy to Clipboard Copied! Toggle word wrap Toggle overflow Message(routingValue str[length] 17)
Message(routingValue str[length] 17)Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Compound Value Restriction
Compound value restriction is used where there is more than one possible value to match. Currently only the
inandnot inevaluators support this. The second operand of this operator must be a comma-separated list of values, enclosed in parentheses. Values may be given as variables, literals, return values or qualified identifiers. Both evaluators are actually syntactic sugar, internally rewritten as a list of multiple restrictions using the operators!=and==.Person($cheese : favouriteCheese) Cheese(type in ("stilton", "cheddar", $cheese))Person($cheese : favouriteCheese) Cheese(type in ("stilton", "cheddar", $cheese))Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Inline Eval Operator (deprecated)
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 eval’s.
Person(girlAge : age, sex = "F") Person(eval(age == girlAge + 2), sex = 'M') // eval() is actually obsolete in this example
Person(girlAge : age, sex = "F") Person(eval(age == girlAge + 2), sex = 'M') // eval() is actually obsolete in this exampleCopy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.14. Operator Precedence Copy linkLink copied to clipboard!
| Operator Type | Operators | Notes |
|---|---|---|
| (nested) property access |
| Not normal Java semantics. |
| List/Map access |
| Not normal Java semantics. |
| constraint binding |
| Not normal Java semantics. |
| multiplicative |
| |
| additive |
| |
| shift |
| |
| relational |
| |
| equality |
| Does not use normal Java (not) same semantics: uses (not) equals semantics instead. |
| non-short circuiting AND |
| |
| non-short circuiting exclusive OR |
| |
| non-short circuiting inclusive OR |
| |
| logical AND |
| |
| logical OR |
| |
| ternary |
| |
| comma-separated AND |
| Not normal Java semantics. |
8.12.15. Fine Grained Property Change Listeners Copy linkLink copied to clipboard!
This feature allows the pattern matching to only react to modification of properties actually constrained or bound inside of a given pattern. This helps with performance and recursion and avoid artificial object splitting.
By default this feature is off in order to make the behavior of the rule engine backward compatible with the former releases. When you want to activate it on a specific bean you have to annotate it with @propertyReactive.
8.12.16. Fine Grained Property Change Listener Example Copy linkLink copied to clipboard!
- DRL example
declare Person @propertyReactive firstName : String lastName : String end
declare Person @propertyReactive firstName : String lastName : String endCopy to Clipboard Copied! Toggle word wrap Toggle overflow - Java class example
@PropertyReactive public static class Person { private String firstName; private String lastName; }@PropertyReactive public static class Person { private String firstName; private String lastName; }Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.17. Working with Fine Grained Property Change Listeners Copy linkLink copied to clipboard!
Using these listeners means you do not need to implement the no-loop attribute to avoid an infinite recursion. The engine recognizes that the pattern matching is done on the property while the RHS of the rule modifies other the properties. On Java classes, you can also annotate any method to say that its invocation actually modifies other properties.
8.12.18. Using Patterns with @watch Copy linkLink copied to clipboard!
Annotating a pattern with @watch allows you to modify the inferred set of properties for which that pattern will react. The properties named in the @watch annotation are added to the ones automatically inferred. You can explicitly exclude one or more of them by beginning their name with a ! and to make the pattern to listen for all or none of the properties of the type used in the pattern respectively with the wildcards * and !*.
8.12.19. @watch Example Copy linkLink copied to clipboard!
This is the @watch annotation in a rule’s LHS:
Since it does not make sense to use this annotation on a pattern using a type not annotated with @PropertyReactive the rule compiler will raise a compilation error if you try to do so. Also the duplicated usage of the same property in @watch (for example like in: @watch(firstName, ! firstName)) will end up in a compilation error.
8.12.20. Using @PropertySpecificOption Copy linkLink copied to clipboard!
You can enable @watch by default or completely disallow it using the on option of the KnowledgeBuilderConfiguration. This new PropertySpecificOption can have one of the following 3 values:
-
DISABLED: the feature is turned off and all the other related annotations are just ignored. -
ALLOWED: this is the default behavior: types are not property reactive unless they are not annotated with@PropertySpecific. -
ALWAYS: all types are property reactive by default.
Alternatively, you can use the drools.propertySpecific system property. For example, if you use Red Hat JBoss EAP, add the property into EAP_HOME/standalone/configuration/standalone.xml:
<system-properties> ... <property name="drools.propertySpecific" value="DISABLED"/> ... </system-properties>
<system-properties>
...
<property name="drools.propertySpecific" value="DISABLED"/>
...
</system-properties>
8.12.21. Basic Conditional Elements Copy linkLink copied to clipboard!
- and
The conditional element
andis used to group other conditional elements into a logical conjunction. Red Hat JBoss BRMS supports both prefixandand infixand. It supports explicit grouping with parentheses. You can also use traditional infix and prefixand.//infixAnd Cheese(cheeseType : type) and Person(favouriteCheese == cheeseType)
//infixAnd Cheese(cheeseType : type) and Person(favouriteCheese == cheeseType)Copy to Clipboard Copied! Toggle word wrap Toggle overflow //infixAnd with grouping (Cheese(cheeseType : type) and (Person(favouriteCheese == cheeseType) or Person(favouriteCheese == cheeseType))
//infixAnd with grouping (Cheese(cheeseType : type) and (Person(favouriteCheese == cheeseType) or Person(favouriteCheese == cheeseType))Copy to Clipboard Copied! Toggle word wrap Toggle overflow Prefix
andis also supported:(and Cheese(cheeseType : type) Person(favouriteCheese == cheeseType))
(and Cheese(cheeseType : type) Person(favouriteCheese == cheeseType))Copy to Clipboard Copied! Toggle word wrap Toggle overflow The root element of the LHS is an implicit prefix
andand does not need to be specified:when Cheese(cheeseType : type) Person(favouriteCheese == cheeseType) then ...
when Cheese(cheeseType : type) Person(favouriteCheese == cheeseType) then ...Copy to Clipboard Copied! Toggle word wrap Toggle overflow - or
This is a shortcut for generating two or more similar rules. Red Hat JBoss BRMS supports both prefix
orand infixor. You can use traditional infix, prefix and explicit grouping parentheses.//infixOr Cheese(cheeseType : type) or Person(favouriteCheese == cheeseType)
//infixOr Cheese(cheeseType : type) or Person(favouriteCheese == cheeseType)Copy to Clipboard Copied! Toggle word wrap Toggle overflow //infixOr with grouping (Cheese(cheeseType : type) or (Person(favouriteCheese == cheeseType) and Person(favouriteCheese == cheeseType))
//infixOr with grouping (Cheese(cheeseType : type) or (Person(favouriteCheese == cheeseType) and Person(favouriteCheese == cheeseType))Copy to Clipboard Copied! Toggle word wrap Toggle overflow (or Person(sex == "f", age > 60) Person(sex == "m", age > 65)(or Person(sex == "f", age > 60) Person(sex == "m", age > 65)Copy to Clipboard Copied! Toggle word wrap Toggle overflow Allows for optional pattern binding. Each pattern must be bound separately.
pensioner : (Person(sex == "f", age > 60) or Person(sex == "m", age > 65))
pensioner : (Person(sex == "f", age > 60) or Person(sex == "m", age > 65))Copy to Clipboard Copied! Toggle word wrap Toggle overflow (or pensioner : Person(sex == "f", age > 60) pensioner : Person(sex == "m", age > 65))(or pensioner : Person(sex == "f", age > 60) pensioner : Person(sex == "m", age > 65))Copy to Clipboard Copied! Toggle word wrap Toggle overflow - not
This checks to ensure an object specified as absent is not included in the Working Memory. It may be followed by parentheses around the condition elements it applies to. In a single pattern you can omit the parentheses.
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - exists
This checks the working memory to see if a specified item exists. The keyword
existsmust be followed by parentheses around the CEs that it applies to. In a single pattern you can omit the parentheses.Copy to Clipboard Copied! Toggle word wrap Toggle overflow
The behavior of the Conditional Element or is different from the connective || for constraints and restrictions in field constraints. The engine cannot interpret the Conditional Element or. Instead, a rule with or is rewritten as a number of subrules. This process ultimately results in a rule that has a single or as the root node and one subrule for each of its CEs. Each subrule can activate and fire like any normal rule; there is no special behavior or interaction between these subrules.
8.12.22. Conditional Element forall Copy linkLink copied to clipboard!
This element evaluates to true when all facts that match the first pattern match all the remaining patterns. It is a scope delimiter. Therefore, it can use any previously bound variable, but no variable bound inside it will be available for use outside of it.
forall can be nested inside other CEs. For instance, forall can be used inside a not CE. Only single patterns have optional parentheses, so with a nested forall parentheses must be used.
8.12.23. forall Examples Copy linkLink copied to clipboard!
- Evaluating to true
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Single pattern forall
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Multi-pattern forall
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Nested forall
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.24. Conditional Element from Copy linkLink copied to clipboard!
The conditional element from enables users to specify an arbitrary source for data to be matched by LHS patterns. This allows the engine to reason over data not in the Working Memory. The data source could be a sub-field on a bound variable or the results of a method call. It is a powerful construction that allows out of the box integration with other application components and frameworks. One common example is the integration with data retrieved on-demand from databases using hibernate named queries.
The expression used to define the object source is any expression that follows regular MVEL syntax. Therefore, it allows you to easily use object property navigation, execute method calls and access maps and collections elements.
Using from with lock-on-active rule attribute can result in rules not being fired.
There are several ways to address this issue:
-
Avoid the use of
fromwhen you can assert all facts into working memory or use nested object references in your constraint expressions (shown below). - Place the variable assigned used in the modify block as the last sentence in your condition (LHS).
-
Avoid the use of
lock-on-activewhen you can explicitly manage how rules within the same rule-flow group place activations on one another.
8.12.25. from Examples Copy linkLink copied to clipboard!
- Reasoning and binding on patterns
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Using a graph notation
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Iterating over all objects
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Use with lock-on-active
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.26. Conditional Element collect Copy linkLink copied to clipboard!
The conditional element collect allows rules to reason over a collection of objects obtained from the given source or from the working memory. In First Oder Logic terms this is the cardinality quantifier.
The result pattern of collect can be any concrete class that implements the java.util.Collection interface and provides a default no-arg public constructor. You can use Java collections like ArrayList, LinkedList and HashSet or your own class, as long as it implements the java.util.Collection interface and provide a default no-arg public constructor.
Variables bound before the collect CE are in the scope of both source and result patterns and therefore you can use them to constrain both your source and result patterns. Any binding made inside collect is not available for use outside of it.
8.12.27. Conditional Element accumulate Copy linkLink copied to clipboard!
The conditional element accumulate is a more flexible and powerful form of the collect element and allows a rule to iterate over a collection of objects while executing custom actions for each of the elements. The accumulate element returns a result object.
The element accumulate supports the use of predefined accumulate functions, as well as the use of inline custom code. However, using inline custom code is not recommended, as it is harder to maintain and might lead to code duplication. On the other hand, accumulate functions are easier to test and reuse.
The conditional element accumulate supports multiple different syntaxes. The preferred is the top-level syntax (as noted below), but all other syntaxes are supported as well for backward compatibility.
Top-Level accumulate Syntax
The top-level accumulate syntax is the most compact and flexible. The simplified syntax is as follows:
accumulate(SOURCE_PATTERN ; FUNCTIONS [;CONSTRAINTS])
accumulate(SOURCE_PATTERN ; FUNCTIONS [;CONSTRAINTS])
Example 8.2. Top-Level accumulate Syntax Example
In the example above, min, max, and average are accumulate functions that calculate the minimum, maximum, and average temperature values over all the readings for each sensor.
Built-in accumulate Functions
Only user-defined custom accumulate functions have to be explicitly imported. The following accumulate functions are imported automatically by the engine:
-
average -
min -
max -
count -
sum -
collectList -
collectSet
These common functions accept any expression as an input. For instance, if you want to calculate an average profit on all items of an order, you can write a rule using the average function as follows:
Accumulate Functions Pluggability
Accumulate functions are all pluggable; if needed, custom and domain-specific functions can be easily added to the engine and rules can start to use them without any restrictions.
To implement a new accumulate function, create a Java class that implements the org.kie.api.runtime.rule.AccumulateFunction interface. To use the function in the rules, import it using the import accumulate statement:
import accumulate CLASS_NAME FUNCTION_NAME
import accumulate CLASS_NAME FUNCTION_NAME
Example 8.3. Importing and Using Custom Accumulate Function
Example 8.4. Implementation of average Function
As an example of an accumulate function, see the following implementation of the average function:
For a list of Maven dependencies, see example Embedded jBPM Engine Dependencies. If you use Red Hat JBoss BRMS, see Embedded Drools Engine Dependencies.
Alternative Syntax
Previous accumulate syntaxes are still supported for backward compatibility.
In case the rule uses a single accumulate function on a given accumulate element, you can add a pattern for the result object and use the from keyword to link it to the accumulate result. See the following example:
Example 8.5. Rule with Alternative Syntax
In this example, the element accumulate uses only one function – sum. In this case, it is possible to write a pattern for the result type of the accumulate function with the constraints inside.
Note that it is not possible to use both the return type and the function binding in the same accumulate statement.
accumulate with Inline Custom Code
Instead of using the accumulate functions, you can define inline custom code.
The use of accumulate with inline custom code is not recommended. It is difficult to maintain and test the rules, as well as reuse the code. Implementing your own accumulate functions allows you to test and use them easily.
The general syntax of the accumulate with inline custom code is as follows:
RESULT_PATTERNA regular pattern that the engine tries to match against the object returned from the
RESULT_EXPRESSION.If the attempt succeeds, the
accumulateconditional element returnstrueand the engine proceeds with an evaluation of the next conditional element in the rule. In the second case,accumulatereturnsfalseand the engine stops evaluating conditional elements for this rule.SOURCE_PATTERN- A regular pattern that the engine tries to match against each of the source objects.
INIT_CODE- A semantic block of code in the selected dialect that is executed once for each tuple before iterating over the source objects.
ACTION_CODE- A semantic block of code in the selected dialect that is executed for each of the source objects.
REVERSE_CODEAn optional semantic block of code in the selected dialect that is 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_CODEblock, so that the engine can do decremental calculation when a source object is modified or retracted. This significantly improves the performance of these operations.RESULT_EXPRESSION- A semantic expression in the selected dialect that is executed after all source objects are iterated.
Example 8.6. Example of Inline Custom Code
In this example, the engine executes the INIT_CODE for each Order in the working memory, initializing the total variable to zero. The engine then iterates over all OrderItem objects for that Order, executing the action for each one. After the iteration, the engine returns the value corresponding to the RESULT_EXPRESSION (in this case, a value of the total variable). Finally, the engine tries to match the result with the Number pattern. If the doubleValue is greater than 100, the rule fires.
The example is using Java programming language as a semantic dialect. In this case, a semicolon as a statement delimiter is mandatory in the init, action, and reverse code blocks. However, since the result is an expression, it does not require a semicolon. If you want to use any other dialect, note that you have to observe the principles of its specific syntax.
Custom Objects
The accumulate conditional element can be used to execute any action on source objects. The following example instantiates and populates a custom object:
Example 8.7. Instantiating Custom Objects
8.12.28. Conditional Element eval Copy linkLink copied to clipboard!
The conditional element eval is essentially a catch-all which allows any semantic code (that returns a primitive boolean) to be executed. This code can refer to variables that were bound in the LHS of the rule, and functions in the rule package. Overuse of eval reduces the declarativeness of your rules and can result in a poorly performing engine. While eval can be used anywhere in the patterns, the best practice is to add it as the last conditional element in the LHS of a rule.
Evals cannot be indexed and thus are not as efficient as field constraints. However this makes them ideal for being used when functions return values that change over time, which is not allowed within field constraints.
8.12.29. eval Conditional Element Examples Copy linkLink copied to clipboard!
This is what eval looks like in use:
p1 : Parameter() p2 : Parameter() eval(p1.getList().containsKey( p2.getItem()))
p1 : Parameter()
p2 : Parameter()
eval(p1.getList().containsKey( p2.getItem()))
p1 : Parameter() p2 : Parameter() // call function isValid in the LHS eval(isValid( p1, p2))
p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval(isValid( p1, p2))
8.12.30. Right Hand Side Copy linkLink copied to clipboard!
The Right Hand Side (RHS) is a common name for the consequence part of a rule. The main purpose of the RHS is to insert, retract (delete), or modify working memory data. The RHS usually contains a list of actions to be executed and should be kept small, thus keeping it declarative and readable.
In case you need imperative or conditional code in the RHS, divide the rule into more rules.
8.12.31. RHS Convenience Methods Copy linkLink copied to clipboard!
See the following list of the RHS convenience methods:
-
update(OBJECT, HANDLE); -
update(OBJECT); -
insert(OBJECT); -
insertLogical(OBJECT); -
retract(HANDLE);
For more information, see Section 8.2.1, “Accessing Working Memory”.
8.12.32. Convenience Methods Using Drools Variable Copy linkLink copied to clipboard!
-
The call
drools.halt()terminates rule execution immediately. This is required for returning control to the point whence the current session was put to work withfireUntilHalt(). -
Methods
insert(Object o),update(Object o)andretract(Object o)can be called ondroolsas well, but due to their frequent use they can be called without the object reference. -
drools.getWorkingMemory()returns theWorkingMemoryobject. -
drools.setFocus(String s)sets the focus to the specified agenda group. -
drools.getRule().getName(), called from a rule’s RHS, returns the name of the rule. -
drools.getTuple()returns the Tuple that matches the currently executing rule, anddrools.getActivation()delivers the corresponding Activation. (These calls are useful for logging and debugging purposes.)
8.12.33. Convenience Methods Using kcontext Variable Copy linkLink copied to clipboard!
-
The call
kcontext.getKieRuntime().halt()terminates rule execution immediately. The accessor
getAgenda()returns a reference to the session’sAgenda, which in turn provides access to the various rule groups: activation groups, agenda groups, and rule flow groups. A fairly common paradigm is the activation of some agenda group, which could be done with the lengthy call:// Give focus to the agenda group CleanUp: kcontext.getKieRuntime().getAgenda().getAgendaGroup("CleanUp").setFocus();// Give focus to the agenda group CleanUp: kcontext.getKieRuntime().getAgenda().getAgendaGroup("CleanUp").setFocus();Copy to Clipboard Copied! Toggle word wrap Toggle overflow You can achieve the same using
drools.setFocus("CleanUp").-
To run a query, you call
getQueryResults(String query), whereupon you may process the results. - A set of methods dealing with event management lets you add and remove event listeners for the Working Memory and the Agenda.
-
Method
getKieBase()returns theKieBaseobject, the backbone of all the Knowledge in your system, and the originator of the current session. -
You can manage globals with
setGlobal(…),getGlobal(…)andgetGlobals(). -
Method
getEnvironment()returns the runtime’sEnvironment.
8.12.34. Modify Statement Copy linkLink copied to clipboard!
- modify
This provides a structured approach to fact updates. It combines the update operation with a number of setter calls to change the object’s fields.
modify (FACT_EXPRESSION) { EXPRESSION [, EXPRESSION]* }modify (FACT_EXPRESSION) { EXPRESSION [, EXPRESSION]* }Copy to Clipboard Copied! Toggle word wrap Toggle overflow 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.
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.35. Query Examples Copy linkLink copied to clipboard!
To return the results use ksession.getQueryResults("name"), where "name" is the query’s name. This returns a list of query results, which allow you to retrieve the objects that matched the query.
- Query for people over the age of 30
query "People over the age of 30" person : Person(age > 30) end
query "People over the age of 30" person : Person(age > 30) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow - Query for people over the age of X, and who live in Y
query "People over the age of x" (int x, String y) person : Person(age > x, location == y) end
query "People over the age of x" (int x, String y) person : Person(age > x, location == y) endCopy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.36. QueryResults Example Copy linkLink copied to clipboard!
We iterate over the returned QueryResults using a standard for loop. Each element is a QueryResultsRow which we can use to access each of the columns in the tuple. These columns can be accessed by bound declaration name or index position:
8.12.37. Queries Calling Other Queries Copy linkLink copied to clipboard!
Queries can call other queries. This combined with optional query arguments provides derivation query style backward chaining. Positional and named syntax is supported for arguments. It is also possible to mix both positional and named, but positional must come first, separated by a semi colon. Literal expressions can be passed as query arguments, but you cannot mix expressions with variables.
Using the ? symbol in this process means the query is pull only and once the results are returned you will not receive further results as the underlying data changes.
8.12.38. Queries Calling Other Queries Example Copy linkLink copied to clipboard!
- Query calling another query
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Using live queries to reactively receive changes over time from query results
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
8.12.39. Unification for Derivation Queries Copy linkLink copied to clipboard!
Red Hat JBoss BRMS supports unification for derivation queries. This means that arguments are optional. It is possible to call queries from Java leaving arguments unspecified using the static field org.drools.runtime.rule.Variable.v. You must use v and not an alternative instance of Variable. These are referred to as out arguments.
The query itself does not declare at compile time whether an argument is in or an out. This can be defined purely at runtime on each use.
8.13. Searching Working Memory Using Query Copy linkLink copied to clipboard!
8.13.1. Queries Copy linkLink copied to clipboard!
Queries are used to retrieve fact sets based on patterns, as they are used in rules. Patterns may make use of optional parameters. Queries can be defined in the Knowledge Base, from where they are called up to return the matching results. While iterating over the result collection, any identifier bound in the query can be used to access the corresponding fact or fact field by calling the get method with the binding variable’s name as its argument. If the binding refers to a fact object, its FactHandle can be retrieved by calling getFactHandle, again with the variable’s name as the parameter. Illustrated below is a query example:
QueryResults results = ksession.getQueryResults("my query", new Object[] {"string"});
for (QueryResultsRow row : results) {
System.out.println(row.get("varName"));
}
QueryResults results = ksession.getQueryResults("my query", new Object[] {"string"});
for (QueryResultsRow row : results) {
System.out.println(row.get("varName"));
}
8.13.2. Live Queries Copy linkLink copied to clipboard!
Invoking queries and processing the results by iterating over the returned set is not a good way to monitor changes over time.
To alleviate this, Red Hat JBoss BRMS provides live queries, which have a listener attached instead of returning an iterable result set. These live queries stay open by creating a view and publishing change events for the contents of this view. To activate, start your query with parameters and listen to changes in the resulting view. The dispose method terminates the query and discontinues this reactive scenario.
8.13.3. ViewChangedEventListener Implementation Example Copy linkLink copied to clipboard!
For an example of Glazed Lists integration for live queries, read the Glazed Lists examples for Drools Live Querries article.
8.14. Domain Specific Languages (DSLs) Copy linkLink copied to clipboard!
Domain Specific Languages (or DSLs) are a way of creating a rule language that is dedicated to your problem domain. A set of DSL definitions consists of transformations from DSL "sentences" to DRL constructs, which lets you use of all the underlying rule language and engine features. You can write rules in DSL rule (or DSLR) files, which will be translated into DRL files.
DSL and DSLR files are plain text files and you can use any text editor to create and modify them. There are also DSL and DSLR editors you can use, both in the IDE as well as in the web based BRMS, although they may not provide you with the full DSL functionality.
8.14.1. DSL Editor Copy linkLink copied to clipboard!
The DSL editor provides a tabular view of the mapping of Language to Rule Expressions. The Language Expression feeds the content assistance for the rule editor so that it can suggest Language Expressions from the DSL configuration. The rule editor loads the DSL configuration when the rule resource is loaded for editing.
DSL feature is useful for simple use cases for non technical users to easily define rules based on sentence snippets. For more complex use cases, we recommend you to use other advanced features like decision tables and DRL rules, that are more expressive and flexible.
8.14.2. Using DSLs Copy linkLink copied to clipboard!
DSLs can serve as a layer of separation between rule authoring (and rule authors) and the technical intricacies resulting from the modeling of domain object and the rule engine’s native language and methods. A DSL hides implementation details and focuses on the rule logic proper. DSL sentences can also act as "templates" for conditional elements and consequence actions that are used repeatedly in your rules, possibly with minor variations. You may define DSL sentences as being mapped to these repeated phrases, with parameters providing a means for accommodating those variations.
8.14.3. DSL Example Copy linkLink copied to clipboard!
[when]Something is {colour}=Something(colour=="{colour}")
[when]Something is {colour}=Something(colour=="{colour}")
[when] indicates the scope of the expression (that is, whether it is valid for the LHS or the RHS of a rule).
The part after the bracketed keyword is the expression that you use in the rule.
The part to the right of the equal sign (=) is the mapping of the expression into the rule language. The form of this string depends on its destination, RHS or LHS. If it is for the LHS, then it ought to be a term according to the regular LHS syntax; if it is for the RHS then it might be a Java statement.
8.14.4. About DSL Parser Copy linkLink copied to clipboard!
Whenever the DSL parser matches a line from the rule file written in the DSL with an expression in the DSL definition, it performs three steps of string manipulation:
- The DSL extracts the string values appearing where the expression contains variable names in brackets.
- The values obtained from these captures are interpolated wherever that name occurs on the right hand side of the mapping.
- The interpolated string replaces whatever was matched by the entire expression in the line of the DSL rule file.
You can use (for instance) a ? to indicate that the preceding character is optional. One good reason to use this is to overcome variations in natural language phrases of your DSL. But, given that these expressions are regular expression patterns, this means that all wildcard characters in Java’s pattern syntax have to be escaped with a preceding backslash (\).
8.14.5. About DSL Compiler Copy linkLink copied to clipboard!
The DSL compiler transforms DSL rule files line by line. If you do not wish for this to occur, ensure that the captures are surrounded by characteristic text (words or single characters). As a result, the matching operation done by the parser plucks out a substring from somewhere within the line. In the example below, quotes are used as distinctive characters. The characters that surround the capture are not included during interpolation, just the contents between them.
8.14.6. DSL Syntax Examples Copy linkLink copied to clipboard!
- Quotes
Use quotes for textual data that a rule editor may want to enter. You can also enclose the capture with words to ensure that the text is correctly matched.
[when]something is "{color}"=Something(color=="{color}") [when]another {state} thing=OtherThing(state=="{state}"[when]something is "{color}"=Something(color=="{color}") [when]another {state} thing=OtherThing(state=="{state}"Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Braces
In a DSL mapping, the braces "{" and "}" should only be used to enclose a variable definition or reference, resulting in a capture. If they should occur literally, either in the expression or within the replacement text on the right hand side, they must be escaped with a preceding backslash (
\).[then]do something= if (foo) \{ doSomething(); \}[then]do something= if (foo) \{ doSomething(); \}Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Mapping with correct syntax example
This is a comment to be ignored.
# 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 = andCopy to Clipboard Copied! Toggle word wrap Toggle overflow - Expanded DSL example
Copy to Clipboard Copied! Toggle word wrap Toggle overflow
If you are capturing plain text from a DSL rule line and want to use it as a string literal in the expansion, you must provide the quotes on the right hand side of the mapping.
8.14.7. Chaining DSL Expressions Copy linkLink copied to clipboard!
DSL expressions can be chained together one one line to be used at once. It must be clear where one ends and the next one begins and where the text representing a parameter ends. Otherwise you risk getting all the text until the end of the line as a parameter value. The DSL expressions are tried, one after the other, according to their order in the DSL definition file. After any match, all remaining DSL expressions are investigated, too.
8.14.8. Adding Constraints to Facts Copy linkLink copied to clipboard!
- Expressing LHS conditions
The DSL facility allows you to add constraints to a pattern by a simple convention: if your DSL expression starts with a hyphen (minus character,
-) it is assumed to be a field constraint and, consequently, is is added to the last pattern line preceding it.In the example, the class
Cheese, has these fields: type, price, age, and country. You can express some LHS condition in normal DRL.Cheese(age < 5, price == 20, type=="stilton", country=="ch")
Cheese(age < 5, price == 20, type=="stilton", country=="ch")Copy to Clipboard Copied! Toggle word wrap Toggle overflow - DSL definitions
The DSL definitions given in this example result in three DSL phrases which may be used to create any combination of constraint involving these fields.
[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}'[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}'Copy to Clipboard Copied! Toggle word wrap Toggle overflow - -
The parser will pick up a line beginning with
-and add it as a constraint to the preceding pattern, inserting a comma when it is required.There is a Cheese with - age is less than 42 - type is 'stilton'
There is a Cheese with - age is less than 42 - type is 'stilton'Copy to Clipboard Copied! Toggle word wrap Toggle overflow Cheese(age<42, type=='stilton')
Cheese(age<42, type=='stilton')Copy to Clipboard Copied! Toggle word wrap Toggle overflow - Defining DSL phrases
Defining DSL phrases for various operators and even a generic expression that handles any field constraint reduces the amount of DSL entries.
Copy to Clipboard Copied! Toggle word wrap Toggle overflow - DSL definition rule
There is a Cheese with - age is less than 42 - rating is greater than 50 - type equals 'stilton'
There is a Cheese with - age is less than 42 - rating is greater than 50 - type equals 'stilton'Copy to Clipboard Copied! Toggle word wrap Toggle overflow In this specific case, a phrase such as "is less than" is replaced by
<, and then the line matches the last DSL entry. This removes the hyphen, but the final result is still added as a constraint to the preceding pattern. After processing all of the lines, the resulting DRL text is:Cheese(age<42, rating > 50, type=='stilton')
Cheese(age<42, rating > 50, type=='stilton')Copy to Clipboard Copied! Toggle word wrap Toggle overflow
The order of the entries in the DSL is important if separate DSL expressions are intended to match the same line, one after the other.
8.14.9. Tips for Developing DSLs Copy linkLink copied to clipboard!
- Write representative samples of the rules your application requires and test them as you develop.
- Rules, both in DRL and in DSLR, refer to entities according to the data model representing the application data that should be subject to the reasoning process defined in rules.
- Writing rules is easier if most of the data model’s types are facts.
- Mark variable parts as parameters. This provides reliable leads for useful DSL entries.
- You may postpone implementation decisions concerning conditions and actions during this first design phase by leaving certain conditional elements and actions in their DRL form by prefixing a line with a greater sign (">"). (This is also handy for inserting debugging statements.)
- New rules can be written by reusing the existing DSL definitions, or by adding a parameter to an existing condition or consequence entry.
- Keep the number of DSL entries small. Using parameters lets you apply the same DSL sentence for similar rule patterns or constraints.
8.14.10. DSL and DSLR Reference Copy linkLink copied to clipboard!
A DSL file is a text file in a line-oriented format. Its entries are used for transforming a DSLR file into a file according to DRL syntax:
-
A line starting with
#or//(with or without preceding white space) is treated as a comment. A comment line starting with#/is scanned for words requesting a debug option, see below. -
Any line starting with an opening bracket (
[) is assumed to be the first line of a DSL entry definition. - Any other line is appended to the preceding DSL entry definition, with the line end replaced by a space.
8.14.11. DSL Entry Description Copy linkLink copied to clipboard!
A DSL entry consists of the following four parts:
-
A scope definition, written as one of the keywords
whenorcondition,thenorconsequence,*andkeyword, enclosed in brackets ([and]). This indicates whether the DSL entry is valid for the condition or the consequence of a rule, or both. A scope indication ofkeywordmeans that the entry has global significance, that is, it is recognized anywhere in a DSLR file. - A type definition, written as a Java class name, enclosed in brackets. This part is optional unless the next part begins with an opening bracket. An empty pair of brackets is valid, too.
A DSL expression consists of a (Java) regular expression, with any number of embedded variable definitions, terminated by an equal sign (
=). A variable definition is enclosed in braces ({and}). It consists of a variable name and two optional attachments, separated by colons (:). If there is one attachment, it is a regular expression for matching text that is to be assigned to the variable. If there are two attachments, the first one is a hint for the GUI editor and the second one the regular expression.Note that all characters that are "magic" in regular expressions must be escaped with a preceding backslash (
\) if they should occur literally within the expression.The remaining part of the line after the delimiting equal sign is the replacement text for any DSLR text matching the regular expression. It may contain variable references, for example a variable name enclosed in braces. Optionally, the variable name may be followed by an exclamation mark (
!) and a transformation function, see below.Note that braces (
{and}) must be escaped with a preceding backslash (\) if they should occur literally within the replacement string.
8.14.12. Debug Options for DSL Expansion Copy linkLink copied to clipboard!
| Word | Description |
|---|---|
|
| Prints the resulting DRL text, with line numbers. |
|
| Prints each expansion step of condition and consequence lines. |
|
|
Dumps the internal representation of all DSL entries with scope |
|
|
Dumps the internal representation of all DSL entries with scope |
|
|
Dumps the internal representation of all DSL entries with scope |
|
| Displays a usage statistic of all DSL entries. |
8.14.13. DSL Definition Example Copy linkLink copied to clipboard!
This is what a DSL definition looks like:
8.14.14. Transformation of DSLR File Copy linkLink copied to clipboard!
The transformation of a DSLR file proceeds as follows:
- The text is read into memory.
-
Each of the
keywordentries is applied to the entire text. The regular expression from the keyword definition is modified by replacing white space sequences with a pattern matching any number of white space characters, and by replacing variable definitions with a capture made from the regular expression provided with the definition, or with the default (.*?). Then, the DSLR text is searched exhaustively for occurrences of strings matching the modified regular expression. Substrings of a matching string corresponding to variable captures are extracted and replace variable references in the corresponding replacement text, and this text replaces the matching string in the DSLR text. Sections of the DSLR text between
whenandthen, andthenandend, respectively, are located and processed in a uniform manner, line by line, as described below.For a line, each DSL entry pertaining to the line’s section is taken in turn, in the order it appears in the DSL file. Its regular expression part is modified: white space is replaced by a pattern matching any number of white space characters; variable definitions with a regular expression are replaced by a capture with this regular expression, its default being
.*?. If the resulting regular expression matches all or part of the line, the matched part is replaced by the suitably modified replacement text.Modification of the replacement text is done by replacing variable references with the text corresponding to the regular expression capture. This text may be modified according to the string transformation function given in the variable reference; see below for details.
If there is a variable reference naming a variable that is not defined in the same entry, the expander substitutes a value bound to a variable of that name, provided it was defined in one of the preceding lines of the current rule.
If a DSLR line in a condition is written with a leading hyphen, the expanded result is inserted into the last line, which should contain a pattern CE, that is, a type name followed by a pair of parentheses. if this pair is empty, the expanded line (which should contain a valid constraint) is simply inserted, otherwise a comma (
,) is inserted beforehand.If a DSLR line in a consequence is written with a leading hyphen, the expanded result is inserted into the last line, which should contain a
modifystatement, ending in a pair of braces ({and}). If this pair is empty, the expanded line (which should contain a valid method call) is simply inserted, otherwise a comma (,) is inserted beforehand.
It is currently not possible to use a line with a leading hyphen to insert text into other conditional element forms (for example accumulate) or it may only work for the first insertion (for example eval).
8.14.15. String Transformation Functions Copy linkLink copied to clipboard!
| Name | Description |
|---|---|
|
| Converts all letters to upper case. |
|
| Converts all letters to lower case. |
|
| Converts the first letter to upper case, and all other letters to lower case. |
|
|
Extracts all digits and |
|
|
Compares the string with string |
8.14.16. Stringing DSL Transformation Functions Copy linkLink copied to clipboard!
- .dsl
A file containing a DSL definition is customarily given the extension
.dsl. It is passed to the Knowledge Builder withResourceType.DSL. For a file using DSL definition, the extension.dslrshould be used. The Knowledge Builder expectsResourceType.DSLR. The IDE, however, relies on file extensions to correctly recognize and work with your rules file.definitions for conditions
# definitions for conditions [when][]There is an? {entity}=${entity!lc}: {entity!ucfirst}() [when][]- with an? {attr} greater than {amount}={attr} <= {amount!num} [when][]- with a {what} {attr}={attr} {what!positive?>0/negative?%lt;0/zero?==0/ERROR}Copy to Clipboard Copied! Toggle word wrap Toggle overflow - DSL passing
The DSL must be passed to the Knowledge Builder ahead of any rules file using the DSL.
For parsing and expanding a DSLR file the DSL configuration is read and supplied to the parser. Thus, the parser can "recognize" the DSL expressions and transform them into native rule language expressions.
KnowledgeBuilder kBuilder = new KnowledgeBuilder(); Resource dsl = ResourceFactory.newClassPathResource(dslPath, getClass()); kBuilder.add(dsl, ResourceType.DSL); Resource dslr = ResourceFactory.newClassPathResource(dslrPath, getClass()); kBuilder.add(dslr, ResourceType.DSLR);
KnowledgeBuilder kBuilder = new KnowledgeBuilder(); Resource dsl = ResourceFactory.newClassPathResource(dslPath, getClass()); kBuilder.add(dsl, ResourceType.DSL); Resource dslr = ResourceFactory.newClassPathResource(dslrPath, getClass()); kBuilder.add(dslr, ResourceType.DSLR);Copy to Clipboard Copied! Toggle word wrap Toggle overflow