mirror of https://github.com/apache/nifi.git
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 <mattyb149@apache.org> This closes #4081
This commit is contained in:
parent
778012412a
commit
abf223d574
|
@ -49,6 +49,11 @@
|
|||
<artifactId>easy-rules-mvel</artifactId>
|
||||
<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>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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.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.reporting.InitializationException;
|
||||
import org.apache.nifi.rules.Rule;
|
||||
import org.apache.nifi.rules.RulesFactory;
|
||||
import org.apache.nifi.util.StringUtils;
|
||||
|
||||
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;
|
||||
|
||||
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.");
|
||||
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();
|
||||
|
||||
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 List<Rule> rules;
|
||||
protected volatile String rulesFileFormat;
|
||||
protected boolean ignoreConditionErrors;
|
||||
protected boolean filterRules;
|
||||
|
||||
@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);
|
||||
properties.add(FILTER_RULES_MISSING_FACTS);
|
||||
this.properties = Collections.unmodifiableList(properties);
|
||||
}
|
||||
|
||||
@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();
|
||||
filterRules = context.getProperty(FILTER_RULES_MISSING_FACTS).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;
|
||||
}
|
||||
|
||||
protected RulesEngine getRulesEngine() {
|
||||
List<Rule> rulesCopy = new ArrayList<>();
|
||||
rules.forEach(rule -> {
|
||||
rulesCopy.add(rule.clone());
|
||||
});
|
||||
return new EasyRulesEngine(rulesFileFormat, ignoreConditionErrors, filterRules, Collections.unmodifiableList(rulesCopy));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
public class EasyRulesEngineService extends EasyRulesEngineProvider 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;
|
||||
private volatile RulesEngine rulesEngine;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
super.onEnabled(context);
|
||||
EasyRulesEngine easyRulesEngine = (EasyRulesEngine) getRulesEngine();
|
||||
List<RuleListener> ruleListeners = new ArrayList<>();
|
||||
ruleListeners.add(new EasyRulesListener(getLogger()));
|
||||
easyRulesEngine.setRuleListeners(ruleListeners);
|
||||
rulesEngine = easyRulesEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private ComponentLog logger;
|
||||
|
||||
EasyRulesListener(ComponentLog logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
org.apache.nifi.rules.engine.EasyRulesEngineService
|
||||
org.apache.nifi.rules.engine.EasyRulesEngineProvider
|
|
@ -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:
|
||||
|
|
|
@ -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){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
]
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.rules.Action;
|
||||
import org.apache.nifi.rules.Rule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* An instance of a RulesEngine which provides access to available rules
|
||||
* </p>
|
||||
* </p>
|
||||
*/
|
||||
public interface RulesEngine {
|
||||
|
||||
|
||||
List<Action> fireRules(Map<String, Object> facts);
|
||||
Map<Rule, Boolean> checkRules(Map<String, Object> facts);
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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;
|
||||
import org.apache.nifi.controller.ControllerService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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 provides access to an instance of a Rules Engine.")
|
||||
public interface RulesEngineProvider extends ControllerService {
|
||||
|
||||
/**
|
||||
* Retrieve an instance of a rules engine
|
||||
* @return RulesEngine instance
|
||||
*/
|
||||
RulesEngine getRulesEngine();
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue