Drools - Running a rule with an empty object - eclipse

I'm trying to write a rule to calculate prices for an insurance product based on conditions. In the 'when' I'm using an object called AdditionalDriver, which contains the details for drivers other than the policy holder. From this, different prices can be calculated based on whether the additional driver is a parent, friend, spouse etc. See below:
when
AdditionalDriver($relToProp : relationToProposer)
then
String relToProp = $relToProp;
if(!relToProp.equals("P"))
{
//prices
}
end
"P" = parent.
This rule works when an additional driver has been added. However, if there is no additional driver, then the object is empty, and so the rule does not run. What do I need to do to get this rule to run, even when the object is empty?
Thanks in advance.

You should write one rule for each of the relative or acquaintance classes:
when
PolicyHolder( $phid: id )
AdditionalDriver( relationToProposer == "P", belongsTo == $phid )
then
//prices
end
For no additional driver being requested, write a rule
when
PolicyHolder( $phid: id )
not AdditionalDriver( belongsTo == $phid )
then
// cheaper prices
end
Don't use conditional statement in your consequences to further distinguish facts. This is a code smell.

Related

Getting value from WHEN part while a condition meets using EXISTS keyword in drools

rule "attaching AV and impact rating"
agenda-group "evaluate likelihood"
dialect "java"
when
Application($threatList:getThreatList())
$av:AttackVector()
exists $threat:Application.Threats(impact == "Disclose Information")from $threatList
exists AttackVector($av == AttackVector.REQUEST_MANIPULATION)
then
RiskRating riskRating=new RiskRating($threat.getImpactRating(),$av.getLikelihood(),$av.getName());
insertLogical(riskRating);
end
I am working on getting the object $threat in THEN part of the above-mentioned rule. If I run the above rule, it says:
Rule Compilation error : [Rule name='attaching AV and impact rating']
referee/security/attack/Rule_attaching_AV_and_impact_rating1426933818.java (7:1053) : $threat cannot be resolved
If I loop through it and get the value in the THEN part, it causes a CARTESIAN product and inserts the values a number of times in the session. My rule looks like this when I get the cartesian product.
rule "attaching AV and impact rating"
agenda-group "evaluate likelihood"
dialect "java"
when
Application($threatList:getThreatList())
$av:AttackVector()
exists $threat:Application.Threats(impact == "Disclose Information")from $threatList
$threat:Application.Threats(impact == "Disclose Information")from $threatList
exists AttackVector($av == AttackVector.REQUEST_MANIPULATION)
then
RiskRating riskRating=new RiskRating($threat.getImpactRating(),$av.getLikelihood(),$av.getName());
insertLogical(riskRating);
end
How do I get the value of $threat in THEN part without having the cartesian product?
Remove the exists operation entirely.
rule "attaching AV and impact rating"
agenda-group "evaluate likelihood"
dialect "java"
when
Application($threatList:getThreatList())
$av: AttackVector()
$threat: Application.Threats(impact == "Disclose Information")from $threatList
exists(AttackVector($av == AttackVector.REQUEST_MANIPULATION))
then
RiskRating riskRating=new RiskRating($threat.getImpactRating(),$av.getLikelihood(),$av.getName());
insertLogical(riskRating);
end
exists means "there is a thing in working memory that matches these conditions/looks like this". It's not used to actually extract or provide a reference to that matching instance. Simply remove the operator and it works as you need -- if there is an Application.Threats that matches your conditions, the rule triggers and the matching value is assigned to the $threat variable.
What you're running into is the fact that you have multiple threats that mean your condition, which is why you're having multiple triggers of the rule -- it will trigger once per matching Application.Threats. The exists keyword mitigates this because it only cares that at least one match exists, but you don't get a reference (because if there's four matches which one will be assigned to the variable? it doesn't make sense, logically.)
So you need to change your rule so that it won't fire multiple times and will instead only fire once when it finds a match. Usually you'd do this by making the consequences do something to working memory that makes the rule no longer eligible to be fired. In your example, you insert a RiskRating object; you could, then, check that no risk rating exists in your conditions:
not( RiskRating( /* insert criteria here or leave empty */ ) )
Alternatively you could retract something from working memory that your rule relies on to be present or a match. For example, if you don't need it for anything later on, you could retract the attack vector:
retract( $av )
Yet another option might be to try and update your getThreatList() implementation to return a Set instead so you don't have duplicates (assuming threats are considered duplicates based on the 'impact' field.) Or you could try to remove all Application.Threats instances that match the criteria from the threatlist being returned.
We simply don't know enough about your use case or rule set to know what data you need or what it looks like, but at the end of the day you simply need the rule to fire once and only once, so to do this you need to somehow update the rule to know that it's no longer valid.

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.)

Drools - fire only once when a condition in child List is met

