Search code examples
javadroolsrule-enginekie

Drools: AfterMatchFiringEvent triggering multiple times for the same rule with OR conditions?


I'm using the following structure in my DrL file built using RuleDescrBuilder APIs provided with the org.drools.compiler.lang.api.

I trigger an after match event to keep track of number of hits for each rule in the backend. However, when the input(Predicate) matches all the given conditions in Rule1, it's triggering multiple afterMatchFiredEvents for one input.

import com.objects.Predicate

global com.Util policyUtil

dialect "java"

rule "Rule1:RuleId"
    salience 2147483647
when
    predicate := Predicate(  )  
    (
    eval( policyUtil.evaluate(condition1) ) or
    eval( policyUtil.evaluate(condition2) ) or
    eval( policyUtil.evaluate(condition3) )
then

...

end

rule "defaultRule:defaultRule"
    salience 0
when
    predicate := Predicate(  )  
then

predicate.setValue1("default1");
predicate.setValue2("Default2");
drools.halt();

end

This is the after match event trigger:

        public void afterMatchFired(AfterMatchFiredEvent event) {
            logger.info("Matching rule Name:: " + event.getMatch().getRule().getName());
            updateHitCountForRule(event.getMatch().getRule().getName());
        }

This is how ruledescbuilder is used.

        RuleDescrBuilder rdb = pdb.newRule();
        CEDescrBuilder<?, ?> cedb = rdb.lhs();
        cedb = cedb.and();
        for(each condition in rule)
            cedb.eval().constraint(constraint).end();

Q: Why is the afterMatchFired triggering multiple times for the same rule with OR conditions? I'm assuming that eval is not being used correctly. If eval is not used correct, what is the right way to build this kind of rule set using the above approach?


Solution

  • That is because of the way Drools deals with or operator between patterns. What's happening behind the scenes is that Drools creates 3 different rules from your original rule:

    rule "Rule1:RuleId 1"
    salience 2147483647
    when
        predicate := Predicate(  )      
        eval( policyUtil.evaluate(condition1) )
    then
        ...
    end
    
    rule "Rule1:RuleId 2"
    salience 2147483647
    when
        predicate := Predicate(  )      
        eval( policyUtil.evaluate(condition2) )
    then
        ...
    end
    
    rule "Rule1:RuleId 3"
    salience 2147483647
    when
        predicate := Predicate(  )      
        eval( policyUtil.evaluate(condition3) )
    then
        ...
    end
    

    As you can see, there is no short-circuit or operator between patterns in Drools. If all of your eval match, you will not only receive the AfterMatchFiredEvent 3 times, the action part of your rule will also be executed 3 times.

    One possible way to avoid this (even if it is a little hacky) is to use a fact as a flag to avoid the multiple execution of the action part:

    rule "Rule1:RuleId"
    salience 2147483647
    when
        not RuleExecuted()
        predicate := Predicate(  )  
        (
            eval( policyUtil.evaluate(condition1) ) or
            eval( policyUtil.evaluate(condition2) ) or
            eval( policyUtil.evaluate(condition3) )
        )
    then
        ...
        insert(new RuleExecuted());
    end
    

    In this scenario, you will still receive 3 BeforeMatchFiredEvent events in your listener, but only 1 AfterMatchFiredEvent. You will also receive 2 MatchCancelledEvent events too.

    Hope it helps,