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.
Related
i have a question about stream mode in Drools.
I'm using this rule
declare MetaMessage
#role(event)
end
rule 'rule1' ruleflow-group 'default'
when
$inMess : MetaMessage() from entry-point 'default'
not(MetaMessage(this != $inMess, this after [0s,10s] $inMess) from entry-point 'default')
then
//do things
end
If i send a MetaMessage, i expect the rule to execute after the 10s specified, but nothing appens.
If i send a new MetaMessage, after 10 seconds, the rule executes.
Edit: if i change the rule and take away the not, it works like a charm
I'm not sure what am i doing wrong.
This is how i create the KieBase
KieBaseConfiguration config = KieServices.Factory.get().newKieBaseConfiguration();
config.setOption(EventProcessingOption.STREAM);
KieBase kieBase = kieHelper.build(config);
KieSession kieSession = kieBase.newKieSession();
Edit 2
I fire the rules using fireAllRules() everytime a new MetaMessage is inserted in a Kafka queue.
So i have a consumer collecting messages and inserting them in the session like this
EntryPoint ep = kieSession.getEntryPoint("default");
ep.insert(metaMessage);
kieSession.fireAllRules();
Edit 3
I have another simple rule that gets executed toghether with the previous one
rule "AccumulatedTest"
when
accumulate(MetaMessage( timestamp > 0 ); $cnt: count(1))
then
log.info("Message n: "+$cnt);
end
The first time a message gets inserted (when the kieSession is newly created) i get the info "Message n: 0".
But then this rule does not fire anymore.
If any other message gets inserted in the session the rule does not fire
You use ruleflow-group 'default', thus you must set focus to agenda group to get rule executed, like getSession().getAgenda().getAgendaGroup("default").setFocus();
Most likely you set focus before or after event insertion. After inserting first message you set focus but no rule was added to the agenda (rule is not yet eligible for execution) at that time, and focus get reset to main agenda group. After 10 seconds you state that no rule was triggered despite that rule was added to 'default' agenda (because main group has focus). You insert second message and set focus to 'default' agenda which executes the rule from the agenda being triggered by the first message.
Rule is getting executed if you
remove ruleflow-group (latest documentation promote agenda-group)
add auto-focus true to the rule
set agenda group focus after 10 seconds
See how agenda group works
Ok, thanks to #EstebanAliverti and #Mike, i have fixed the problem.
Let me elaborate:
I had to create a scheduled fireAllRules() that runs every 1s (as #EstebanAliverti suggested)
I removed the ruleflow-group from the rule because (as #Mike suggested) the focus switches back to the default agenda group and, in my implementation, i cannot pass the agenda group from the scheduled execution
So now the fireAllRules() runs every 1s and there is no ruleflow or agenda group
Now the rule looks like this and it works as intended
declare MetaMessage
#role(event)
end
rule 'rule1'
when
$inMess : MetaMessage()
not(MetaMessage(this != $inMess, this after [0s,10s] $inMess) )
then
//do things
end
Last but not least, since i removed the entrypoint as i wasn't using the funcionality, i now insert the messages directly inside the KieSession
kieSession.insert(metaMessage);
kieSession.fireAllRules();
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.)
I am trying to migrate Drools from version 5.2 to 7.21. I rebuilded the code to KIE API and all looked fine, but now I get a problem in DRL files. In "when" section in Drools file I need to use statement "finished != true". In v5.2 it worked fine, but in v7.21 not...
My code:
rule"..."
when
element : Operation( person.id == $person.getId(), finished != true )
then
(...)
end
I've done some tests, and the results are wierd:
finished != true -> it doesn't work and all objects with "finished == true" are in the results too
finished == false -> like above
finished -> it's working fine and only the objects with "finished == true" are in the results
finished == true -> like above
I need to use finished != true or something similar. How can I fix it? Is it an error in new Drools version?
I found a workaround which is working for me. If someone have better way to do it, please share it here.
rule"..."
when
$booleanTrue : Boolean(booleanValue == true) from 1 == 1
element : Operation( person.id == $person.getId(), finished != $booleanTrue )
then
(...)
end
Simply use below:
rule"..."
when
element : Operation( person.id == $person.getId(), finished)
then
(...)
end
Or
rule"..."
when
element : Operation( person.id == $person.getId(), !finished)
then
(...)
end
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.
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.