issue #119: Add separate concepts for conditions and actions
parent
ca5eb83923
commit
4ff3f90705
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.api;
|
||||
|
||||
/**
|
||||
* This interface represents a rule's action.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||
*/
|
||||
public interface Action {
|
||||
|
||||
/**
|
||||
* Execute the action when the rule evaluates to true.
|
||||
*
|
||||
* @param facts known at the time of execution of the action
|
||||
* @throws Exception when unable to execute the action
|
||||
*/
|
||||
void execute(Facts facts) throws Exception;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.api;
|
||||
|
||||
/**
|
||||
* This interface represents a rule's condition.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||
*/
|
||||
public interface Condition {
|
||||
|
||||
/**
|
||||
* Evaluate the condition according to the known facts.
|
||||
*
|
||||
* @param facts known when evaluating the rule.
|
||||
*
|
||||
* @return true if the rule should be triggered, false otherwise
|
||||
*/
|
||||
boolean evaluate(Facts facts);
|
||||
|
||||
/**
|
||||
* A NoOp {@link Condition} that always returns false.
|
||||
*/
|
||||
Condition FALSE = new Condition() {
|
||||
@Override
|
||||
public boolean evaluate(Facts facts) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A NoOp {@link Condition} that always returns true.
|
||||
*/
|
||||
Condition TRUE = new Condition() {
|
||||
@Override
|
||||
public boolean evaluate(Facts facts) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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.core;
|
||||
|
||||
import org.jeasy.rules.api.Action;
|
||||
import org.jeasy.rules.api.Condition;
|
||||
import org.jeasy.rules.api.Facts;
|
||||
import org.jeasy.rules.api.Rule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class DefaultRule implements Rule {
|
||||
|
||||
private String name = Rule.DEFAULT_NAME;
|
||||
private String description = Rule.DEFAULT_DESCRIPTION;
|
||||
private int priority = Rule.DEFAULT_PRIORITY;
|
||||
|
||||
private Condition condition = Condition.FALSE;
|
||||
private List<Action> actions = new ArrayList<>();
|
||||
|
||||
DefaultRule(String name, String description, int priority, Condition condition, List<Action> actions) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.priority = priority;
|
||||
this.condition = condition;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(Facts facts) {
|
||||
return condition.evaluate(facts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Facts facts) throws Exception {
|
||||
for (Action action : actions) {
|
||||
action.execute(facts);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rules are unique according to their names within a rule set (Rules object).
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!o.getClass().isAssignableFrom(Rule.class)) return false;
|
||||
|
||||
Rule that = (Rule) o;
|
||||
|
||||
return name.equals(that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final Rule rule) {
|
||||
int priorityComparisonResult = Integer.compare(priority, rule.getPriority());
|
||||
if (priorityComparisonResult != 0) {
|
||||
return priorityComparisonResult;
|
||||
} else {
|
||||
return name.compareTo(rule.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.core;
|
||||
|
||||
import org.jeasy.rules.api.Action;
|
||||
import org.jeasy.rules.api.Condition;
|
||||
import org.jeasy.rules.api.Rule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RuleBuilder {
|
||||
|
||||
private String name = Rule.DEFAULT_NAME;
|
||||
private String description = Rule.DEFAULT_DESCRIPTION;
|
||||
private int priority = Rule.DEFAULT_PRIORITY;
|
||||
|
||||
private Condition condition = Condition.FALSE;
|
||||
private List<Action> actions = new ArrayList<>();
|
||||
|
||||
public static RuleBuilder aNewRule() {
|
||||
return new RuleBuilder();
|
||||
}
|
||||
|
||||
public RuleBuilder named(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RuleBuilder withDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RuleBuilder withPriority(int priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RuleBuilder when(Condition condition) {
|
||||
this.condition = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RuleBuilder then(Action action) {
|
||||
this.actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Rule build() {
|
||||
return new DefaultRule(name, description, priority, condition, actions);
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.core;
|
||||
|
||||
import org.jeasy.rules.api.Action;
|
||||
import org.jeasy.rules.api.Condition;
|
||||
import org.jeasy.rules.api.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class DefaultRuleTest extends AbstractTest {
|
||||
|
||||
@Mock
|
||||
private Condition condition;
|
||||
@Mock
|
||||
private Action action1, action2;
|
||||
|
||||
@Test
|
||||
public void WhenConditionIsTrue_ThenActionsShouldBeExecutedInOrder() throws Exception {
|
||||
// given
|
||||
when(condition.evaluate(facts)).thenReturn(true);
|
||||
Rule rule = RuleBuilder.aNewRule()
|
||||
.when(condition)
|
||||
.then(action1)
|
||||
.then(action2)
|
||||
.build();
|
||||
rules.register(rule);
|
||||
|
||||
// when
|
||||
rulesEngine.fire(rules, facts);
|
||||
|
||||
// then
|
||||
InOrder inOrder = Mockito.inOrder(action1, action2);
|
||||
inOrder.verify(action1).execute(facts);
|
||||
inOrder.verify(action2).execute(facts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void WhenConditionIsFalse_ThenActionsShouldNotBeExecuted() throws Exception {
|
||||
// given
|
||||
when(condition.evaluate(facts)).thenReturn(false);
|
||||
Rule rule = RuleBuilder.aNewRule()
|
||||
.when(condition)
|
||||
.then(action1)
|
||||
.then(action2)
|
||||
.build();
|
||||
rules.register(rule);
|
||||
|
||||
// when
|
||||
rulesEngine.fire(rules, facts);
|
||||
|
||||
// then
|
||||
verify(action1, never()).execute(facts);
|
||||
verify(action2, never()).execute(facts);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.core;
|
||||
|
||||
import org.jeasy.rules.api.Action;
|
||||
import org.jeasy.rules.api.Condition;
|
||||
import org.jeasy.rules.api.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)public class RuleBuilderTest {
|
||||
|
||||
@Mock
|
||||
private Condition condition;
|
||||
@Mock
|
||||
private Action action1, action2;
|
||||
|
||||
@Test
|
||||
public void testDefaultRuleCreationWithDefaultValues() throws Exception {
|
||||
// when
|
||||
Rule rule = RuleBuilder.aNewRule().build();
|
||||
|
||||
// then
|
||||
assertThat(rule.getName()).isEqualTo(Rule.DEFAULT_NAME);
|
||||
assertThat(rule.getDescription()).isEqualTo(Rule.DEFAULT_DESCRIPTION);
|
||||
assertThat(rule.getPriority()).isEqualTo(Rule.DEFAULT_PRIORITY);
|
||||
assertThat(rule).isInstanceOf(DefaultRule.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultRuleCreationWithCustomValues() throws Exception {
|
||||
// when
|
||||
Rule rule = RuleBuilder.aNewRule()
|
||||
.named("myRule")
|
||||
.withDescription("myRuleDescription")
|
||||
.withPriority(3)
|
||||
.when(condition)
|
||||
.then(action1)
|
||||
.then(action2)
|
||||
.build();
|
||||
|
||||
// then
|
||||
assertThat(rule.getName()).isEqualTo("myRule");
|
||||
assertThat(rule.getDescription()).isEqualTo("myRuleDescription");
|
||||
assertThat(rule.getPriority()).isEqualTo(3);
|
||||
assertThat(rule).isInstanceOf(DefaultRule.class);
|
||||
assertThat(rule).extracting("condition").containsExactly(condition);
|
||||
assertThat(rule).extracting("actions").containsExactly(asList(action1, action2));
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package org.jeasy.rules.tutorials.airco;
|
||||
|
||||
import org.jeasy.rules.annotation.Action;
|
||||
import org.jeasy.rules.annotation.Condition;
|
||||
import org.jeasy.rules.annotation.Fact;
|
||||
import org.jeasy.rules.annotation.Rule;
|
||||
import org.jeasy.rules.api.Facts;
|
||||
|
||||
@Rule(name = "air conditioning rule", description = "if it is hot, decrease temperature" )
|
||||
public class AirConditioningRule {
|
||||
|
||||
@Condition
|
||||
public boolean isItHot(@Fact("temperature") int temperature) {
|
||||
return temperature > 25;
|
||||
}
|
||||
|
||||
@Action
|
||||
public void coolAir(Facts facts) {
|
||||
System.out.println("It is hot! cooling air..");
|
||||
Integer temperature = facts.get("temperature");
|
||||
facts.put("temperature", temperature - 1);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.jeasy.rules.tutorials.airco;
|
||||
|
||||
import org.jeasy.rules.api.Action;
|
||||
import org.jeasy.rules.api.Facts;
|
||||
|
||||
public class DecreaseTemperatureAction implements Action {
|
||||
|
||||
static DecreaseTemperatureAction decreaseTemperature() {
|
||||
return new DecreaseTemperatureAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Facts facts) throws Exception {
|
||||
System.out.println("It is hot! cooling air..");
|
||||
Integer temperature = facts.get("temperature");
|
||||
facts.put("temperature", temperature - 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.jeasy.rules.tutorials.airco;
|
||||
|
||||
import org.jeasy.rules.api.Condition;
|
||||
import org.jeasy.rules.api.Facts;
|
||||
|
||||
public class HighTemperatureCondition implements Condition {
|
||||
|
||||
static HighTemperatureCondition itIsHot() {
|
||||
return new HighTemperatureCondition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(Facts facts) {
|
||||
Integer temperature = facts.get("temperature");
|
||||
return temperature > 25;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue