This is an automated email from the ASF dual-hosted git repository.

mattyb149 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/master by this push:
     new abf223d  NIFI-7163 - added RulesEngine and RulesEngineProvider 
interfaces, enhanced easy rules to support provider interface and refactored to 
extract rules engine implementation
abf223d is described below

commit abf223d574f0d5c1d3484374535e6ae1d0f6c6c0
Author: Yolanda M. Davis <yolanda.m.da...@gmail.com>
AuthorDate: Mon Feb 24 15:27:39 2020 -0500

    NIFI-7163 - added RulesEngine and RulesEngineProvider interfaces, enhanced 
easy rules to support provider interface and refactored to extract rules engine 
implementation
    
    NIFI-7163 - updated documentation and comments
    
    NIFI-7163 - fix checkstyle issues
    
    Signed-off-by: Matthew Burgess <mattyb...@apache.org>
    
    This closes #4081
---
 .../nifi-easyrules-service/pom.xml                 |   6 +
 .../org/apache/nifi/rules/RulesMVELCondition.java  |   3 +-
 .../org/apache/nifi/rules/RulesSPELCondition.java  |   3 +-
 ...java => AbstractEasyRulesEngineController.java} | 114 +++---------
 .../apache/nifi/rules/engine/EasyRulesEngine.java  | 178 +++++++++++++++++++
 .../nifi/rules/engine/EasyRulesEngineProvider.java |  37 ++++
 .../nifi/rules/engine/EasyRulesEngineService.java  | 191 +++------------------
 .../org.apache.nifi.controller.ControllerService   |   3 +-
 .../additionalDetails.html                         |   5 +-
 .../org/apache/nifi/rules/TestRulesCondition.java  | 100 +++++++++++
 .../nifi/rules/engine/TestEasyRulesEngine.java     |  87 ++++++++++
 .../rules/engine/TestEasyRulesEngineProvider.java  |  59 +++++++
 .../rules/engine/TestEasyRulesEngineService.java   |  25 ++-
 .../src/test/resources/test_nifi_rules_filter.json |  34 ++++
 .../main/java/org/apache/nifi/rules/Action.java    |  12 +-
 .../src/main/java/org/apache/nifi/rules/Rule.java  |  24 ++-
 .../rules/{Action.java => engine/RulesEngine.java} |  40 ++---
 ...EngineService.java => RulesEngineProvider.java} |  21 +--
 .../nifi/rules/engine/RulesEngineService.java      |   2 +-
 19 files changed, 626 insertions(+), 318 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/pom.xml 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/pom.xml
index c36cfe4..9a0ae5a 100644
--- a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/pom.xml
+++ b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/pom.xml
@@ -50,6 +50,11 @@
             <version>3.3.0</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-jexl3</artifactId>
+            <version>3.1</version>
+        </dependency>
+        <dependency>
             <groupId>org.jeasy</groupId>
             <artifactId>easy-rules-spel</artifactId>
             <version>3.3.0</version>
@@ -76,6 +81,7 @@
                     <excludes combine.children="append">
                         
<exclude>src/test/resources/test_nifi_rules.json</exclude>
                         
<exclude>src/test/resources/test_nifi_rules.yml</exclude>
+                        
<exclude>src/test/resources/test_nifi_rules_filter.json</exclude>
                         
<exclude>src/test/resources/test_mvel_rules.json</exclude>
                         
<exclude>src/test/resources/test_mvel_rules.yml</exclude>
                         
<exclude>src/test/resources/test_spel_rules.json</exclude>
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesMVELCondition.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesMVELCondition.java
index 7bd615f..969d445 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesMVELCondition.java
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesMVELCondition.java
@@ -19,7 +19,6 @@ package org.apache.nifi.rules;
 
 import org.jeasy.rules.api.Condition;
 import org.jeasy.rules.api.Facts;
-import org.jeasy.rules.mvel.MVELCondition;
 import org.mvel2.MVEL;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,7 +27,7 @@ import java.io.Serializable;
 
 public class RulesMVELCondition implements Condition {
 
-    private static final Logger LOGGER = 
LoggerFactory.getLogger(MVELCondition.class);
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RulesMVELCondition.class);
     private String expression;
     private Serializable compiledExpression;
     private boolean ignoreConditionErrors;
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesSPELCondition.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesSPELCondition.java
index e753f93..881da6b 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesSPELCondition.java
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/RulesSPELCondition.java
@@ -18,7 +18,6 @@ package org.apache.nifi.rules;
 
 import org.jeasy.rules.api.Condition;
 import org.jeasy.rules.api.Facts;
-import org.jeasy.rules.spel.SpELCondition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.expression.Expression;
@@ -28,7 +27,7 @@ import 
org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
 
 public class RulesSPELCondition implements Condition {
-    private static final Logger LOGGER = 
LoggerFactory.getLogger(SpELCondition.class);
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RulesSPELCondition.class);
     private final ExpressionParser parser = new SpelExpressionParser();
     private String expression;
     private Expression compiledExpression;
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/AbstractEasyRulesEngineController.java
similarity index 67%
copy from 
nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
copy to 
nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/AbstractEasyRulesEngineController.java
index 187f0fe..b7b5e9d 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/AbstractEasyRulesEngineController.java
@@ -16,8 +16,6 @@
  */
 package org.apache.nifi.rules.engine;
 
-import org.apache.nifi.annotation.documentation.CapabilityDescription;
-import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.annotation.lifecycle.OnEnabled;
 import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
@@ -30,19 +28,9 @@ import 
org.apache.nifi.controller.ControllerServiceInitializationContext;
 import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.reporting.InitializationException;
-import org.apache.nifi.rules.Action;
-import org.apache.nifi.rules.ActionHandler;
 import org.apache.nifi.rules.Rule;
 import org.apache.nifi.rules.RulesFactory;
-import org.apache.nifi.rules.RulesMVELCondition;
-import org.apache.nifi.rules.RulesSPELCondition;
 import org.apache.nifi.util.StringUtils;
-import org.jeasy.rules.api.Condition;
-import org.jeasy.rules.api.Facts;
-import org.jeasy.rules.api.RuleListener;
-import org.jeasy.rules.api.Rules;
-import org.jeasy.rules.core.DefaultRulesEngine;
-import org.jeasy.rules.core.RuleBuilder;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -52,15 +40,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-/**
- * Implementation of RulesEngineService interface
- *
- * @see RulesEngineService
- */
-@CapabilityDescription("Defines and execute the rules stored in NiFi or 
EasyRules file formats for a given set of facts. Supports " +
-        "rules stored as JSON or YAML file types.")
-@Tags({ "rules","rules-engine","engine","actions","facts" })
-public class EasyRulesEngineService  extends AbstractControllerService 
implements RulesEngineService {
+public abstract class AbstractEasyRulesEngineController extends 
AbstractControllerService {
 
     static final AllowableValue YAML = new AllowableValue("YAML", "YAML", 
"YAML file configuration type.");
     static final AllowableValue JSON = new AllowableValue("JSON", "JSON", 
"JSON file configuration type.");
@@ -116,10 +96,23 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
             .allowableValues("true", "false")
             .build();
 
+    static final PropertyDescriptor FILTER_RULES_MISSING_FACTS = new 
PropertyDescriptor.Builder()
+            .name("rules-filter-missing-facts")
+            .displayName("Filter Rules With Missing Facts")
+            .description("When set to true, the rules engine will first filter 
out any rule where fact are not available before " +
+                    "executing a check or firing that rule. When running a 
check rules this will return only rules " +
+                    "that were evaluated after filtering. NOTE: This is only 
applicable for the NIFI Rules Format (which allows" +
+                    " specification of fact variables) and will be ignored for 
other formats.")
+            .required(true)
+            .defaultValue("false")
+            .allowableValues("true", "false")
+            .build();
+
     protected List<PropertyDescriptor> properties;
-    protected volatile List<Rule> rules;
+    protected List<Rule> rules;
     protected volatile String rulesFileFormat;
-    private boolean ignoreConditionErrors;
+    protected boolean ignoreConditionErrors;
+    protected boolean filterRules;
 
     @Override
     protected void init(ControllerServiceInitializationContext config) throws 
InitializationException {
@@ -130,6 +123,7 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
         properties.add(RULES_BODY);
         properties.add(RULES_FILE_FORMAT);
         properties.add(IGNORE_CONDITION_ERRORS);
+        properties.add(FILTER_RULES_MISSING_FACTS);
         this.properties = Collections.unmodifiableList(properties);
     }
 
@@ -145,6 +139,8 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
         final String rulesFileType = 
context.getProperty(RULES_FILE_TYPE).getValue();
         rulesFileFormat = context.getProperty(RULES_FILE_FORMAT).getValue();
         ignoreConditionErrors = 
context.getProperty(IGNORE_CONDITION_ERRORS).asBoolean();
+        filterRules = 
context.getProperty(FILTER_RULES_MISSING_FACTS).asBoolean();
+
         try{
             if(StringUtils.isEmpty(rulesFile)){
                 rules = RulesFactory.createRulesFromString(rulesBody, 
rulesFileType, rulesFileFormat);
@@ -174,79 +170,15 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
             results.add(new ValidationResult.Builder().subject("Rules Body or 
Rules File").valid(false).explanation(
                     "exactly one of Rules File or Rules Body must be 
set").build());
         }
-
         return results;
     }
 
-
-    /**
-     * Return the list of actions what should be executed for a given set of 
facts
-     * @param facts a Map of key and facts values, as objects, that should be 
evaluated by the rules engine
-     * @return
-     */
-    @Override
-    public List<Action> fireRules(Map<String, Object> facts) {
-        final List<Action> actions = new ArrayList<>();
-        if (rules == null || facts == null || facts.isEmpty()) {
-            return null;
-        }else {
-            org.jeasy.rules.api.Rules easyRules = convertToEasyRules(rules, 
(action, eventFacts) ->
-                    actions.add(action));
-            Facts easyFacts = new Facts();
-            facts.forEach(easyFacts::put);
-            DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
-            rulesEngine.registerRuleListener(new EasyRulesListener());
-            rulesEngine.fire(easyRules, easyFacts);
-            return actions;
-        }
-    }
-
-
-    protected Rules convertToEasyRules(List<Rule> rules, ActionHandler 
actionHandler) {
-        final Rules easyRules = new Rules();
+    protected RulesEngine getRulesEngine() {
+        List<Rule> rulesCopy = new ArrayList<>();
         rules.forEach(rule -> {
-            RuleBuilder ruleBuilder = new RuleBuilder();
-            Condition condition = 
rulesFileFormat.equalsIgnoreCase(SPEL.getValue())
-                                 ? new RulesSPELCondition(rule.getCondition(), 
ignoreConditionErrors): new RulesMVELCondition(rule.getCondition(), 
ignoreConditionErrors);
-            ruleBuilder.name(rule.getName())
-                    .description(rule.getDescription())
-                    .priority(rule.getPriority())
-                    .when(condition);
-            for (Action action : rule.getActions()) {
-                ruleBuilder.then(facts -> {
-                    actionHandler.execute(action, facts.asMap());
-                });
-            }
-            easyRules.register(ruleBuilder.build());
+            rulesCopy.add(rule.clone());
         });
-        return easyRules;
-    }
-
-    private class EasyRulesListener implements RuleListener {
-        @Override
-        public boolean beforeEvaluate(org.jeasy.rules.api.Rule rule, Facts 
facts) {
-            return true;
-        }
-
-        @Override
-        public void afterEvaluate(org.jeasy.rules.api.Rule rule, Facts facts, 
boolean b) {
-
-        }
-
-        @Override
-        public void beforeExecute(org.jeasy.rules.api.Rule rule, Facts facts) {
-
-        }
-
-        @Override
-        public void onSuccess(org.jeasy.rules.api.Rule rule, Facts facts) {
-            getLogger().debug("Rules was successfully processed for: {}",new 
Object[]{rule.getName()});
-        }
-
-        @Override
-        public void onFailure(org.jeasy.rules.api.Rule rule, Facts facts, 
Exception e) {
-            getLogger().warn("Rule execution failed for: {}", new 
Object[]{rule.getName()}, e);
-        }
+        return new EasyRulesEngine(rulesFileFormat, ignoreConditionErrors, 
filterRules, Collections.unmodifiableList(rulesCopy));
     }
 
 }
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngine.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngine.java
new file mode 100644
index 0000000..3a950a8
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngine.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.engine;
+
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.rules.Rule;
+import org.apache.nifi.rules.RulesMVELCondition;
+import org.apache.nifi.rules.RulesSPELCondition;
+import org.jeasy.rules.api.Condition;
+import org.jeasy.rules.api.Facts;
+import org.jeasy.rules.api.RuleListener;
+import org.jeasy.rules.api.Rules;
+import org.jeasy.rules.core.BasicRule;
+import org.jeasy.rules.core.DefaultRulesEngine;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+
+public class EasyRulesEngine implements RulesEngine {
+
+    protected String rulesFileFormat;
+    protected boolean ignoreConditionErrors;
+    protected boolean filterRulesMissingFacts;
+    protected Rules easyRules;
+    protected List<RuleListener> ruleListeners;
+    protected DefaultRulesEngine rulesEngine;
+
+
+    public EasyRulesEngine(String rulesFileFormat, boolean 
ignoreConditionErrors, boolean filterRulesMissingFacts, List<Rule> rules) {
+        this.rulesFileFormat = rulesFileFormat;
+        this.ignoreConditionErrors = ignoreConditionErrors;
+        this.filterRulesMissingFacts = filterRulesMissingFacts;
+        this.easyRules = convertToEasyRules(rules, rulesFileFormat, 
ignoreConditionErrors);
+        this.rulesEngine = new DefaultRulesEngine();
+        if (getRuleListeners() != null) {
+            rulesEngine.registerRuleListeners(getRuleListeners());
+        }
+    }
+
+    /**
+     * Return the list of actions what should be executed for a given set of 
facts
+     *
+     * @param facts a Map of key and facts values, as objects, that should be 
evaluated by the rules engine
+     * @return
+     */
+    @Override
+    public List<Action> fireRules(Map<String, Object> facts) {
+        final List<Action> actions = new ArrayList<>();
+        Map<Rule, Boolean> checkedRules = checkRules(facts);
+        checkedRules.forEach((checkedRule, executeRule) -> {
+            if (executeRule) {
+                actions.addAll(checkedRule.getActions());
+            }
+        });
+        return actions;
+    }
+
+    /**
+     * Return a Map with Rule as a key and Boolean as a value indicating that 
the rule's conditions were met
+     *
+     * @param facts Map of keys and values contains facts to evaluate against 
rules
+     * @return
+     */
+    @Override
+    public Map<Rule, Boolean> checkRules(Map<String, Object> facts) {
+        Map<Rule, Boolean> checkedRules = new HashMap<>();
+        if (easyRules == null || facts == null || facts.isEmpty()) {
+            return null;
+        } else {
+            Facts easyFacts = new Facts();
+            facts.forEach(easyFacts::put);
+
+            Map<org.jeasy.rules.api.Rule, Boolean> checkedEasyRules = 
rulesEngine.check(filterRulesMissingFacts ? filterByAvailableFacts(facts) : 
easyRules, easyFacts);
+            checkedEasyRules.forEach((checkedRuled, executeAction) -> {
+                checkedRules.put(((NiFiEasyRule) checkedRuled).getNifiRule(), 
executeAction);
+            });
+
+        }
+        return checkedRules;
+    }
+
+    public List<Rule> getRules() {
+        return StreamSupport.stream(easyRules.spliterator(), false)
+                .map(easyRule -> ((NiFiEasyRule) easyRule)
+                        .getNifiRule()).collect(Collectors.toList());
+    }
+
+    List<RuleListener> getRuleListeners() {
+        return ruleListeners;
+    }
+
+    void setRuleListeners(List<RuleListener> ruleListeners) {
+        this.ruleListeners = ruleListeners;
+    }
+
+    private org.jeasy.rules.api.Rules convertToEasyRules(List<Rule> rules, 
String rulesFileFormat, Boolean ignoreConditionErrors) {
+        final Rules easyRules = new Rules();
+        for (Rule rule : rules) {
+            easyRules.register(new NiFiEasyRule(rule, rulesFileFormat, 
ignoreConditionErrors));
+        }
+        return easyRules;
+    }
+
+    private Rules filterByAvailableFacts(Map<String, Object> facts) {
+        Set<String> factVariables = facts.keySet();
+        List<org.jeasy.rules.api.Rule> filteredEasyRules = 
StreamSupport.stream(easyRules.spliterator(), false)
+                .filter(easyRule -> ((NiFiEasyRule) 
easyRule).getNifiRule().getFacts() == null || 
factVariables.containsAll(((NiFiEasyRule) easyRule)
+                        
.getNifiRule().getFacts())).collect(Collectors.toList());
+
+        return new Rules(new HashSet(filteredEasyRules));
+    }
+
+    private static class NiFiEasyRule extends BasicRule {
+
+        private Condition condition;
+        private Rule nifiRule;
+
+        NiFiEasyRule(Rule nifiRule, String rulesFileFormat, Boolean 
ignoreConditionErrors) {
+            super(nifiRule.getName(), nifiRule.getDescription(), 
nifiRule.getPriority());
+            this.condition = rulesFileFormat.equalsIgnoreCase("spel")
+                    ? new RulesSPELCondition(nifiRule.getCondition(), 
ignoreConditionErrors) : new RulesMVELCondition(nifiRule.getCondition(), 
ignoreConditionErrors);
+            this.nifiRule = nifiRule;
+        }
+
+        public boolean evaluate(Facts facts) {
+
+            final Facts evaluateFacts;
+
+            if (nifiRule.getFacts() != null) {
+
+                List<Map.Entry<String, Object>> filteredFacts = 
StreamSupport.stream(facts.spliterator(), false)
+                        .filter(fact -> 
nifiRule.getFacts().contains(fact.getKey())).collect(Collectors.toList());
+
+                if (filteredFacts.size() > 0) {
+                    evaluateFacts = new Facts();
+                    filteredFacts.forEach(filteredFact -> {
+                        evaluateFacts.put(filteredFact.getKey(), 
filteredFact.getValue());
+                    });
+                } else {
+                    evaluateFacts = facts;
+                }
+
+            } else {
+                evaluateFacts = facts;
+            }
+
+            return this.condition.evaluate(evaluateFacts);
+
+        }
+
+        Rule getNifiRule() {
+            return nifiRule;
+        }
+    }
+
+
+}
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineProvider.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineProvider.java
new file mode 100644
index 0000000..83b897a
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.engine;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+
+
+@CapabilityDescription("Provides an instance of a rules engine to the caller.  
Supports " +
+        "rules stored as JSON or YAML file types.")
+@Tags({ "rules","rules-engine","engine","actions","facts" })
+public class EasyRulesEngineProvider extends AbstractEasyRulesEngineController 
implements RulesEngineProvider {
+
+    /**
+     * Returns a rules engine instance
+     * @return
+     */
+    @Override
+    public RulesEngine getRulesEngine() {
+        return super.getRulesEngine();
+    }
+
+}
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
index 187f0fe..520a0e0 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/java/org/apache/nifi/rules/engine/EasyRulesEngineService.java
@@ -19,38 +19,17 @@ package org.apache.nifi.rules.engine;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.annotation.lifecycle.OnEnabled;
-import org.apache.nifi.components.AllowableValue;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.components.ValidationResult;
-import org.apache.nifi.components.Validator;
-import org.apache.nifi.controller.AbstractControllerService;
 import org.apache.nifi.controller.ConfigurationContext;
-import org.apache.nifi.controller.ControllerServiceInitializationContext;
-import org.apache.nifi.expression.ExpressionLanguageScope;
-import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.logging.ComponentLog;
 import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.rules.Action;
-import org.apache.nifi.rules.ActionHandler;
-import org.apache.nifi.rules.Rule;
-import org.apache.nifi.rules.RulesFactory;
-import org.apache.nifi.rules.RulesMVELCondition;
-import org.apache.nifi.rules.RulesSPELCondition;
-import org.apache.nifi.util.StringUtils;
-import org.jeasy.rules.api.Condition;
 import org.jeasy.rules.api.Facts;
 import org.jeasy.rules.api.RuleListener;
-import org.jeasy.rules.api.Rules;
-import org.jeasy.rules.core.DefaultRulesEngine;
-import org.jeasy.rules.core.RuleBuilder;
+
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Implementation of RulesEngineService interface
@@ -60,125 +39,21 @@ import java.util.Set;
 @CapabilityDescription("Defines and execute the rules stored in NiFi or 
EasyRules file formats for a given set of facts. Supports " +
         "rules stored as JSON or YAML file types.")
 @Tags({ "rules","rules-engine","engine","actions","facts" })
-public class EasyRulesEngineService  extends AbstractControllerService 
implements RulesEngineService {
-
-    static final AllowableValue YAML = new AllowableValue("YAML", "YAML", 
"YAML file configuration type.");
-    static final AllowableValue JSON = new AllowableValue("JSON", "JSON", 
"JSON file configuration type.");
-    static final AllowableValue NIFI = new AllowableValue("NIFI", "NIFI", 
"NiFi rules formatted file.");
-    static final AllowableValue MVEL = new AllowableValue("MVEL", "Easy Rules 
MVEL", "Easy Rules File format using MVFLEX Expression Language");
-    static final AllowableValue SPEL = new AllowableValue("SPEL", "Easy Rules 
SpEL", "Easy Rules File format using Spring Expression Language");
-
-    static final PropertyDescriptor RULES_FILE_PATH = new 
PropertyDescriptor.Builder()
-            .name("rules-file-path")
-            .displayName("Rules File Path")
-            .description("Path to location of rules file. Only one of Rules 
File or Rules Body may be used")
-            .required(false)
-            .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR)
-            
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
-            .build();
-
-    static final PropertyDescriptor RULES_BODY = new 
PropertyDescriptor.Builder()
-            .name("rules-body")
-            .displayName("Rules Body")
-            .description("Body of rules file to execute. Only one of Rules 
File or Rules Body may be used")
-            .required(false)
-            .addValidator(Validator.VALID)
-            
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
-            .build();
-
-    static final PropertyDescriptor RULES_FILE_TYPE = new 
PropertyDescriptor.Builder()
-            .name("rules-file-type")
-            .displayName("Rules File Type")
-            .description("File or Body type for rules definition. Supported 
types are YAML and JSON")
-            .required(true)
-            .allowableValues(JSON,YAML)
-            .defaultValue(JSON.getValue())
-            .build();
-
-    static final PropertyDescriptor RULES_FILE_FORMAT = new 
PropertyDescriptor.Builder()
-            .name("rules-file-format")
-            .displayName("Rules File Format")
-            .description("Format for rules. Supported formats are NiFi Rules, 
Easy Rules files with MVEL Expression Language" +
-                    " and Easy Rules files with Spring Expression Language.")
-            .required(true)
-            .allowableValues(NIFI,MVEL,SPEL)
-            .defaultValue(NIFI.getValue())
-            .build();
-
-    static final PropertyDescriptor IGNORE_CONDITION_ERRORS = new 
PropertyDescriptor.Builder()
-            .name("rules-ignore-condition-errors")
-            .displayName("Ignore Condition Errors")
-            .description("When set to true, rules engine will ignore errors 
for any rule that encounters issues " +
-                    "when compiling rule conditions (including syntax errors 
and/or missing facts). Rule will simply return as false " +
-                    "and engine will continue with execution.")
-            .required(true)
-            .defaultValue("false")
-            .allowableValues("true", "false")
-            .build();
-
-    protected List<PropertyDescriptor> properties;
-    protected volatile List<Rule> rules;
-    protected volatile String rulesFileFormat;
-    private boolean ignoreConditionErrors;
+public class EasyRulesEngineService  extends EasyRulesEngineProvider 
implements RulesEngineService {
 
-    @Override
-    protected void init(ControllerServiceInitializationContext config) throws 
InitializationException {
-        super.init(config);
-        final List<PropertyDescriptor> properties = new ArrayList<>();
-        properties.add(RULES_FILE_TYPE);
-        properties.add(RULES_FILE_PATH);
-        properties.add(RULES_BODY);
-        properties.add(RULES_FILE_FORMAT);
-        properties.add(IGNORE_CONDITION_ERRORS);
-        this.properties = Collections.unmodifiableList(properties);
-    }
+    private volatile RulesEngine rulesEngine;
 
     @Override
-    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
-        return properties;
-    }
-
     @OnEnabled
     public void onEnabled(final ConfigurationContext context) throws 
InitializationException {
-        final String rulesFile = 
context.getProperty(RULES_FILE_PATH).getValue();
-        final String rulesBody = context.getProperty(RULES_BODY).getValue();
-        final String rulesFileType = 
context.getProperty(RULES_FILE_TYPE).getValue();
-        rulesFileFormat = context.getProperty(RULES_FILE_FORMAT).getValue();
-        ignoreConditionErrors = 
context.getProperty(IGNORE_CONDITION_ERRORS).asBoolean();
-        try{
-            if(StringUtils.isEmpty(rulesFile)){
-                rules = RulesFactory.createRulesFromString(rulesBody, 
rulesFileType, rulesFileFormat);
-            }else{
-                rules = RulesFactory.createRulesFromFile(rulesFile, 
rulesFileType, rulesFileFormat);
-            }
-        } catch (Exception fex){
-            throw new InitializationException(fex);
-        }
-    }
-
-    /**
-     * Custom validation for ensuring exactly one of Script File or Script 
Body is populated
-     *
-     * @param validationContext provides a mechanism for obtaining externally
-     *                          managed values, such as property values and 
supplies convenience methods
-     *                          for operating on those values
-     * @return A collection of validation results
-     */
-    @Override
-    public Collection<ValidationResult> customValidate(ValidationContext 
validationContext) {
-        Set<ValidationResult> results = new HashSet<>();
-
-        // Verify that exactly one of "script file" or "script body" is set
-        Map<PropertyDescriptor, String> propertyMap = 
validationContext.getProperties();
-        if (StringUtils.isEmpty(propertyMap.get(RULES_FILE_PATH)) == 
StringUtils.isEmpty(propertyMap.get(RULES_BODY))) {
-            results.add(new ValidationResult.Builder().subject("Rules Body or 
Rules File").valid(false).explanation(
-                    "exactly one of Rules File or Rules Body must be 
set").build());
-        }
-
-        return results;
+        super.onEnabled(context);
+        EasyRulesEngine easyRulesEngine = (EasyRulesEngine) getRulesEngine();
+        List<RuleListener> ruleListeners = new ArrayList<>();
+        ruleListeners.add(new EasyRulesListener(getLogger()));
+        easyRulesEngine.setRuleListeners(ruleListeners);
+        rulesEngine = easyRulesEngine;
     }
 
-
     /**
      * Return the list of actions what should be executed for a given set of 
facts
      * @param facts a Map of key and facts values, as objects, that should be 
evaluated by the rules engine
@@ -186,43 +61,17 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
      */
     @Override
     public List<Action> fireRules(Map<String, Object> facts) {
-        final List<Action> actions = new ArrayList<>();
-        if (rules == null || facts == null || facts.isEmpty()) {
-            return null;
-        }else {
-            org.jeasy.rules.api.Rules easyRules = convertToEasyRules(rules, 
(action, eventFacts) ->
-                    actions.add(action));
-            Facts easyFacts = new Facts();
-            facts.forEach(easyFacts::put);
-            DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
-            rulesEngine.registerRuleListener(new EasyRulesListener());
-            rulesEngine.fire(easyRules, easyFacts);
-            return actions;
-        }
+        return rulesEngine.fireRules(facts);
     }
 
+    private static class EasyRulesListener implements RuleListener {
 
-    protected Rules convertToEasyRules(List<Rule> rules, ActionHandler 
actionHandler) {
-        final Rules easyRules = new Rules();
-        rules.forEach(rule -> {
-            RuleBuilder ruleBuilder = new RuleBuilder();
-            Condition condition = 
rulesFileFormat.equalsIgnoreCase(SPEL.getValue())
-                                 ? new RulesSPELCondition(rule.getCondition(), 
ignoreConditionErrors): new RulesMVELCondition(rule.getCondition(), 
ignoreConditionErrors);
-            ruleBuilder.name(rule.getName())
-                    .description(rule.getDescription())
-                    .priority(rule.getPriority())
-                    .when(condition);
-            for (Action action : rule.getActions()) {
-                ruleBuilder.then(facts -> {
-                    actionHandler.execute(action, facts.asMap());
-                });
-            }
-            easyRules.register(ruleBuilder.build());
-        });
-        return easyRules;
-    }
+        private ComponentLog logger;
+
+        EasyRulesListener(ComponentLog logger) {
+            this.logger = logger;
+        }
 
-    private class EasyRulesListener implements RuleListener {
         @Override
         public boolean beforeEvaluate(org.jeasy.rules.api.Rule rule, Facts 
facts) {
             return true;
@@ -232,7 +81,6 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
         public void afterEvaluate(org.jeasy.rules.api.Rule rule, Facts facts, 
boolean b) {
 
         }
-
         @Override
         public void beforeExecute(org.jeasy.rules.api.Rule rule, Facts facts) {
 
@@ -240,13 +88,14 @@ public class EasyRulesEngineService  extends 
AbstractControllerService implement
 
         @Override
         public void onSuccess(org.jeasy.rules.api.Rule rule, Facts facts) {
-            getLogger().debug("Rules was successfully processed for: {}",new 
Object[]{rule.getName()});
+            logger.debug("Rules was successfully processed for: {}",new 
Object[]{rule.getName()});
         }
 
         @Override
         public void onFailure(org.jeasy.rules.api.Rule rule, Facts facts, 
Exception e) {
-            getLogger().warn("Rule execution failed for: {}", new 
Object[]{rule.getName()}, e);
+            logger.warn("Rule execution failed for: {}", new 
Object[]{rule.getName()}, e);
         }
     }
 
+
 }
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
index 5e5f394..3fa1fae 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -12,4 +12,5 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-org.apache.nifi.rules.engine.EasyRulesEngineService
\ No newline at end of file
+org.apache.nifi.rules.engine.EasyRulesEngineService
+org.apache.nifi.rules.engine.EasyRulesEngineProvider
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/docs/org.apache.nifi.rules.engine.EasyRulesEngineService/additionalDetails.html
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/docs/org.apache.nifi.rules.engine.EasyRulesEngineService/additionalDetails.html
index 48207df..eae2159 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/docs/org.apache.nifi.rules.engine.EasyRulesEngineService/additionalDetails.html
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/main/resources/docs/org.apache.nifi.rules.engine.EasyRulesEngineService/additionalDetails.html
@@ -21,8 +21,9 @@
 </head>
 <body>
 <h2>General</h2>
-<p>The Easy Rules Engine Service supports execution of a centralized set of 
rules (stored as files or provided within the service configuration) against a 
provided set of data called facts.  Facts sent to the service are processed 
against
-    the rules engine to determine what, if any, actions should be executed 
based on the conditions defined within the rules. The list of actions are 
returned to the caller to handle as needed.
+<p>The Easy Rules Engine Service supports execution of a centralized set of 
rules (stored as files or provided within the service configuration) against a 
provided set of data called facts.  It supports both the RulesEngineProvider
+    and RulesEngineService interfaces, allowing callers to send facts to the 
service to process against a centralized rules engine, or allowing them to 
retrieve an instance of a rules engine to process facts locally.
+    Upon execution, the rules engine will determine what rules have been met 
and return a list of actions that should be executed based on the conditions 
defined within the rules.
 </p>
 <p>
     Rules can be implemented in any of the following formats:
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/TestRulesCondition.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/TestRulesCondition.java
new file mode 100644
index 0000000..d988a9a
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/TestRulesCondition.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules;
+
+import org.jeasy.rules.api.Condition;
+import org.jeasy.rules.api.Facts;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TestRulesCondition {
+
+    @Test
+    public void testRulesMVELConditionPassed(){
+        String expression = "predictedTimeToBytesBackpressureMillis <= 
14400000";
+        Facts facts = new Facts();
+        facts.put("predictedTimeToBytesBackpressureMillis",13300000);
+        Condition condition = new RulesMVELCondition(expression, false);
+        long start = System.currentTimeMillis();
+        boolean passed = condition.evaluate(facts);
+        long end = System.currentTimeMillis();
+        System.out.println("Total Time: " + (end - start));
+        assertTrue(passed);
+    }
+
+    @Test
+    public void testRulesMVELConditionFailed(){
+        String expression = "predictedQueuedCount > 50";
+        Facts facts = new Facts();
+        facts.put("predictedQueuedCount",49);
+        Condition condition = new RulesMVELCondition(expression, false);
+        assertFalse(condition.evaluate(facts));
+    }
+
+    @Test
+    public void testRulesMVELConditionError(){
+        String expression = "predictedQueuedCount > 50";
+        Facts facts = new Facts();
+        facts.put("predictedQueued",100);
+        Condition condition = new RulesMVELCondition(expression, false);
+        try {
+            condition.evaluate(facts);
+            fail();
+        }catch (Exception ignored){
+        }
+    }
+
+    @Test
+    public void testRulesSPELConditionPassed(){
+        String expression = "#predictedQueuedCount > 50";
+        Facts facts = new Facts();
+        facts.put("predictedQueuedCount",100);
+        Condition condition = new RulesSPELCondition(expression, false);
+        long start = System.currentTimeMillis();
+        boolean passed = condition.evaluate(facts);
+        long end = System.currentTimeMillis();
+        System.out.println("Total Time: " + (end - start));
+        assertTrue(passed);
+    }
+
+    @Test
+    public void testRulesSPELConditionFailed(){
+        String expression = "#predictedQueuedCount > 50";
+        Facts facts = new Facts();
+        facts.put("predictedQueuedCount",49);
+        Condition condition = new RulesSPELCondition(expression, false);
+        assertFalse(condition.evaluate(facts));
+    }
+
+    @Test
+    public void testRulesSPELConditionError(){
+        String expression = "predictedQueuedCount > 50";
+        Facts facts = new Facts();
+        facts.put("predictedQueuedCount",100);
+        Condition condition = new RulesSPELCondition(expression, false);
+        try {
+            condition.evaluate(facts);
+            fail();
+        }catch (Exception ignored){
+        }
+    }
+
+
+}
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngine.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngine.java
new file mode 100644
index 0000000..092f63d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngine.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.engine;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.rules.Rule;
+import org.apache.nifi.rules.RulesFactory;
+import org.junit.Test;
+
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class TestEasyRulesEngine {
+
+    @Test
+    public void testCheckRules() throws Exception {
+        String testYamlFile = "src/test/resources/test_nifi_rules.yml";
+        List<Rule> rules = RulesFactory.createRulesFromFile(testYamlFile, 
"YAML", "NIFI");
+        final EasyRulesEngine service = new EasyRulesEngine("NIFI",true,false, 
rules);
+        Map<String, Object> facts = new HashMap<>();
+        facts.put("predictedQueuedCount",60);
+        facts.put("predictedTimeToBytesBackpressureMillis",311111);
+        Map<Rule, Boolean> checkedRules = service.checkRules(facts);
+        assertNotNull(checkedRules);
+        assertEquals(2,checkedRules.values().size());
+    }
+
+    @Test
+    public void testFireRules() throws Exception {
+        String testYamlFile = "src/test/resources/test_nifi_rules.yml";
+        List<Rule> rules = RulesFactory.createRulesFromFile(testYamlFile, 
"YAML", "NIFI");
+        final EasyRulesEngine service = new EasyRulesEngine("NIFI",true,false, 
rules);
+        Map<String, Object> facts = new HashMap<>();
+        facts.put("predictedQueuedCount",60);
+        facts.put("predictedTimeToBytesBackpressureMillis",299999);
+        List<Action> actions = service.fireRules(facts);
+        assertNotNull(actions);
+        assertEquals(3,actions.size());
+    }
+
+    @Test
+    public void testIgnoreErrorConditions() throws Exception {
+        String testYamlFile = "src/test/resources/test_nifi_rules.yml";
+        List<Rule> rules = RulesFactory.createRulesFromFile(testYamlFile, 
"YAML", "NIFI");
+        final EasyRulesEngine service = new EasyRulesEngine("NIFI",false, 
false, rules);
+        Map<String, Object> facts = new HashMap<>();
+        facts.put("predictedQueuedCount",60);
+        facts.put("predictedTimeToBytesBackpressure",311111);
+        try {
+            service.fireRules(facts);
+            fail("Error condition exception was not thrown");
+        }catch (Exception ignored){
+        }
+
+    }
+
+    @Test
+    public void testFilterRulesMissingFacts() throws Exception {
+        String testYamlFile = "src/test/resources/test_nifi_rules.yml";
+        List<Rule> rules = RulesFactory.createRulesFromFile(testYamlFile, 
"YAML", "NIFI");
+        final EasyRulesEngine service = new EasyRulesEngine("NIFI",false, 
true, rules);
+        Map<String, Object> facts = new HashMap<>();
+        facts.put("predictedQueuedCount",60);
+        Map<Rule, Boolean> checkedRules = service.checkRules(facts);
+        assertEquals(1, checkedRules.size());
+    }
+
+}
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineProvider.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineProvider.java
new file mode 100644
index 0000000..538bb76
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.rules.engine;
+
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TestEasyRulesEngineProvider {
+
+    @Test
+    public void testGetRulesEngine() throws InitializationException, 
IOException {
+        final TestRunner runner = 
TestRunners.newTestRunner(TestProcessor.class);
+        final RulesEngineProvider service = new MockEasyRulesEngineProvider();
+        runner.addControllerService("easy-rules-engine-service-test",service);
+        runner.setProperty(service, EasyRulesEngineService.RULES_FILE_PATH, 
"src/test/resources/test_nifi_rules.yml");
+        runner.setProperty(service,EasyRulesEngineService.RULES_FILE_TYPE, 
"YAML");
+        runner.setProperty(service,EasyRulesEngineService.RULES_FILE_FORMAT, 
"NIFI");
+        runner.enableControllerService(service);
+        runner.assertValid(service);
+        Map<String, Object> facts = new HashMap<>();
+        facts.put("predictedQueuedCount",60);
+        facts.put("predictedTimeToBytesBackpressureMillis",299999);
+        RulesEngine engine = service.getRulesEngine();
+        assertNotNull(engine);
+        List<Action> actions = engine.fireRules(facts);
+        assertNotNull(actions);
+        assertEquals(actions.size(), 3);
+
+    }
+
+    private static class MockEasyRulesEngineProvider extends 
EasyRulesEngineProvider {
+
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineService.java
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineService.java
index bc7a785..5513212 100644
--- 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineService.java
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/java/org/apache/nifi/rules/engine/TestEasyRulesEngineService.java
@@ -228,7 +228,7 @@ public class TestEasyRulesEngineService {
         try {
             service.fireRules(facts);
             fail("Expected exception to be thrown");
-        }catch (PropertyAccessException pae){
+        }catch (Exception pae){
             assert true;
         }
     }
@@ -273,7 +273,7 @@ public class TestEasyRulesEngineService {
         try {
             service.fireRules(facts);
             fail("Expected exception to be thrown");
-        }catch (PropertyAccessException pae){
+        }catch (Exception pae){
             assert true;
         }
     }
@@ -345,7 +345,26 @@ public class TestEasyRulesEngineService {
             fail();
         }
     }
-    private class MockEasyRulesEngineService extends EasyRulesEngineService {
+
+    @Test
+    public void testFilterRulesMissingFactsTrue() throws 
InitializationException, IOException {
+        final TestRunner runner = 
TestRunners.newTestRunner(TestProcessor.class);
+        final RulesEngineService service = new MockEasyRulesEngineService();
+        runner.addControllerService("easy-rules-engine-service-test",service);
+        runner.setProperty(service, EasyRulesEngineService.RULES_FILE_PATH, 
"src/test/resources/test_nifi_rules_filter.json");
+        runner.setProperty(service,EasyRulesEngineService.RULES_FILE_TYPE, 
"JSON");
+        runner.setProperty(service,EasyRulesEngineService.RULES_FILE_FORMAT, 
"NIFI");
+        
runner.setProperty(service,EasyRulesEngineService.FILTER_RULES_MISSING_FACTS, 
"true");
+        runner.enableControllerService(service);
+        runner.assertValid(service);
+        Map<String, Object> facts = new HashMap<>();
+        facts.put("predictedQueuedCount",60);
+        List<Action> actions = service.fireRules(facts);
+        assertNotNull(actions);
+        assertEquals(actions.size(), 1);
+    }
+
+    private static class MockEasyRulesEngineService extends 
EasyRulesEngineService {
 
     }
 
diff --git 
a/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/resources/test_nifi_rules_filter.json
 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/resources/test_nifi_rules_filter.json
new file mode 100644
index 0000000..b99fb62
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-easyrules-bundle/nifi-easyrules-service/src/test/resources/test_nifi_rules_filter.json
@@ -0,0 +1,34 @@
+[
+  {
+    "name": "Queue Size",
+    "description": "Queue size check greater than 50",
+    "priority": 1,
+    "condition": "predictedQueuedCount > 50",
+    "actions": [
+      {
+        "type": "LOG",
+        "attributes": {
+          "logLevel": "debug",
+          "message": "Queue Size Over 50 is detected!"
+        }
+      }
+    ],
+    "facts": ["predictedQueuedCount"]
+  },
+  {
+    "name": "Time To Back Pressure",
+    "description": "Back pressure time less than 5 minutes",
+    "priority": 2,
+    "condition": "predictedTimeToBytesBackpressureMillis < 300000 && 
predictedTimeToBytesBackpressureMillis >= 0",
+    "actions": [
+      {
+        "type": "LOG",
+        "attributes": {
+          "logLevel": "warn",
+          "message": "Back Pressure prediction less than 5 minutes!"
+        }
+      }
+    ],
+    "facts":["predictedTimeToBytesBackpressureMillis"]
+  }
+]
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
index 434808f..b5f0829 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.rules;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -23,7 +24,7 @@ import java.util.Map;
  * The type of action is dictated by the type field and attributes are used as 
parameters to configure
  * the Action's executor/handler
  */
-public class Action {
+public class Action implements Cloneable{
     private String type;
     private Map<String,String> attributes;
 
@@ -50,4 +51,13 @@ public class Action {
     public void setAttributes(Map<String, String> attributes) {
         this.attributes = attributes;
     }
+
+    public Action clone(){
+        Action action = new Action();
+        action.setType(type);
+        Map<String, String> attributeMap = new HashMap<>(attributes);
+        action.setAttributes(attributeMap);
+        return action;
+    }
+
 }
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Rule.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Rule.java
index 8adbb21..d6f69d3 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Rule.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Rule.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.rules;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -23,7 +24,7 @@ import java.util.List;
  *  one or more {@link Action}
  */
 
-public class Rule {
+public class Rule implements Cloneable{
     private String name;
     private String description;
     private Integer priority;
@@ -90,4 +91,25 @@ public class Rule {
     public void setFacts(List<String> facts) {
         this.facts = facts;
     }
+
+    @Override
+    public Rule clone(){
+        Rule rule = new Rule();
+        rule.setName(name);
+        rule.setDescription(description);
+        rule.setPriority(priority);
+        rule.setCondition(condition);
+
+        if (actions != null) {
+            final List<Action> actionList = new ArrayList<>();
+            rule.setActions(actionList);
+            actions.forEach(action -> actionList.add((Action)action.clone()));
+        }
+        if (facts != null){
+            final List<String> factList = new ArrayList<>();
+            rule.setFacts(factList);
+            factList.addAll(facts);
+        }
+        return rule;
+    }
 }
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngine.java
similarity index 50%
copy from 
nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
copy to 
nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngine.java
index 434808f..9c66c15 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/Action.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngine.java
@@ -14,40 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.rules;
+package org.apache.nifi.rules.engine;
 
+import org.apache.nifi.rules.Action;
+import org.apache.nifi.rules.Rule;
+
+import java.util.List;
 import java.util.Map;
 
 /**
- * An Action that should be performed given a fact has met the condition 
specified in a Rule.
- * The type of action is dictated by the type field and attributes are used as 
parameters to configure
- * the Action's executor/handler
+ * <p>
+ * An instance of a RulesEngine which provides access to available rules
+ * </p>
+ * </p>
  */
-public class Action {
-    private String type;
-    private Map<String,String> attributes;
-
-    public Action() {
-    }
-
-    public Action(String type, Map<String, String> attributes) {
-        this.type = type;
-        this.attributes = attributes;
-    }
-
-    public String getType() {
-        return type;
-    }
+public interface RulesEngine {
 
-    public void setType(String type) {
-        this.type = type;
-    }
 
-    public Map<String, String> getAttributes() {
-        return attributes;
-    }
+    List<Action> fireRules(Map<String, Object> facts);
+    Map<Rule, Boolean> checkRules(Map<String, Object> facts);
 
-    public void setAttributes(Map<String, String> attributes) {
-        this.attributes = attributes;
-    }
 }
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineProvider.java
similarity index 53%
copy from 
nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
copy to 
nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineProvider.java
index f00e0ba..7f55360 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineProvider.java
@@ -19,29 +19,20 @@ package org.apache.nifi.rules.engine;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.controller.ControllerService;
-import org.apache.nifi.rules.Action;
-
-import java.util.List;
-import java.util.Map;
 
 /**
  * <p>
- * A Controller Service that is responsible for executing rules engine against 
provided facts.  The subsequent
- * actions can be executed either by the rules engine or a list of {@link 
Action} can be returned and interrogated/executed by
- * the caller.
+ * A Controller Service that is responsible for providing an instance of a 
Rules Engine.
  * </p>
  * </p>
  */
 @Tags({"rules", "rules-engine","facts","actions"})
-@CapabilityDescription("Specifies a Controller Service which hosts a rules 
engine, that can be used to determine actions that should be performed given 
provided facts.")
-public interface RulesEngineService extends ControllerService {
+@CapabilityDescription("Specifies a Controller Service which provides access 
to an instance of a Rules Engine.")
+public interface RulesEngineProvider extends ControllerService {
 
     /**
-     * Returns the list of Actions that should be triggered as determined by 
the rules engine for the the given facts.
-     * Action in this case have not been executed by the engine.
-     * @param facts a Map of key and facts values, as objects, that should be 
evaluated by the rules engine
-     * @return a List of Actions that should be triggered
+     * Retrieve an instance of a rules engine
+     * @return RulesEngine instance
      */
-    List<Action> fireRules(Map<String, Object> facts);
-
+    RulesEngine getRulesEngine();
 }
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
index f00e0ba..45ab999 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-rules-engine-service-api/src/main/java/org/apache/nifi/rules/engine/RulesEngineService.java
@@ -26,7 +26,7 @@ import java.util.Map;
 
 /**
  * <p>
- * A Controller Service that is responsible for executing rules engine against 
provided facts.  The subsequent
+ * A Controller Service that is responsible for executing a rules engine 
against provided facts.  The subsequent
  * actions can be executed either by the rules engine or a list of {@link 
Action} can be returned and interrogated/executed by
  * the caller.
  * </p>

Reply via email to