summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java')
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java323
1 files changed, 323 insertions, 0 deletions
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java b/src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java
new file mode 100644
index 0000000..b151f6b
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java
@@ -0,0 +1,323 @@
+/*
+ * ============LICENSE_START===================================================
+ * Copyright (c) 2018 Amdocs
+ * ============================================================================
+ * Licensed 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.
+ * ============LICENSE_END=====================================================
+ */
+package org.onap.aai.validation.ruledriven.rule;
+
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.GroovyObject;
+import groovy.lang.MetaMethod;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.validation.logging.LogHelper;
+import org.onap.aai.validation.reader.data.AttributeValues;
+import org.onap.aai.validation.ruledriven.configuration.GroovyConfigurationException;
+import org.onap.aai.validation.ruledriven.configuration.RuleSection;
+import org.onap.aai.validation.util.StringUtils;
+
+/**
+ * Rule based on a Groovy script
+ *
+ */
+public class GroovyRule implements Rule {
+
+ private static final Logger applicationLogger = LogHelper.INSTANCE;
+
+ /**
+ * Do not allow special characters (e.g. ?) in an attribute name. Hyphens and underscores are accepted.
+ */
+ private static final Pattern ATTRIBUTE_NAME_WHITELIST = Pattern.compile("^[a-zA-Z0-9-_.\\*\\[\\]]*$");
+ private static final Pattern ATTRIBUTE_NAME_BLACKLIST = Pattern.compile("^(|null)$");
+
+ private String errorCategory;
+ private String errorMessage;
+ private String severity;
+ private List<String> attributes;
+ private List<String> attributePaths; // where in the JSON entity to read the attributes from
+
+ private String methodName;
+ private GroovyObject groovyObject;
+ private List<String> originalFields;
+ private String originalExpression;
+ private String groovyExpression; // NOSONAR stored for debugging purposes
+ private boolean ruleIsValid = true;
+ private String name;
+
+
+ /**
+ * @param ruleConfig
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ * @throws IOException
+ * @throws GroovyConfigurationException
+ */
+ public GroovyRule(RuleSection ruleConfig)
+ throws InstantiationException, IllegalAccessException, IOException, GroovyConfigurationException {
+ setName(ruleConfig.getName());
+ setErrorCategory(ruleConfig.getCategory());
+ setErrorMessage(ruleConfig.getErrorMessage());
+ setSeverity(ruleConfig.getSeverity());
+ setAttributes(ruleConfig.getAttributes());
+ setAttributePaths(ruleConfig.getAttributes());
+
+ this.originalFields = new ArrayList<>();
+ for (String field : ruleConfig.getExpressionFieldNames()) {
+ originalFields.add(StringUtils.stripPrefix(field, "."));
+ }
+
+ Class<?> groovyClass = createRule(ruleConfig.getExpressionFieldNames(), ruleConfig.getExpression());
+
+ if (groovyClass != null) {
+ groovyObject = (GroovyObject) groovyClass.newInstance();
+
+ for (MetaMethod method : groovyObject.getMetaClass().getMethods()) {
+ if (method.getName().startsWith("rule")) {
+ methodName = method.getName();
+ }
+ }
+
+ try {
+ executeWithSampleData();
+ } catch (IllegalArgumentException e) { // NOSONAR
+ if (e.getCause() instanceof InvokerInvocationException
+ && e.getCause().getCause() instanceof MissingMethodException) {
+ applicationLogger
+ .debug("WARNING: Rule \"" + getName() + "\" does not accept \"1\" for all input values");
+ } else {
+ ruleIsValid = false;
+ }
+ }
+ } else {
+ ruleIsValid = false;
+ }
+ }
+
+ public boolean isValid() {
+ return ruleIsValid;
+ }
+
+ /**
+ * Run the rule expression on the specified attribute values
+ *
+ * @param attributeValues
+ * @return
+ */
+ @Override
+ public Boolean execute(AttributeValues attributeValues) {
+ // Obtain the values of each of the attributes to pass into the rule
+ List<Object> valueList = new ArrayList<>();
+ for (String attrName : this.attributePaths) {
+ valueList.add(attributeValues.get(attrName));
+ }
+ Object[] attrValuesArray = valueList.toArray();
+ return execute(attrValuesArray);
+ }
+
+ /**
+ * Apply the rule to some attribute(s)
+ *
+ * @param values
+ *
+ * @param groovyObject an instance/object of a Groovy class that implements one or more rule methods
+ * @return the Boolean result of evaluating the expression
+ */
+ @Override
+ public Boolean execute(Object... values) {
+ Object result = null;
+ try {
+ result = groovyObject.invokeMethod(getRuleMethod(), values);
+ } catch (MissingPropertyException | MissingMethodException | InvokerInvocationException e) {
+ throw new IllegalArgumentException(e);
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException("Argument is null", e);
+ }
+
+ if (result instanceof Number) {
+ return !result.equals(0);
+ } else {
+ return (Boolean) result;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "GroovyRule \"" + name + "\" " + attributePaths + " -> " + originalFields + " {" + originalExpression
+ + "}";
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getErrorCategory() {
+ return errorCategory;
+ }
+
+ public void setErrorCategory(String errorCategory) {
+ this.errorCategory = errorCategory;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public String getSeverity() {
+ return severity;
+ }
+
+ public void setSeverity(String severity) {
+ this.severity = severity;
+ }
+
+ @Override
+ public List<String> getAttributePaths() {
+ return attributePaths;
+ }
+
+ private String getRuleMethod() {
+ return methodName;
+ }
+
+ private void setAttributePaths(List<String> attributePaths) {
+ this.attributePaths = attributePaths;
+ }
+
+ private void setAttributes(List<String> attributes) {
+ this.attributes = new ArrayList<>();
+ for (String attribute : attributes) {
+ // Strip any prefixes containing the . character
+ this.attributes.add(attribute.substring(attribute.lastIndexOf('.') + 1));
+ }
+ }
+
+ /**
+ * @param fields
+ * @param expression
+ * @return
+ * @throws IOException
+ * @throws GroovyConfigurationException
+ */
+ private Class<?> createRule(List<String> fields, String expression)
+ throws IOException, GroovyConfigurationException {
+ originalExpression = expression;
+ groovyExpression = expression;
+ String methodParams = "";
+
+ int i = 1;
+ for (String attribute : fields) {
+ if (isValidAttributeName(attribute)) {
+ String fieldName = "field" + i++;
+ methodParams = appendParameter(methodParams, fieldName);
+ // Strip any prefixes from the attribute name in case of JayWay expression attributes.
+ attribute = StringUtils.stripPrefix(attribute, ".");
+ String regex = "\\b" + attribute + "\\b(?!\\()";
+ groovyExpression = groovyExpression.replaceAll(regex, fieldName);
+ } else {
+ ruleIsValid = false;
+ return null;
+ }
+ }
+
+ return loadGroovyClass("def rule(" + methodParams + ") {" + groovyExpression + "}");
+ }
+
+ private String appendParameter(String methodParams, String fieldName) {
+ StringBuilder newParams = new StringBuilder();
+ if (methodParams.length() > 0) {
+ newParams.append(methodParams).append(", ");
+ }
+ newParams.append("Object ").append(fieldName);
+ return newParams.toString();
+ }
+
+ private boolean isValidAttributeName(String attributeName) {
+ if (ATTRIBUTE_NAME_BLACKLIST.matcher(attributeName).matches()) {
+ return false;
+ }
+
+ if (!ATTRIBUTE_NAME_WHITELIST.matcher(attributeName).matches()) {
+ return false;
+ }
+
+ // Make sure that the attribute name is NOT purely a number
+ try (Scanner sc = new Scanner(attributeName.trim())) {
+ if (sc.hasNextInt()) {
+ sc.nextInt(); // Consume the integer
+ return sc.hasNext(); // Name is valid if there is more content
+ }
+ }
+
+ return true; // Not an integer
+ }
+
+ private void executeWithSampleData() {
+ Object[] values = new Object[attributes.size()];
+ int i = 0;
+ for (String attribute : attributes) {
+ if (attribute.contains("[*]")) {
+ values[i++] = Arrays.asList("{}");
+ } else {
+ values[i++] = "1";
+ }
+ }
+ execute(values);
+ }
+
+ /**
+ * Load and parse a Groovy script to create an anonymous class
+ *
+ * @param script a file containing the Groovy scripting language
+ * @return the Java Class for accessing the Groovy methods
+ * @throws IOException
+ * @throws GroovyConfigurationException
+ */
+ @SuppressWarnings("rawtypes")
+ private static Class loadGroovyClass(String expression) throws IOException, GroovyConfigurationException {
+ ClassLoader parent = GroovyRule.class.getClassLoader();
+ GroovyClassLoader loader = new GroovyClassLoader(parent);
+ Class groovyClass;
+ try {
+ groovyClass = loader.parseClass(expression);
+ } catch (CompilationFailedException e) {
+ throw new GroovyConfigurationException(e);
+ } finally {
+ loader.close();
+ }
+ return groovyClass;
+ }
+
+}