drools rule get value from a map - drools

How to get the perticular value in the drools when block.
I am looking for something like this but its not working:
I have inserted Hashmap into Working memory and trying to retrieve it in When
$expiry_date:HashMap(get("CREDIT_CARD_EXPIRATION_DATE"));
eval(ageInDays($expiry_date)>10) ;
I get below error
[42,37]: [ERR 101] Line 42:37 no viable alternative at input '"CREDIT_CARD_EXPIRATION_DATE"' in rule "Rule1" in pattern HashMap

For maps/lists/arrays, you can use [] syntax to access elements. Also, if you are using Drools 5.3+, evals are basically irrelevant now.
rule X
when
HashMap( ageInDays(this["CREDIT_CARD_EXPIRATION_DATE"]) > 10 )
then
...
end
With Drools 5.1/5.2, you could do:
rule X
when
HashMap( eval( ageInDays(this["CREDIT_CARD_EXPIRATION_DATE"]) > 10 ) )
then
...
end

Usually its better to insert more typed objects than just a hash map. Can you explain the information that are you trying to process and why do you choose to insert a hashmap instead of a typed object?
I'm pretty sure that you can do something like:
HashMap($expire: keys["CREDIT_CARD_EXPIRATION_DATE"] )
eval(ageInDays($expire) > 10)
I didn't test it but you should look in that direction if you can't insert more typed facts.
Cheers

Related

LHS Make Empty List Trigger Rule

I am new to Drools so please bear over with the terminology!
Can I make rule trigger even though an empty list is involved in the LHS?
I have the code below.
There are two rules. One rule that insert facts and another rule that work on facts.
Part of fact B is to hold a list of facts A.
If this list is not empty then I am able to work on fact B.
If this list is empty then I am not able to work on fact B.
How can I write the code so I am able to work on fact B even though the list is empty.
If I activate the line below "b.As.add(a);" then I can work on fact B.
If I deactivate the line below "b.As.add(a);" then I can work not on fact B.
declare A
nameA : String
end
declare B
nameB : String
As : java.util.ArrayList
end
rule "insertfacts"
when
then
A a = new A();
a.setNameA("A");
B b = new B();
b.setNameB("B");
b.As = new java.util.ArrayList();
b.As.add(a); // Only with this line rule checkfacts fires
insert( b );
end
rule "checkfacts"
when
$b : B();
$a : A() from $b.As;
then
// take action based on object $b
end
Thanks
Thomas S
To check if an object is present in working memory, you should use not() or exists() (depending on what you're trying to do.)
So your insert-facts rule should be like this:
rule "insertfacts"
when
not(B())
then
A a = new A();
a.setNameA("A");
B b = new B();
b.setNameB("B");
b.As = new java.util.ArrayList();
b.As.add(a); // Only with this line rule checkfacts fires
insert( b );
end
This way you won't end up with extra B instances because insertfacts will only trigger when B doesn't exist (so, once.)
To check for an empty list, you can do size == 0 as a check:
rule "As list is empty"
when
$b: B()
ArrayList( size == 0 ) from $b.As
then
// At this point you know that As is an empty list,
// so you can insert stuff if you want
end
Alternatively, if you want to check that a specific object is not in the list, you can use not memberOf.
rule "Some instance A is not in the As list"
when
$a: A() // the A instance we're trying to check
$b: B( $a not memberOf As )
then
// at this point As is a list of any size which doesn't contain the $a instance
end
Similarly there's memberOf for checking that the specific object is in the list:
rule "Some instance A is in the As list"
when
$a: A()
$b: B( $a memberOf As )
then
// As is a list of any size which DOES contain $a
end
Note that there are two complementary operators you could be using to check that something is or is not part of a list: memberOf and contains. They're roughly the same, just with the arguments in different orders. See this other question for more information about those two operators.
Of course, if you don't care at all about the contents of the As ArrayList, then just don't check anything against it in the "when" clause. If you're sure you'll never have a situation where it's undefined, you can even skip the null check.
rule "We don't care whether there's something in As"
when
$b: B( $as: As )
then
// at this point we have access to the variable $as which will be
// the arraylist; it may be empty, it may have stuff in it
end
... but you could always do a null check if you want to be safe:
$b: B( $as: As != null)
It was mentioned in a comment that you plan to write regular code in the "then" clause and loop through the list. Usually you don't want to do that -- if you just want to do work against the members of the list or a subset of the list (eg items of the list which meet certain criteria), Drools will implement the iteration out of the box for you.
As a simple example, let's say that you want to find all of the A instances inside of the list which have the name "PURCHASE" and update the name to be "VERIFIED_PURCHASE".
rule "Update PURCHASE to VERIFIED_PURCHASE"
when
B( $as: As != null )
$a: A( name == "PURCHASE" ) from $as
then
$a.name = "VERIFIED_PURCHASE"
end
(You could add an update call on the right hand side if it's important to reevaluate rule matches, but in this simple example it's not needed.)
What will happen here, and what's usually a little difficult for people new to Drools to wrap their heads around, is that Drools will internally iterate through the As array list and check each item to see if it matches the requested criteria (name == "PURCHASE"). For each item it finds that matches the criteria, it will trigger the right hand side.
So for this example, if As has 10 items and 3 are named "PURCHASE", this rule's consequences ("then" clause) will trigger 3 times, once per matched item.
If you need a collection of these matches on the right hand side, you can use collect or accumulate depending on how complex your use case is.
rule "Get all purchases"
when
B( $as: As != null )
$purchases: ArrayList() from collect( A( name == "PURCHASE" ) from $as )
then
// do something with $purchases here
end
Of course, you shouldn't then turn around and iterate through $purchases on the right hand side -- use the built-in way I showed previously.
The Drools engine is extremely good at optimizing performance; anything you put in the "when" clause gets optimized by Drools, which is why you want to do the iteration there (and let Drools leverage its own internal capabilities at that.)
The right hand side ("then" clause) is not optimized, so by putting the iteration and other logic on that side is going to make your rules perform worse in comparison. You may not notice the difference in a toy example like the ones we're working on in this question, but once you start processing hundreds or thousands of requests per minute, it will definitely start showing.
(I spent 10 years of my career supporting an embarrassingly huge collection of rules (scale is millions of rules) and I've never had the need to actually write a for-loop or any sort of iteration in the "then" clause. And the few places where I did find them as added by other engineers, I was able to remove them for non-negligible performance improvements. Let the framework do what it's good at.)

newbie - Drools filter from same fact

I am newbie to drools. Need understanding how to cascade filtered data from one condition to next one as shown below.
rule "rulename"
when
$var1 : MainObj(filter1)
$var2 : SubObj() from $var1.getSubObj // need all SubObj without any filter here for then clause.
$var3 : SubObj(conditions) from $var2 // Need a subset of SubObj for another then clause
then
$var2.doSomething()
$var3.doVar3Stuff()
end
How can I achieve that?
Continuation of my original post..
i have below rule computing statistics of a rule execution as below.
rule "myrule"
when
$a:wrapper(isActive("ruleName") && dataTypeCode.equals("typeCodeinDB"))
$total : Number() from accumulate($entity: MyObj(ObjAttribute=='testValue') from $a.getMyObj() , count( $entity ))
$filter: MyObj(ObjAttribute=="testValue" && ObjAttribute2.equals("ValidValue")) from $a.getMyObj()
then
$filter.addToResults($filter.getRuleConstituents(),1,drools.getRule().getName(),null); // Add data for insertion in DB at later stage
$filter.addPayloadExecution($a.dataTypeCode(),$a.getMyObj().toString(),$a.getMyObj().size()); //Total Count of MyObj
$filter.addRuleExecutions(drools.getRule().getName(), $total); // anotehr slice of data
end
Here.. i am computing two stats.. Total count of MYObj(), apply some filter for data validation and compute count of valid list ($filter is valid data)
When I run that code I am getting error as below
Exception executing consequence for rule "MyRule" in moneta.rules: [Error: $filter.addRuleExecutions(drools.getRule().getName(), $total): null]
[Near : {... $filter.addToResults($filter.get ....}]
^
[Line: 1, Column: 1]
Need advice on resolving this.
I can't tell if you're trying to change the data in one rule and make it visible in another, or if you're trying to do a single complex condition in a single rule. So I'll outline how both work.
Complex conditions
The pseudo-code in your rule indicates that you have a main object with a filter and a collection of sub-objects. You need to get a list of all sub-objects without a filter, and also to get a list of sub-objects that meet some sort of condition.
To collect these sublists, we have two available operations: accumulate and collect. Since you didn't give a real example, just some psuedo-code, I can't say for sure which is the correct one for you to use. The example I present below uses collect because it is appropriate for this use case. For more information about these operations, refer to the Drools documentation. I've linked to the section on operators; scroll down to see the details for collect and accumulate.
So imagine I have an app that is modeling a school, and my KindergartenClass object has a List of Student objects. In this case, KindergartenClass is the main object, and Student is the sub-object. My Students have names, ages, and an Allergies object that indicates food allergies. Students without Allergies have no allergy object. This is going to be the filter for the example rule -- finding the students without allergies.
The rule would then look like:
rule "EXAMPLE"
when
KindergartenClass( $students: students ) // $var1
// Find all students with no allergies, $var2
$var2: List() from collect( Student( allergies == null ) from $students )
// Find all students who are older than 5, $var3
$var3: List() from collect( Student( age > 5 ) from $students )
then
...
end
Obviously your right-hand-side would have to be adjusted because 'List' has no doSomething() and doVar3Stuff() methods. But without an actual example rule for what you're attempting I can't be more specific here.
You could get the individual students from the lists like this:
$studentWithoutAllergies: Student() from $var2
$studentOlderThan5: Student() from $var3
... but then your then-clause would trigger once for every single $studentWithoutAllergies and $studentOlderThan5. Most people don't want a cartesian product all elements in two lists and then having their consequences firing for each of those products.
Of course, you also talk about "cascading", though it's rather unclear what you mean. If you mean that you want $var3 to be a subset of what you found in $var2, then it's as simple as changing the from clause. So in the example I've been working with, if you actually want all students older than 5 who have no allergies, you can simply change the when clause to be:
// Find all students with no allergies, $var2
$var2: List() from collect( Student( allergies == null ) from $students )
// Find the subset of the non-allergy students who are older than 5, $var3
$var3: List() from collect( Student( age > 5 ) from $var2 )
Changing the data that triggers the rule
There are two keywords available for changing the data available to the left hand side (when clause): update and insert. Which one you use depends on what you're trying to do, and come with very different considerations in terms of performance.
When you ask Drools to fire a set of rules, it first goes through all of the rules and determines if the rule fires, given the data you've inputted. Basically it just goes through, in order, all of the when clauses and decides if the rule is activated or not. Then, once it has the sub-set of rules, in order, it goes through, one by one, executing the right hand side.
If you call update in your right hand side (then clause), then this process repeats: all rules are evaluated again, from the top, including the rule you just fired. If you call insert in your then clause, then the new data is put into working memory, and Drools re-evaluates all of the remaining rules in its execution list. (So, for example, if Drools had decided that rules A, B, C, and D met their when criteria, and B inserts a new fact into working memory, then C and D would be re-evaluated to make sure that they were still valid.)
This is how you'd use them:
rule "Example rule with Update"
when
$input: MyAwesomeInputObject(someValue == 0)
then
$input.setSomeValue(99);
$input.doSomeStuff();
update($input);
end
rule "Example rule with Insert"
when
Person(name == "Bob")
not( Age() )
then
Age age = new Age(18);
insert(age); // there is now an Age object in working memory
end
I'm not 100% familiar with your use case, but I'm going to assume you want to fire all of the filtering rules a second time after your initial rule fires, with the updated data. So I'd do something like this:
rule "rulename"
when
$var1 : MainObj(filter1)
$var2 : SubObj() from $var1.getSubObj // need all SubObj without any filter here for then clause.
$var3 : SubObj(conditions) from $var2 // Need a subset of SubObj for another then clause
then
$var2.doSomething();
$var3.doVar3Stuff();
update($var1); // update the var1 in working memory
end
I have no idea what your when clause is trying to do, since it doesn't appear to match the comments and the syntax is completely wonky. So only the right hand side (then clause) has been adjusted. I have added update($var1) so it will refire the rules with the new instance of $var1 that should have your changes in it.
Of course, once you start re-firing all rules, you run the risk of having rules loop. If this does happen, you will see spikes of CPU and apparent thread deadlocks (this is useful to alert on.) Note that there is a no-loop property, but it won't help when you're calling update.

How to convert "like each" into a functional form?

Let's say I have a column of a table whose data type is a character array. I want to pass in a functional select where clause, where the column is in a list of given strings. However, I cannot simply use (in; `col; myList) for reasons. Instead, I need to do the equivalent of:
max col like/: myList
which effectively gives the same result. However, I have tried to put this in functional form
(max; (like/:; `col; myList))
And I am getting a type error. Any ideas on how I could make this work?
A nice trick when dealing with this problem is using parse on a string of the select statement you want to functionalize. For example:
q)parse"select from t where max col like/: myList"
?
`t
,,(max;((/:;like);`col;`myList))
0b
()
Or specifically in your case you want the 3rd element of the result list (the where clause):
q)(parse"select from t where max col like/: myList")2
max ((/:;like);`col;`myList)
I even think using this pattern in your actual code can be a good idea, as functionalized statements like max ((/:;like);`col;`myList) can get pretty unreadable pretty quickly!
Hope that helps!
(any; ((/:;like); `col; enlist,myList))
it should be: (max;((/:;like);`col;`mylist))

drools rule for fact with list values and multiple conditions

i am attempting to write a rule that will compare a fact list values to a multiple condition rule
rule: only valid legislations
when
fact:Fact (legislationList == ('L1' && 'L2') OR 'L3')
then
fact.printMessage();
end
ie. if fact.legislationList={'L1', 'L2') then fire
if fact.legislationList={'L1', 'L4') then dont fire
i looked at the 'from' clause but wasnt clear how that would solve my issue.
is there some voodoo method/solution that will help me?
thanks
-lp
Assuming that legislationList is a List<String>, then you could use Drools' 'contains' operator:
rule: only valid legislations
when
fact:Fact ((legislationList contains "L1" && legislationList contains "L2") || legislationList contains "L3" )
then
fact.printMessage();
end
You can abbreviate the syntax a bit, but I wanted to be explicit.
Hope it helps,

Drools: using rule "exists" for a Map(Scala)

I have a class "Records" which has a Map[String,Double](Scala) attribute "payOrders" and I want to find out whether the map contains value larger than 50. The rule I wrote is like this:
rule "user1"
dialect "mvel"
no-loop
when
$user:Records($pay:payOrders.values)
exists(Number(doubleValue > 50) from $pay)
then
System.out.println("user1")
end
The problem is there is no error but the rule doesn't work! No outputs.
Then, I tried to print $pay. The output is $pay:MapLike(300.0). First I thought drools can't analyze this type, so I revised $user:Records($pay:payOrders.values) to $user:Records($pay:payOrders.values.toList). But it still prints nothing. It seems that once I added the exists line, the rule just don't work.
Can anyone help me? Thanks!