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.
Related
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.
Below is some simple example of the dsl. Let's say in WHEN block the true is "city == "NY"", is there a way to know, in the "then" block, which condition is true?
rule "First Promotion"
when
m : Promotion( city == "NY" || products == "SCHERP_S" || assignedProduct == "SCHERP_XL" )
then
**//Here I have to know what was true in WHEN block. For example city value is NY.**
end
thank you!
technically not since you used ||, but you could do multiple rules
rule "First Promotion_city"
when
m : Promotion( city == "NY")
then
end
rule "First Promotion_products"
when
m : Promotion( products == "SCHERP_S")
then
end
rule "First Promotion_assigned"
when
m : Promotion( assignedProduct == "SCHERP_XL")
then
end
Simply splitting the rule has the unpleasant side effect of requiring the multiplication of the condition (or using rule extension) and the multiplication of the consequences, with all disadvantages of code repetition.
A better solution is to use truth inference to create a suitable representation for a more to-the-point value.
Lets have a
class Reason {
Promotion promo;
String field;
String value;
Public( Promotion promo, String field, String value ){...}
//...
}
This can be used to register the reason for the disjunction using rules like this:
rule "First Promotion_city"
when
m : Promotion( $city: city == "NY" || "LA" )
then
insertLogical( new Reason( m, "city", $city ) );
end
I have added LA to show that multiple values can be handled with one rule.
The actual rule becomes
rule "First Promotion"
when
m : Promotion( )
r: Reason( promo == m )
then
//... access r for details
end
The disjunction may be true for more than one value, so you may have several Reason facts inserted. I can't advise on this since I don't know the "overall picture".
Clearly, the distinction on the RHS requires logic to decide what was true. But in the Q, there was no indication in which way one "has to know what was true". Accessing the original fact is, of course, the straightforward possibility. Having a value such as r.getField as a distinction right away, though, may be handy for selecting data from a map, etc. You get my drift.
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 am creating a rule which needs to fire when one or more conditions are met. The rule looks as follows:
rule "Demo Rule"
when
$data : Data (val == 1 || val == 2)
then
System.out.println($data);
end
I have a test for the rule, which has two matching Data objects (val 1 and val 2). The rule fires correctly, as it gets to the print statement. However, I can't find a way to parse $data and get access to the individual matching Data objects. The println results as follows:
custom.package.Data< val: 1, text:'Test1' >
custom.package.Data< val: 2, text:'Test2' >
So, I can see that multiple entries are present. But attempting to call as an array ($data[0]) or $data$1 or anything I could think of all result in parsing errors by Drools (complaining that $data is a Data object, not an array or list or other iterable).
The rule is fired once per each object matching your condition. So, you'll need to access the Data object directly as $data.val and data.text
If you need/want the objects in a list, you can use collect for that. Then your rule would be like
rule "Demo Rule"
when
$dataList : ArrayList() from collect( Data (val == 1 || val == 2) )
then
System.out.println($dataList);
end