diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/api/Rule.java b/easyrules-core/src/main/java/io/github/benas/easyrules/api/Rule.java index 8445765..52f527b 100644 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/api/Rule.java +++ b/easyrules-core/src/main/java/io/github/benas/easyrules/api/Rule.java @@ -55,6 +55,18 @@ public interface Rule { */ void setDescription(String description); + /** + * Getter for rule priority. + * @return rule priority + */ + int getPriority(); + + /** + * Setter for rule priority. + * @param priority the priority to set + */ + void setPriority(int priority); + /** * Rule conditions abstraction : this method encapsulates the rule's conditions. * @return true if the rule should be applied, false else diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/AbstractRulesEngine.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/AbstractRulesEngine.java index a7830eb..d57113a 100644 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/AbstractRulesEngine.java +++ b/easyrules-core/src/main/java/io/github/benas/easyrules/core/AbstractRulesEngine.java @@ -29,6 +29,11 @@ public abstract class AbstractRulesEngine implements RulesEngine { */ protected boolean skipOnFirstAppliedRule; + /** + * Parameter to skip next rules if priority exceeds a user defined threshold. + */ + protected int rulePriorityThreshold; + /** * The JMX server instance in which rule's MBeans will be registered. */ @@ -57,7 +62,7 @@ public abstract class AbstractRulesEngine implements RulesEngine { /* * Register a JMX MBean for a rule. */ - private void registerJmxMBean(final R rule) { + protected void registerJmxMBean(final R rule) { ObjectName name; try { diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/AnnotatedRulesEngine.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/AnnotatedRulesEngine.java new file mode 100644 index 0000000..6c0c7f8 --- /dev/null +++ b/easyrules-core/src/main/java/io/github/benas/easyrules/core/AnnotatedRulesEngine.java @@ -0,0 +1,322 @@ +package io.github.benas.easyrules.core; + +import io.github.benas.easyrules.annotation.Action; +import io.github.benas.easyrules.annotation.Condition; +import io.github.benas.easyrules.annotation.Priority; +import io.github.benas.easyrules.annotation.Rule; +import io.github.benas.easyrules.util.EasyRulesConstants; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.logging.Level; + +/** + * Annotated rules engine implementation. + * + * @author Mahmoud Ben Hassine (md.benhassine@gmail.com) + */ +public class AnnotatedRulesEngine extends AbstractRulesEngine { + + private List ruleBeans; + + /** + * Construct an annotated rules engine with default values. + */ + public AnnotatedRulesEngine() { + this(false, EasyRulesConstants.DEFAULT_RULE_PRIORITY_THRESHOLD); + } + + /** + * Constructs an annotated rules engine. + * + * @param skipOnFirstAppliedRule true if the engine should skip next rule after the first applied rule + */ + public AnnotatedRulesEngine(boolean skipOnFirstAppliedRule) { + this(skipOnFirstAppliedRule, EasyRulesConstants.DEFAULT_RULE_PRIORITY_THRESHOLD); + } + + /** + * Constructs an annotated rules engine. + * + * @param rulePriorityThreshold rule priority threshold + */ + public AnnotatedRulesEngine(int rulePriorityThreshold) { + this(false, rulePriorityThreshold); + } + + /** + * Constructs an annotated rules engine. + * + * @param skipOnFirstAppliedRule true if the engine should skip next rule after the first applied rule + * @param rulePriorityThreshold rule priority threshold + */ + public AnnotatedRulesEngine(boolean skipOnFirstAppliedRule, int rulePriorityThreshold) { + rules = new TreeSet(); + ruleBeans = new ArrayList(); + this.skipOnFirstAppliedRule = skipOnFirstAppliedRule; + this.rulePriorityThreshold = rulePriorityThreshold; + } + + @Override + public void registerJmxRule(Object rule) { + registerRule(rule); + registerJmxMBean(rule); + } + + @Override + public void registerRule(Object rule) { + + //check if rule class is annotated with @Rule + if (!isRuleClassWellDefined(rule)) { + throw new IllegalArgumentException("Rule " + rule + " is not annotated with " + Rule.class.getClass()); + } + + //check if condition method is well defined + Method conditionMethod = getConditionMethod(rule); + if (null == conditionMethod) { + throw new IllegalArgumentException("Rule " + rule + " does not have a public method annotated with " + Condition.class.getClass()); + } + + if (!isConditionMethodWellDefined(conditionMethod)) { + throw new IllegalArgumentException("Condition method " + conditionMethod + " defined in rule " + rule + " must be public, have no parameters and return boolean type."); + } + + //check if action methods are well defined + List actionMethods = getActionMethodBeans(rule); + if (actionMethods.isEmpty()) { + throw new IllegalArgumentException("Rule " + rule + " does not have a public method annotated with " + Action.class.getClass()); + } + + for (ActionMethodBean actionMethodBean : actionMethods) { + Method actionMethod = actionMethodBean.getMethod(); + if (!isActionMethodWellDefined(actionMethod)) { + throw new IllegalArgumentException("Action method " + actionMethod + " defined in rule " + rule + " must be public and have no parameters."); + } + } + + //get rule priority for later use + List priorityMethods = getPriorityMethods(rule); + int priority = EasyRulesConstants.DEFAULT_RULE_PRIORITY; + // more than one method annotated with @Priority + if (priorityMethods.size() > 1) { + throw new IllegalArgumentException("Rule " + rule + " have more than one method annotated with " + Priority.class.getClass()); + } + //exactly one method annotated with @Priority + if (!priorityMethods.isEmpty()) { + Method priorityMethod = priorityMethods.get(0); + if (!isPriorityMethodWellDefined(priorityMethod)) { + throw new IllegalArgumentException("Priority method " + priorityMethod + " defined in rule " + rule + " must be public, have no parameters and return integer type."); + } + try { + priority = (Integer) priorityMethod.invoke(rule); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Unable to access method " + priorityMethod + " to get priority of rule " + rule, e); + } catch (InvocationTargetException e) { + throw new IllegalArgumentException("Unable to invoke method " + priorityMethod + " to get priority of rule " + rule, e); + } + } + + ruleBeans.add(new RuleBean(priority, rule)); + } + + @Override + public void fireRules() { + + if (ruleBeans.isEmpty()) { + LOGGER.warning("No rules registered! Nothing to apply."); + return; + } + + //sort rules according to their priorities + Collections.sort(ruleBeans); + + for (RuleBean ruleBean : ruleBeans) { + + Object rule = ruleBean.getRule(); + + Rule annotation = rule.getClass().getAnnotation(Rule.class); + String name = annotation.name(); + + Method conditionMethod = getConditionMethod(rule); + List actionMethods = getActionMethodBeans(rule); + + //sort actions according to their execution order + Collections.sort(actionMethods); + + try { + + int priority = ruleBean.getPriority(); + if (priority > rulePriorityThreshold) { + LOGGER.log(Level.INFO, "Rule priority threshold {0} exceeded at rule {1} (priority={2}), next applicable rules will be skipped.", + new Object[]{rulePriorityThreshold, name, priority}); + break; + } + + Boolean shouldApplyRule = (Boolean) conditionMethod.invoke(rule); + + //apply the rule + if (shouldApplyRule) { + LOGGER.log(Level.INFO, "Rule {0} triggered.", new Object[]{name}); + try { + //execute all actions in the defined order + for (ActionMethodBean actionMethodBean : actionMethods) { + actionMethodBean.getMethod().invoke(rule); + } + LOGGER.log(Level.INFO, "Rule {0} performed successfully.", new Object[]{name}); + + if (skipOnFirstAppliedRule) { + LOGGER.info("Next rules will be skipped according to parameter skipOnFirstAppliedRule."); + break; + } + + } catch (Exception exception) { + LOGGER.log(Level.SEVERE, "Rule '" + name + "' performed with error.", exception); + } + } + } catch (IllegalAccessException e) { + LOGGER.log(Level.SEVERE, "Unable to access method on rule " + rule, e); + } catch (InvocationTargetException e) { + LOGGER.log(Level.SEVERE, "Unable to invoke method on rule " + rule, e); + } + + } + + } + + @Override + public void clearRules() { + ruleBeans.clear(); + super.clearRules(); + } + + /* + * Private Utility methods + */ + + private Method getConditionMethod(Object rule) { + Method[] methods = rule.getClass().getMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(Condition.class)) { + return method; + } + } + return null; + } + + private List getActionMethodBeans(Object rule) { + Method[] methods = rule.getClass().getMethods(); + List actionMethodBeans = new ArrayList(); + for (Method method : methods) { + if (method.isAnnotationPresent(Action.class)) { + Action actionAnnotation = method.getAnnotation(Action.class); + int order = actionAnnotation.order(); + actionMethodBeans.add(new ActionMethodBean(method, order)); + } + } + return actionMethodBeans; + } + + private List getPriorityMethods(Object rule) { + Method[] methods = rule.getClass().getMethods(); + List priorityMethods = new ArrayList(); + for (Method method : methods) { + if (method.isAnnotationPresent(Priority.class)) { + priorityMethods.add(method); + } + } + return priorityMethods; + } + + private boolean isRuleClassWellDefined(Object rule) { + return rule.getClass().isAnnotationPresent(Rule.class); + } + + private boolean isConditionMethodWellDefined(Method method) { + return Modifier.isPublic(method.getModifiers()) + && method.getReturnType().equals(Boolean.TYPE) + && method.getParameterTypes().length == 0; + } + + private boolean isActionMethodWellDefined(Method method) { + return Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0; + } + + private boolean isPriorityMethodWellDefined(Method method) { + return Modifier.isPublic(method.getModifiers()) + && method.getReturnType().equals(Integer.TYPE) + && method.getParameterTypes().length == 0; + } + + /** + * Private utility class that associates an action method and its execution order. + */ + private final class ActionMethodBean implements Comparable { + + private Method method; + + private int order; + + ActionMethodBean(Method method, int order) { + this.method = method; + this.order = order; + } + + public int getOrder() { + return order; + } + + public Method getMethod() { + return method; + } + + @Override + public int compareTo(ActionMethodBean actionMethodBean) { + if (order < actionMethodBean.getOrder()) { + return -1; + } else if (order == actionMethodBean.getOrder()) { + return 0; + } else { + return 1; + } + } + + } + + /** + * Private utility class that associates a rule to its priority. + */ + private final class RuleBean implements Comparable { + + private int priority; + + private Object rule; + + private RuleBean(int priority, Object rule) { + this.priority = priority; + this.rule = rule; + } + + private int getPriority() { + return priority; + } + + private Object getRule() { + return rule; + } + + @Override + public int compareTo(RuleBean ruleBean) { + if (priority < ruleBean.getPriority()) { + return -1; + } else if (priority == ruleBean.getPriority()) { + return 0; + } else { + return 1; + } + } + + } + +} diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicPriorityRule.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicPriorityRule.java deleted file mode 100644 index 5a7d132..0000000 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicPriorityRule.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2014, Mahmoud Ben Hassine (md.benhassine@gmail.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 io.github.benas.easyrules.core; - -import io.github.benas.easyrules.api.PriorityRule; -import io.github.benas.easyrules.util.EasyRulesConstants; - -/** - * Priority rule implementation that provides common methods.
- * - * This class adds rule priority to fire rules according to their priorities. - * - * @author Mahmoud Ben Hassine (md.benhassine@gmail.com) - */ -public class BasicPriorityRule extends BasicRule implements PriorityRule, Comparable { - - /** - * Rule priority. - */ - protected int priority; - - public BasicPriorityRule() { - super(); - this.priority = EasyRulesConstants.DEFAULT_RULE_PRIORITY; - } - - public BasicPriorityRule(final String name) { - super(name); - this.priority = EasyRulesConstants.DEFAULT_RULE_PRIORITY; - } - - public BasicPriorityRule(final String name, final String description) { - this(name, description, EasyRulesConstants.DEFAULT_RULE_PRIORITY); - } - - public BasicPriorityRule(final String name, final String description, final int priority) { - super(name, description); - this.priority = priority; - } - - @Override - public int compareTo(final PriorityRule rule) { - if (priority < rule.getPriority()) { - return -1; - } else if (priority == rule.getPriority()) { - return 0; - } else { - return 1; - } - } - - @Override - public int getPriority() { - return priority; - } - - @Override - public void setPriority(int priority) { - this.priority = priority; - } - -} diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicRule.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicRule.java index af9fa8d..2579dcd 100644 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicRule.java +++ b/easyrules-core/src/main/java/io/github/benas/easyrules/core/BasicRule.java @@ -35,7 +35,7 @@ import io.github.benas.easyrules.util.EasyRulesConstants; * * @author Mahmoud Ben Hassine (md.benhassine@gmail.com) */ -public class BasicRule implements Rule { +public class BasicRule implements Rule, Comparable { /** * Rule name. @@ -47,18 +47,29 @@ public class BasicRule implements Rule { */ protected String description; + /** + * Rule priority. + */ + private int priority; + public BasicRule() { this(EasyRulesConstants.DEFAULT_RULE_NAME, - EasyRulesConstants.DEFAULT_RULE_DESCRIPTION); + EasyRulesConstants.DEFAULT_RULE_DESCRIPTION, + EasyRulesConstants.DEFAULT_RULE_PRIORITY); } public BasicRule(final String name) { - this(name, EasyRulesConstants.DEFAULT_RULE_DESCRIPTION); + this(name, EasyRulesConstants.DEFAULT_RULE_DESCRIPTION, EasyRulesConstants.DEFAULT_RULE_PRIORITY); } public BasicRule(final String name, final String description) { + this(name, description, EasyRulesConstants.DEFAULT_RULE_PRIORITY); + } + + public BasicRule(final String name, final String description, final int priority) { this.name = name; this.description = description; + this.priority = priority; } /** @@ -87,6 +98,14 @@ public class BasicRule implements Rule { this.description = description; } + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + /* * Rules are unique according to their names within a rules engine registry. */ @@ -112,4 +131,15 @@ public class BasicRule implements Rule { return name; } + @Override + public int compareTo(final Rule rule) { + if (priority < rule.getPriority()) { + return -1; + } else if (priority == rule.getPriority()) { + return 0; + } else { + return 1; + } + } + } diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositePriorityRule.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositePriorityRule.java deleted file mode 100644 index aff4cae..0000000 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositePriorityRule.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2014, Mahmoud Ben Hassine (md.benhassine@gmail.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 io.github.benas.easyrules.core; - -import io.github.benas.easyrules.api.PriorityRule; -import io.github.benas.easyrules.util.EasyRulesConstants; - -import java.util.Set; -import java.util.TreeSet; - -/** - * Class representing a composite rule composed of a set of priority rules.
- * - * A composite rule is triggered if ALL conditions of its composing rules are satisfied. - * When a composite rule is applied, actions of ALL composing rules are performed in their - * natural order. - * - * @author Mahmoud Ben Hassine (md.benhassine@gmail.com) - */ -public class CompositePriorityRule extends BasicPriorityRule { - - /** - * The set of composing priority rules. - */ - protected Set rules; - - public CompositePriorityRule() { - this(EasyRulesConstants.DEFAULT_RULE_NAME, - EasyRulesConstants.DEFAULT_RULE_DESCRIPTION); - } - - public CompositePriorityRule(final String name) { - this(name, EasyRulesConstants.DEFAULT_RULE_DESCRIPTION); - } - - public CompositePriorityRule(final String name, final String description) { - super(name, description); - rules = new TreeSet(); - } - - /** - * A composite rule is triggered if ALL conditions of all composing rules are evaluated to true. - * @return true if ALL conditions of composing rules are evaluated to true - */ - @Override - public boolean evaluateConditions() { - if (!rules.isEmpty()) { - for (PriorityRule rule : rules) { - if (!rule.evaluateConditions()) { - return false; - } - } - return true; - } - return false; - } - - /** - * When a composite priority rule is applied, ALL actions of composing rules are performed - * in their natural order. - * - * @throws Exception thrown if an exception occurs during actions performing - */ - @Override - public void performActions() throws Exception { - for (PriorityRule rule : rules) { - rule.performActions(); - } - } - - /** - * Add a priority rule to the composite rule. - * @param rule the priority rule to add - */ - public void addRule(final PriorityRule rule) { - rules.add(rule); - } - - /** - * Remove a priority rule from the composite rule. - * @param rule the priority rule to remove - */ - public void removeRule(final PriorityRule rule) { - rules.remove(rule); - } - -} diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositeRule.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositeRule.java index c4e763e..34b885e 100644 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositeRule.java +++ b/easyrules-core/src/main/java/io/github/benas/easyrules/core/CompositeRule.java @@ -77,7 +77,8 @@ public class CompositeRule extends BasicRule { } /** - * When a composite rule is applied, ALL actions of composing rules are performed. + * When a composite rule is applied, ALL actions of composing rules are performed + * in their natural order. * * @throws Exception thrown if an exception occurs during actions performing */ diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/DefaultRulesEngine.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/DefaultRulesEngine.java index 31d182e..aa8e3eb 100644 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/DefaultRulesEngine.java +++ b/easyrules-core/src/main/java/io/github/benas/easyrules/core/DefaultRulesEngine.java @@ -26,8 +26,9 @@ package io.github.benas.easyrules.core; import io.github.benas.easyrules.api.Rule; import io.github.benas.easyrules.api.RulesEngine; +import io.github.benas.easyrules.util.EasyRulesConstants; -import java.util.HashSet; +import java.util.TreeSet; import java.util.logging.Level; /** @@ -35,17 +36,44 @@ import java.util.logging.Level; * * This implementation handles a set of rules with unique name. * + * Rules are fired according to their natural order which is priority by default. + * * @author Mahmoud Ben Hassine (md.benhassine@gmail.com) */ public class DefaultRulesEngine extends AbstractRulesEngine { + /** + * Construct a default rules engine with default values. + */ public DefaultRulesEngine() { - this(false); + this(false, EasyRulesConstants.DEFAULT_RULE_PRIORITY_THRESHOLD); } + /** + * Constructs a default rules engine. + * @param skipOnFirstAppliedRule true if the engine should skip next rule after the first applied rule + */ public DefaultRulesEngine(boolean skipOnFirstAppliedRule) { - rules = new HashSet(); + this(skipOnFirstAppliedRule, EasyRulesConstants.DEFAULT_RULE_PRIORITY_THRESHOLD); + } + + /** + * Constructs a default rules engine. + * @param rulePriorityThreshold rule priority threshold + */ + public DefaultRulesEngine(int rulePriorityThreshold) { + this(false, rulePriorityThreshold); + } + + /** + * Constructs a default rules engine. + * @param skipOnFirstAppliedRule true if the engine should skip next rule after the first applied rule + * @param rulePriorityThreshold rule priority threshold + */ + public DefaultRulesEngine(boolean skipOnFirstAppliedRule, int rulePriorityThreshold) { + rules = new TreeSet(); this.skipOnFirstAppliedRule = skipOnFirstAppliedRule; + this.rulePriorityThreshold = rulePriorityThreshold; } @Override @@ -56,8 +84,17 @@ public class DefaultRulesEngine extends AbstractRulesEngine { return; } + //resort rules in case priorities were modified via JMX + rules = new TreeSet(rules); + for (Rule rule : rules) { + if (rule.getPriority() > rulePriorityThreshold) { + LOGGER.log(Level.INFO, "Rule priority threshold {0} exceeded at rule {1} (priority={2}), next applicable rules will be skipped.", + new Object[] {rulePriorityThreshold, rule.getName(), rule.getPriority()}); + break; + } + if (rule.evaluateConditions()) { LOGGER.log(Level.INFO, "Rule {0} triggered.", new Object[]{rule.getName()}); try { diff --git a/easyrules-core/src/main/java/io/github/benas/easyrules/core/PriorityRulesEngine.java b/easyrules-core/src/main/java/io/github/benas/easyrules/core/PriorityRulesEngine.java deleted file mode 100644 index de3e6f1..0000000 --- a/easyrules-core/src/main/java/io/github/benas/easyrules/core/PriorityRulesEngine.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2014, Mahmoud Ben Hassine (md.benhassine@gmail.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 io.github.benas.easyrules.core; - -import io.github.benas.easyrules.api.PriorityRule; -import io.github.benas.easyrules.util.EasyRulesConstants; - -import java.util.TreeSet; -import java.util.logging.Level; - -/** - * Implementation of {@link io.github.benas.easyrules.api.RulesEngine} that holds a sorted set of - * priority rules according to their priority. Rules are fired according to their natural order - * (lower values represent higher priorities). - * - * @author Mahmoud Ben Hassine (md.benhassine@gmail.com) - */ -public class PriorityRulesEngine extends AbstractRulesEngine { - - /** - * Parameter to skip next rules if priority exceeds a user defined threshold. - */ - protected int rulePriorityThreshold; - - /** - * Construct a priority rules engine with default values. - */ - public PriorityRulesEngine() { - this(false, EasyRulesConstants.DEFAULT_RULE_PRIORITY_THRESHOLD); - } - - /** - * Constructs a priority rules engine. - * @param skipOnFirstAppliedRule true if the engine should skip next rule after the first applied rule - */ - public PriorityRulesEngine(boolean skipOnFirstAppliedRule) { - this(skipOnFirstAppliedRule, EasyRulesConstants.DEFAULT_RULE_PRIORITY_THRESHOLD); - } - - /** - * Constructs a priority rules engine. - * @param rulePriorityThreshold rule priority threshold - */ - public PriorityRulesEngine(int rulePriorityThreshold) { - this(false, rulePriorityThreshold); - } - - /** - * Constructs a priority rules engine. - * @param skipOnFirstAppliedRule true if the engine should skip next rule after the first applied rule - * @param rulePriorityThreshold rule priority threshold - */ - public PriorityRulesEngine(boolean skipOnFirstAppliedRule, int rulePriorityThreshold) { - rules = new TreeSet(); - this.skipOnFirstAppliedRule = skipOnFirstAppliedRule; - this.rulePriorityThreshold = rulePriorityThreshold; - } - - @Override - public void fireRules() { - - if (rules.isEmpty()) { - LOGGER.warning("No rules registered! Nothing to apply."); - return; - } - - //resort rules in case priorities were modified via JMX - rules = new TreeSet(rules); - - for (PriorityRule priorityRule : rules) { - - if (priorityRule.getPriority() > rulePriorityThreshold) { - LOGGER.log(Level.INFO, "Rules priority threshold {0} exceeded at rule {1} (priority={2}), next applicable rules will be skipped.", - new Object[] {rulePriorityThreshold, priorityRule.getName(), priorityRule.getPriority()}); - break; - } - - if (priorityRule.evaluateConditions()) { - LOGGER.log(Level.INFO, "Rule {0} triggered.", new Object[]{priorityRule.getName()}); - try { - priorityRule.performActions(); - LOGGER.log(Level.INFO, "Rule {0} performed successfully.", new Object[]{priorityRule.getName()}); - if (skipOnFirstAppliedRule) { - LOGGER.info("Next rules will be skipped according to parameter skipOnFirstAppliedRule."); - break; - } - } catch (Exception exception) { - LOGGER.log(Level.SEVERE, "Rule '" + priorityRule.getName() + "' performed with error.", exception); - } - } - - } - - } - -}