JBoss.orgCommunity Documentation
Declarative Agenda is experimental, and all aspects are highly likely to change in the future. @Eager and @Direct are temporary annotations to control the behaviour of rules, which will also change as Declarative Agenda evolves. Annotations instead of attributes where chosen, to reflect their experimental nature.
The declarative agenda allows to use rules to control which other rules can fire and when. While this will add a lot more overhead than the simple use of salience, the advantage is it is declarative and thus more readable and maintainable and should allow more use cases to be achieved in a simpler fashion.
This feature is off by default and must be explicitly enabled, that is because it is considered highly experimental for the moment and will be subject to change, but can be activated on a given KieBase by adding the declarativeAgenda='enabled' attribute in the corresponding kbase tag of the kmodule.xml file as in the following example.
Example 10.1. Enabling the Declarative Agenda
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="DeclarativeKBase" declarativeAgenda="enabled">
<ksession name="KSession">
</kbase>
</kmodule>
The basic idea is:
All rule's Matches are inserted into WorkingMemory as facts. So you can now do pattern matching against a Match. The rule's metadata and declarations are available as fields on the Match object.
You can use the kcontext.blockMatch( Match match ) for the current rule to block the selected match. Only when that rule becomes false will the match be eligible for firing. If it is already eligible for firing and is later blocked, it will be removed from the agenda until it is unblocked.
A match may have multiple blockers and a count is kept. All blockers must became false for the counter to reach zero to enable the Match to be eligible for firing.
kcontext.unblockAllMatches( Match match ) is an over-ride rule that will remove all blockers regardless
An activation may also be cancelled, so it never fires with cancelMatch
An unblocked Match is added to the Agenda and obeys normal salience, agenda groups, ruleflow groups etc.
The @Direct annotations allows a rule to fire as soon as it's matched, this is to be used for rules that block/unblock matches, it is not desirable for these rules to have side effects that impact else where.
Example 10.2. New RuleContext methods
void blockMatch(Match match);
void unblockAllMatches(Match match);
void cancelMatch(Match match);
Here is a basic example that will block all matches from rules that have metadata @department('sales'). They will stay blocked until the blockerAllSalesRules rule becomes false, i.e. "go2" is retracted.
Example 10.3. Block rules based on rule metadata
rule rule1 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule rule2 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule blockerAllSalesRules @Direct @Eager when
$s : String( this == 'go2' )
$i : Match( department == 'sales' )
then
list.add( $i.rule.name + ':' + $s );
kcontext.blockMatch( $i );
end
Further than annotate the blocking rule with @Direct, it is also necessary to annotate all the rules that could be potentially blocked by it with @Eager. This is because, since the Match has to be evaluated by the pattern matching of the blocking rule, the potentially blocked ones cannot be evaluated lazily, otherwise won't be any Match to be evaluated.
This example shows how you can use active property to count the number of active or inactive (already fired) matches.
Example 10.4. Count the number of active/inactive Matches
rule rule1 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule rule2 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule rule3 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule countActivateInActive @Direct @Eager when
$s : String( this == 'go2' )
$active : Number( this == 1 ) from accumulate( $a : Match( department == 'sales', active == true ), count( $a ) )
$inActive : Number( this == 2 ) from accumulate( $a : Match( department == 'sales', active == false ), count( $a ) )
then
kcontext.halt( );
end
When the field of a fact is a collection it is possible to bind and reason over
all the items in that collection on by one using the from
keyword.
Nevertheless, when it is required to browse a graph of object the extensive use of the
from
conditional element may result in a verbose and cubersome syntax
like in the following example:
Example 10.5. Browsing a graph of objects with from
rule "Find all grades for Big Data exam" when
$student: Student( $plan: plan )
$exam: Exam( course == "Big Data" ) from $plan.exams
$grade: Grade() from $exam.grades
then /* RHS */ end
In this example it has been assumed to use a domain model consisting of a
Student
who has a Plan
of study: a Plan
can have zero or more Exam
s and an Exam
zero or more
Grade
s. Note that only the root object of the graph (the Student
in this case) needs to be in the working memory in order to make this works.
By borrowing ideas from XPath, this syntax can be made more succinct, as XPath has a
compact notation for navigating through related elements while handling collections and
filtering constraints. This XPath-inspired notation has been called OOPath
since it is explictly intended to browse graph of objects. Using this notation the former
example can be rewritten as it follows:
Example 10.6. Browsing a graph of objects with OOPath
rule "Find all grades for Big Data exam" when
Student( $grade: /plan/exams{course == "Big Data"}/grades )
then /* RHS */ end
Formally, the core grammar of an OOPath
expression can be defined in EBNF notation in this way.
OOPExpr = ( "/" | "?/" ) OOPSegment { ( "/" | "?/" | "." ) OOPSegment } ;
OOPSegment = [ID ( ":" | ":=" )] ID ["[" Number "]"] ["{" Constraints "}"];
In practice an OOPath
expression has the following features.
It has to start with /
or with a ?/
in case of a completely
non-reactive OOPath (see below).
It can dereference a single property of an object with the .
operator
It can dereference a multiple property of an object using the /
operator.
If a collection is returned, it will iterate over the values in the collection
While traversing referenced objects it can filter away those not satisfying one or more constraints, written as predicate expressions between curly brackets like in:
Student( $grade: /plan/exams{ course == "Big Data" }/grades )
A constraint can also have a beckreference to an object of the graph traversed before the currently iterated one. For example the following OOPath:
Student( $grade: /plan/exams/grades{ result > ../averageResult } )
will match only the grades having a result above the average for the passed exam.
A constraint can also recursively be another OOPath as it follows:
Student( $exam: /plan/exams{ /grades{ result > 20 } } )
Items can also be accessed by their index by putting it between square brackets like in:
Student( $grade: /plan/exams[0]/grades )
To adhere to Java convention OOPath indexes are 0-based, compared to XPath 1-based
At the moment Drools is not able to react to updates involving a deeply nested object traversed
during the evaluation of an OOPath
expression. To make these objects reactive
to changes it is then necessary to make them extend the class
org.drools.core.phreak.ReactiveObject
. It is planned to overcome this
limitation by implementing a mechanism that automatically instruments the classes belonging
to a specific domain model.
Having extendend that class, the domain objects can notify the engine when one of
its field has been updated by invoking the inherited method notifyModification
as in the following example:
Example 10.7. Notifying the engine that an exam has been moved to a different course
public void setCourse(String course) {
this.course = course;
notifyModification(this);
}
In this way when using an OOPath like the following:
Student( $grade: /plan/exams{ course == "Big Data" }/grades )
if an exam is moved to a different course, the rule is re-triggered and the list of grades matching the rule recomputed.
It is also possible to have reactivity only in one subpart of the OOPath as in:
Student( $grade: /plan/exams{ course == "Big Data" }?/grades )
Here, using the ?/
separator instead of the /
one, the engine will
react to a change made to an exam, or if an exam is added to the plan, but not if a new grade is added to an
existing exam. Of course if a OOPath chunk is not reactive, all remaining part of the OOPath from there till
the end of the expression will be non-reactive as well. For instance the following OOPath
Student( $grade: ?/plan/exams{ course == "Big Data" }/grades )
will be completely non-reactive. For this reason it is not allowed to use the
?/
separator more than once in the same OOPath so an expression like:
Student( $grade: /plan?/exams{ course == "Big Data" }?/grades )
will cause a compile time error.