Merge branch 'unit-rule-group'
commit
b874b761ac
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jeasy</groupId>
|
||||||
|
<artifactId>easy-rules</artifactId>
|
||||||
|
<version>3.2.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easy-rules-support</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>Easy Rules Support module</name>
|
||||||
|
<description>Support classes module</description>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<url>git@github.com:j-easy/easy-rules.git</url>
|
||||||
|
<connection>scm:git:git@github.com:j-easy/easy-rules.git</connection>
|
||||||
|
<developerConnection>scm:git:git@github.com:j-easy/easy-rules.git</developerConnection>
|
||||||
|
<tag>HEAD</tag>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<issueManagement>
|
||||||
|
<system>GitHub</system>
|
||||||
|
<url>https://github.com/j-easy/easy-rules/issues</url>
|
||||||
|
</issueManagement>
|
||||||
|
|
||||||
|
<ciManagement>
|
||||||
|
<system>Travis CI</system>
|
||||||
|
<url>https://travis-ci.org/j-easy/easy-rules</url>
|
||||||
|
</ciManagement>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>benas</id>
|
||||||
|
<name>Mahmoud Ben Hassine</name>
|
||||||
|
<url>http://benas.github.io</url>
|
||||||
|
<email>mahmoud.benhassine@icloud.com</email>
|
||||||
|
<roles>
|
||||||
|
<role>Lead developer</role>
|
||||||
|
</roles>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>http://opensource.org/licenses/mit-license.php</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeasy</groupId>
|
||||||
|
<artifactId>easy-rules-core</artifactId>
|
||||||
|
<version>${parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j-api.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -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<Rule> rules;
|
||||||
|
|
||||||
|
private Map<Object, Rule> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue