Drools - Multiple matches in then clause - drools

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

Related

Returning query results within rules (when clause of drl file) in drools

I need help in retrieving query results within when clause of drl file in drools.
Example rule file having query:
query getUsersForCard (Long ipCardNumber)
$listOfUsers : UsersList()
$listOfUserCards : User(cardNumber == ipCardNumber, $cardNum : cardNumber) from $listOfUsers.user_list
end
rule "matchUser"
when
getUsersForCard("4444333322221112L";)
then
System.out.println( "$$$card number in VisaMessage matched with card number in UsersList$$$" );
end
How to obtain $cardNum aftergetUsersForCard query call so that then clause gets printed? I don't want to retrieve $cardNum in Java code from Working Memory but rather should be able to do it within the drl file itself.
Help is much appreciated!
Going back to the original question: "Can query results be accessed from the LHS of a rule?", the answer is: "Yes, using unification (Sections 8.8.3.3.6 and 8.9)".
A query in Drools can have input and output parameters. Input parameters are those that have a value (are bound) when the query is invoked. Those parameters that don't have a value (unbound) are considered output parameters.
The first thing to do is to rewrite your query using unification:
query getUsersForCard (Long ipCardNumber, Long $cardNum)
$listOfUsers : UsersList()
$listOfUserCards : User(
cardNumber == ipCardNumber,
$cardNum := cardNumber
) from $listOfUsers.user_list
end
The important thing to notice is the := (unification) sign being used. This operator is basically saying: "If the variable has a value, then I'll act as an == operator. Otherwise I'll act as a variable binding.
When invoking your query from a rule, you need to make sure you don't provide any value for the second parameter in the query. Given that you are already using positional arguments, that's easy to do:
rule "matchUser"
when
getUsersForCard("4444333322221112L", $cardNum;)
then
System.out.println( "$$$card number in VisaMessage matched with card number in UsersList$$$: "+$cardNum );
end
When the query is invoked, $cardNum will not have a value and it will set by the query because of the unification mechanism.
Hope it helps,
You cannot retrieve query results within the when clause the way I think you think it can be done. But there is no need to do so, simply write the rule as
rule "match card number"
when
$listOfUsers: UsersList()
$user: User(cardNumber == "4444333322221112L") from $listOfUsers
then ... end
Assuming the text of the println indicates what you really want to do, you might use
rule "match card number"
when
VisaMessage( $cardNo: cardNumber )
$listOfUsers: UsersList()
$user: User(cardNumber == $cardNo) from $listOfUsers
then ... end

Triggering more then 1 rules in DROOLS DRL File

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.

Salience value nos working correctly when mixing XLS and DRL files?

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.

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