I am using Drools rule engine and I was wondering if it was possible to define when a rule can be executed. For example:
If executing a rule A leads to executing 10 rules B1 to B10, is it possible to choose to execute only one of the Bi rules as a result of rule A??
An example would be :
rule "Rule A"
When
$var : Data(value>10)
then
doSmthg();
Event e = new Event();
insert(e);
end;
rule "Rule B"
When
$var : Data(range >100)
then
doSmthg();
Event e = new Event();
insert(e);
end;
rule "Rule C"
When
$e : Event()
then
doSmthg();
end;
Firing Rule A would lead to the execution of rule C.
Firing Rule B would lead to the execution of rule C.
Is there a way for me to make it so that rule C won't be fired even after rule A is executed? At the same time, firing B should still lead to the execution of rule C.
EDIT:
I have other rules that I need to be fired if Rule A is executed for example :
rule "Rule D"
When
$e : Event()
then
doSmthgElse();
end;
so in this case, I just want to disable Rule C from being fired without altering my other rules.
You are triggering subsequent rules by inserting data into working memory. If you don't want a rule to trigger a 'downstream' rule, don't insert the data. Or, alternatively, update the downstream rule to not trigger on the condition.
Consider these three rules, which are the same as yours but with syntax cleaned up and indentations fixed.
rule "Rule A"
when
Data( value>10 )
then
doSmthg();
insert(new Event());
end
rule "Rule B"
when
Data(range > 100)
then
doSmthg();
insert(new Event());
end
rule "Rule C"
when
$e : Event()
then
doSmthg();
end
Let's assume your input is Data( value = 100, range = 500 ). This is what will happen:
Rule A will hit because the condition value > 10 is met.
Rule B will hit because the condition range > 100 is met.
Rule C will hit twice because there are two instances of Event in working memory (one added by Rule A, one added by Rule B.)
Per your question, we don't want Rule C to fire because of Rule A in this condition. To do this, we need to change Rule A to not trigger Rule C. This is simple: remove the insert(new Event()) from the right hand side of Rule A.
rule "New Rule A"
when
Data( value > 10 )
then
doSmthg();
// No insert!
end
Rule B will still trigger because its condition (range > 100) is still met and it still inserts the Event instance into working memory.
Alternatively if what you're actually trying to do is have both A and B trigger C, but only to trigger C once, you can do that by inserting a semaphor into working memory to indicate that C is fired.
Here is an example:
rule "Rule A"
when
Data( value>10 )
then
doSmthg();
insert(new Event());
end
rule "Rule B"
when
Data(range > 100)
then
doSmthg();
insert(new Event());
end
rule "Rule C"
when
not( EventTriggered() )
$e : Event()
then
doSmthg();
insert( new EventTriggered() )
end
For the same input ( Data( value = 50, range = 500 ) ) this is what will happen:
Rule A will insert Event into working memory
Rule B will insert Event into working memory
Rule C will trigger from one of these Events. It will insert an instance of EventTriggered into working memory.
Rule C will not trigger a second time because the not() condition is not satisfied.
Using this setup, an input of Data( value = 5, range = 500 ) will still trigger Rule C via B:
Rule A will NOT fire (condition not met, value <= 10)
Rule B will fire, Event inserted into working memory
Rule C will fire.
And further, an input of Data( value = 50, range = 0) will also trigger Rule C via A:
Rule A will fire, Event inserted into working memory
Rule B will NOT fire (condition not met, range <= 100)
Rule C will fire.
Which solution you choose depends on what your actual requirements are.
If your requirement is that Rule A should never trigger Rule C, then the first solution (removing the insert statement) is the way to go.
If your requirement is that both Rule A and Rule B should trigger Rule C, but only once, then the second solution is the way to go.
Related
I have a simple rule case here
salience 50
no-loop true
rule "1"
when
input: Input(a == 20, b == 16026)
then
modify(input) {setZ(3)}
end
salience 40
no-loop true
rule "2"
when
input: Input(a == 20, c == 209)
then
modify(input) {setZ(9)}
end
If I leave the above rules as is, they go into a continuous loop.
However, if I modify both rules from:
modify(input) {setZ(9)}
to:
input.setZ(9);
Then the rules execute in order as expected.
My question is: Do I need to use the modify keyword? What does the modify keyword do?
modify (or update) must be used if the Drools Engine is to re-evaluate rules according to the new value for the modified fact object. Omitting this will not trigger rules where constraints match the new value.
For these two rules, modify is not necessary. But if there is a rule
rule x
when
Input( z == 9 || == 3 )
then ... end
you would have to use it. In this case, add constraints to your rules:
Input( ..., z != 3 )
and
Input( ..., z != 9 )
respectively, and it will work and you won't even need no-loop any more.
I came across a particular scenario when updating composed objects in Drools:
declare A
#propertyReactive
flag: Boolean
end
declare B
flag: Boolean
end
declare C
#propertyReactive
attrA: A
attrB: B
end
rule "Create A"
when
not A()
then
insert(new A());
System.out.println("OBJECT A CREATED");
end
rule "Create B"
when
not B()
then
insert(new B());
System.out.println("OBJECT B CREATED");
end
rule "Create C"
when
$A:A()
$B:B()
not C()
then
insert(new C($A,$B));
System.out.println("OBJECT C CREATED");
end
rule "Modify A"
when
$A:A(flag == false)
C()
then
modify($A) {setFlag(true)};
String $output = "Now A is " + $A.getFlag();
System.out.println($output);
end
rule "Print C when C is False"
when
C($A:attrA, attrA.flag == false, $B:attrB)
then
String $output = "A is " + $A.getFlag() + " and B is " + $B.getFlag();
System.out.println($output);
end
rule "Print C when C is True"
when
C($A:attrA, attrA.flag == true, $B:attrB)
then
String $output = "A is " + $A.getFlag() + " and B is " + $B.getFlag();
System.out.println($output);
end
rule "Print C when C is True 2"
when
C($A:attrA, $B:attrB)
A(this == $A, flag == true)
then
String $output = "2 A is " + $A.getFlag() + " and B is " + $B.getFlag();
System.out.println($output);
end
The output is:
OBJECT A CREATED
OBJECT B CREATED
OBJECT C CREATED
A is false and B is false
Now A is true
2 A is true and B is false
So I have the following questions:
Why is rule "Print C when C is True" not firing?
Why do I need to rewrite that rule as "Print C when C is True 2" to make it work?
Does anybody have an explanation for this?
It looks like Drools has an issue in working with accessors of nested classes...
Thank you very much.
Drools only reacts on changes performed to the objects used in your patterns and not to any nested object reference they may contain. And this is not entirely true either.
When you modify a fact in Drools (or any of its nested object references), you have the option to let Drools know about this modification or not. In your case, you are never notifying Drools about them. That is why your rules depend on the (not deterministic) evaluation order.
If you want to let Drools "know" about the modifications on a fact, then you must use the modify or update functions in the RHS of the rules where these modifications happen.
In your particular case, you have not only nested objects, but nested facts: A and C. Even if A is a fact, changes on A (even if properly notified to Drools) will never trigger the re-evaluation of a pattern like this:
C(attrA.flag == true)
The reason why is because the type of the pattern is C and not A. In these kind of scenarios, a rule written like your Print C when C is True 2 rule is a better approach.
Hope it helps,
I have a drl file containing 10 rules. Each has a condition like -
Rule 1
when
field1 == "X"
then
status == "A"
Rule 2
when
field1 == "Y"
then
status == "A"
So as seen based on value of variable field1 rules are to be executed.
Now I have a object containing more then 1 value like List. So if the List contains both values X & Y, only Rule 1 is applied and then processing is stopped. Rule 2 is never triggered.
Is there any way I can ask DROOLS to continue applying Rules even if 1 rule is successful ?
The correct way of writing a rule that matches a String field (called field) in a class Type is
rule "match field"
when
Type( field == "X" )
then
//...
end
You can write another rule where you match against "Y", which is indicated if you need to do something else. If you want to do the same, you can write a slightly more complex test:
rule "match field"
when
Type( field == "X" || == "Y" )
then
//...
end
(Yes, this unusual syntax is permitted in Drools' DRL language.)
To test for a value contained in a List, you can write
rule "match list element"
when
List( this contains "X" )
then
//...
end
Again, you can write another rule where you test for contains "Y", and it will fire, too, as long as the List<String> contains "X" and "Y".
A bare List as a fact in Working Memory is usually not a good idea. But if a class member is a List, a similar form can be used:
rule "match value in list member"
when
Type( list contains "X" )
then
//...
end
There is no need for special precaution to be taken for more than one rule firing. The standard activation session.fireAllRules() fires until all activations have been processed.
Some rule attributes may have the effect of terminating the processing of activations prematurely - use them cautiously.
Try calling fireAllRules(); available in Drools session.
It provides you following variances:
fireAllRules();
fireAllRules(AgendaFilter agendaFilter)
fireAllRules(AgendaFilter agendaFilter, int max)
fireAllRules(int max)
In last two variances, max is the upper limit on the rules to be fired. In your example, if you use max as 10, it can fire all 10 rules provided they meet the criteria.
Please Note: The rules should be configured in such a way that they qualify for execution based on the updated state. Request you to review the criteria fragment (the when clause of the rules and ensure that they indeed result into positive evaluation during execution.
e.g. if rules are written like below:
Rule 1
when
field1 == "X"
then
status == "A"
Rule 2
when
field1 == "Y"
then
status == "A"
then either field1 == "X" is true or field1 == "Y" is true and thus will trigger one rule at MAX.
But if we write the rules like
Rule 1
when
field1 == "X" || field1 == "Y"
then
status == "A"
Rule 2
when
field1 == "X" || field1 == "Y"
then
status == "A"
then both the rules i.e. Rule 1 and Rule 2 will qualify if field1 is X or Y.
Hope this helps.
I'm going to give you a bit of a different answer because I don't know what drools is but I can tell you how we do this in other languages. Basically it requires a string builder concept and some logic touch point changes.
Set your string:
string yourLongerStatusStringSetter = '';
Work through your checking and append your string:
Rule 1 when field1 == "X" then yourLongerStatusStringSetter &= "Meets Status A criteria."
Rule 2 when field1 == "Y" then yourLongerStatusStringSetter &= " Meets Status B criteria."
Continue rules and checks and appending (provided criteria is met)
Output your 'statuses':
howeverYouWriteYourOutput( yourLongerStatusStringSetter )
Which will print to the screen:
Meets Status A criteria. Meets Status B criteria
Just food for thought on an alternate approach.
I have different rules defined in DRL text files and XLS spreadsheet files. My XLS rules are executed first I am not able to understand why. The example is to manage a form with different categories and answers. The answer has a score that is used to return the user some information.
My first DRL rule is to sum all scores from all questions in a category:
package Form;
import ...;
rule "Sum Category Score"
salience 500
when
$form : SubmittedForm();
$cat : CategoryWithScore(score == null) from $form.categories;
$categoryScore : Number() from
accumulate($q : Question() from $cat.getQuestions(),
init( int $total = 0 ),
action( $total += $q.getAnswer().getScore(); ),
reverse( $total -= $q.getAnswer().getScore(); ),
result( $total )
);
then
$cat.setScore($categoryScore);
end
And with a XLS file, I define the result of a category depending on the total score.
As we can see, the DRL file has salience 500 and the XLS file has salience 250. Then I expect that the DRL rule is executed first.
If I print the XLS in rule format, everything seems correct:
package ScoreClassification;
//generated from Decision Table
import ...;
no-loop true
salience 250
// rule values at B13, header at B8
rule "Form Score Classification_13"
when
$form : SubmittedForm(); $cat : CategoryWithScore($cat.getText() == 'Cat1', $cat.getScore() >= 0, $cat.getScore() < 40) from $form.getCategoriesWithScore();
then
$cat.setResult('Good');
end
// rule values at B14, header at B8
...
I have put some simple System.out.println in the methods getScore and setScore of the category, to see what is happening. And I can see that getScore is executed first! (and has null value) and later setScore that assign correctly the value.
Why salience is not respected?
Three things.
First, you must use modify to effect a change of a fact in working memory.
rule "Sum Category Score"
...
then
modify( $cat ){ setScore( $categoryScore ) }
end
Otherwise, rule evaluation will never see the changed value.
Added after OPs 1st comment
Second, if the modification is in an object held in a collection within a fact and extracted using from, things begin to get murky. I avoid this, and recommend you to do the same. Insert the CategoryWithScore facts, and your problem is solved. (You may have to ascertain that the selected CategoryWithScore facts belong to the same SubmittedForm, if there is more than one form in WM at the same time.)
Third, it is a common misconception that priority (or salience) has an effect on the ordere of left hand side evaluations, and can be used to delay the evaluation of a rule where, constraint expressions may run into NPEs. It is best practice to write constraints so that null values result in a short-circuited false.
If you think that this clutters rules: yes, you are right. But even pure Java code is more robust when class members are set to some default or out-of-band value (might be -1 in your case) rather than left at null.
I have an ArrayList in working memory that I am iterating with a 'from' statement in my when clause. However, the rule is iterating backwards through the array. Is there a way to control this behavior?
List Creation:
List<Map<String,String>> list = new ArrayList<Map<String,String>>();
Map<String,String> m1 = ...
Map<String,String> m2 = ...
list.add(m1);
list.add(m2);
ImportItem item = new ImportItem(list); //model object. Constructor arg returned by getInput()
StatelessKnowledgeSession session = knowledgeBase.newStatelessKnowledgeSession();
session.insert(item);
session.fireAllRules();
Drools rules:
rule "My Rule"
dialect "java"
when
item : ImportItem(ignore == false) //model object that holds the input
map : Map() from item.getInput() // == the ArrayList from above
then
System.out.println(map);
end
Produces
<output of m2>
<output of m1>
The iteration is not backwards, as you can see if you attach an audit log to your session. The execution of the rules is what follows, by default, a LIFO order (in case you are not using any other conflict resolution strategy).
My suggestion for you is don't try to encapsulate the objects in data structures. If you are reading lines of a file and you need to process these lines in order, just use a class to represent the line and insert the lines directly into the session. To guarantee execution order, you can use the line number as a parameter to salience.
For instance:
rule "Process lines in order"
// salience executes from higher number to lowest number, so using the
// negative value you will executem them from the first to the last
salience (- $lineNumber )
when
$line : Line( $lineNumber : lineNumber )
then
// do something with $line
end
Obviously the same trick can be used with the "from" approach you took, but the engine is much more efficient matching facts in the working memory than it is iterating over collections of objects.
Hope it helps.
Edson
You should always try to write the rules so that the order doesn't count. If however, you really really need to have rules firing in certain order, or in this case, get the facts from a list in a certain order. Make sure that whatever changes in the rule engine internals doesn't break up your rules when you update to a newer version of the engine.
You can't trust the "from" even if it did the iteration the other way around.
There is one problem with this solution. If you update the ImportItem and need to iterate through the input again, you need to retract the MyIterator that is related to it.
declare MyIterator
index:int
lastIndex:int
importItem:ImportItem
end
rule "Insert ImportItem iterator"
when
item : ImportItem()
not MyIterator( importItem == item )
then
MyIterator myIterator = new MyIterator();
myIterator.setImportItem( item );
myIterator.setIndex( 0 );
myIterator.setLastIndex( item.getInput().size() );
insert( myIterator );
end
rule "My Rule"
when
myIterator :MyIterator( index < lastIndex )
item : ImportItem( this == myIterator.importItem,
ignore == false )
map : Map() from item.getInput().get( myIterator.getIndex() )
then
System.out.println(map);
myIterator.setIndex( myIterator.getIndex() + 1 );
update( myIterator );
end