diff --git a/easy-rules-core/src/main/java/org/jeasy/rules/core/CompositeRule.java b/easy-rules-core/src/main/java/org/jeasy/rules/core/CompositeRule.java index 4dd10d2e..fbfb6f62 100644 --- a/easy-rules-core/src/main/java/org/jeasy/rules/core/CompositeRule.java +++ b/easy-rules-core/src/main/java/org/jeasy/rules/core/CompositeRule.java @@ -37,8 +37,11 @@ * A composite rule is triggered if ALL conditions of its composing rules are satisfied. * When a composite rule is applied, actions of ALL composing rules are performed. * + * @deprecated use UnitRuleGroup instead. This class is deprecated in v3.2 and will be removed in v3.3 . + * * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) */ +@Deprecated public class CompositeRule extends BasicRule { /** diff --git a/easy-rules-support/pom.xml b/easy-rules-support/pom.xml new file mode 100644 index 00000000..347674a2 --- /dev/null +++ b/easy-rules-support/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + org.jeasy + easy-rules + 3.2.0-SNAPSHOT + + + easy-rules-support + jar + Easy Rules Support module + Support classes module + + + git@github.com:j-easy/easy-rules.git + scm:git:git@github.com:j-easy/easy-rules.git + scm:git:git@github.com:j-easy/easy-rules.git + HEAD + + + + GitHub + https://github.com/j-easy/easy-rules/issues + + + + Travis CI + https://travis-ci.org/j-easy/easy-rules + + + + + benas + Mahmoud Ben Hassine + http://benas.github.io + mahmoud.benhassine@icloud.com + + Lead developer + + + + + + + MIT License + http://opensource.org/licenses/mit-license.php + + + + + + org.jeasy + easy-rules-core + ${parent.version} + + + + org.slf4j + slf4j-simple + ${slf4j-api.version} + test + + + junit + junit + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/easy-rules-support/src/main/java/org/jeasy/rules/support/CompositeRule.java b/easy-rules-support/src/main/java/org/jeasy/rules/support/CompositeRule.java new file mode 100644 index 00000000..242d795d --- /dev/null +++ b/easy-rules-support/src/main/java/org/jeasy/rules/support/CompositeRule.java @@ -0,0 +1,116 @@ +/** + * 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 org.jeasy.rules.core.BasicRule; +import org.jeasy.rules.core.RuleProxy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * Base class representing a composite rule composed of a set of rules. + * + * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + */ +public abstract class CompositeRule extends BasicRule { + + /** + * The set of composing rules. + */ + protected Set rules; + + private Map proxyRules; + + /** + * Create a new {@link CompositeRule}. + */ + public CompositeRule() { + this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY); + } + + /** + * Create a new {@link CompositeRule}. + * + * @param name rule name + */ + public CompositeRule(final String name) { + this(name, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY); + } + + /** + * Create a new {@link CompositeRule}. + * + * @param name rule name + * @param description rule description + */ + public CompositeRule(final String name, final String description) { + this(name, description, Rule.DEFAULT_PRIORITY); + } + + /** + * Create a new {@link CompositeRule}. + * + * @param name rule name + * @param description rule description + * @param priority rule priority + */ + public CompositeRule(final String name, final String description, final int priority) { + super(name, description, priority); + rules = new TreeSet<>(); + proxyRules = new HashMap<>(); + } + + @Override + public abstract boolean evaluate(Facts facts); + + @Override + public abstract void execute(Facts facts) throws Exception; + + /** + * Add a rule to the composite rule. + * @param rule the rule to add + */ + public void addRule(final Object rule) { + Rule proxy = RuleProxy.asRule(rule); + rules.add(proxy); + proxyRules.put(rule, proxy); + } + + /** + * Remove a rule from the composite rule. + * @param rule the rule to remove + */ + public void removeRule(final Object rule) { + Rule proxy = proxyRules.get(rule); + if (proxy != null) { + rules.remove(proxy); + } + } + +} diff --git a/easy-rules-support/src/main/java/org/jeasy/rules/support/UnitRuleGroup.java b/easy-rules-support/src/main/java/org/jeasy/rules/support/UnitRuleGroup.java new file mode 100644 index 00000000..70d96211 --- /dev/null +++ b/easy-rules-support/src/main/java/org/jeasy/rules/support/UnitRuleGroup.java @@ -0,0 +1,65 @@ +package org.jeasy.rules.support; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; + +/** + * A unit rule group is a composite rule that acts as a unit: Either all rules are applied or nothing is applied. + * + * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + */ +public class UnitRuleGroup extends CompositeRule { + + /** + * Create a unit rule group. + */ + public UnitRuleGroup() { + } + + /** + * Create a unit rule group. + * @param name of the composite rule + */ + public UnitRuleGroup(String name) { + super(name); + } + + /** + * Create a unit rule group. + * @param name of the composite rule + * @param description of the composite rule + */ + public UnitRuleGroup(String name, String description) { + super(name, description); + } + + /** + * Create a unit rule group. + * @param name of the composite rule + * @param description of the composite rule + * @param priority of the composite rule + */ + public UnitRuleGroup(String name, String description, int priority) { + super(name, description, priority); + } + + @Override + public boolean evaluate(Facts facts) { + if (!rules.isEmpty()) { + for (Rule rule : rules) { + if (!rule.evaluate(facts)) { + return false; + } + } + return true; + } + return false; + } + + @Override + public void execute(Facts facts) throws Exception { + for (Rule rule : rules) { + rule.execute(facts); + } + } +} diff --git a/easy-rules-support/src/test/java/org/jeasy/rules/support/UnitRuleGroupTest.java b/easy-rules-support/src/test/java/org/jeasy/rules/support/UnitRuleGroupTest.java new file mode 100644 index 00000000..c8cbf797 --- /dev/null +++ b/easy-rules-support/src/test/java/org/jeasy/rules/support/UnitRuleGroupTest.java @@ -0,0 +1,177 @@ +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.Rule; +import org.jeasy.rules.api.Rules; +import org.jeasy.rules.core.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class UnitRuleGroupTest { + + @Mock + private Rule rule1, rule2; + + private Facts facts = new Facts(); + private Rules rules = new Rules(); + + private DefaultRulesEngine rulesEngine = new DefaultRulesEngine(); + + private org.jeasy.rules.support.UnitRuleGroup unitRuleGroup; + + + @Before + public void setUp() throws Exception { + when(rule1.evaluate(facts)).thenReturn(true); + when(rule2.evaluate(facts)).thenReturn(true); + when(rule2.compareTo(rule1)).thenReturn(1); + } + + @Test + public void whenNoComposingRulesAreRegistered_thenUnitRuleGroupShouldEvaluateToFalse() { + // given + unitRuleGroup = new UnitRuleGroup(); + + // when + boolean evaluationResult = unitRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isFalse(); + } + + @Test + public void compositeRuleAndComposingRulesMustBeExecuted() throws Exception { + // Given + unitRuleGroup = new UnitRuleGroup(); + unitRuleGroup.addRule(rule1); + unitRuleGroup.addRule(rule2); + rules.register(unitRuleGroup); + + // When + rulesEngine.fire(rules, facts); + + // Then + verify(rule1).execute(facts); + verify(rule2).execute(facts); + } + + public void compositeRuleMustNotBeExecutedIfAComposingRuleEvaluatesToFalse() throws Exception { + // Given + when(rule2.evaluate(facts)).thenReturn(false); + unitRuleGroup.addRule(rule1); + unitRuleGroup.addRule(rule2); + rules.register(unitRuleGroup); + + // When + rulesEngine.fire(rules, facts); + + // Then + /* + * The composing rules should not be executed + * since not all rules conditions evaluate to TRUE + */ + + //Rule 1 should not be executed + verify(rule1, never()).execute(facts); + //Rule 2 should not be executed + verify(rule2, never()).execute(facts); + } + + @Test + public void whenARuleIsRemoved_thenItShouldNotBeEvaluated() throws Exception { + // Given + unitRuleGroup = new UnitRuleGroup(); + unitRuleGroup.addRule(rule1); + unitRuleGroup.addRule(rule2); + unitRuleGroup.removeRule(rule2); + rules.register(unitRuleGroup); + + // When + rulesEngine.fire(rules, facts); + + // Then + //Rule 1 should be executed + verify(rule1).execute(facts); + + //Rule 2 should not be evaluated nor executed + verify(rule2, never()).evaluate(facts); + verify(rule2, never()).execute(facts); + } + + @Test + public void testCompositeRuleWithAnnotatedComposingRules() throws Exception { + // Given + MyRule rule = new MyRule(); + unitRuleGroup = new UnitRuleGroup(); + unitRuleGroup.addRule(rule); + rules.register(unitRuleGroup); + + // When + rulesEngine.fire(rules, facts); + + // Then + assertThat(rule.isExecuted()).isTrue(); + } + + @Test + public void whenAnnotatedRuleIsRemoved_thenItsProxyShouldBeRetrieved() throws Exception { + // Given + MyRule rule = new MyRule(); + MyAnnotatedRule annotatedRule = new MyAnnotatedRule(); + unitRuleGroup = new UnitRuleGroup(); + unitRuleGroup.addRule(rule); + unitRuleGroup.addRule(annotatedRule); + unitRuleGroup.removeRule(annotatedRule); + rules.register(unitRuleGroup); + + // When + rulesEngine.fire(rules, facts); + + // Then + assertThat(rule.isExecuted()).isTrue(); + assertThat(annotatedRule.isExecuted()).isFalse(); + } + + @org.jeasy.rules.annotation.Rule + public class MyRule { + boolean executed; + @Condition + public boolean when() { + return true; + } + @Action + public void then() { + executed = true; + } + public boolean isExecuted() { + return executed; + } + } + + @org.jeasy.rules.annotation.Rule + public static class MyAnnotatedRule { + private boolean executed; + @Condition + public boolean evaluate() { + return true; + } + @Action + public void execute() { + executed = true; + } + public boolean isExecuted() { + return executed; + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 52aabbb7..19af0adb 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ easy-rules-core easy-rules-tutorials easy-rules-mvel + easy-rules-support pom