From f88a2cda5bde1c8b5c202b38fd5642a0fff51851 Mon Sep 17 00:00:00 2001 From: Kate Rose Date: Fri, 4 May 2018 16:44:56 -0600 Subject: [PATCH] Add composite rule support for MVEL from a YAML file Resolves #155 --- .../rules/mvel/MVELActivationRuleGroup.java | 60 ++++++++++++ .../jeasy/rules/mvel/MVELCompositeRule.java | 54 +++++++++++ .../rules/mvel/MVELConditionalRuleGroup.java | 93 +++++++++++++++++++ .../jeasy/rules/mvel/MVELRuleDefinition.java | 71 ++++++++++++-- .../rules/mvel/MVELRuleDefinitionReader.java | 20 +++- .../jeasy/rules/mvel/MVELUnitRuleGroup.java | 64 +++++++++++++ .../jeasy/rules/mvel/AnnotatedMVELRule.java | 26 ++++++ .../mvel/MVELActivationRuleGroupTest.java | 88 ++++++++++++++++++ .../mvel/MVELConditionalRuleGroupTest.java | 83 +++++++++++++++++ .../mvel/MVELRuleDefinitionReaderTest.java | 31 +++++++ .../jeasy/rules/mvel/MVELRuleFactoryTest.java | 25 +++++ .../rules/mvel/MVELUnitRuleGroupTest.java | 87 +++++++++++++++++ .../src/test/resources/composite-rule.yml | 23 +++++ easy-rules-support/pom.xml | 2 +- 14 files changed, 716 insertions(+), 11 deletions(-) create mode 100644 easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELActivationRuleGroup.java create mode 100644 easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELCompositeRule.java create mode 100644 easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELConditionalRuleGroup.java create mode 100644 easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELUnitRuleGroup.java create mode 100644 easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/AnnotatedMVELRule.java create mode 100644 easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELActivationRuleGroupTest.java create mode 100644 easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELConditionalRuleGroupTest.java create mode 100644 easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELUnitRuleGroupTest.java create mode 100644 easy-rules-mvel/src/test/resources/composite-rule.yml diff --git a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELActivationRuleGroup.java b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELActivationRuleGroup.java new file mode 100644 index 0000000..c910709 --- /dev/null +++ b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELActivationRuleGroup.java @@ -0,0 +1,60 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; + +public class MVELActivationRuleGroup extends MVELCompositeRule { + + private Rule selectedRule; + + public MVELActivationRuleGroup() { super(); } + + /** + * Set rule name. + * + * @param name of the rule + * @return this rule + */ + public MVELActivationRuleGroup name(String name) { + this.name = name; + return this; + } + + /** + * Set rule description. + * + * @param description of the rule + * @return this rule + */ + public MVELActivationRuleGroup description(String description) { + this.description = description; + return this; + } + + /** + * Set rule priority. + * + * @param priority of the rule + * @return this rule + */ + public MVELActivationRuleGroup priority(int priority) { + this.priority = priority; + return this; + } + + @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-mvel/src/main/java/org/jeasy/rules/mvel/MVELCompositeRule.java b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELCompositeRule.java new file mode 100644 index 0000000..d8bfa3c --- /dev/null +++ b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELCompositeRule.java @@ -0,0 +1,54 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Rule; +import org.jeasy.rules.core.RuleProxy; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +public abstract class MVELCompositeRule extends MVELRule { + + protected Set rules; + + private Map proxyRules; + + /** + * Create a new MVEL composite rule. + */ + public MVELCompositeRule() { + super(); + rules = new TreeSet<>(); + proxyRules = new HashMap<>(); + } + + /** + * 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); + } + } + + /** + * Get the member rules in this composite. + */ + public Set getRules() { + return rules; + } + +} diff --git a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELConditionalRuleGroup.java b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELConditionalRuleGroup.java new file mode 100644 index 0000000..7d5f13d --- /dev/null +++ b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELConditionalRuleGroup.java @@ -0,0 +1,93 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; + +import java.util.*; + +public class MVELConditionalRuleGroup extends MVELCompositeRule { + + private Set successfulEvaluations; + private Rule conditionalRule; + + public MVELConditionalRuleGroup() { super(); } + + /** + * Set rule name. + * + * @param name of the rule + * @return this rule + */ + public MVELConditionalRuleGroup name(String name) { + this.name = name; + return this; + } + + /** + * Set rule description. + * + * @param description of the rule + * @return this rule + */ + public MVELConditionalRuleGroup description(String description) { + this.description = description; + return this; + } + + /** + * Set rule priority. + * + * @param priority of the rule + * @return this rule + */ + public MVELConditionalRuleGroup priority(int priority) { + this.priority = priority; + return this; + } + + + @Override + public boolean evaluate(Facts facts) { + successfulEvaluations = new HashSet<>(); + conditionalRule = getRuleWithHighestPriority(); + if (conditionalRule.evaluate(facts)) { + for (Rule rule : rules) { + if (rule != conditionalRule && rule.evaluate(facts)) { + successfulEvaluations.add(rule); + } + } + return true; + } + return false; + } + + @Override + public void execute(Facts facts) throws Exception { + conditionalRule.execute(facts); + for (Rule rule : successfulEvaluations) { + rule.execute(facts); + } + } + + private Rule getRuleWithHighestPriority() { + List copy = sortRules(); + // make sure that we only have one rule with the highest priority + Rule highest = copy.get(0); + if (copy.size() > 1 && copy.get(1).getPriority() == highest.getPriority()) { + throw new IllegalArgumentException("Only one rule can have highest priority"); + } + return highest; + } + + private List sortRules() { + List copy = new ArrayList<>(rules); + Collections.sort(copy, new Comparator() { + @Override + public int compare(Rule o1, Rule o2) { + Integer i2 = o2.getPriority(); + return i2.compareTo(o1.getPriority()); + } + }); + return copy; + } +} diff --git a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinition.java b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinition.java index 831030d..d69328d 100644 --- a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinition.java +++ b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinition.java @@ -24,7 +24,10 @@ package org.jeasy.rules.mvel; import org.jeasy.rules.api.Rule; +import org.jeasy.rules.api.Rules; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; class MVELRuleDefinition { @@ -34,6 +37,11 @@ class MVELRuleDefinition { private int priority = Rule.DEFAULT_PRIORITY; private String condition; private List actions; + private Rules subrules; + private String compositeRuleType; + private List allowedCompositeTypes = new ArrayList<>( + Arrays.asList("UnitRuleGroup", "ConditionalRuleGroup", "ActivationRuleGroup") + ); public String getName() { return name; @@ -75,15 +83,62 @@ class MVELRuleDefinition { this.actions = actions; } + public void setSubrules(List subruleDefinitions, String compositeRuleType) { + subrules = new Rules(); + for (MVELRuleDefinition ruleDef : subruleDefinitions) { + MVELRule r = ruleDef.create(); + subrules.register(r); + } + setCompositeRuleType(compositeRuleType); + } + + public void setCompositeRuleType(String compositeRuleType) { this.compositeRuleType = compositeRuleType; } + + public String getCompositeRuleType() { return compositeRuleType; } + + public Rules getSubrules() { return subrules; } + MVELRule create() { - MVELRule mvelRule = new MVELRule() - .name(name) - .description(description) - .priority(priority) - .when(condition); - for (String action : actions) { - mvelRule.then(action); + if (subrules == null) { + MVELRule mvelRule = new MVELRule() + .name(name) + .description(description) + .priority(priority) + .when(condition); + for (String action : actions) { + mvelRule.then(action); + } + return mvelRule; + } else { + if (allowedCompositeTypes.contains(compositeRuleType)) { + MVELCompositeRule compositeRule; + + switch (compositeRuleType) { + case "UnitRuleGroup": + compositeRule = new MVELUnitRuleGroup(); + break; + case "ActivationRuleGroup": + compositeRule = new MVELActivationRuleGroup(); + break; + case "ConditionalRuleGroup": + compositeRule = new MVELConditionalRuleGroup(); + break; + default: + throw new IllegalArgumentException("Invalid composite rule type"); + } + + compositeRule.name(name); + compositeRule.description(description); + compositeRule.priority(priority); + + for (Rule rule : subrules) { + compositeRule.addRule(rule); + } + + return compositeRule; + } else { + throw new IllegalArgumentException("Invalid composite rule type"); + } } - return mvelRule; } } \ No newline at end of file diff --git a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinitionReader.java b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinitionReader.java index 5e06373..af7e8d7 100644 --- a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinitionReader.java +++ b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELRuleDefinitionReader.java @@ -64,18 +64,34 @@ class MVELRuleDefinitionReader { Integer priority = (Integer) map.get("priority"); ruleDefinition.setPriority(priority != null ? priority : Rule.DEFAULT_PRIORITY); + String compositeRuleType = (String) map.get("compositeType"); + String condition = (String) map.get("condition"); - if (condition == null ) { + if (condition == null && compositeRuleType == null) { throw new IllegalArgumentException("The rule condition must be specified"); } ruleDefinition.setCondition(condition); List actions = (List) map.get("actions"); - if (actions == null || actions.isEmpty()) { + if ((actions == null || actions.isEmpty()) && compositeRuleType == null) { throw new IllegalArgumentException("The rule action(s) must be specified"); } ruleDefinition.setActions(actions); + List subrules = (List) map.get("subrules"); + if (subrules != null && compositeRuleType == null) { + throw new IllegalArgumentException("Non-composite rules cannot have subrules"); + } else if (subrules == null && compositeRuleType != null) { + throw new IllegalArgumentException("Composite rules must have subrules specified"); + } else if (subrules != null) { + List subruleDefinitions = new ArrayList<>(); + for (Object rule : subrules){ + Map subruleMap = (Map) rule; + subruleDefinitions.add(createRuleDefinitionFrom(subruleMap)); + } + ruleDefinition.setSubrules(subruleDefinitions, compositeRuleType); + } + return ruleDefinition; } } diff --git a/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELUnitRuleGroup.java b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELUnitRuleGroup.java new file mode 100644 index 0000000..c0071ca --- /dev/null +++ b/easy-rules-mvel/src/main/java/org/jeasy/rules/mvel/MVELUnitRuleGroup.java @@ -0,0 +1,64 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; + +public class MVELUnitRuleGroup extends MVELCompositeRule { + + public MVELUnitRuleGroup() { + super(); + } + + /** + * Set rule name. + * + * @param name of the rule + * @return this rule + */ + public MVELUnitRuleGroup name(String name) { + this.name = name; + return this; + } + + /** + * Set rule description. + * + * @param description of the rule + * @return this rule + */ + public MVELUnitRuleGroup description(String description) { + this.description = description; + return this; + } + + /** + * Set rule priority. + * + * @param priority of the rule + * @return this rule + */ + public MVELUnitRuleGroup priority(int priority) { + this.priority = priority; + return this; + } + + @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-mvel/src/test/java/org/jeasy/rules/mvel/AnnotatedMVELRule.java b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/AnnotatedMVELRule.java new file mode 100644 index 0000000..4c6dc18 --- /dev/null +++ b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/AnnotatedMVELRule.java @@ -0,0 +1,26 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Facts; + + +@org.jeasy.rules.annotation.Rule(name = "myRule", description = "my rule description") +public class AnnotatedMVELRule extends MVELRule { + + private boolean executed; + private boolean evaluated; + + public boolean evaluate(Facts facts) { + evaluated = true; + return super.evaluate(facts); + } + + public void execute(Facts facts) throws Exception { + super.execute(facts); + executed = true; + } + + public boolean isExecuted() { return executed; } + + public boolean isEvaluated() { return evaluated; } + +} diff --git a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELActivationRuleGroupTest.java b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELActivationRuleGroupTest.java new file mode 100644 index 0000000..fb30a9c --- /dev/null +++ b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELActivationRuleGroupTest.java @@ -0,0 +1,88 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.annotation.Action; +import org.jeasy.rules.annotation.Condition; +import org.jeasy.rules.annotation.Priority; +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rule; +import org.jeasy.rules.api.Rules; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MVELActivationRuleGroupTest { + private Facts facts = new Facts(); + private MVELActivationRuleGroup activationRuleGroup; + private MVELRule subrule1 = new AnnotatedMVELRule() + .name("subrule1") + .priority(1) + .when("person.age > 18") + .then("person.setAdult(true);"); + private MVELRule subrule2 = new AnnotatedMVELRule() + .name("subrule2") + .priority(2) + .when("person.age > 13") + .then("person.setTeenager(true);"); + + @Before + public void setUp() throws Exception { + activationRuleGroup = new MVELActivationRuleGroup() + .name("activation rule group") + .description("description") + .priority(1); + } + + @Test + public void whenTheRuleIsTriggered_conditionEvaluatesTrue() throws Exception { + activationRuleGroup.addRule(subrule1); + activationRuleGroup.addRule(subrule2); + + // given + facts.put("person", new Person("foo", 20)); + + // when + boolean evaluationResult = activationRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isTrue(); + assertThat(((AnnotatedMVELRule) subrule1).isEvaluated()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isEvaluated()).isFalse(); + } + + @Test + public void whenTheRuleIsTriggered_conditionEvaluatesFalse() throws Exception { + activationRuleGroup.addRule(subrule1); + activationRuleGroup.addRule(subrule2); + + // given + facts.put("person", new Person("foo", 12)); + + // when + boolean evaluationResult = activationRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isFalse(); + assertThat(((AnnotatedMVELRule) subrule1).isEvaluated()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isEvaluated()).isTrue(); + } + + @Test + public void whenTheConditionIsTrue_thenActionsShouldBeExecuted() throws Exception { + activationRuleGroup.addRule(subrule1); + activationRuleGroup.addRule(subrule2); + + // given + Person foo = new Person("foo", 20); + facts.put("person", foo); + + // when + activationRuleGroup.evaluate(facts); + activationRuleGroup.execute(facts); + + // then + assertThat(foo.isAdult()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule1).isExecuted()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isExecuted()).isFalse(); + } +} diff --git a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELConditionalRuleGroupTest.java b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELConditionalRuleGroupTest.java new file mode 100644 index 0000000..198a0a4 --- /dev/null +++ b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELConditionalRuleGroupTest.java @@ -0,0 +1,83 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Facts; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MVELConditionalRuleGroupTest { + private Facts facts = new Facts(); + private MVELConditionalRuleGroup conditionalRuleGroup; + private MVELRule subrule1 = new AnnotatedMVELRule() + .name("subrule1") + .priority(1) + .when("person.age > 18") + .then("person.orderDrink();"); + private MVELRule subrule2 = new AnnotatedMVELRule() + .name("subrule2") + .priority(2) + .when("person.age > 18") + .then("person.setAdult(true);"); + + @Before + public void setUp() throws Exception { + conditionalRuleGroup = new MVELConditionalRuleGroup() + .name("conditional rule group") + .description("description") + .priority(1); + } + + @Test + public void whenTheRuleIsTriggered_conditionEvaluatesTrue() throws Exception { + conditionalRuleGroup.addRule(subrule1); + conditionalRuleGroup.addRule(subrule2); + + // given + facts.put("person", new Person("foo", 20)); + + // when + boolean evaluationResult = conditionalRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isTrue(); + assertThat(((AnnotatedMVELRule) subrule1).isEvaluated()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isEvaluated()).isTrue(); + } + + @Test + public void whenTheRuleIsTriggered_conditionEvaluatesFalse() throws Exception { + conditionalRuleGroup.addRule(subrule1); + conditionalRuleGroup.addRule(subrule2); + + // given + facts.put("person", new Person("foo", 16)); + + // when + boolean evaluationResult = conditionalRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isFalse(); + assertThat(((AnnotatedMVELRule) subrule1).isEvaluated()).isFalse(); + assertThat(((AnnotatedMVELRule) subrule2).isEvaluated()).isTrue(); + } + + @Test + public void whenTheConditionIsTrue_thenActionsShouldBeExecuted() throws Exception { + conditionalRuleGroup.addRule(subrule1); + conditionalRuleGroup.addRule(subrule2); + + // given + Person foo = new Person("foo", 20); + facts.put("person", foo); + + // when + conditionalRuleGroup.evaluate(facts); + conditionalRuleGroup.execute(facts); + + // then + assertThat(foo.isAdult()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule1).isExecuted()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isExecuted()).isTrue(); + } +} diff --git a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleDefinitionReaderTest.java b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleDefinitionReaderTest.java index ae8dc1a..1587452 100644 --- a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleDefinitionReaderTest.java +++ b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleDefinitionReaderTest.java @@ -153,4 +153,35 @@ public class MVELRuleDefinitionReaderTest { // then assertThat(ruleDefinitions).hasSize(0); } + + @Test + public void testRuleDefinitionReading_withCompositeAndBasicRules() throws Exception { + // given + File compositeRuleDescriptor = new File("src/test/resources/composite-rule.yml"); + + // when + List ruleDefinitions = ruleDefinitionReader.readAll(new FileReader(compositeRuleDescriptor)); + + // then + assertThat(ruleDefinitions).hasSize(2); + + // then + MVELRuleDefinition ruleDefinition = ruleDefinitions.get(0); + assertThat(ruleDefinition).isNotNull(); + assertThat(ruleDefinition.getName()).isEqualTo("Movie id rule"); + assertThat(ruleDefinition.getDescription()).isEqualTo("description"); + assertThat(ruleDefinition.getCompositeRuleType()).isEqualTo("UnitRuleGroup"); + assertThat(ruleDefinition.getSubrules().isEmpty()).isFalse(); + for (Rule subrule : ruleDefinition.getSubrules()) { + assertThat(subrule.getPriority()).isEqualTo(1); + } + + ruleDefinition = ruleDefinitions.get(1); + assertThat(ruleDefinition).isNotNull(); + assertThat(ruleDefinition.getName()).isEqualTo("weather rule"); + assertThat(ruleDefinition.getDescription()).isEqualTo("when it rains, then take an umbrella"); + assertThat(ruleDefinition.getSubrules()).isNull(); + assertThat(ruleDefinition.getCondition()).isEqualTo("rain == True"); + assertThat(ruleDefinition.getActions()).isEqualTo(Collections.singletonList("System.out.println(\"It rains, take an umbrella!\");")); + } } \ No newline at end of file diff --git a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleFactoryTest.java b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleFactoryTest.java index 398aeab..cc567e8 100644 --- a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleFactoryTest.java +++ b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELRuleFactoryTest.java @@ -91,4 +91,29 @@ public class MVELRuleFactoryTest { assertThat(adultRule.getDescription()).isEqualTo("when age is greater then 18, then mark as adult"); assertThat(adultRule.getPriority()).isEqualTo(1); } + + @Test + public void testRuleCreationFromFileReader__withCompositeRules() throws Exception{ + // given + File rulesDescriptor = new File("src/test/resources/composite-rule.yml"); + + // when + Rules rules = MVELRuleFactory.createRulesFrom(new FileReader(rulesDescriptor)); + + // then + assertThat(rules).hasSize(2); + Iterator iterator = rules.iterator(); + + Rule rule = iterator.next(); + assertThat(rule).isNotNull(); + assertThat(rule.getName()).isEqualTo("Movie id rule"); + assertThat(rule.getDescription()).isEqualTo("description"); + assertThat(rule.getPriority()).isEqualTo(1); + + rule = iterator.next(); + assertThat(rule).isNotNull(); + assertThat(rule.getName()).isEqualTo("weather rule"); + assertThat(rule.getDescription()).isEqualTo("when it rains, then take an umbrella"); + assertThat(rule.getPriority()).isEqualTo(1);; + } } \ No newline at end of file diff --git a/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELUnitRuleGroupTest.java b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELUnitRuleGroupTest.java new file mode 100644 index 0000000..b0cc5c4 --- /dev/null +++ b/easy-rules-mvel/src/test/java/org/jeasy/rules/mvel/MVELUnitRuleGroupTest.java @@ -0,0 +1,87 @@ +package org.jeasy.rules.mvel; + +import org.jeasy.rules.api.Facts; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MVELUnitRuleGroupTest { + private Facts facts = new Facts(); + private MVELUnitRuleGroup unitRuleGroup; + private MVELRule subrule1 = new AnnotatedMVELRule() + .name("subrule1") + .priority(1) + .when("person.age > 18") + .then("person.setAdult(true);"); + private MVELRule subrule2 = new AnnotatedMVELRule() + .name("subrule2") + .priority(1) + .when("person.age >= 21") + .then("person.setCanDrinkInUS(true);"); + + @Before + public void setUp() throws Exception { + unitRuleGroup = new MVELUnitRuleGroup() + .name("unit rule group") + .description("description") + .priority(1); + } + + @Test + public void whenTheRuleIsTriggered_conditionEvaluatesTrue() throws Exception { + unitRuleGroup.addRule(subrule1); + unitRuleGroup.addRule(subrule2); + + // given + facts.put("person", new Person("foo", 21)); + + // when + boolean evaluationResult = unitRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isTrue(); + assertThat(((AnnotatedMVELRule) subrule1).isEvaluated()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isEvaluated()).isTrue(); + } + + @Test + public void whenTheRuleIsTriggered_conditionEvaluatesFalse() throws Exception { + MVELRule subrule3 = new AnnotatedMVELRule() + .name("subrule3") + .priority(1) + .when("person.age < 18") + .then("System.out.println(\"Not an adult\");"); + unitRuleGroup.addRule(subrule1); + unitRuleGroup.addRule(subrule3); + + // given + facts.put("person", new Person("foo", 20)); + + // when + boolean evaluationResult = unitRuleGroup.evaluate(facts); + + // then + assertThat(evaluationResult).isFalse(); + assertThat(((AnnotatedMVELRule) subrule1).isEvaluated()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule3).isEvaluated()).isTrue(); + } + + @Test + public void whenTheConditionIsTrue_thenActionsShouldBeExecuted() throws Exception { + unitRuleGroup.addRule(subrule1); + unitRuleGroup.addRule(subrule2); + + // given + Person foo = new Person("foo", 20); + facts.put("person", foo); + + // when + unitRuleGroup.execute(facts); + + // then + assertThat(foo.isAdult()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule1).isExecuted()).isTrue(); + assertThat(((AnnotatedMVELRule) subrule2).isExecuted()).isTrue(); + } +} diff --git a/easy-rules-mvel/src/test/resources/composite-rule.yml b/easy-rules-mvel/src/test/resources/composite-rule.yml new file mode 100644 index 0000000..650b7f8 --- /dev/null +++ b/easy-rules-mvel/src/test/resources/composite-rule.yml @@ -0,0 +1,23 @@ +name: Movie id rule +compositeType: UnitRuleGroup +priority: 1 +subrules: + - name: Time is evening + description: If it's later than 7pm + priority: 1 + condition: "day.hour > 19" + actions: + - "person.shouldProvideId(true);" + - name: Movie is rated R + description: If the movie is rated R + priority: 1 + condition: "movie.rating == R" + actions: + - "person.shouldProvideId(true);" +--- +name: weather rule +description: when it rains, then take an umbrella +priority: 1 +condition: "rain == True" +actions: + - "System.out.println(\"It rains, take an umbrella!\");" \ No newline at end of file diff --git a/easy-rules-support/pom.xml b/easy-rules-support/pom.xml index e049d56..77665ff 100644 --- a/easy-rules-support/pom.xml +++ b/easy-rules-support/pom.xml @@ -52,7 +52,7 @@ org.jeasy easy-rules-core - ${parent.version} + ${project.version}