summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/onap/aai/validation/ruledriven
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/onap/aai/validation/ruledriven')
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/RuleDrivenValidator.java278
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/RuleManager.java91
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/EntitySection.java101
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/GroovyConfigurationException.java103
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/RuleSection.java200
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/RulesConfigurationLoader.groovy309
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/SettingsSection.java48
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ContentBuilder.java198
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/build/EntityBuilder.java70
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/build/RuleBuilder.java59
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/build/UseRuleBuilder.java46
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ValidationBuilder.java78
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java323
-rw-r--r--src/main/java/org/onap/aai/validation/ruledriven/rule/Rule.java81
14 files changed, 1985 insertions, 0 deletions
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/RuleDrivenValidator.java b/src/main/java/org/onap/aai/validation/ruledriven/RuleDrivenValidator.java
new file mode 100644
index 0000000..476c098
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/RuleDrivenValidator.java
@@ -0,0 +1,278 @@
+/*
+ * ============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;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.onap.aai.validation.Validator;
+import org.onap.aai.validation.config.RuleIndexingConfig;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.logging.ApplicationMsgs;
+import org.onap.aai.validation.logging.LogHelper;
+import org.onap.aai.validation.reader.EventReader;
+import org.onap.aai.validation.reader.OxmReader;
+import org.onap.aai.validation.reader.data.AttributeValues;
+import org.onap.aai.validation.reader.data.Entity;
+import org.onap.aai.validation.result.ValidationResult;
+import org.onap.aai.validation.result.Violation;
+import org.onap.aai.validation.result.Violation.ViolationType;
+import org.onap.aai.validation.ruledriven.configuration.EntitySection;
+import org.onap.aai.validation.ruledriven.configuration.GroovyConfigurationException;
+import org.onap.aai.validation.ruledriven.configuration.RulesConfigurationLoader;
+import org.onap.aai.validation.ruledriven.rule.Rule;
+
+/**
+ * Validator using explicit rules
+ *
+ */
+public class RuleDrivenValidator implements Validator {
+
+ private static LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ private static final String RULES_CONFIG_FILE_SUFFIX = ".groovy";
+
+ private Path configurationPath;
+ private OxmReader oxmReader;
+ private EventReader eventReader;
+ private Optional<RuleIndexingConfig> ruleIndexingConfig;
+ // Map of event type name against RuleManager for that event type
+ private Map<String, RuleManager> ruleManagers;
+
+
+ /**
+ * Construct a Validator that is configured using rule files
+ *
+ * @param configurationPath path to the Groovy rules files
+ * @param oxmReader required for validating entity types
+ * @param eventReader a reader for extracting entities from each event to be validated
+ */
+ public RuleDrivenValidator(final Path configurationPath, final OxmReader oxmReader,
+ final EventReader eventReader, final RuleIndexingConfig ruleIndexingConfig) {
+ this.configurationPath = configurationPath;
+ this.oxmReader = oxmReader;
+ this.eventReader = eventReader;
+ this.ruleIndexingConfig = Optional.ofNullable(ruleIndexingConfig);
+ this.ruleManagers = null;
+ }
+
+ @Override
+ public void initialise() throws ValidationServiceException {
+ ruleManagers = new HashMap<>();
+ for (String eventType : getSupportedEventTypes()) {
+ ruleManagers.put(eventType.toLowerCase(Locale.getDefault()), loadRulesConfiguration(eventType));
+ }
+ validateRulesConfiguration();
+ }
+
+ private RuleManager loadRulesConfiguration(String eventType) throws ValidationServiceException {
+ StringBuilder rulesText = new StringBuilder();
+ try (Stream<Path> paths = Files.find(configurationPath.resolve(eventType), 1,
+ (path, basicFileAttributes) -> path.toFile().getName().matches(".*\\" + RULES_CONFIG_FILE_SUFFIX));) {
+ paths.forEach(appendFileContent(rulesText));
+ } catch (IOException e) {
+ throw new ValidationServiceException(ValidationServiceError.RULES_FILE_ERROR,
+ configurationPath.toAbsolutePath(), e);
+ }
+
+ try {
+ return RulesConfigurationLoader.loadConfiguration(rulesText.toString());
+ } catch (GroovyConfigurationException e) {
+ throw new ValidationServiceException(ValidationServiceError.RULES_FILE_ERROR, e,
+ configurationPath.toAbsolutePath() + File.separator + "*" + RULES_CONFIG_FILE_SUFFIX);
+ }
+ }
+
+ private void validateRulesConfiguration() throws ValidationServiceException {
+ for (RuleManager ruleManager : ruleManagers.values()) {
+ for (EntitySection entity : ruleManager.getEntities()) {
+ if(ruleIndexingConfig.isPresent() && ruleIndexingConfig.get().skipOxmValidation(entity.getName())) {
+ continue;
+ }
+ if (oxmReader != null && oxmReader.getPrimaryKeys(entity.getName()).isEmpty()) {
+ throw new ValidationServiceException(ValidationServiceError.OXM_MISSING_KEY,
+ entity.getName() + " defined in " + configurationPath.toAbsolutePath() + File.separator
+ + "*" + RULES_CONFIG_FILE_SUFFIX);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to expose the configured rules. This simplifies testing of the validator.
+ *
+ * @param entityType
+ * @param eventType
+ * @return the rules defined for this entityType
+ */
+ public List<Rule> getRulesForEntity(String entityType, String eventType) {
+ return ruleManagers.get(eventType.toLowerCase(Locale.getDefault())).getRulesForEntity(entityType);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.onap.aai.validation.Validator#validate(java.lang.String)
+ */
+ @Override
+ public List<ValidationResult> validate(String event) throws ValidationServiceException {
+ List<ValidationResult> validationResults = new ArrayList<>();
+
+ Entity entity = getEventReader().getEntity(event);
+ Optional<String> eventType = eventReader.getEventType(event);
+ List<Rule> rules = getRulesToApply(entity, eventType).orElse(null);
+ if (rules == null) {
+ throw new ValidationServiceException(ValidationServiceError.RULES_NOT_DEFINED, eventType.orElse(null));
+ }
+ ValidationResult validationResult = new ValidationResult(entity);
+ Violation.Builder builder = new Violation.Builder(entity);
+
+ for (Rule rule : rules) {
+ AttributeValues attributeValues = entity.getAttributeValues(rule.getAttributePaths());
+
+ // Execute the rule for this particular set of attribute values.
+ boolean valid = false;
+ try {
+ valid = rule.execute(attributeValues);
+ } catch (IllegalArgumentException e) {
+ throw new ValidationServiceException(ValidationServiceError.RULE_EXECUTION_ERROR, e, rule,
+ attributeValues);
+ }
+
+ applicationLogger.debug(String.format("%s|%s|\"%s\"|%s", entity.getType(), entity.getIds(), rule.getName(),
+ valid ? "pass" : "fail"));
+
+ if (!valid) {
+ //@formatter:off
+ Violation violation = builder
+ .category(rule.getErrorCategory())
+ .severity(rule.getSeverity())
+ .violationType(ViolationType.RULE)
+ .validationRule(rule.getName())
+ .violationDetails(attributeValues.generateReport())
+ .errorMessage(rule.getErrorMessage())
+ .build();
+ //@formatter:on
+
+ validationResult.addViolation(violation);
+ }
+ }
+ validationResults.add(validationResult);
+
+ return validationResults;
+ }
+
+ private Optional<List<Rule>> getRulesToApply(Entity entity, Optional<String> eventType)
+ throws ValidationServiceException {
+ Optional<List<Rule>> rules = Optional.empty();
+ if (eventType.isPresent()) {
+ Optional<RuleManager> ruleManager = getRuleManager(eventType.get().toLowerCase(Locale.getDefault()));
+ if (ruleManager.isPresent()) {
+ if (ruleIndexingConfig.isPresent() && ruleIndexingConfig.get().getIndexedEvents() != null &&
+ ruleIndexingConfig.get().getIndexedEvents().contains(eventType.get())) {
+ rules = getRulesByIndex(entity, eventType.get(), ruleManager.get());
+ } else {
+ rules = Optional.of(ruleManager.get().getRulesForEntity(entity.getType()));
+ }
+ }
+ }
+ return rules;
+ }
+
+ private Optional<List<Rule>> getRulesByIndex(Entity entity, String eventType, RuleManager ruleManager) {
+ String rulesKey = generateKey(entity, eventType);
+ applicationLogger.debug(String.format("Retrieving indexed rules for key '%s'", rulesKey));
+ Optional<List<Rule>> rules = Optional.of(ruleManager.getRulesForEntity(rulesKey));
+ if (rules.get().isEmpty() && ruleIndexingConfig.isPresent()) {
+ if (ruleIndexingConfig.get().getDefaultIndexKey() == null || ruleIndexingConfig.get().getDefaultIndexKey().isEmpty()) {
+ applicationLogger.debug("Default index value not configured, unable to get rules");
+ applicationLogger.error(ApplicationMsgs.CANNOT_VALIDATE_ERROR, eventType);
+ return rules;
+ }
+ String defaultKey = RuleManager.generateKey(new String[] {ruleIndexingConfig.get().getDefaultIndexKey()});
+ rules = Optional.of(ruleManager.getRulesForEntity(defaultKey));
+ }
+ return rules;
+ }
+
+ private String generateKey(Entity entity, String eventType) {
+ if (!ruleIndexingConfig.isPresent() || ruleIndexingConfig.get().getIndexAttributes() == null ||
+ ruleIndexingConfig.get().getIndexAttributes().isEmpty()) {
+ applicationLogger.debug(String.format(
+ "Event '%s' is configured to use indexed rules but indexing attributes are not configured", eventType));
+ return "";
+ }
+ try {
+ AttributeValues attributeValues = entity.getAttributeValues(ruleIndexingConfig.get().getIndexAttributes());
+ applicationLogger.debug("Generating index using attributes: " + attributeValues.generateReport().toString());
+ Collection<Object> values = attributeValues.generateReport().values();
+ return RuleManager.generateKey(values.stream().toArray(String[]::new));
+ } catch (ValidationServiceException e) {
+ applicationLogger.debug("Failed to retrieve index key attributes from event");
+ applicationLogger.error(ApplicationMsgs.CANNOT_VALIDATE_ERROR, eventType);
+ return "";
+ }
+ }
+
+ private EventReader getEventReader() {
+ return this.eventReader;
+ }
+
+ private Optional<RuleManager> getRuleManager(String eventType) throws ValidationServiceException {
+ if (ruleManagers == null) {
+ initialise();
+ }
+ return Optional.ofNullable(ruleManagers.get(eventType));
+ }
+
+ /**
+ * Read the text content of the specified Path and append this to the specified String
+ *
+ * @param sb StringBuilder for the rule configuration text
+ * @return a Consumer function that appends file content
+ */
+ private Consumer<? super Path> appendFileContent(StringBuilder sb) {
+ return path -> {
+ try {
+ for (String line : Files.readAllLines(path)) {
+ sb.append(line).append("\n");
+ }
+ } catch (IOException e) {
+ applicationLogger.error(ApplicationMsgs.READ_FILE_ERROR, e, path.toString());
+ }
+ };
+ }
+
+ private Collection<String> getSupportedEventTypes() {
+ String[] list = configurationPath.toFile().list((current, name) -> new File(current, name).isDirectory());
+ return list == null ? Collections.emptyList() : Arrays.asList(list);
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/RuleManager.java b/src/main/java/org/onap/aai/validation/ruledriven/RuleManager.java
new file mode 100644
index 0000000..c21ce24
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/RuleManager.java
@@ -0,0 +1,91 @@
+/*
+ * ============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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.onap.aai.validation.ruledriven.configuration.EntitySection;
+import org.onap.aai.validation.ruledriven.configuration.GroovyConfigurationException;
+import org.onap.aai.validation.ruledriven.configuration.RuleSection;
+import org.onap.aai.validation.ruledriven.rule.GroovyRule;
+import org.onap.aai.validation.ruledriven.rule.Rule;
+
+/**
+ * Helper class storing the relationships from entity type to rules. This class constructs the actual rules from the
+ * supplied configuration.
+ *
+ */
+public class RuleManager {
+
+ private Map<String, List<Rule>> rulesMap = new LinkedHashMap<>();
+ private List<EntitySection> entities;
+
+ /**
+ * Create the rules for each type of entity based on the supplied configuration
+ *
+ * @param entities configuration (all entities)
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ * @throws GroovyConfigurationException
+ * @throws IOException
+ */
+ public RuleManager(List<EntitySection> entities)
+ throws InstantiationException, IllegalAccessException, GroovyConfigurationException, IOException {
+ this.entities = entities;
+ for (EntitySection entity : entities) {
+ List<Rule> rules = new ArrayList<>();
+ for (RuleSection section : entity.getRules()) {
+ rules.add(new GroovyRule(section));
+ }
+ rulesMap.put(entity.getType(), rules);
+ }
+ }
+
+ public List<EntitySection> getEntities() {
+ return entities;
+ }
+
+ /**
+ * @param entityType
+ * @return the rules configured for this entity type
+ */
+ public List<Rule> getRulesForEntity(String entityType) {
+ List<Rule> rules = rulesMap.get(entityType);
+ return rules == null ? Collections.emptyList() : rules;
+ }
+
+ public static String generateKey(String[] indices) {
+ SortedSet<String> sortedIndices = new TreeSet<>();
+ Collections.addAll(sortedIndices, indices);
+ StringBuilder sb = new StringBuilder();
+ Iterator<String> iterator = sortedIndices.iterator();
+ while (iterator.hasNext()) {
+ sb.append("[");
+ sb.append(iterator.next());
+ sb.append("]");
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/EntitySection.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/EntitySection.java
new file mode 100644
index 0000000..357ed87
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/EntitySection.java
@@ -0,0 +1,101 @@
+/*
+ * ============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.configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.onap.aai.validation.ruledriven.configuration.build.EntityBuilder;
+
+/**
+ * Rules Configuration: entity {} section.
+ */
+public class EntitySection {
+
+ private String name;
+ private String type;
+ private final SortedSet<String> indices = new TreeSet<>();
+ private final List<RuleSection> rules = new ArrayList<>();
+
+ /**
+ * Rules are specified within an entity section.
+ */
+ public EntitySection() {
+ // Deliberately empty - invoked when an entity section is read from the rules DSL
+ }
+
+ @Override
+ public String toString() {
+ return new EntityBuilder(this).toString();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setType(String entityType) {
+ this.type = entityType;
+ }
+
+ public void setIndices(List<String> indices) {
+ for(String index : indices) {
+ this.indices.add(index.trim());
+ }
+ }
+
+ public void setIndices(String[] indices) {
+ for(String index : indices) {
+ this.indices.add(index.trim());
+ }
+ }
+
+ public SortedSet<String> getIndices() {
+ return this.indices;
+ }
+
+ /**
+ * Adds the rule.
+ *
+ * @param rule
+ * the rule
+ */
+ public void addRule(RuleSection rule) {
+ this.rules.add(rule);
+ }
+
+ /**
+ * <p>
+ * Getter for property {@link #rules}.
+ * </p>
+ *
+ * @return Value for property <tt>rules</tt>.
+ */
+ public List<RuleSection> getRules() {
+ return this.rules;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/GroovyConfigurationException.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/GroovyConfigurationException.java
new file mode 100644
index 0000000..3b1b98a
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/GroovyConfigurationException.java
@@ -0,0 +1,103 @@
+/*
+ * ============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.configuration;
+
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.MultipleCompilationErrorsException;
+import org.onap.aai.validation.exception.BaseValidationServiceException;
+import org.onap.aai.validation.exception.ValidationServiceError;
+
+/**
+ * Configuration load/parse exceptions
+ *
+ */
+@SuppressWarnings("serial")
+public class GroovyConfigurationException extends BaseValidationServiceException {
+
+ private String invalidToken; // NOSONAR
+ private String configText; // NOSONAR
+
+ /**
+ * @param text
+ */
+ public GroovyConfigurationException(String text) {
+ super(ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId(), ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId() + ", " + text);
+ }
+
+ /**
+ * @param text
+ * @param configuration
+ */
+ public GroovyConfigurationException(String text, String configuration) {
+ super(text);
+ this.configText = configuration;
+ }
+
+ /**
+ * @param e
+ * exception for a missing method
+ * @param configuration
+ */
+ public GroovyConfigurationException(MissingMethodException e, String configuration) {
+ super(ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId(), "Invalid keyword " + e.getMethod(), e);
+ setInvalidToken(e.getMethod());
+ this.configText = configuration;
+ }
+
+ /**
+ * @param e
+ * @param configuration
+ */
+ public GroovyConfigurationException(MissingPropertyException e, String configuration) {
+ super(ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId(), "Invalid keyword " + e.getProperty(), e);
+ setInvalidToken(e.getProperty());
+ this.configText = configuration;
+ }
+
+ /**
+ * @param e
+ */
+ public GroovyConfigurationException(CompilationFailedException e) {
+ super(ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId(), ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId() + ", "
+ + ValidationServiceError.RULE_UNEXPECTED_TOKEN.getMessage() + "; Caused by: " + e.getMessage(), e);
+ }
+
+ /**
+ * @param e
+ * @param configuration
+ */
+ public GroovyConfigurationException(MultipleCompilationErrorsException e, String configuration) {
+ super(ValidationServiceError.RULE_UNEXPECTED_TOKEN.getId(), "", e);
+ this.configText = configuration;
+ }
+
+ public String getInvalidToken() {
+ return invalidToken;
+ }
+
+ public void setInvalidToken(String token) {
+ this.invalidToken = token;
+ }
+
+ public String getConfigText() {
+ return configText;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/RuleSection.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/RuleSection.java
new file mode 100644
index 0000000..b03bcf3
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/RuleSection.java
@@ -0,0 +1,200 @@
+/*
+ * ============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.configuration;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.onap.aai.validation.ruledriven.configuration.build.RuleBuilder;
+
+/**
+ * Rules Configuration: rule {} section.
+ */
+public class RuleSection {
+
+ private String name;
+ private boolean isGenericRule;
+ private String description;
+ private String category;
+ private String errorMessage;
+ private String type;
+ private String objectId;
+ private String severity;
+ private List<String> attributes = new ArrayList<>();
+ private List<String> fields = new ArrayList<>();
+ private String expression;
+
+ /**
+ * Rules may be defined within an entity {} section.
+ */
+ public RuleSection() {
+ isGenericRule = false;
+ }
+
+ /**
+ * @param attribute
+ */
+ public void addAttribute(String attribute) {
+ if (this.attributes == null) {
+ this.attributes = new ArrayList<>();
+ }
+ this.attributes.add(attribute);
+ }
+
+ private void addAttributeMapping(String field) {
+ if (this.fields == null) {
+ this.fields = new ArrayList<>();
+ }
+ this.fields.add(field);
+ }
+
+ private void addAttributeNames(List<String> fieldNames) {
+ for (String attribute : fieldNames) {
+ addAttributeMapping(attribute);
+ }
+ }
+
+ private void addAttributes(List<String> attributes) {
+ for (String attribute : attributes) {
+ addAttribute(attribute);
+ }
+ }
+
+ /**
+ * Make a copy of a generically defined rule so that we can tailor it for our specific entity.
+ *
+ * @param genericRule
+ */
+ public void copyFrom(RuleSection genericRule) {
+ setCategory(genericRule.getCategory());
+ setDescription(genericRule.getDescription());
+ setSeverity(genericRule.getSeverity());
+ setExpression(genericRule.getExpression());
+ setErrorMessage(genericRule.getErrorMessage());
+ if (genericRule.getAttributes() != null) {
+ if (getAttributes().isEmpty()) {
+ addAttributes(genericRule.getAttributes());
+ } else {
+ // Map the attributes
+ addAttributeNames(genericRule.getAttributes());
+ }
+ }
+
+ if (getAttributes().isEmpty()) {
+ throw new IllegalArgumentException("No attributes defined");
+ }
+ }
+
+ public List<String> getAttributes() {
+ return new ArrayList<>(attributes);
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ public List<String> getExpressionFieldNames() {
+ if (fields.size() < attributes.size()) {
+ return attributes;
+ } else {
+ return fields;
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getObjectId() {
+ return objectId;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ public boolean isGeneric() {
+ return isGenericRule;
+ }
+
+ public void setAttributes(List<String> attributes) {
+ this.attributes = attributes;
+ }
+
+ public void setAttributes(String[] attributes) {
+ this.attributes = new ArrayList<>(Arrays.asList(attributes));
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public void setExpression(String expression) {
+ this.expression = expression;
+ }
+
+ public void setIsGeneric(boolean isGenericRule) {
+ this.isGenericRule = isGenericRule;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setObject(String object) {
+ this.type = object;
+ }
+
+ public void setObjectId(String objectId) {
+ this.objectId = objectId;
+ }
+
+ public void setSeverity(String severity) {
+ this.severity = severity;
+ }
+
+ @Override
+ public String toString() {
+ return new RuleBuilder(this, "").toString();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/RulesConfigurationLoader.groovy b/src/main/java/org/onap/aai/validation/ruledriven/configuration/RulesConfigurationLoader.groovy
new file mode 100644
index 0000000..5fabde9
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/RulesConfigurationLoader.groovy
@@ -0,0 +1,309 @@
+/*
+ * ============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.configuration
+
+import groovy.lang.Closure
+import groovy.lang.DelegatesTo
+import groovy.lang.ExpandoMetaClass
+import java.io.File
+import java.util.List
+import org.onap.aai.validation.ruledriven.RuleManager
+
+class RulesConfigurationLoader {
+
+ static RuleManager loadConfiguration(File dsl) {
+ return loadConfiguration(dsl.text)
+ }
+
+ static RuleManager loadConfiguration(String dsl) throws GroovyConfigurationException {
+ SettingsSection globalConfiguration = new SettingsSection()
+ def List<EntitySection> entities = []
+ def List<RuleSection> rules = []
+ Script dslScript
+
+ try {
+ dslScript = new GroovyShell().parse(dsl)
+ } catch (org.codehaus.groovy.control.MultipleCompilationErrorsException e) {
+ throw new GroovyConfigurationException(e, dsl)
+ }
+
+ dslScript.metaClass = createEMC(dslScript.class, { ExpandoMetaClass emc ->
+
+ emc.settings = { Closure cl ->
+ cl.delegate = new SettingsDelegate(globalConfiguration)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ }
+
+ emc.entity = { Closure cl ->
+ EntitySection entityConfiguration = new EntitySection()
+ cl.delegate = new EntityDelegate(entityConfiguration)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ entities.add(entityConfiguration)
+ }
+
+ emc.rule = { Closure cl ->
+ RuleSection ruleConfiguration = new RuleSection()
+ cl.delegate = new RuleDelegate(ruleConfiguration)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ rules.add(ruleConfiguration)
+ }
+ })
+
+ try {
+ dslScript.run()
+ } catch (MissingMethodException | MissingPropertyException e) {
+ throw new GroovyConfigurationException(e, dsl)
+ }
+
+ loadGenericRules(entities, rules)
+ checkForDuplicateRules(rules)
+
+ return new RuleManager(entities)
+ }
+
+ static void loadGenericRules(List<EntitySection> entities, List<RuleSection> rules) {
+ for (entity in entities) {
+ for (rule in entity.getRules()) {
+ if (rule.isGeneric()) {
+ def namedRule = rules.find() { item ->
+ item.getName() == rule.getName()
+ }
+ if (namedRule == null) {
+ throw new GroovyConfigurationException("rule '" + rule.getName() + "' is not defined")
+ }
+ try {
+ rule.copyFrom(namedRule)
+ } catch (IllegalArgumentException e) {
+ throw new GroovyConfigurationException("rule '" + rule.getName() + "' has no attributes defined, referenced by entity '" + entity.getName() + "'")
+ }
+ }
+ if (rule.getExpression() == null) {
+ throw new GroovyConfigurationException("rule '" + rule.getName() + "' does not have an expression defined")
+ }
+ }
+ }
+ }
+
+ static void checkForDuplicateRules(List<RuleSection> rules) {
+ def duplicates = rules.countBy{ rule -> rule.name }.grep{ it.value > 1 }.collect{ it.key }
+
+ rules.each { rule ->
+ if (rule.name in duplicates) {
+ throw new GroovyConfigurationException("Generic rule '" + rule.name + "' is duplicated")
+ }
+ }
+ }
+
+ static ExpandoMetaClass createEMC(Class scriptClass, Closure cl) {
+ ExpandoMetaClass emc = new ExpandoMetaClass(scriptClass, false)
+ cl(emc)
+ emc.initialize()
+ return emc
+ }
+}
+
+// Parse the settings {} block
+class SettingsDelegate {
+ private SettingsSection configuration
+
+ SettingsDelegate(SettingsSection configuration) {
+ this.configuration = configuration
+ }
+
+ void environment(String environment) {
+ this.configuration.setEnvironment environment
+ }
+}
+
+// Parse an entity {} block
+class EntityDelegate {
+ private EntitySection configuration
+
+ EntityDelegate(EntitySection configuration) {
+ this.configuration = configuration
+ }
+
+ void name(String name) {
+ this.configuration.setName name
+ }
+
+ void type(String name) {
+ if (!configuration.name) configuration.name = name
+ configuration.type = name
+ configuration.getRules().each { rule ->
+ rule.setObject configuration.type
+ }
+ }
+
+ def indexing(@DelegatesTo(strategy=Closure.DELEGATE_FIRST, value=IndexingDelegate) Closure cl) {
+ cl.delegate = new IndexingDelegate(configuration)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ }
+
+ def validation(@DelegatesTo(strategy=Closure.DELEGATE_FIRST, value=ValidationDelegate) Closure cl) {
+ cl.delegate = new ValidationDelegate(configuration)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ }
+
+ void methodMissing(String name, Object args) {
+ throw new MissingMethodException(name, this.class, args as Object[])
+ }
+
+ def propertyMissing(String name) {
+ throw new MissingMethodException(name, this.class)
+ }
+}
+
+
+// Parse an indexing {} block within an entity block
+class IndexingDelegate {
+ private EntitySection configuration
+
+ IndexingDelegate(EntitySection configuration) {
+ this.configuration = configuration
+ }
+
+ void indices(String... indices) {
+ this.configuration.setIndices indices
+ def index = RuleManager.generateKey(indices)
+ this.configuration.type = index
+ this.configuration.getRules().each { rule ->
+ rule.setObject configuration.type
+ }
+ }
+
+ void methodMissing(String name, Object args) {
+ throw new MissingMethodException(name, this.class, args as Object[])
+ }
+
+ def propertyMissing(String name) {
+ throw new MissingMethodException(name, this.class)
+ }
+}
+
+// Parse a validation {} block within an entity block
+class ValidationDelegate {
+ private EntitySection configuration
+ private String id
+
+ ValidationDelegate(EntitySection configuration) {
+ this.configuration = configuration
+ }
+
+ void useRule(@DelegatesTo(strategy=Closure.DELEGATE_FIRST, value=UseRuleDelegate) Closure cl) {
+ cl.delegate = new UseRuleDelegate(configuration, configuration.type, id)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ }
+
+ void rule(@DelegatesTo(strategy=Closure.DELEGATE_FIRST, value=RuleDelegate) Closure cl) {
+ RuleSection ruleConfiguration = new RuleSection()
+ ruleConfiguration.setObject configuration.type
+ ruleConfiguration.setObjectId this.id
+ cl.delegate = new RuleDelegate(ruleConfiguration)
+ cl.resolveStrategy = Closure.DELEGATE_FIRST
+ cl()
+ configuration.addRule(ruleConfiguration)
+ }
+
+ void methodMissing(String name, Object args) {
+ throw new MissingMethodException(name, this.class, args as Object[])
+ }
+}
+
+// Parse a rule {} block
+class RuleDelegate {
+ private RuleSection configuration
+
+ RuleDelegate(RuleSection configuration) {
+ this.configuration = configuration
+ }
+
+ void name(String name) {
+ this.configuration.setName name
+ }
+
+ void category(String category) {
+ this.configuration.setCategory category
+ }
+
+ void description(String description) {
+ this.configuration.setDescription description
+ }
+
+ void errorText(String text) {
+ this.configuration.setErrorMessage text
+ }
+
+ void severity(String severity) {
+ this.configuration.setSeverity severity
+ }
+
+ void attributes(String... attributesList) {
+ this.configuration.setAttributes attributesList
+ }
+
+ void validate(String validate) {
+ this.configuration.setExpression validate
+ }
+
+ void methodMissing(String name, Object args) {
+ throw new MissingMethodException(name, this.class, args as Object[])
+ }
+
+ def propertyMissing(String name) {
+ throw new MissingMethodException(name, this.class)
+ }
+}
+
+class UseRuleDelegate {
+ private EntitySection configuration
+ private String objectName
+ private String id
+ private RuleSection ruleConfig
+
+ UseRuleDelegate(EntitySection configuration, String objectName, String id) {
+ this.configuration = configuration
+ this.objectName = objectName
+ this.id = id
+ }
+
+ void name(String name) {
+ ruleConfig = new RuleSection()
+ ruleConfig.setIsGeneric true;
+ ruleConfig.setName name;
+ ruleConfig.setObject this.objectName
+ ruleConfig.setObjectId this.id
+ this.configuration.addRule ruleConfig
+ }
+
+ void attributes(String[] attributes) {
+ if (ruleConfig.attributes.empty) {
+ attributes.each {ruleConfig.addAttribute it}
+ }
+ }
+
+ def propertyMissing(String name) {
+ throw new MissingMethodException(name, this.class)
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/SettingsSection.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/SettingsSection.java
new file mode 100644
index 0000000..d59d07d
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/SettingsSection.java
@@ -0,0 +1,48 @@
+/*
+ * ============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.configuration;
+
+import org.onap.aai.validation.ruledriven.configuration.build.EntityBuilder;
+
+/**
+ * Parse the settings configuration block.
+ */
+public class SettingsSection {
+
+ private String environment;
+
+ public SettingsSection() {
+ // Deliberately empty
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(new EntityBuilder());
+ return sb.toString();
+ }
+
+ public String getEnvironment() {
+ return environment;
+ }
+
+ public void setEnvironment(String environment) {
+ this.environment = environment;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ContentBuilder.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ContentBuilder.java
new file mode 100644
index 0000000..320e29e
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ContentBuilder.java
@@ -0,0 +1,198 @@
+/*
+ * ============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.configuration.build;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+
+/**
+ * Helper class for building internal configuration settings.
+ */
+public class ContentBuilder {
+
+ private final String contentName;
+ protected List<ContentBuilder> items = new ArrayList<>();
+ private StringBuilder content = new StringBuilder();
+ protected String indent = "";
+
+ /**
+ * Construct an empty section.
+ */
+ public ContentBuilder() {
+ this.contentName = null;
+ }
+
+ /**
+ * Instantiates a new content builder.
+ *
+ * @param contentName
+ * the content name
+ */
+ public ContentBuilder(String contentName) {
+ this.contentName = contentName;
+ }
+
+ @Override
+ public String toString() {
+ return build();
+ }
+
+ /**
+ * Builds the configuration section
+ *
+ * @return the configuration as a string
+ */
+ public String build() {
+ StringBuilder sb = new StringBuilder();
+ appendPrefix(sb);
+ // Build child items
+ for (ContentBuilder item : items) {
+ sb.append(item.build());
+ }
+ // Any ad-hoc content goes here
+ sb.append(content);
+ appendSuffix(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Adds the content.
+ *
+ * @param item
+ * the item
+ */
+ public void addContent(ContentBuilder item) {
+ items.add(item);
+ }
+
+ /**
+ * Adds the content.
+ *
+ * @param stringContent
+ * the string content
+ */
+ public void addContent(String stringContent) {
+ content.append(stringContent);
+ }
+
+ /**
+ * Append content as a new line.
+ *
+ * @param stringContent
+ * the string
+ */
+ public void appendLine(String stringContent) {
+ content.append(indent).append(stringContent).append(System.lineSeparator());
+ }
+
+ /**
+ * Append properties.
+ *
+ * @param props
+ * the props
+ */
+ public void appendProperties(Properties props) {
+ @SuppressWarnings("unchecked")
+ Enumeration<String> e = (Enumeration<String>) props.propertyNames();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ appendValue(key, props.get(key));
+ }
+ }
+
+ /**
+ * Append value.
+ *
+ * @param key
+ * the key
+ * @param value
+ * the value
+ * @return
+ */
+ public ContentBuilder appendValue(String key, Object value) {
+ if (value == null) {
+ return this;
+ }
+
+ addContent(indent + "\t" + key + " ");
+
+ if (value instanceof String) {
+ addStringValue((String) value);
+ } else if (value instanceof Number) {
+ addStringValue(value.toString());
+ } else if (value instanceof List<?>) {
+ boolean first = true;
+ for (Object element : (List<?>) value) {
+ if (!first) {
+ addContent(", ");
+ }
+ addStringValue((String) element);
+ first = false;
+ }
+ } else if (value instanceof Properties) {
+ appendLine("{");
+ appendProperties((Properties) value);
+ appendLine("}");
+ } else {
+ throw new IllegalArgumentException(key);
+ }
+ addContent(System.lineSeparator());
+
+ return this;
+ }
+
+ /**
+ * Adds the string value.
+ *
+ * @param value
+ * the value
+ */
+ private void addStringValue(String value) {
+ addContent("'" + value + "'");
+ }
+
+ /**
+ * Append suffix.
+ *
+ * @param sb
+ * the sb
+ */
+ private void appendSuffix(StringBuilder sb) {
+ if (contentName != null) {
+ sb.append(indent).append("}").append(System.lineSeparator());
+ }
+ }
+
+ /**
+ * Append prefix.
+ *
+ * @param sb
+ * the sb
+ */
+ private void appendPrefix(StringBuilder sb) {
+ if (contentName != null) {
+ sb.append(indent);
+ sb.append(contentName);
+ sb.append(" {").append(System.lineSeparator());
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/EntityBuilder.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/EntityBuilder.java
new file mode 100644
index 0000000..166ae50
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/EntityBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * ============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.configuration.build;
+
+import java.util.Properties;
+import org.onap.aai.validation.ruledriven.configuration.EntitySection;
+import org.onap.aai.validation.ruledriven.configuration.RuleSection;
+
+/**
+ * Builder for entity config section
+ *
+ */
+public class EntityBuilder extends ContentBuilder {
+
+ /**
+ * Create an empty entity section
+ */
+ public EntityBuilder() {
+ super("entity");
+ }
+
+ /**
+ * @param auditConfiguration
+ */
+ public EntityBuilder(EntitySection auditConfiguration) {
+ this();
+ appendValue("name", auditConfiguration.getName());
+
+ for (RuleSection rule : auditConfiguration.getRules()) {
+ addContent(new ValidationBuilder(rule).toString());
+ }
+ }
+
+ /**
+ * Add an empty validation section to this entity
+ *
+ * @return
+ */
+ public ValidationBuilder validation() {
+ ValidationBuilder item = new ValidationBuilder();
+ addContent(item);
+ return item;
+ }
+
+ /**
+ * @param props
+ * @return
+ */
+ public ValidationBuilder validation(Properties props) {
+ ValidationBuilder item = validation();
+ item.appendProperties(props);
+ return item;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/RuleBuilder.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/RuleBuilder.java
new file mode 100644
index 0000000..07fb5b4
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/RuleBuilder.java
@@ -0,0 +1,59 @@
+/*
+ * ============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.configuration.build;
+
+import org.onap.aai.validation.ruledriven.configuration.RuleSection;
+
+/**
+ * Builder for rule config section.
+ */
+public class RuleBuilder extends ContentBuilder {
+
+ /**
+ * Instantiates a new rule builder.
+ */
+ public RuleBuilder() {
+ super("rule");
+ }
+
+ /**
+ * Instantiates a new rule builder using the existing rule configuration
+ *
+ * @param ruleConfig
+ * the rule configuration to clone
+ * @param indent
+ * the indent/prefix for the section
+ */
+ public RuleBuilder(RuleSection ruleConfig, String indent) {
+ this();
+ this.indent = indent;
+ if (ruleConfig.isGeneric()) {
+ appendLine(indent + "\t// Generic Rule");
+ }
+ appendValue("name", ruleConfig.getName());
+ appendValue("category", ruleConfig.getCategory());
+ appendValue("description", ruleConfig.getDescription());
+ appendValue("severity", ruleConfig.getSeverity());
+ if (ruleConfig.isGeneric()) {
+ appendLine(indent + "\t// Passing " + ruleConfig.getAttributes());
+ }
+ appendValue("attributes", ruleConfig.getExpressionFieldNames());
+ appendValue("validate", ruleConfig.getExpression());
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/UseRuleBuilder.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/UseRuleBuilder.java
new file mode 100644
index 0000000..fe72bac
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/UseRuleBuilder.java
@@ -0,0 +1,46 @@
+/*
+ * ============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.configuration.build;
+
+import org.onap.aai.validation.ruledriven.configuration.RuleSection;
+
+/**
+ * Content Builder for a useRule section
+ *
+ */
+public class UseRuleBuilder extends ContentBuilder {
+
+ /**
+ * Build a useRule section
+ */
+ public UseRuleBuilder() {
+ super("useRule");
+ }
+
+ /**
+ * @param ruleConfig
+ * @param indent
+ */
+ public UseRuleBuilder(RuleSection ruleConfig, String indent) {
+ this();
+ this.indent = indent;
+ appendValue("name", ruleConfig.getName());
+ appendValue("attributes", ruleConfig.getAttributes());
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ValidationBuilder.java b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ValidationBuilder.java
new file mode 100644
index 0000000..44f3c4c
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ValidationBuilder.java
@@ -0,0 +1,78 @@
+/*
+ * ============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.configuration.build;
+
+import org.onap.aai.validation.ruledriven.configuration.RuleSection;
+
+/**
+ * Builder for validation config section
+ *
+ */
+public class ValidationBuilder extends ContentBuilder {
+ /**
+ * Create an empty validation section
+ */
+ public ValidationBuilder() {
+ super("validation");
+ indent = "\t";
+ }
+
+ /**
+ * Create a validation section using the existing rule configuration
+ *
+ * @param ruleConfig
+ */
+ public ValidationBuilder(RuleSection ruleConfig) {
+ this();
+ appendValue("object", ruleConfig.getType());
+ appendValue("objectId", ruleConfig.getObjectId());
+ if (ruleConfig.isGeneric()) {
+ addContent(new UseRuleBuilder(ruleConfig, indent + "\t").toString());
+ } else {
+ addContent(new RuleBuilder(ruleConfig, indent + "\t").toString());
+ }
+ }
+
+ /**
+ * Add a useRule section
+ *
+ * @param name
+ * @return the new useRule section
+ */
+ public UseRuleBuilder useRule(String name) {
+ UseRuleBuilder item = new UseRuleBuilder();
+ item.indent = "\t\t";
+ item.appendValue("name", name);
+ addContent(item);
+ return item;
+ }
+
+ /**
+ * Add a rule section comprising the specified name item
+ *
+ * @param name
+ * @return
+ */
+ public RuleBuilder rule(String name) {
+ RuleBuilder item = new RuleBuilder();
+ item.indent = "\t\t";
+ item.appendValue("name", name);
+ addContent(item);
+ return item;
+ }
+} \ No newline at end of file
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;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/ruledriven/rule/Rule.java b/src/main/java/org/onap/aai/validation/ruledriven/rule/Rule.java
new file mode 100644
index 0000000..1196db0
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ruledriven/rule/Rule.java
@@ -0,0 +1,81 @@
+/*
+ * ============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 java.util.List;
+import org.onap.aai.validation.reader.data.AttributeValues;
+
+/**
+ * A rule that accepts one or more attributes and returns a Boolean result (when executed)
+ */
+public interface Rule {
+
+ /**
+ * Gets the name of the rule
+ *
+ * @return the name
+ */
+ String getName();
+
+ /**
+ * Gets the error message.
+ *
+ * @return the error message
+ */
+ String getErrorMessage();
+
+ /**
+ * Gets the error category.
+ *
+ * @return the error category
+ */
+ String getErrorCategory();
+
+ /**
+ * Gets the severity.
+ *
+ * @return the severity
+ */
+ String getSeverity();
+
+ /**
+ * Gets the paths to the attributes to pass to the rule
+ *
+ * @return the attribute paths
+ */
+ List<String> getAttributePaths();
+
+ /**
+ * Execute the rule.
+ *
+ * @param values
+ * the attribute values to pass to the rule
+ * @return a boolean representing the rule evaluation (meaning success/failure)
+ */
+ Boolean execute(AttributeValues values);
+
+ /**
+ * Execute the rule.
+ *
+ * @param values
+ * the attribute values to pass to the rule
+ * @return a boolean representing the rule evaluation (meaning success/failure)
+ */
+ Boolean execute(Object... values);
+
+}