relative new comer to drools and rules engines.
I have three rules that essentially check if different instances of the same object exist, if they don't, they create one and insert it into the working memory. After performing a series of other related rules, theres a rule that checks for validity of the models.
The goal here is to have three different instances of the same object that represent three different entities and proceed as long as 2 of them are in a valid state.
They're defined as follows:
rule "SetupA"
salience 80
lock-on-active
when
not InvalidModel(name == "A-Name")
then
InvalidModel invalidA = new InvalidModel();
invalidA.setName("A-Name");
insert(invalidA);
end
rule "SetupB"
salience 80
lock-on-active
when
not InvalidModel(name == "B-Name")
then
InvalidModel invalidB = new InvalidModel();
invalidB.setName("B-Name");
insert(invalidB);
end
rule "SetupC"
salience 80
lock-on-active
when
not InvalidModel(name == "C-Name")
then
InvalidModel invalidC = new InvalidModel();
invalidC.setName("C-Name");
insert(invalidC);
end
rule "VALIDATE_MODEL_FOR_ANY_INVALID_FLAGS"
salience -10
when
$invalidList: List(size > 1) from collect(InvalidModel(isNotFound || isFlagged || isNotActive))
then
setDecision(result, "Denied");
end
However, what's happening is that it seems like these rules are matched on every request to this ruleset which I interpret as the facts not existing in the Working Memory. I'd expect the rules to be fired once, when each of the facts for the respective names dont exist, and then to not fire again as there is nothing that would remove them from the working memory. However, my audit log of the rules that fire show that for every request those rules get fired.
Furthermore, the fourth rule's condition seems to not be performing as expected as it returns a response like such
=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================
My ask is for some guidance on where my logic/drools language structure may have gone astray. Thanks for your time.
Related
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.
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.)
I am using decision tables and would like to trigger one rule per input item.
I am using decision have set the Sequential = true and defined all rules as part of the same ACTIVATION-GROUP.
When I trigger the drools rules engine using below it just evaluates for the first input item and others are ignored. The behavior I want is to evaluate at most 1 rule per input item (rule order defined by the Salience).
kieStatelessSession.execute(inputList)
I can get this working by sending one item at a time to the kieStatelessSession, but would prefer to execute all at once.
I am using Drools verison 6.5.0.FINAL and Java 7.
There is no out of the box support in Drools for what you are trying to achieve. If you want your rules to be evaluated once for each fact, you will need to code it yourself.
One approach could be to have another type of facts to mark when one of the inputs is processed:
declare Marker
fact : Object
end
//Bellow are the rules that should be coming from your decision table.
//Each rule will do whatever it needs to do, and then it will create a
//Marker fact for the fact that was processed.
//These rules now include a "not" Conditional Element to avoid a fact to be
//evaluated more than once.
rule "Rule 1"
salience 100
when
$fact: Object(...) //your conditions
not Marker(fact == $fact)
then
//... Your logic
insert(new Marker($fact));
end
...
rule "Rule 50"
salience 50
when
$fact: Object(...) //your conditions
not Marker(fact == $fact)
then
//... Your logic
insert(new Marker($fact));
end
Hope it helps,
I want to run a rule that calls Java method and passes the fact (or rather, its logical value) from another rule, although I don't know if the java methods are at all important for this problem. It's not easy to describe so I'll try to show it based on an example:
rule "Some rule determining fact"
when
... //some conditions
then
insert(new SomeCondition(callSomeJavaMethodReturningBoolean()))
end
rule "Some rule using SomeCondition"
when
SomeCondition($value: value)
... //some other conditions
then
insert(callJavaMethodUsingSomeCondition($value))
end
The problem here is that the first rule doesn't always fire, so SomeCondition is not always definded and second rule isn't evaluated.
My second try was to create two separate rules like this:
rule "Some rule determining fact"
when
... //some conditions
then
insert(new SomeCondition(callSomeJavaMethodReturningBoolean()))
end
rule "SomeConditionTrueRule"
when
SomeCondition(value == true)
... //some other conditions
then
insert(callJavaMethodUsingSomeCondition(true))
end
rule "SomeConditionFalseRule"
when
not SomeCondition() or SomeCondition(value == false)
... //some other conditions
then
insert(callJavaMethodUsingSomeCondition(false))
end
This doesn't work as intended either, because it first evaluates SomeConditionFalseRule before even evaluating my first rule. I'll appreciate any suggestions on how to solve this problem. Drools version used is 6.5.0 if that matters. Also, I want to avoid using salience if possible since I've read it is a bad practice (correct me if I'm wrong).
In this case, you need to separate the 2 groups of rules (the ones inserting SomeCondition objects and the ones executing the java code).
The easiest way would be to use a lower salience in the second group:
rule "Some rule determining fact"
when
... //some conditions
then
insert(new SomeCondition(callSomeJavaMethodReturningBoolean()))
end
rule "SomeConditionTrueRule"
salience -1
when
SomeCondition(value == true)
... //some other conditions
then
insert(callJavaMethodUsingSomeCondition(true))
end
rule "SomeConditionFalseRule"
salience -1
when
not SomeCondition() or SomeCondition(value == false)
... //some other conditions
then
insert(callJavaMethodUsingSomeCondition(false))
end
A more robust approach would be to use 2 agenda-groups and to activate them one after the other.
But the idea is that the rules executing the java code give time to the rules determining what needs to be executed to come up with the final decision.
In the case I've presented above, as soon as you create your session, you will have an activation (A1) for SomeConditionFalseRule but the activation will not be executed until you call fireAllRules(). If you then insert the necessary facts to make SomeConditionTrueRule true, you will have now an activation (A2) for it.
At this point, the agenda will look like this: | A1 | A2 |.
When calling fireAllRules(), Drools will pick the activation in the agenda with the higher salience (by default, rules have a salience of 0). In this case, A2 will be picked and executed.
The execution of A2 will insert a new Fact that will make A1 invalid and it will create a new activation (A3) for SomeConditionTrueRule. Drools will then proceed to remove A1 from the agenda, so the rule SomeConditionFalseRule will not be executed.
The agenda after A2 is executed will look like this: | A3 |
Hope it helps,
As an exercise in teaching myself drools I'm working on rules for bidding in the bridge card game. The rules work independently of each other (enforced by use of drools.halt() in the rules) but when I try to extend the example by removing the halt() calls, I get behavior I didn't expect. In the example below I am commenting out the halt() in the first rule, and adding the size() condition to the 2nd rule to replace it, trying to prevent the 2nd rule from firing. I would not expect the 2nd rule to fire because the consequence of the first rule added a bid to the Auction's collection, and so subsequently in rule 2 the count should not be zero. I've tried explicitly adding the 'update' or 'modify' directives in the first rule, but that didn't make any difference.
rule "rule1"
salience 100
when
$auction : Auction( $currentBidder : currentBidder != null )
$hand : Hand( owner.equals($currentBidder), getTotalPoints(getLongestSuit()) >= 13 )
then
$auction.bid($currentBidder, new Bid(1, $hand.getLongestSuit()));
//drools.halt();
end
rule "rule2"
salience 1
when
$auction : Auction( $currentBidder : currentBidder != null, getPlayerBids().size() == 0 )
Hand(owner.equals($currentBidder))
then
$auction.bid($currentBidder, new Pass());
drools.halt();
end
When you change an object, you need to tell the engine you updated it. So try adding update($action); at the end of the first rule.