Why does this Drools scenario cause an infinite loop? - java

I'm working on writing a rule set in Drools and the following situation causes an infinite loop by retriggering the rule:
rule "My Rule"
when
a1:ObjectA()
b1:ObjectB(field1 > 0 || a1.field1 in (1,2,3))
ObjectB(field2 > 10)
then
modify( b1 ) { setField3(5) };
end
The following rule change doesn't result in an infinite loop, i.e., when a1 is no longer referenced within ObjectB:
rule "My Rule"
when
b1:ObjectB(field1 > 0 || field4 in (1,2,3))
ObjectB(field2 > 10)
then
modify( b1 ) { setField3(5) };
end
Another scenario which doesn't cause an infinite loop is when I change the || to an && in the second when line:
rule "My Rule"
when
a1:ObjectA()
b1:ObjectB(field1 > 0 && a1.field1 in (1,2,3))
ObjectB(field2 > 10)
then
modify( b1 ) { setField3(5) };
end
From the Drools docs I understand that calling modify(){} will "trigger a revaluation of all patterns of the matching object type in the knowledge base," but since the field I'm modifying, field3, isn't used in the LHS conditions, I didn't think it should reevaluate. However, I am faily certain it has to do with referencing a1.field1 within ObjectB, but I can't find a concrete reason why. Thanks in advance!

It matters at the object level, not the field level. Since you modify a1, the rule is re-evaluated because it relies on the ObjectA objects in working memory. Note that the docs indicate it will "trigger a reevaluation of all patterns of the matching object type in the knowledge base." Not the parameter values.
One way you could avoid this would be to add a constraint like field3 != 5 on the left hand side. That is:
rule "My Rule"
when
a1: ObjectA( field3 != 5 ) // <-- if field3 is already 5, don't fire the rule
b1: ObjectB(field1 > 0 || a1.field1 in (1,2,3))
exists(ObjectA(field2 > 10))
then
modify( a1 ) { setField3(5) };
end
Basically you need to make the rule no longer eligible for re-fire.
Alternatively, depending on your structure you could try to use no-loop, but that only keeps the rule from refiring when immediately triggered by the right hand side. If you have multiple modifications/updates/etc going on, you could get into a "broader" looping scenario where multiple rules cause reevaluation. (Example: Rule A does a re-evaluate, no-loop keeps A from firing again; then B causes re-eval, so A can trigger because no-loop does not apply.)

Related

How to select 2 different attributes from an event in Drools

I am trying to write a drools rule that check if two events happens from the same stream. I have a compliance rules class which contains logic (in the working memory) to be compared with events coming from entry point. all I need is to detect the occurrence of two events, for example I want to detect that event A occurred and after that B occurred. I wrote this role in drools syntax
$comrule : Comprules ( pattern == "response" , isBefore == false)
Event (task == $comrule.antecedent) from entry-point StoreOne
Event (task == $comrule.consequent) from entry-point StoreOne
the problem is this technique doesn't work. the only one working is when I wrote this
Event (task == $comrule.antecedent) from entry-point StoreOne
not Event (task == $comrule.consequent) from entry-point StoreOne
I read the drools documentation but I couldn't find any solve to this problem
any help will be appreciated
The typical pattern for checking that two Events occur in the right order is this:
Comprules( pattern == "response", !isBefore, $a: antecedent, $b: consequent )
$one: Event( task == $a ) from entry-point StoreOne
$two: Event( task == $b, this after $one ) from entry-point StoreOne
Using not tests for the absence of a fact, which would be the situation after $one has arrived while $two still is absent.

Regular expression in Drools

