Skip to content

Commit

Permalink
[DROOLS-7554] when allowing multiple firings from the same event, eve…
Browse files Browse the repository at this point in the history
…ry event still having at least a partial match should be retained in the working memory (#81)
  • Loading branch information
mariofusco authored Sep 17, 2023
1 parent a4305b1 commit 5da912f
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import org.drools.ansible.rulebook.integration.api.domain.RuleMatch;
import org.drools.core.common.InternalFactHandle;
import org.drools.core.reteoo.LeftTuple;
import org.drools.core.reteoo.RightTuple;
import org.drools.core.reteoo.RuleTerminalNodeLeftTuple;
import org.drools.core.reteoo.Tuple;
import org.kie.api.runtime.rule.AgendaFilter;
import org.kie.api.runtime.rule.FactHandle;
import org.kie.api.runtime.rule.Match;
Expand Down Expand Up @@ -56,14 +60,44 @@ public boolean accept(Match match) {
}

for (InternalFactHandle fh : fhs) {
if (fh.isEvent()) {
if (fh.isEvent() && discardMatchedEvent(fh)) {
eventsToBeDeleted.add(fh);
}
}

return validMatch;
}

private boolean discardMatchedEvent(InternalFactHandle fh) {
// if an event can match multiple rules and is still part of a partial match it shouldn't be discarded yet
return !(rulesExecutorSession.isMatchMultipleRules() && hasPartialMatch(fh));
}

private static boolean hasPartialMatch(InternalFactHandle fh) {
// check if all the tuples created from this fact reached the terminal node, otherwise it is a partial match
for (RightTuple rt = fh.getLinkedTuples().getFirstRightTuple(0); rt != null; rt = rt.getHandleNext()) {
if (isPartialMatch(rt)) {
return true;
}
}
for (LeftTuple lt = fh.getLinkedTuples().getFirstLeftTuple(0); lt != null; lt = lt.getHandleNext()) {
if (isPartialMatch(lt)) {
return true;
}
}
return false;
}

private static boolean isPartialMatch(Tuple tuple) {
// the tuple is a partial match if never reached a terminal node
for (; tuple != null; tuple = tuple.getFirstChild()) {
if (tuple instanceof RuleTerminalNodeLeftTuple) {
return false;
}
}
return true;
}

public List<Match> finalizeAndGetResults(boolean event) {
rulesExecutorSession.registerMatchedEvents(eventsToBeDeleted);
for (FactHandle toBeDeleted : eventsToBeDeleted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

package org.drools.ansible.rulebook.integration.api;

import java.util.List;

import org.junit.Test;
import org.kie.api.runtime.rule.Match;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class MultipleRuleMatchTest {
Expand Down Expand Up @@ -128,4 +128,124 @@ public void processEventsWithMatchMultipleRules_shouldMatchMultipleRules() {

rulesExecutor.dispose();
}

@Test
public void retainLeftPartialMatchesWithMatchMultipleRules() {
checkPartialMatchesWithMatchMultipleRules(true, true);
}

@Test
public void discardLeftPartialMatchesWithMatchMultipleRules() {
checkPartialMatchesWithMatchMultipleRules(false, true);
}

@Test
public void retainRightPartialMatchesWithMatchMultipleRules() {
checkPartialMatchesWithMatchMultipleRules(true, false);
}

@Test
public void discardRightPartialMatchesWithMatchMultipleRules() {
checkPartialMatchesWithMatchMultipleRules(false, false);
}

private static void checkPartialMatchesWithMatchMultipleRules(boolean matchMultipleRules, boolean partialOnLeft) {
String rules =
"{\n" +
" \"match_multiple_rules\":" + matchMultipleRules + ",\n" +
" \"rules\": [\n" +
" {\n" +
" \"Rule\": {\n" +
" \"name\": \"R1\",\n" +
" \"condition\": {\n" +
" \"AllCondition\": [\n" +
" {\n" +
" \"EqualsExpression\": {\n" +
" \"lhs\": {\n" +
" \"Event\": \"i\"\n" +
" },\n" +
" \"rhs\": {\n" +
" \"Integer\": 0\n" +
" }\n" +
" }\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"actions\": [\n" +
" {\n" +
" \"Action\": {\n" +
" \"action\": \"debug\",\n" +
" \"action_args\": {\n" +
" \"msg\": \"First one matches\"\n" +
" }\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"enabled\": true\n" +
" }\n" +
" },\n" +
" {\n" +
" \"Rule\": {\n" +
" \"name\": \"R2\",\n" +
" \"condition\": {\n" +
" \"AllCondition\": [\n" +
" {\n" +
" \"EqualsExpression\": {\n" +
" \"lhs\": {\n" +
" \"Event\": \" " + (partialOnLeft ? "i" : "j") + " \"\n" +
" },\n" +
" \"rhs\": {\n" +
" \"Integer\": 0\n" +
" }\n" +
" }\n" +
" },\n" +
" {\n" +
" \"EqualsExpression\": {\n" +
" \"lhs\": {\n" +
" \"Event\": \"" + (partialOnLeft ? "j" : "i") + " \"\n" +
" },\n" +
" \"rhs\": {\n" +
" \"Integer\": 0\n" +
" }\n" +
" }\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"actions\": [\n" +
" {\n" +
" \"Action\": {\n" +
" \"action\": \"debug\",\n" +
" \"action_args\": {\n" +
" \"msg\": \"Second one matches\"\n" +
" }\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"enabled\": true\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";

RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(rules);

List<Match> matchedRules = rulesExecutor.processEvents("{ \"i\" : 0 }").join();
assertThat(matchedRules).hasSize(1);
assertThat(matchedRules.stream().map(m -> m.getRule().getName())).contains("R1");

matchedRules = rulesExecutor.processEvents("{ \"j\" : 0 }").join();
assertThat(matchedRules).hasSize(matchMultipleRules ? 1 : 0);

if (matchMultipleRules) {
// when multiple match is allowed i=0 should be retained and now used to also fire R2
assertThat(matchedRules.stream().map(m -> m.getRule().getName())).contains("R2");
// if both rules fired now the working memory should be empty
assertThat(rulesExecutor.getAllFacts()).isEmpty();
} else {
// if R2 never fired j=0 should still be there
assertThat(rulesExecutor.getAllFacts().size()).isEqualTo(1);
}

rulesExecutor.dispose();
}
}

0 comments on commit 5da912f

Please sign in to comment.