diff --git a/easy-rules-support/src/main/java/org/jeasy/rules/support/ActivationRuleGroup.java b/easy-rules-support/src/main/java/org/jeasy/rules/support/ActivationRuleGroup.java new file mode 100644 index 0000000..2509a0e --- /dev/null +++ b/easy-rules-support/src/main/java/org/jeasy/rules/support/ActivationRuleGroup.java @@ -0,0 +1,96 @@ +/** + * The MIT License + * + * Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jeasy.rules.support; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; + +import java.util.TreeSet; + +/** + * An activation rule group is a composite rule that fires the first applicable rule and ignores other rules in + * the group (XOR logic). Rules are first sorted by their natural order (priority by default) within the group. + * + * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + */ +public class ActivationRuleGroup extends CompositeRule { + + private Rule selectedRule; + + /** + * Create an activation rule group. + */ + public ActivationRuleGroup() { + rules = new TreeSet<>(rules); + } + + /** + * Create an activation rule group. + * + * @param name of the activation rule group + */ + public ActivationRuleGroup(String name) { + super(name); + rules = new TreeSet<>(rules); + } + + /** + * Create a conditional rule group. + * + * @param name of the activation rule group + * @param description of the activation rule group + */ + public ActivationRuleGroup(String name, String description) { + super(name, description); + rules = new TreeSet<>(rules); + } + + /** + * Create an activation rule group. + * + * @param name of the activation rule group + * @param description of the activation rule group + * @param priority of the activation rule group + */ + public ActivationRuleGroup(String name, String description, int priority) { + super(name, description, priority); + rules = new TreeSet<>(rules); + } + + @Override + public boolean evaluate(Facts facts) { + for (Rule rule : rules) { + if (rule.evaluate(facts)) { + selectedRule = rule; + return true; + } + } + return false; + } + + @Override + public void execute(Facts facts) throws Exception { + selectedRule.execute(facts); + } +} diff --git a/easy-rules-support/src/test/java/org/jeasy/rules/support/ActivationRuleGroupTest.java b/easy-rules-support/src/test/java/org/jeasy/rules/support/ActivationRuleGroupTest.java new file mode 100644 index 0000000..738315a --- /dev/null +++ b/easy-rules-support/src/test/java/org/jeasy/rules/support/ActivationRuleGroupTest.java @@ -0,0 +1,123 @@ +/** + * The MIT License + * + * Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jeasy.rules.support; + +import org.jeasy.rules.annotation.Action; +import org.jeasy.rules.annotation.Condition; +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rules; +import org.jeasy.rules.core.DefaultRulesEngine; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(MockitoJUnitRunner.class) +public class ActivationRuleGroupTest { + + private Facts facts = new Facts(); + private Rules rules = new Rules(); + + private DefaultRulesEngine rulesEngine = new DefaultRulesEngine(); + + @Test + public void onlySelectedRuleShouldBeExecuted_whenComposingRulesHaveDifferentPriorities() { + // given + Rule1 rule1 = new Rule1(); + Rule2 rule2 = new Rule2(); + ActivationRuleGroup activationRuleGroup = new ActivationRuleGroup("my activation rule", "rule1 xor rule2"); + activationRuleGroup.addRule(rule1); + activationRuleGroup.addRule(rule2); + rules.register(activationRuleGroup); + + // when + rulesEngine.fire(rules, facts); + + // then + assertThat(rule1.isExecuted()).isTrue(); + assertThat(rule2.isExecuted()).isFalse(); + } + + @Test + public void onlySelectedRuleShouldBeExecuted_whenComposingRulesHaveSamePriority() { + // given + Rule2 rule2 = new Rule2(); + Rule3 rule3 = new Rule3(); + ActivationRuleGroup activationRuleGroup = new ActivationRuleGroup("my activation rule", "rule2 xor rule3"); + activationRuleGroup.addRule(rule2); + activationRuleGroup.addRule(rule3); + rules.register(activationRuleGroup); + + // when + rulesEngine.fire(rules, facts); + + // then + // we don't know upfront which rule will be selected, but only one of them should be executed + if (rule2.isExecuted()) { + assertThat(rule3.isExecuted()).isFalse(); + } else { + assertThat(rule3.isExecuted()).isTrue(); + } + } + + @org.jeasy.rules.annotation.Rule(priority = 1) + public class Rule1 { + private boolean executed; + + @Condition + public boolean when() { return true; } + + @Action + public void then() { executed = true; } + + public boolean isExecuted() { return executed; } + } + + @org.jeasy.rules.annotation.Rule(priority = 2) + public class Rule2 { + private boolean executed; + + @Condition + public boolean when() { return true; } + + @Action + public void then() { executed = true; } + + public boolean isExecuted() { return executed; } + } + + @org.jeasy.rules.annotation.Rule(priority = 2) + public class Rule3 { + private boolean executed; + + @Condition + public boolean when() { return true; } + + @Action + public void then() { executed = true; } + + public boolean isExecuted() { return executed; } + } +} \ No newline at end of file