Triggering more then 1 rules in DROOLS DRL File - drools

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.

Related

Drools: multiple matching rules

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.

How to know in "then" block what was true in "when"

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.

Drools - Multiple matches in then clause

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

Matching a list (of tags) with another and detecting presence of common elements

My requirement is to match tags. In the example, this particular HourConstraint checks the TeacherHour assigned to Hour(23).
Specifically, it checks TeacherHour.attributes["tags"] for the values ["asst_ct","teacher_john_smith"] and detects atleast one match, two in this case (both "asst_ct" and "teacher_john_smith") .
TeacherHour:
id: 47
assigned_hour: Null
attributes:Map<List<String>>
"tags":["asst_ct","no_strenuous_duties","kinda_boring","teacher_john_smith"]
"another_attribute":[...]
HourConstraint:
hour: Hour(23)
attribute: "tags"
values_list: ["asst_ct","teacher_john_smith"]
Question: How do I detect the presence (true or false) of common elements between two lists?
Drools Expert has memberOf and contains, but they check a scalar vs a collection, never a collection vs a collection.
I see two potential ways:
introduce a function boolean isIntersecting(list,list) and tell Drools to use that for truth checking
Implement TeacherHour.attributes[] as a string instead of a list and HourConstraint.valueslist as a regular expression that can match that list
There are a few options. Most straight forward is to use the Collections class to do that for you:
rule X
when
$t: TeacherHour( )
HourConstraint( Collections.disjoint( $t.attributes["tags"], values_list ) == false )
...
If this is something you would use often in your rules, then I recommend wrapping that function in a pluggable operator, supported by Drools. Lets say you name the operator "intersect", you can then write your rules like this:
rule X
when
$t: TeacherHour( )
HourConstraint( values_list intersect $t.attributes["tags"] )
...
A third option, is to use "from", but that is less efficient in runtime as it causes iterations on the first list:
rule X
when
$t: TeacherHour( )
$tag : String() from $t.attributes["tags"]
exists( HourConstraint( values_list contains $tag ) )
...

Why does the 'from' keyword iterate backwards?

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