I keep getting compile errors when I try to write my rules.
I am trying to translate this condition into drools
if(model.type.series != null && model.type.series.name.mathes(".*FANR.*") ||
model.type.series.name.matches(".*SANA.*"))
//do something....
This is what I have...
rule "Rule 01" salience 0
when
m : model(type.series != null,
type.series.name.matches(".*FANR.*") ||
type.series.name.matches(".*SANA.*")
a : Result(state == Result.GOOD )
then
a.setState(RESULT.BAD);
....
end
What I was trying to do is to use regular expression to match the part of the string where the 'name' is String type. As I am fair new to drools I don't see where it can cause problems, any help would be appreciated
Use correct Drools syntax, according to the matches operator, as described in the Drools manual.
rule FANRorSANA
when
$n: model($v: type.series.name matches ".*(FANR|SANA).*")
then
And you can use the power of regular expressions for testing alternatives.

Drools + Hibernate: Need to simulate Left Outer Join with OR condition in rule

Using the rule below, I am attempting to match an Account using a rule with an OR logical condition. In this situation, I have a table of Accounts and a table of Insurance records for those accounts. Not all accounts have insurance records. The Hibernate DAO objects exist and there is an association from Account to Insurance. I am not observing expected behavior from this rule. In this situation, I would expect accounts 1, 2, 3, and 4 to match this rule, as the rule should match any Account with status "Inactive" or any account with an Insurance CURRENT_IND value of 'N'. However, only accounts 2 and 4 match the rule. An account will match the rule only if it has an Insurance record. I want all Account records with status = 'Inactive', regardless of their Insurance record's existence.
I am currently testing this with Drools 5.6.10.FINAL and Hibernate 3.6.0.
Is it possible to create such a rule using Hibernate with Drools? What is causing the rule to filter on Insurance records' existence?
package com.app.Testing
import com.app.abilities.RuleBuilder.EvalObject;
import com.app.dao.Insurance;
import com.app.dao.Account;
rule "Null Test"
when
$V1 : Account( )
$V2 : Insurance( ) from $V1.getInsurance()
$V3 : EvalObject(
$V2.getCurrentInd == "N" ||
$V1.getStatus == "Inactive"
)
then
reply.getReplyList().add("Null Test");
end
Example data:
Account:
ACCT_ID STATUS
1 Inactive
2 Inactive
3 Inactive
4 Active
5 Active
Insurance:
ID CURRENT_IND
2 N
4 N
5 Y
Accessing a readily available field of some fact using "from" is completeley unnecessary - you can access via a bound variable or a getter. This is what causes the problem: a null value can't be "frommed" into a pattern and so any Account with insurance == null will block further evaluation.
Moreover, I find the use of a completely fact (EvalObject) for writing a boolean rather baroque. A simple eval should do nicely, even if you have to use the oh-so-terrible Java syntax.
Altogether, the following rule fires on all four of your Accounts:
rule "Better Test"
when
$V1: Account( $sta: status, $ins: insurance )
eval( $sta.equals( "Inactive" ) ||
$ins != null && $ins.getCurrentInd().equals( "N" ) )
then
System.out.println("Better Test " + $V1.getId() );
end
Logic is, of course, a many splendoured thing and it will follow the tug of the leash if applied nicely. Thus, you could also use a couple of rules:
rule "Third Test A"
when
$V1: Account( $sta: status == "Inactive" )
then
System.out.println("Third Test A " + $V1.getId() );
end
rule "Third Test B"
when
$V1: Account( $sta: status == "Active" )
Insurance( currentInd == "N" ) from $V1.getInsurance()
then
System.out.println("Third Test B " + $V1.getId() );
end
"Ahh", I hear you say, "he's making me use two rules for what can be done with one? Duplicate the RHS code? Phooey!"
No, not me: this was just the bridge to the final. How about this:
rule "Fourth Test"
when
$V1: Account( $sta: status == "Inactive" )
or
($V1: Account( $sta: status == "Active" )
and
Insurance( currentInd == "N" ) from $V1.getInsurance()
)
then
System.out.println("Fourth Test " + $V1.getId() );
end
Edit If an account may be associated with any number of Insurance facts (i.e., getInsurance returns a Collection<Insurance>) the last three rules work just as fine. However, they will fire for each associated Insurance where currentInd == "N". To reduce this to a single activation, prefix the Insurance pattern with exists (or not for a somewhat different effect).
It is always worthwhile to consider entering dependant objects (Insurance) as facts as well. Also, a link from the child to the parent (Account) may improve matters.

Error while traversing Null array in DRools rule

I am using arrayList in .drl rule file.
In one rule i am checking list is null or not and setting setFocus(2nd rule) .
in 2nd rule i am getting the element from list, bt in this rule i am getting list is null error.
I want to check list is null or not and getting particular element from that arraylist in one rule.
rule "Rule chesks client had already received Notifications or Not"
salience 10
no-loop true
when
event : Event($listOfClientNotifications : clientNotifications)
eval($listOfClientNotifications < 1)
then
event.setMessage("list is null");
end
2nd rule:
rule "Rule chesks "
salience 05
no-loop true
when
event : Event($listOfClientNotifications : clientNotifications)
value : ClientNotifications() from $listOfClientNotifications; // <<< !!!
then
event.setMessage("Value "+**value.getMessage()**);
end
This <<< !!! is where the null error occurs.
Testing a list (correctly!) against null in one rule doesn't avoid running into a NPE in another rule. Rule evaluations are not, repeat not, synchronized with rule executions, salience and activations groups notwithstanding.
This is a correct check:
rule "check list is null"
when
event : Event(clientNotifications == null)
then
event.setMessage("list is null");
end
Guard against running into NPE
rule "use notifications from list"
when
event : Event($listOfClientNotifications : clientNotifications != null)
value : ClientNotifications() from $listOfClientNotifications;
then
event.setMessage("Value "+ value.getMessage()); // strange, but maybe?
end

Drools on web application

I'm very new to drools but I want to integrate it on my existing project. I'm using Spring MVC framework. I successfully implemented the simple example hello world from the free project of drools. What I want to do now is:
Send a bean to the rules for it to evaluate.
Modify the bean depending on the rules
Send it back to the controller to make a response to the user.
The step 1 I'm already done with it. I was able to insert the bean in rules. What I have for now in my rules is something like this:
global String $test;
rule "Excellent"
when
$m: FLTBean ( listeningScore > 85 )
$p: FLTBean ( listeningScore < 101 )
then
$test = "Excellent";
System.out.println( $test );
end
For step 2 and step 3 I don't know how can I do that. If possible please give me a simple code to be able to do this. I want to have nested rules. With 2 nested rules as an example would be great.
Thanks in advance.
There are a couple of ways you can do this, depending on whether you are using a stateless or stateful session.
rule "Excellent"
no-loop
when
$m: FLTBean ( listeningScore > 85 && listeningScore < 101 )
then
$m.setRating("Excellent")
update( $m )
end
In which case your Java code for a stateless session could be:
FLTBean flt = new FLTBean();
flt.setScore(91);
List<Object> facts = new ArrayList<Object>();
facts.add(flt);
ksession.execute(facts);
System.out.println("Result is " + flt.getRating());
If you are using a stateful session then you can insert facts, fire rules and then query facts out of the working memory. Your rule can insert new facts into the working memory like so:
rule "Excellent"
when
$m: FLTBean ( listeningScore > 85 && listeningScore < 101 )
then
insert( new FLTResult("Excellent") )
end
To get the result back out again, you can use the Drools API to find any objects in the working memory.
/** Provide a reference to the session and the class name
* of the fact you are searching for.
*/
public Collection<Object> findFacts(final StatefulKnowledgeSession session,
final String factClass) {
ObjectFilter filter = new ObjectFilter() {
#Override
public boolean accept(Object object) {
return object.getClass().getSimpleName().equals(factClass);
}
};
Collection<Object> results = session.getObjects(filter);
return results;
}
// And call that like so:
FLTBean flt = new FLTBean();
flt.setScore(91);
ksession.insert(flt);
ksession.fireAllRules();
results = findFacts(ksession, "FLTResult");
One option is to write a query for the bean in order to get it back from the rule engine once the rules have been executed. This could become cumbersome if you have a lot of beans to fetch. The docs show you an examples of this approach.
Another option could be to have global collection where you collect all the beans at the end of rule execution. Just make sure to have the "collect" rule to be low salience, so that it'll be last to execute. This rule would be something like below
rule 'collect results'
salience -500
when
$beans : ArrayList() from collect( MyBean() )
then
someGlobal.setBeans( $beans);
end
In fact, you can probably add directly to a global List if you want. You can also add some conditions with the collect if you don't want all the beans.

Categories