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