I would like the "then" clause to only execute one time, but it is executing for each child object in the list that matches.
If any item in the list meets the condition I want to break out and have only one execution of the then clause.
rule "Profile - Delinquent"
when
$c : CreditReportAll( $creditLiability : creditLiability )
$cs: CreditLiability( paymentPatternData.contains("X")) from $creditLiability
then
CreditUserSegment $cu = new CreditUserSegment();
$cu.setSegmentCode("delinquent");
$c.addUserSegmentToList($cu);
end
In order to only fire the rule once when any item in the $creditLiability list meets the condition, you either need to write your rule such that it doesn't iterate across the list, or you need to update your rule so that once it does fire, it changes the facts in working memory to not allow it to fire again.
No iteration
The easiest way to do this is to change your rule to not iterate across the list. To do this, we use the exists keyword like this:
rule "Profile - Delinquent"
when
$c : CreditReportAll( $creditLiability : creditLiability )
exists( CreditLiability( paymentPatternData.contains("X")) from $creditLiability )
then
CreditUserSegment $cu = new CreditUserSegment();
$cu.setSegmentCode("delinquent");
$c.addUserSegmentToList($cu);
end
The exists keyword will match when the there is at least one element present that matches the required condition. Note that we don't assign a variable to the result anymore, because it doesn't make any sense (eg. there's no assignment of $cs here; it would be ambiguous as to what it would even refer to.)
The downside to this approach is that if you update working memory in any other rule (eg by calling insert, modify, update, and so on), you may end up triggering this rule again because the conditions on the left hand side will still remain valid and matching. To alleviate this you may be able to leverage the no-loop rule attribute (depending on your setup). Otherwise you'll want to update your rule (or data in working memory) so that your rule is no longer valid.
Invalidate the rule
The other way to only trigger the rule once is to make the rule no longer valid to fire after it fires once. A trivial way to do this (likely not best practice in this case) would be to insert a flag into working memory and check on its presence. In this case I will use a simple string "DELINQUENT" as the flag.
rule "Profile - Delinquent"
when
not(String(this == "DELINQUENT"))
$c : CreditReportAll( $creditLiability : creditLiability )
$cs: CreditLiability( paymentPatternData.contains("X")) from $creditLiability
then
CreditUserSegment $cu = new CreditUserSegment();
$cu.setSegmentCode("delinquent");
$c.addUserSegmentToList($cu);
insert("DELINQUENT");
end
When the rule fires, it inserts a string that says "DELINQUENT" into working memory. The rule conditions are such that the rule only fires if this string doesn't exist in working memory. Thus after the first execution, the rule will not execute again unless a rule retracts that string.
This solution increases the memory footprint of the rule execution because there is more information in working memory. However unlike the other solution (which is more elegant), this version will not re-fire if another rule retriggers execution (eg. via update.)

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.

Boolean flags handing in Drools conditions

We are working on a monitoring application in which we follow the processing of a task in a set of applications.
We have a set of drools rules matching our needs but we have some performance issues (we may have easily up to 50k objects in session).
We are looking for best pactices
This question is about bloolean flag usage.
We are working to remove most of org.drools.core.rule.constraint.MvelConstraint: Exception jitting: ... warns.
We have often such warn on boolean flags.
for example in:
rule "PropagateDeprecation"
when
$parent:BaseChainStep( $parent.Deprecated )
$child:BaseChainStep( $parent.Id == $child.Parent, !$child.Deprecated )
then
modify($child){
setDeprecated(true)
}
end
we have warn on both $parent.Deprecated and !$child.Deprecated.
We would like to understand why there is such warn on boolean flags.
We would like to know also the impacts of the warn on composed conditions.
For example in:
rule "App1_TriggerExpected"
when $chainStep:App1ChainStep(
HasChain
, HasParent
, !$chainStep.Deprecated
, Status in ("error", "closed")
, Places != null
, Analysis != null)
then
..
end
if we have the warn on the first condition HasChain, how is resolved the when clause ?
Does other conditions are evaluated too (with iteration on all App1ChainStep objects) or some "index" are still used to help ?
If its matter, we are using flags as boolean (and not Boolean) to ensure false value as default.
Edit:
The problem may be linked to extended classes. In our use case we have something like:
declare BaseChainStep
parent : GUID
deprecated : boolean
end
declare App1ChainStep extends BaseChainStep
// specific App1 fields
end
BaseChainStep fields may be manipulated in rules using App1ChainStep objects or BaseChainStep objects.
rule "deprecateApp1"
when $app1:App1ChainStep( BusinessLogicCondition )
then
modify($app1) {
setDeprecated(true)
}
end
Then the deprecated flag is propagated to App1 children using "PropagateDeprecation" rule.
Boolean flag causing warn are declared in BaseChainStep class.
Although you are deviating from the conventional way of accessing attributes, this should not trigger the warning you have reported. I can't reproduce this using 6.3.0. You should add (a) the Drools version (b) the Java code for a class BaseChainStep with which the problem can be reproduced with the rule as shown.
This is another (much simpler) way of writing rules combining boolean attributes:
rule bool1
when
X( big, fast )
then
System.out.println( "big fast X" );
end
You may even use boolean operators:
rule bool2
when
X( big && ! fast )
then
System.out.println( "big slow X" );
end
Note the simple use of field name, assuming conventional naming, e.g., big for the field, isBig and setBig for the accessors.