update implementation according to the new design of v3

pull/81/head
Mahmoud Ben Hassine 8 years ago
parent 74d37b5eec
commit eb7567e45b

@ -23,12 +23,13 @@
*/
package org.easyrules.core;
import org.easyrules.api.Facts;
import org.easyrules.api.Rule;
/**
* Basic rule implementation class that provides common methods.
*
* You can extend this class and override {@link BasicRule#evaluate()} and {@link BasicRule#execute()} to provide rule
* You can extend this class and override {@link BasicRule#evaluate(Facts)} and {@link BasicRule#execute(Facts)} to provide rule
* conditions and actions logic.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
@ -92,14 +93,14 @@ public class BasicRule implements Rule, Comparable<Rule> {
/**
* {@inheritDoc}
*/
public boolean evaluate() {
public boolean evaluate(Facts facts) {
return false;
}
/**
* {@inheritDoc}
*/
public void execute() throws Exception {
public void execute(Facts facts) throws Exception {
// no op
}

@ -23,6 +23,7 @@
*/
package org.easyrules.core;
import org.easyrules.api.Facts;
import org.easyrules.api.Rule;
import java.util.HashMap;
@ -93,10 +94,10 @@ public class CompositeRule extends BasicRule {
* @return true if <strong>ALL</strong> conditions of composing rules are evaluated to true
*/
@Override
public boolean evaluate() {
public boolean evaluate(Facts facts) {
if (!rules.isEmpty()) {
for (Rule rule : rules) {
if (!rule.evaluate()) {
if (!rule.evaluate(facts)) {
return false;
}
}
@ -112,9 +113,9 @@ public class CompositeRule extends BasicRule {
* @throws Exception thrown if an exception occurs during actions performing
*/
@Override
public void execute() throws Exception {
public void execute(Facts facts) throws Exception {
for (Rule rule : rules) {
rule.execute();
rule.execute(facts);
}
}

@ -23,16 +23,13 @@
*/
package org.easyrules.core;
import org.easyrules.api.Rule;
import org.easyrules.api.RuleListener;
import org.easyrules.api.RulesEngine;
import org.easyrules.api.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.String.format;
import static org.easyrules.core.RuleProxy.asRule;
/**
* Default {@link org.easyrules.api.RulesEngine} implementation.
@ -43,28 +40,27 @@ import static org.easyrules.core.RuleProxy.asRule;
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
class DefaultRulesEngine implements RulesEngine {
public final class DefaultRulesEngine implements RulesEngine {
private static final Logger LOGGER = Logger.getLogger(RulesEngine.class.getName());
/**
* The rules set.
*/
protected Set<Rule> rules;
/**
* The engine parameters
*/
protected RulesEngineParameters parameters;
private RulesEngineParameters parameters;
/**
* The registered rule listeners.
*/
private List<RuleListener> ruleListeners;
public DefaultRulesEngine() {
this.parameters = new RulesEngineParameters();
this.ruleListeners = new ArrayList<>();
}
DefaultRulesEngine(final RulesEngineParameters parameters, final List<RuleListener> ruleListeners) {
this.parameters = parameters;
this.rules = new TreeSet<>();
this.ruleListeners = ruleListeners;
if (parameters.isSilentMode()) {
Utils.muteLoggers();
@ -76,81 +72,41 @@ class DefaultRulesEngine implements RulesEngine {
return parameters;
}
@Override
public void registerRule(final Object rule) {
rules.add(asRule(rule));
}
@Override
public void unregisterRule(final Object rule) {
rules.remove(asRule(rule));
}
@Override
public void unregisterRule(final String ruleName){
Rule rule = findRuleByName(ruleName);
if (rule != null) {
unregisterRule(rule);
}
}
@Override
public Set<Rule> getRules() {
return rules;
}
@Override
public List<RuleListener> getRuleListeners() {
return ruleListeners;
}
@Override
public void clearRules() {
rules.clear();
LOGGER.info("Rules cleared.");
}
@Override
public void fireRules() {
public void fire(Rules rules, Facts facts) {
if (rules.isEmpty()) {
LOGGER.warning("No rules registered! Nothing to apply");
return;
}
sort(rules);
logEngineParameters();
sortRules();
logRegisteredRules();
applyRules();
log(rules, facts);
apply(rules, facts);
}
@Override
public Map<Rule, Boolean> checkRules() {
public Map<Rule, Boolean> check(Rules rules, Facts facts) {
LOGGER.info("Checking rules");
sortRules();
sort(rules);
Map<Rule, Boolean> result = new HashMap<>();
for (Rule rule : rules) {
if (shouldBeEvaluated(rule)) {
result.put(rule, rule.evaluate());
if (shouldBeEvaluated(rule, facts)) {
result.put(rule, rule.evaluate(facts));
}
}
return result;
}
private Rule findRuleByName(String ruleName){
for(Rule rule : rules){
if(rule.getName().equalsIgnoreCase(ruleName))
return rule;
}
return null;
}
private void sortRules() {
rules = new TreeSet<>(rules);
private void sort(Rules rules) {
rules.sort();
}
private void applyRules() {
private void apply(Rules rules, Facts facts) {
LOGGER.info("Rules evaluation started");
for (Rule rule : rules) {
@ -165,18 +121,18 @@ class DefaultRulesEngine implements RulesEngine {
break;
}
if (!shouldBeEvaluated(rule)) {
if (!shouldBeEvaluated(rule, facts)) {
LOGGER.log(Level.INFO, "Rule ''{0}'' has been skipped before being evaluated", name);
continue;
}
if (rule.evaluate()) {
if (rule.evaluate(facts)) {
LOGGER.log(Level.INFO, "Rule ''{0}'' triggered", name);
triggerListenersAfterEvaluate(rule, true);
try {
triggerListenersBeforeExecute(rule);
rule.execute();
triggerListenersBeforeExecute(rule, facts);
rule.execute(facts);
LOGGER.log(Level.INFO, "Rule ''{0}'' performed successfully", name);
triggerListenersOnSuccess(rule);
triggerListenersOnSuccess(rule, facts);
if (parameters.isSkipOnFirstAppliedRule()) {
LOGGER.info("Next rules will be skipped since parameter skipOnFirstAppliedRule is set");
@ -184,7 +140,7 @@ class DefaultRulesEngine implements RulesEngine {
}
} catch (Exception exception) {
LOGGER.log(Level.SEVERE, String.format("Rule '%s' performed with error", name), exception);
triggerListenersOnFailure(rule, exception);
triggerListenersOnFailure(rule, exception, facts);
if (parameters.isSkipOnFirstFailedRule()) {
LOGGER.info("Next rules will be skipped since parameter skipOnFirstFailedRule is set");
break;
@ -203,27 +159,27 @@ class DefaultRulesEngine implements RulesEngine {
}
private void triggerListenersOnFailure(final Rule rule, final Exception exception) {
private void triggerListenersOnFailure(final Rule rule, final Exception exception, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.onFailure(rule, exception);
ruleListener.onFailure(rule, exception, facts);
}
}
private void triggerListenersOnSuccess(final Rule rule) {
private void triggerListenersOnSuccess(final Rule rule, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.onSuccess(rule);
ruleListener.onSuccess(rule, facts);
}
}
private void triggerListenersBeforeExecute(final Rule rule) {
private void triggerListenersBeforeExecute(final Rule rule, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.beforeExecute(rule);
ruleListener.beforeExecute(rule, facts);
}
}
private boolean triggerListenersBeforeEvaluate(Rule rule) {
private boolean triggerListenersBeforeEvaluate(Rule rule, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
if (!ruleListener.beforeEvaluate(rule)) {
if (!ruleListener.beforeEvaluate(rule, facts)) {
return false;
}
}
@ -236,8 +192,8 @@ class DefaultRulesEngine implements RulesEngine {
}
}
private boolean shouldBeEvaluated(Rule rule) {
return triggerListenersBeforeEvaluate(rule);
private boolean shouldBeEvaluated(Rule rule, Facts facts) {
return triggerListenersBeforeEvaluate(rule, facts);
}
private void logEngineParameters() {
@ -248,17 +204,17 @@ class DefaultRulesEngine implements RulesEngine {
LOGGER.log(Level.INFO, "Skip on first failed rule: {0}", parameters.isSkipOnFirstFailedRule());
}
private void logRegisteredRules() {
private void log(Rules rules, Facts facts) {
LOGGER.log(Level.INFO, "Registered rules:");
for (Rule rule : rules) {
LOGGER.log(Level.INFO, format("Rule { name = '%s', description = '%s', priority = '%s'}",
rule.getName(), rule.getDescription(), rule.getPriority()));
}
}
@Override
public String toString() {
return parameters.getName();
LOGGER.log(Level.INFO, "Known facts:");
for (Map.Entry<String, Object> fact : facts) {
LOGGER.log(Level.INFO, format("Fact { %s : %s }", fact.getKey(), fact.getValue().toString()));
}
}
}

@ -23,14 +23,13 @@
*/
package org.easyrules.core;
import org.easyrules.annotation.Action;
import org.easyrules.annotation.Condition;
import org.easyrules.annotation.Priority;
import org.easyrules.annotation.Rule;
import org.easyrules.annotation.*;
import org.easyrules.api.Facts;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
@ -43,7 +42,7 @@ import static java.lang.String.format;
*/
class RuleDefinitionValidator {
public void validateRuleDefinition(final Object rule) {
void validateRuleDefinition(final Object rule) {
checkRuleClass(rule);
checkConditionMethod(rule);
checkActionMethods(rule);
@ -69,7 +68,7 @@ class RuleDefinitionValidator {
Method conditionMethod = conditionMethods.get(0);
if (!isConditionMethodWellDefined(conditionMethod)) {
throw new IllegalArgumentException(format("Condition method '%s' defined in rule '%s' must be public, have no parameters and return boolean type.", conditionMethod, rule.getClass().getName()));
throw new IllegalArgumentException(format("Condition method '%s' defined in rule '%s' must be public, may have parameters annotated with @Fact (and/or a parameter of type Facts) and return boolean type.", conditionMethod, rule.getClass().getName()));
}
}
@ -81,7 +80,7 @@ class RuleDefinitionValidator {
for (Method actionMethod : actionMethods) {
if (!isActionMethodWellDefined(actionMethod)) {
throw new IllegalArgumentException(format("Action method '%s' defined in rule '%s' must be public and have no parameters.", actionMethod, rule.getClass().getName()));
throw new IllegalArgumentException(format("Action method '%s' defined in rule '%s' must be public and may have parameters annotated with @Fact (and/or a parameter of type Facts).", actionMethod, rule.getClass().getName()));
}
}
}
@ -110,14 +109,32 @@ class RuleDefinitionValidator {
}
private boolean isConditionMethodWellDefined(final Method method) {
Parameter[] parameters = method.getParameters();
return Modifier.isPublic(method.getModifiers())
&& method.getReturnType().equals(Boolean.TYPE)
&& method.getParameterTypes().length == 0;
&& validParameters(parameters);
}
private boolean validParameters(Parameter[] parameters) {
List<Parameter> notAnnotatedParams = new ArrayList<>();
for (Parameter parameter : parameters) {
if (parameter.getAnnotation(Fact.class) == null) {
notAnnotatedParams.add(parameter);
}
}
if (notAnnotatedParams.size() > 1) {
return false;
} else if (notAnnotatedParams.size() == 1) {
Parameter parameter = notAnnotatedParams.get(0);
return parameter.getType().isAssignableFrom(Facts.class);
}
return true;
}
private boolean isActionMethodWellDefined(final Method method) {
Parameter[] parameters = method.getParameters();
return Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0;
&& validParameters(parameters);
}
private boolean isPriorityMethodWellDefined(final Method method) {

@ -23,25 +23,25 @@
*/
package org.easyrules.core;
import org.easyrules.annotation.Action;
import org.easyrules.annotation.Condition;
import org.easyrules.annotation.Priority;
import org.easyrules.annotation.Rule;
import org.easyrules.annotation.*;
import org.easyrules.api.Facts;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;
import java.util.logging.Logger;
class RuleProxy implements InvocationHandler {
public class RuleProxy implements InvocationHandler {
private static final Logger LOGGER = Logger.getLogger(RuleProxy.class.getName());
private Object target;
private static RuleDefinitionValidator ruleDefinitionValidator = new RuleDefinitionValidator();
public RuleProxy(final Object target) {
private RuleProxy(final Object target) {
this.target = target;
}
@ -78,11 +78,17 @@ class RuleProxy implements InvocationHandler {
return getRulePriority();
}
if (methodName.equals("evaluate")) {
return getConditionMethod().invoke(target, args); // validated upfront
Facts facts = (Facts) args[0];
Method conditionMethod = getConditionMethod();
List<Object> actualParameters = getActualParameters(conditionMethod, facts);
return conditionMethod.invoke(target, actualParameters.toArray()); // validated upfront
}
if (methodName.equals("execute")) {
for (ActionMethodOrderBean actionMethodBean : getActionMethodBeans()) {
actionMethodBean.getMethod().invoke(target);
Facts facts = (Facts) args[0];
Method actiomMethod = actionMethodBean.getMethod();
List<Object> actualParameters = getActualParameters(actiomMethod, facts);
actiomMethod.invoke(target, actualParameters.toArray());
}
}
if (methodName.equals("equals")) {
@ -106,6 +112,25 @@ class RuleProxy implements InvocationHandler {
return null;
}
private List<Object> getActualParameters(Method method, Facts facts) {
Parameter[] parameters = method.getParameters(); // validated upfront
List<Object> actualParameters = new ArrayList<>();
for (Parameter parameter : parameters) {
Fact annotation = parameter.getAnnotation(Fact.class); // validated upfront
if (annotation == null) { // validated upfront, there may be only one parameter not annotated and which is of type Facts.class
actualParameters.add(facts);
} else {
String factName = annotation.value();
Object fact = facts.get(factName);
if (fact == null) {
throw new RuntimeException(String.format("No fact named %s found in known facts", factName));
}
actualParameters.add(fact);
}
}
return actualParameters;
}
private int compareTo(final org.easyrules.api.Rule otherRule) throws Exception {
String otherName = otherRule.getName();
int otherPriority = otherRule.getPriority();

@ -23,6 +23,8 @@
*/
package org.easyrules.core;
import org.easyrules.api.RulesEngine;
/**
* Parameters of the rules engine.
*
@ -60,6 +62,11 @@ public class RulesEngineParameters {
*/
private boolean silentMode;
public RulesEngineParameters() {
this.name = RulesEngine.DEFAULT_NAME;
this.priorityThreshold = RulesEngine.DEFAULT_RULE_PRIORITY_THRESHOLD;
}
public RulesEngineParameters(String name, boolean skipOnFirstAppliedRule, boolean skipOnFirstFailedRule, int priorityThreshold, boolean silentMode) {
this.name = name;
this.skipOnFirstAppliedRule = skipOnFirstAppliedRule;

Loading…
Cancel
Save