summaryrefslogtreecommitdiffstats
path: root/src/main/java/org
diff options
context:
space:
mode:
authorPrudence Au <prudence.au@amdocs.com>2018-08-13 17:06:59 -0400
committerPierre Rioux <pierre.rioux@amdocs.com>2018-08-21 11:21:26 -0400
commitc604f64b971491f8c9b953adce54b847d7946e26 (patch)
tree134f7fc91b4da9e04c564c92337d44df420c8df0 /src/main/java/org
parent3baa3ebd0000b15f1c54c736f4a307731b16b923 (diff)
Initial submission for validation service
Change-Id: I9372430f1ae347373d5a9a0c7a427d7bd393d61e Issue-ID: LOG-427 Signed-off-by: Prudence Au (prudence.au@amdocs.com) Signed-off-by: Geora Barsky <georab@amdocs.com> Signed-off-by: Pierre Rioux <pierre.rioux@amdocs.com>
Diffstat (limited to 'src/main/java/org')
-rw-r--r--src/main/java/org/onap/aai/auth/AAIAuthException.java34
-rw-r--r--src/main/java/org/onap/aai/auth/AAIMicroServiceAuth.java103
-rw-r--r--src/main/java/org/onap/aai/auth/AAIMicroServiceAuthCore.java269
-rw-r--r--src/main/java/org/onap/aai/auth/FileWatcher.java58
-rw-r--r--src/main/java/org/onap/aai/validation/ValidationServiceApplication.java57
-rw-r--r--src/main/java/org/onap/aai/validation/Validator.java45
-rw-r--r--src/main/java/org/onap/aai/validation/config/EventReaderConfig.java163
-rw-r--r--src/main/java/org/onap/aai/validation/config/ModelConfig.java39
-rw-r--r--src/main/java/org/onap/aai/validation/config/PropertiesConfig.java41
-rw-r--r--src/main/java/org/onap/aai/validation/config/RestConfig.java225
-rw-r--r--src/main/java/org/onap/aai/validation/config/RuleIndexingConfig.java73
-rw-r--r--src/main/java/org/onap/aai/validation/config/TopicAdminConfig.java103
-rw-r--r--src/main/java/org/onap/aai/validation/config/TopicConfig.java243
-rw-r--r--src/main/java/org/onap/aai/validation/config/TopicPropertiesConfig.java75
-rw-r--r--src/main/java/org/onap/aai/validation/config/ValidationControllerConfig.java82
-rw-r--r--src/main/java/org/onap/aai/validation/config/ValidationServiceAuthConfig.java46
-rw-r--r--src/main/java/org/onap/aai/validation/controller/ValidationController.java380
-rw-r--r--src/main/java/org/onap/aai/validation/data/client/RestClient.java121
-rw-r--r--src/main/java/org/onap/aai/validation/exception/BaseValidationServiceException.java66
-rw-r--r--src/main/java/org/onap/aai/validation/exception/ValidationServiceError.java103
-rw-r--r--src/main/java/org/onap/aai/validation/exception/ValidationServiceException.java78
-rw-r--r--src/main/java/org/onap/aai/validation/factory/DMaaPEventPublisherFactory.java34
-rw-r--r--src/main/java/org/onap/aai/validation/logging/ApplicationMsgs.java74
-rw-r--r--src/main/java/org/onap/aai/validation/logging/LogHelper.java548
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/ModelCacheManager.java182
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/ModelId.java86
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/Filter.java75
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMapper.java89
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMappingReader.java69
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ValueConfiguration.java124
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/parser/XMLModelParser.java152
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/validator/InstanceReader.java316
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/validator/ModelDrivenValidator.java306
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/validator/ModelReader.java236
-rw-r--r--src/main/java/org/onap/aai/validation/modeldriven/validator/ViolationInfo.java88
-rw-r--r--src/main/java/org/onap/aai/validation/publisher/MessagePublisher.java47
-rw-r--r--src/main/java/org/onap/aai/validation/publisher/ValidationEventPublisher.java164
-rw-r--r--src/main/java/org/onap/aai/validation/reader/EntityReader.java61
-rw-r--r--src/main/java/org/onap/aai/validation/reader/EventEntityReader.java118
-rw-r--r--src/main/java/org/onap/aai/validation/reader/EventReader.java215
-rw-r--r--src/main/java/org/onap/aai/validation/reader/InstanceEntityReader.java75
-rw-r--r--src/main/java/org/onap/aai/validation/reader/JsonReader.java186
-rw-r--r--src/main/java/org/onap/aai/validation/reader/OxmConfigTranslator.java100
-rw-r--r--src/main/java/org/onap/aai/validation/reader/OxmReader.java99
-rw-r--r--src/main/java/org/onap/aai/validation/reader/data/AttributeValues.java139
-rw-r--r--src/main/java/org/onap/aai/validation/reader/data/Entity.java135
-rw-r--r--src/main/java/org/onap/aai/validation/reader/data/EntityId.java91
-rw-r--r--src/main/java/org/onap/aai/validation/result/ValidationResult.java244
-rw-r--r--src/main/java/org/onap/aai/validation/result/Violation.java418
-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
-rw-r--r--src/main/java/org/onap/aai/validation/services/EventPollingService.java121
-rw-r--r--src/main/java/org/onap/aai/validation/services/InfoService.java57
-rw-r--r--src/main/java/org/onap/aai/validation/services/RequestHeaders.java53
-rw-r--r--src/main/java/org/onap/aai/validation/services/ValidateService.java77
-rw-r--r--src/main/java/org/onap/aai/validation/services/ValidateServiceImpl.java160
-rw-r--r--src/main/java/org/onap/aai/validation/servlet/StartupServlet.java95
-rw-r--r--src/main/java/org/onap/aai/validation/util/GsonUtil.java124
-rw-r--r--src/main/java/org/onap/aai/validation/util/JsonUtil.java94
-rw-r--r--src/main/java/org/onap/aai/validation/util/StringUtils.java100
72 files changed, 9741 insertions, 0 deletions
diff --git a/src/main/java/org/onap/aai/auth/AAIAuthException.java b/src/main/java/org/onap/aai/auth/AAIAuthException.java
new file mode 100644
index 0000000..5bbfc11
--- /dev/null
+++ b/src/main/java/org/onap/aai/auth/AAIAuthException.java
@@ -0,0 +1,34 @@
+/*
+ * ============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.auth;
+
+public class AAIAuthException extends Exception {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public AAIAuthException(String string) {
+ super(string);
+ }
+
+ public AAIAuthException(String string, Exception e) {
+ super(string, e);
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/auth/AAIMicroServiceAuth.java b/src/main/java/org/onap/aai/auth/AAIMicroServiceAuth.java
new file mode 100644
index 0000000..fc40e0b
--- /dev/null
+++ b/src/main/java/org/onap/aai/auth/AAIMicroServiceAuth.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.auth;
+
+import java.security.cert.X509Certificate;
+import javax.inject.Inject;
+import javax.security.auth.x500.X500Principal;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.Cookie;
+import org.onap.aai.validation.config.ValidationServiceAuthConfig;
+import org.onap.aai.validation.logging.LogHelper;
+
+public class AAIMicroServiceAuth {
+
+ private static LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ private ValidationServiceAuthConfig validationServiceAuthConfig;
+
+ @Inject
+ public AAIMicroServiceAuth(final ValidationServiceAuthConfig validationServiceAuthConfig) throws AAIAuthException {
+ this.validationServiceAuthConfig = validationServiceAuthConfig;
+ if (!validationServiceAuthConfig.isAuthenticationDisable()) {
+ AAIMicroServiceAuthCore.init(validationServiceAuthConfig.getAuthPolicyFile());
+ }
+ }
+
+ public boolean authBasic(String username, String authFunction) throws AAIAuthException {
+ return AAIMicroServiceAuthCore.authorize(username, authFunction);
+ }
+
+ public String authUser(String authUser, String authFunction) throws AAIAuthException {
+ StringBuilder username = new StringBuilder();
+
+ username.append(authUser);
+ if (!authBasic(username.toString(), authFunction)) {
+ return "AAI_9101";
+
+ }
+ return "OK";
+ }
+
+ public boolean authCookie(Cookie cookie, String authFunction, StringBuilder username) throws AAIAuthException {
+ if (cookie == null) {
+ return false;
+ }
+ applicationLogger.debug("Got one:" + cookie);
+
+ return AAIMicroServiceAuthCore.authorize(username.toString(), authFunction);
+ }
+
+ public boolean validateRequest(HttpServletRequest req, String action, String apiPath) throws AAIAuthException {
+
+ applicationLogger.debug("validateRequest: " + apiPath);
+ applicationLogger.debug("validationServiceConfig.isAuthenticationDisable(): "
+ + validationServiceAuthConfig.isAuthenticationDisable());
+
+ if (validationServiceAuthConfig.isAuthenticationDisable()) {
+ return true;
+ }
+ String[] ps = apiPath.split("/");
+ String authPolicyFunctionName = ps[0];
+ if (ps.length > 1) {
+ if (ps[0].matches("v\\d+")) {
+ authPolicyFunctionName = ps[1];
+ } else {
+ authPolicyFunctionName = ps[0];
+ }
+ }
+
+ String cipherSuite = (String) req.getAttribute("javax.servlet.request.cipher_suite");
+ String authUser = null;
+ if (cipherSuite != null) {
+ X509Certificate[] certChain = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+ if (certChain != null) {
+ X509Certificate clientCert = certChain[0];
+ X500Principal subjectDN = clientCert.getSubjectX500Principal();
+ authUser = subjectDN.toString();
+ }
+ }
+
+ if (authUser == null) {
+ return false;
+ }
+
+ String status = authUser(authUser.toLowerCase(), action + ":" + authPolicyFunctionName);
+ return "OK".equals(status);
+ }
+}
diff --git a/src/main/java/org/onap/aai/auth/AAIMicroServiceAuthCore.java b/src/main/java/org/onap/aai/auth/AAIMicroServiceAuthCore.java
new file mode 100644
index 0000000..25273fc
--- /dev/null
+++ b/src/main/java/org/onap/aai/auth/AAIMicroServiceAuthCore.java
@@ -0,0 +1,269 @@
+/*
+ * ============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.auth;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.TimeUnit;
+import org.onap.aai.validation.logging.ApplicationMsgs;
+import org.onap.aai.validation.logging.LogHelper;
+
+/**
+ * Authentication and authorization by user and role.
+ */
+public class AAIMicroServiceAuthCore {
+
+ private static LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ public static final String APPCONFIG_DIR = (System.getProperty("CONFIG_HOME") == null)
+ ? Paths.get(System.getProperty("APP_HOME"), "appconfig").toString() : System.getProperty("CONFIG_HOME");
+
+ private static Path appConfigAuthDir = Paths.get(APPCONFIG_DIR, "auth");
+ private static Path defaultAuthFileName = appConfigAuthDir.resolve("auth_policy.json");
+
+ private static boolean usersInitialized = false;
+ private static HashMap<String, AAIAuthUser> users;
+ private static boolean timerSet = false;
+ private static String policyAuthFileName;
+
+ public enum HTTP_METHODS {
+ GET,
+ PUT,
+ DELETE,
+ HEAD,
+ POST
+ }
+
+ // Don't instantiate
+ private AAIMicroServiceAuthCore() {}
+
+ public static String getDefaultAuthFileName() {
+ return defaultAuthFileName.toString();
+ }
+
+ public static void setDefaultAuthFileName(String defaultAuthFileName) {
+ AAIMicroServiceAuthCore.defaultAuthFileName = Paths.get(defaultAuthFileName);
+ }
+
+ public static synchronized void init(String authPolicyFile) throws AAIAuthException {
+
+ try {
+ policyAuthFileName = AAIMicroServiceAuthCore.getConfigFile(authPolicyFile);
+ } catch (IOException e) {
+ applicationLogger.debug("Exception while retrieving policy file.");
+ applicationLogger.error(ApplicationMsgs.PROCESS_REQUEST_ERROR, e);
+ throw new AAIAuthException(e.getMessage());
+ }
+ if (policyAuthFileName == null) {
+ throw new AAIAuthException("Auth policy file could not be found");
+ }
+ AAIMicroServiceAuthCore.reloadUsers();
+
+ TimerTask task = new FileWatcher(new File(policyAuthFileName)) {
+ @Override
+ protected void onChange(File file) {
+ // here we implement the onChange
+ applicationLogger.debug("File " + file.getName() + " has been changed!");
+ try {
+ AAIMicroServiceAuthCore.reloadUsers();
+ } catch (AAIAuthException e) {
+ applicationLogger.error(ApplicationMsgs.PROCESS_REQUEST_ERROR, e);
+ }
+ applicationLogger.debug("File " + file.getName() + " has been reloaded!");
+ }
+ };
+
+ if (!timerSet) {
+ timerSet = true;
+ Timer timer = new Timer();
+ long period = TimeUnit.SECONDS.toMillis(1);
+ timer.schedule(task, new Date(), period);
+ applicationLogger.debug("Config Watcher Interval = " + period);
+ }
+ }
+
+ public static String getConfigFile(String authPolicyFile) throws IOException {
+ File authFile = new File(authPolicyFile);
+ if (authFile.exists()) {
+ return authFile.getCanonicalPath();
+ }
+ authFile = appConfigAuthDir.resolve(authPolicyFile).toFile();
+ if (authFile.exists()) {
+ return authFile.getCanonicalPath();
+ }
+ if (getDefaultAuthFileName() != null) {
+ authFile = new File(getDefaultAuthFileName());
+ if (authFile.exists()) {
+ return getDefaultAuthFileName();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @throws AAIAuthException
+ */
+ public static synchronized void reloadUsers() throws AAIAuthException {
+ users = new HashMap<>();
+ ObjectMapper mapper = new ObjectMapper();
+
+ try {
+ applicationLogger.debug("Reading from " + policyAuthFileName);
+ JsonNode rootNode = mapper.readTree(new File(policyAuthFileName));
+ JsonNode rolesNode = rootNode.path("roles");
+
+ for (JsonNode roleNode : rolesNode) {
+ String roleName = roleNode.path("name").asText();
+ AAIAuthRole r = new AAIAuthRole();
+ JsonNode usersNode = roleNode.path("users");
+ JsonNode functionsNode = roleNode.path("functions");
+ for (JsonNode functionNode : functionsNode) {
+ addFunctionToRole(r, roleName, functionNode);
+ }
+ for (JsonNode userNode : usersNode) {
+ String name = userNode.path("username").asText().toLowerCase();
+ AAIAuthUser user;
+ if (users.containsKey(name)) {
+ user = users.get(name);
+ } else {
+ user = new AAIAuthUser();
+ }
+
+ applicationLogger.debug("Assigning " + roleName + " to user " + name);
+ user.addRole(roleName, r);
+ users.put(name, user);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ throw new AAIAuthException("Auth policy file could not be found", e);
+ } catch (JsonProcessingException e) {
+ throw new AAIAuthException("Error processing Auth policy file ", e);
+ } catch (IOException e) {
+ throw new AAIAuthException("Error reading Auth policy file", e);
+ }
+
+ usersInitialized = true;
+ }
+
+ /**
+ * @param role
+ * @param roleName
+ * @param functionNode
+ */
+ private static void addFunctionToRole(AAIAuthRole role, String roleName, JsonNode functionNode) {
+ String functionName = functionNode.path("name").asText();
+ JsonNode methodsNode = functionNode.path("methods");
+
+ if (methodsNode.size() == 0) {
+ for (HTTP_METHODS method : HTTP_METHODS.values()) {
+ String fullFunctionName = method.toString() + ":" + functionName;
+ applicationLogger.debug("Installing (all methods) " + fullFunctionName + " on role " + roleName);
+ role.addAllowedFunction(fullFunctionName);
+ }
+ } else {
+ for (JsonNode methodNode : methodsNode) {
+ String methodName = methodNode.path("name").asText();
+ String fullFunctionName = methodName + ":" + functionName;
+ applicationLogger.debug("Installing function " + fullFunctionName + " on role " + roleName);
+ role.addAllowedFunction(fullFunctionName);
+ }
+ }
+ }
+
+ public static class AAIAuthUser {
+ private HashMap<String, AAIAuthRole> roles;
+
+ public AAIAuthUser() {
+ this.roles = new HashMap<>();
+ }
+
+ public void addRole(String roleName, AAIAuthRole r) {
+ this.roles.put(roleName, r);
+ }
+
+ public boolean checkAllowed(String checkFunc) {
+ for (Entry<String, AAIAuthRole> role_entry : roles.entrySet()) {
+ AAIAuthRole role = role_entry.getValue();
+ if (role.hasAllowedFunction(checkFunc)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+
+ public static class AAIAuthRole {
+ private List<String> allowedFunctions;
+
+ public AAIAuthRole() {
+ this.allowedFunctions = new ArrayList<>();
+ }
+
+ public void addAllowedFunction(String func) {
+ this.allowedFunctions.add(func);
+ }
+
+ public void delAllowedFunction(String delFunc) {
+ if (this.allowedFunctions.contains(delFunc)) {
+ this.allowedFunctions.remove(delFunc);
+ }
+ }
+
+ public boolean hasAllowedFunction(String functionName) {
+ return allowedFunctions.contains(functionName) ? true : false;
+ }
+ }
+
+ public static boolean authorize(String username, String authFunction) throws AAIAuthException {
+ if (!usersInitialized || users == null) {
+ throw new AAIAuthException("Auth module not initialized");
+ }
+
+ if (users.containsKey(username)) {
+ if (users.get(username).checkAllowed(authFunction)) {
+ logAuthenticationResult(username, authFunction, "AUTH ACCEPTED");
+ return true;
+ } else {
+ logAuthenticationResult(username, authFunction, "AUTH FAILED");
+ return false;
+ }
+ } else {
+ logAuthenticationResult(username, authFunction, "User not found");
+ return false;
+ }
+ }
+
+ private static void logAuthenticationResult(String username, String authFunction, String result) {
+ applicationLogger.debug(result + ": " + username + " on function " + authFunction);
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/auth/FileWatcher.java b/src/main/java/org/onap/aai/auth/FileWatcher.java
new file mode 100644
index 0000000..edc8bfe
--- /dev/null
+++ b/src/main/java/org/onap/aai/auth/FileWatcher.java
@@ -0,0 +1,58 @@
+/*
+ * ============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.auth;
+
+import java.io.File;
+import java.util.TimerTask;
+
+public abstract class FileWatcher extends TimerTask {
+ private long timeStamp;
+ private File file;
+
+ /**
+ * Instantiates a new file watcher.
+ *
+ * @param file the file
+ */
+ public FileWatcher(File file) {
+ this.file = file;
+ this.timeStamp = file.lastModified();
+ }
+
+ /**
+ * runs a timer task
+ *
+ * @see java.util.TimerTask.run
+ */
+ @Override
+ public final void run() {
+ long newTimeStamp = file.lastModified();
+
+ if ((newTimeStamp - this.timeStamp) > 500) {
+ this.timeStamp = newTimeStamp;
+ onChange(file);
+ }
+ }
+
+ /**
+ * On change.
+ *
+ * @param file the file
+ */
+ protected abstract void onChange(File file);
+}
diff --git a/src/main/java/org/onap/aai/validation/ValidationServiceApplication.java b/src/main/java/org/onap/aai/validation/ValidationServiceApplication.java
new file mode 100644
index 0000000..8bf7a44
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/ValidationServiceApplication.java
@@ -0,0 +1,57 @@
+/*
+ * ============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;
+
+import org.eclipse.jetty.util.security.Password;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ImportResource;
+
+import java.util.HashMap;
+
+
+/**
+ * Validation Service Spring Boot Application.
+ */
+
+@SpringBootApplication
+@ComponentScan(basePackages = "org.onap.aai.validation")
+@ImportResource("classpath:validation-service-beans.xml")
+public class ValidationServiceApplication extends SpringBootServletInitializer {
+
+ // public static void main(String[] args) {
+ // SpringApplication.run(ValidationServiceApplication.class, args);
+ // }
+
+
+ public static void main(String[] args) {
+ HashMap<String, Object> props = new HashMap<>();
+ String keyStorePassword = System.getProperty("KEY_STORE_PASSWORD");
+ if (keyStorePassword != null && !keyStorePassword.isEmpty()) {
+ props.put("server.ssl.key-store-password", Password.deobfuscate(keyStorePassword));
+ }
+ new ValidationServiceApplication()
+ .configure(new SpringApplicationBuilder(ValidationServiceApplication.class).properties(props))
+ .run(args);
+ }
+
+
+}
diff --git a/src/main/java/org/onap/aai/validation/Validator.java b/src/main/java/org/onap/aai/validation/Validator.java
new file mode 100644
index 0000000..c3ecc2a
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/Validator.java
@@ -0,0 +1,45 @@
+/*
+ * ============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;
+
+import java.util.List;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.result.ValidationResult;
+
+/**
+ * Validator (e.g. model-driven or rule-based)
+ *
+ */
+public interface Validator {
+
+ /**
+ * This method should be called (once) before validate() to ensure that all configuration is correctly loaded.
+ *
+ * @throws ValidationServiceException
+ */
+ public void initialise() throws ValidationServiceException;
+
+ /**
+ * Validate the entity or entities found in the event.
+ *
+ * @param event JSON containing the entity or entities to validate
+ * @return a list of validation results
+ * @throws ValidationServiceException
+ */
+ public List<ValidationResult> validate(String event) throws ValidationServiceException;
+}
diff --git a/src/main/java/org/onap/aai/validation/config/EventReaderConfig.java b/src/main/java/org/onap/aai/validation/config/EventReaderConfig.java
new file mode 100644
index 0000000..c819351
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/EventReaderConfig.java
@@ -0,0 +1,163 @@
+/*
+ * ============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.config;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Loaded via Spring from src/main/resources/event-reader.properties.
+ */
+public class EventReaderConfig extends PropertiesConfig {
+
+ @Value("${event.domain.path}")
+ private String eventDomainPath;
+
+ @Value("${event.action.path}")
+ private String eventActionPath;
+
+ @Value("${event.type.path}")
+ private String eventTypePath;
+
+ @Value("${event.entity.type.path}")
+ private String entityTypePath;
+
+ @Value("${event.entity.type.top.path}")
+ private String topEntityTypePath;
+
+ @Value("${event.entity.link.path}")
+ private String entityLinkPath;
+
+ @Value("${event.entity.link.delimiter}")
+ private String entityLinkDelimiter;
+
+ @Value("${event.entity.path}")
+ private String entityPath;
+
+ @Value("${event.entity.nested.path}")
+ private String nestedEntityPath;
+
+ /** Entity relative path. Use when the entity has been extracted from the event. */
+ @Value("${entity.id.path}")
+ private String entityIdPath;
+
+ /** Entity relative path. Use when the entity has been extracted from the event. */
+ @Value("${entity.resource.version.path}")
+ private String entityResourceVersionPath;
+
+ public String getEventDomainPath() {
+ return eventDomainPath;
+ }
+
+ public void setEventDomainPath(String eventDomainPath) {
+ this.eventDomainPath = eventDomainPath;
+ }
+
+ public String getEventActionPath() {
+ return eventActionPath;
+ }
+
+ public void setEventActionPath(String eventActionPath) {
+ this.eventActionPath = eventActionPath;
+ }
+
+ public String getEventTypePath() {
+ return eventTypePath;
+ }
+
+ public void setEventTypePath(String eventTypePath) {
+ this.eventTypePath = eventTypePath;
+ }
+
+ public String getTopEntityTypePath() {
+ return topEntityTypePath;
+ }
+
+ public void setTopEntityTypePath(String topEntityTypePath) {
+ this.topEntityTypePath = topEntityTypePath;
+ }
+
+ public String getEntityLinkPath() {
+ return entityLinkPath;
+ }
+
+ public void setEntityLinkPath(String entityLinkPath) {
+ this.entityLinkPath = entityLinkPath;
+ }
+
+ public String getEntityLinkDelimiter() {
+ return entityLinkDelimiter;
+ }
+
+ public void setEntityLinkDelimiter(String entityLinkDelimiter) {
+ this.entityLinkDelimiter = entityLinkDelimiter;
+ }
+
+ public String getEntityTypePath() {
+ return entityTypePath;
+ }
+
+ public void setEntityTypePath(String entityTypePath) {
+ this.entityTypePath = entityTypePath;
+ }
+
+ public String getEntityPath() {
+ return entityPath;
+ }
+
+ public void setEntityPath(String entityPath) {
+ this.entityPath = entityPath;
+ }
+
+ /**
+ * Formats the nested entity path using the entity type provided.
+ *
+ * @param entityType
+ * an entity type
+ * @return the formatted nested entity path
+ */
+ public String getNestedEntityPath(String entityType) {
+ return formatter(nestedEntityPath, entityType);
+ }
+
+ public void setNestedEntityPath(String nestedEntityPath) {
+ this.nestedEntityPath = nestedEntityPath;
+ }
+
+ /**
+ * Formats the entity ID path using the entity type provided.
+ *
+ * @param entityType
+ * an entity type
+ * @return the formatted entity ID path
+ */
+ public String getEntityIdPath(String entityType) {
+ return formatter(entityIdPath, entityType);
+ }
+
+ public void setEntityIdPath(String entityIdPath) {
+ this.entityIdPath = entityIdPath;
+ }
+
+ public String getEntityResourceVersionPath() {
+ return entityResourceVersionPath;
+ }
+
+ public void setEntityResourceVersionPath(String entityResourceVersionPath) {
+ this.entityResourceVersionPath = entityResourceVersionPath;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/config/ModelConfig.java b/src/main/java/org/onap/aai/validation/config/ModelConfig.java
new file mode 100644
index 0000000..02e14bc
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/ModelConfig.java
@@ -0,0 +1,39 @@
+/*
+ * ============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.config;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Model Configuration Bean
+ *
+ */
+public class ModelConfig {
+
+ @Value("${model.cache.expirySeconds}")
+ private Long modelCacheExpirySeconds;
+
+ public Long getModelCacheExpirySeconds() {
+ return modelCacheExpirySeconds;
+ }
+
+ public void setModelCacheExpirySeconds(Long modelCacheExpirySeconds) {
+ this.modelCacheExpirySeconds = modelCacheExpirySeconds;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/config/PropertiesConfig.java b/src/main/java/org/onap/aai/validation/config/PropertiesConfig.java
new file mode 100644
index 0000000..cf4bcb2
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/PropertiesConfig.java
@@ -0,0 +1,41 @@
+/*
+ * ============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.config;
+
+import java.text.MessageFormat;
+
+/**
+ * Base properties configuration class.
+ */
+public class PropertiesConfig {
+
+ /**
+ * Replaces place-holders in property values.
+ *
+ * @param s
+ * a string with place-holders in the form {n}
+ * @param args
+ * values for place-holders
+ * @return a formated String with replaced place-holders.
+ */
+ public String formatter(String s, Object... args) {
+ MessageFormat formatter = new MessageFormat("");
+ formatter.applyPattern(s);
+ return formatter.format(args);
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/config/RestConfig.java b/src/main/java/org/onap/aai/validation/config/RestConfig.java
new file mode 100644
index 0000000..e99d6c2
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/RestConfig.java
@@ -0,0 +1,225 @@
+/*
+ * ============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.config;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Configuration required to establish REST client requests with an application.
+ */
+public class RestConfig {
+
+ @Value("${host}")
+ private String host;
+
+ @Value("${port}")
+ private Integer port;
+
+ @Value("${httpProtocol}")
+ private String protocol;
+
+ @Value("${baseModelURI}")
+ private String baseModelURI;
+
+ @Value("${trustStorePath}")
+ private String trustStorePath;
+
+ @Value("${trustStorePassword.x}")
+ private String trustStorePassword;
+
+ @Value("${keyStorePath}")
+ private String keyStorePath;
+
+ @Value("${keyStorePassword.x}")
+ private String keyStorePassword;
+
+ @Value("${keyManagerFactoryAlgorithm}")
+ private String keyManagerFactoryAlgorithm;
+
+ @Value("${keyStoreType}")
+ private String keyStoreType;
+
+ @Value("${securityProtocol}")
+ private String securityProtocol;
+
+ @Value("${connectionTimeout}")
+ private Integer connectionTimeout;
+
+ @Value("${readTimeout}")
+ private Integer readTimeout;
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(Integer port) {
+ this.port = port;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getBaseModelURI() {
+ return baseModelURI;
+ }
+
+ public void setBaseModelURI(String baseModelURI) {
+ this.baseModelURI = baseModelURI;
+ }
+
+ public String getTrustStorePath() {
+ return trustStorePath;
+ }
+
+ public void setTrustStorePath(String trustStorePath) {
+ this.trustStorePath = trustStorePath;
+ }
+
+ /**
+ * Assumes the password is encrypted.
+ *
+ * @return the decrypted password
+ */
+ public String getTrustStorePassword() {
+ return trustStorePassword;
+ }
+
+ public void setTrustStorePassword(String trustStorePassword) {
+ this.trustStorePassword = trustStorePassword;
+ }
+
+ public String getKeyStorePath() {
+ return keyStorePath;
+ }
+
+ public void setKeyStorePath(String keyStorePath) {
+ this.keyStorePath = keyStorePath;
+ }
+
+ /**
+ * Assumes the password is encrypted.
+ *
+ * @return the decrypted password
+ */
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public void setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ }
+
+ public String getKeyManagerFactoryAlgorithm() {
+ return keyManagerFactoryAlgorithm;
+ }
+
+ public void setKeyManagerFactoryAlgorithm(String keyManagerFactoryAlgorithm) {
+ this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
+ }
+
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ public void setKeyStoreType(String keyStoreType) {
+ this.keyStoreType = keyStoreType;
+ }
+
+ public String getSecurityProtocol() {
+ return securityProtocol;
+ }
+
+ public void setSecurityProtocol(String securityProtocol) {
+ this.securityProtocol = securityProtocol;
+ }
+
+ public Integer getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ public void setConnectionTimeout(Integer connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ public Integer getReadTimeout() {
+ return readTimeout;
+ }
+
+ public void setReadTimeout(Integer readTimeout) {
+ this.readTimeout = readTimeout;
+ }
+
+ @Override
+ public String toString() {
+ return "RestConfig [host=" + host + ", port=" + port + ", protocol=" + protocol + ", baseModelURI="
+ + baseModelURI + ", trustStorePath=" + trustStorePath + ", trustStorePassword=" + trustStorePassword
+ + ", keyStorePath=" + keyStorePath + ", keyStorePassword=" + keyStorePassword
+ + ", keyManagerFactoryAlgorithm=" + keyManagerFactoryAlgorithm + ", keyStoreType=" + keyStoreType
+ + ", securityProtocol=" + securityProtocol + ", connectionTimeout=" + connectionTimeout
+ + ", readTimeout=" + readTimeout + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.baseModelURI, this.connectionTimeout, this.host, this.keyManagerFactoryAlgorithm,
+ this.keyStorePassword, this.keyStorePath, this.keyStoreType, this.port, this.protocol, this.readTimeout,
+ this.securityProtocol, this.trustStorePassword, this.trustStorePath);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RestConfig)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ RestConfig rhs = (RestConfig) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(baseModelURI, rhs.baseModelURI)
+ .append(connectionTimeout, rhs.connectionTimeout)
+ .append(host, rhs.host)
+ .append(keyManagerFactoryAlgorithm, rhs.keyManagerFactoryAlgorithm)
+ .append(keyStorePassword, rhs.keyStorePassword)
+ .append(keyStorePath, rhs.keyStorePath)
+ .append(keyStoreType, rhs.keyStoreType)
+ .append(port, rhs.port)
+ .append(protocol, rhs.protocol)
+ .append(readTimeout, rhs.readTimeout)
+ .append(securityProtocol, rhs.securityProtocol)
+ .append(trustStorePassword, rhs.trustStorePassword)
+ .append(trustStorePath, rhs.trustStorePath)
+ .isEquals();
+ // @formatter:on
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/config/RuleIndexingConfig.java b/src/main/java/org/onap/aai/validation/config/RuleIndexingConfig.java
new file mode 100644
index 0000000..1d27705
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/RuleIndexingConfig.java
@@ -0,0 +1,73 @@
+/*
+ * ============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.config;
+
+import java.util.List;
+
+/**
+ * Loads the properties needed by the controller using spring.
+ */
+public class RuleIndexingConfig extends PropertiesConfig {
+
+ private List<String> indexedEvents;
+
+ private List<String> excludedOxmValidationEvents;
+
+ private List<String> indexAttributes;
+
+ private String defaultIndexKey;
+
+ public List<String> getIndexedEvents() {
+ return indexedEvents;
+ }
+
+ public void setIndexedEvents(List<String> indexedEvents) {
+ this.indexedEvents = indexedEvents;
+ }
+
+ public List<String> getExcludedOxmValidationEvents() {
+ return excludedOxmValidationEvents;
+ }
+
+ public void setExcludedOxmValidationEvents(List<String> excludedOxmValidationEvents) {
+ this.excludedOxmValidationEvents = excludedOxmValidationEvents;
+ }
+
+ public List<String> getIndexAttributes() {
+ return indexAttributes;
+ }
+
+ public void setIndexAttributes(List<String> indexAttributes) {
+ this.indexAttributes = indexAttributes;
+ }
+
+ public String getDefaultIndexKey() {
+ return defaultIndexKey;
+ }
+
+ public void setDefaultIndexKey(String defaultIndexKey) {
+ this.defaultIndexKey = defaultIndexKey;
+ }
+
+ public boolean skipOxmValidation(String event) {
+ if(excludedOxmValidationEvents == null) {
+ return false;
+ }
+ return excludedOxmValidationEvents.contains(event);
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/config/TopicAdminConfig.java b/src/main/java/org/onap/aai/validation/config/TopicAdminConfig.java
new file mode 100644
index 0000000..22fbe97
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/TopicAdminConfig.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.config;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Configuration bean with topic administration properties that are loaded via Spring configuration.
+ */
+public class TopicAdminConfig {
+
+ @Value("${topic.publish.enable}")
+ private boolean publishEnable;
+
+ @Value("${topic.publish.retries}")
+ private Long publishRetries;
+
+ @Value("${topic.consume.enable}")
+ private boolean consumeEnable;
+
+ @Value("${topic.consume.polling.interval.seconds}")
+ private Long consumePollingIntervalSeconds;
+
+ public boolean isPublishEnable() {
+ return publishEnable;
+ }
+
+ public void setPublishEnable(boolean publishEnable) {
+ this.publishEnable = publishEnable;
+ }
+
+ public Long getPublishRetries() {
+ return publishRetries;
+ }
+
+ public void setPublishRetries(Long publishRetries) {
+ this.publishRetries = publishRetries;
+ }
+
+ public boolean isConsumeEnable() {
+ return consumeEnable;
+ }
+
+ public void setConsumeEnable(boolean consumeEnable) {
+ this.consumeEnable = consumeEnable;
+ }
+
+ public Long getConsumePollingIntervalSeconds() {
+ return consumePollingIntervalSeconds;
+ }
+
+ public void setConsumePollingIntervalSeconds(Long consumePollingIntervalSeconds) {
+ this.consumePollingIntervalSeconds = consumePollingIntervalSeconds;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.consumeEnable, this.consumePollingIntervalSeconds, this.publishEnable,
+ this.publishRetries);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TopicAdminConfig)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ TopicAdminConfig rhs = (TopicAdminConfig) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(consumeEnable, rhs.consumeEnable)
+ .append(consumePollingIntervalSeconds, rhs.consumePollingIntervalSeconds)
+ .append(publishEnable, rhs.publishEnable)
+ .append(publishRetries, rhs.publishRetries)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return "TopicAdminConfig [publishEnable=" + publishEnable + ", publishRetries=" + publishRetries
+ + ", consumeEnable=" + consumeEnable + ", consumePollingIntervalSeconds="
+ + consumePollingIntervalSeconds + "]";
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/config/TopicConfig.java b/src/main/java/org/onap/aai/validation/config/TopicConfig.java
new file mode 100644
index 0000000..99742ac
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/TopicConfig.java
@@ -0,0 +1,243 @@
+/*
+ * ============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.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Properties;
+import javax.annotation.Resource;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * Gets the configuration of the topics. The topics are configured using Spring in topic-config-beans.xml.
+ */
+
+@Component("TopicConfig")
+public class TopicConfig {
+
+ private List<String> consumerTopicNames;
+
+ private List<String> publisherTopicNames;
+
+ @Resource(name = "topicProperties")
+ private Properties topicProperties;
+
+ List<Topic> consumerTopics = new ArrayList<>();
+ List<Topic> publisherTopics = new ArrayList<>();
+
+ @Autowired
+ public TopicConfig (@Value("${consumer.topic.names}") final String consumerNames, @Value("${publisher.topic.names}") final String publisherNames){
+
+ consumerTopicNames = Arrays.asList(consumerNames.split(","));;
+ publisherTopicNames = Arrays.asList(publisherNames.split(","));;
+
+
+ }
+ /**
+ * Gets the configuration of topics for consumption.
+ *
+ * @return a list of topic configurations.
+ */
+ public List<Topic> getConsumerTopics()
+ {
+ return populateTopics(consumerTopics, consumerTopicNames);
+ }
+
+
+ /**
+ * Gets the configuration of topics for publishing.
+ *
+ * @return a list of topic configurations.
+ */
+ public List<Topic> getPublisherTopics() {
+ return populateTopics(publisherTopics, publisherTopicNames);
+ }
+
+ /**
+ * Populates the topics list with topic objects created from each item in the topicNames list.
+ *
+ * @param topics
+ * The topic list to populate.
+ * @param topicNames
+ * The list of topic names to populate the topic list with.
+ * @return The populated topic list.
+ */
+ private List<Topic> populateTopics(List<Topic> topics, List<String> topicNames) {
+ if (topics.isEmpty()) {
+ for (String topicName : topicNames) {
+ Topic topicConfig = new Topic();
+ topicConfig.setName(getTopicProperties().getProperty(topicName + ".name"));
+ topicConfig.setHost(getTopicProperties().getProperty(topicName + ".host"));
+ topicConfig.setUsername(getTopicProperties().getProperty(topicName + ".username"));
+ topicConfig.setPassword(getTopicProperties().getProperty(topicName + ".password"));
+ topicConfig.setPartition(getTopicProperties().getProperty(topicName + ".publisher.partition"));
+ topicConfig.setConsumerGroup(getTopicProperties().getProperty(topicName + ".consumer.group"));
+ topicConfig.setConsumerId(getTopicProperties().getProperty(topicName + ".consumer.id"));
+ topicConfig.setTransportType(getTopicProperties().getProperty(topicName + ".transport.type"));
+ topics.add(topicConfig);
+ }
+ }
+ return topics;
+ }
+
+ public List<String> getConsumerTopicNames() {
+ return consumerTopicNames;
+ }
+
+ public void setConsumerTopicNames(List<String> consumerTopicNames) {
+ this.consumerTopicNames = consumerTopicNames;
+ }
+
+ public List<String> getPublisherTopicNames() {
+ return publisherTopicNames;
+ }
+
+ public void setPublisherTopicNames(List<String> publisherTopicNames) {
+ this.publisherTopicNames = publisherTopicNames;
+ }
+
+ public Properties getTopicProperties() {
+ return topicProperties;
+ }
+
+ public void setTopicProperties(Properties topicProperties) {
+ this.topicProperties = topicProperties;
+ }
+
+ /**
+ * Defines the properties of a single topic for consumption.
+ */
+ public class Topic {
+ private String name;
+ private String host;
+ private String username;
+ private String password;
+ private String partition;
+ private String consumerGroup;
+ private String consumerId;
+ private String transportType;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPartition() {
+ return partition;
+ }
+
+ public void setPartition(String partition) {
+ this.partition = partition;
+ }
+
+ public String getConsumerGroup() {
+ return consumerGroup;
+ }
+
+ public void setConsumerGroup(String consumerGroup) {
+ this.consumerGroup = consumerGroup;
+ }
+
+ public String getConsumerId() {
+ return consumerId;
+ }
+
+ public void setConsumerId(String consumerId) {
+ this.consumerId = consumerId;
+ }
+
+ public List<String> getHosts() {
+ return Arrays.asList(host.split(","));
+ }
+
+ public String getTransportType() {
+ return transportType;
+ }
+
+ public void setTransportType(String transportType) {
+ this.transportType = transportType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.consumerGroup, this.consumerId, this.host, this.username, this.name, this.partition,
+ this.password, this.transportType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Topic)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ Topic rhs = (Topic) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(consumerGroup, rhs.consumerGroup)
+ .append(consumerId, rhs.consumerId)
+ .append(host, rhs.host)
+ .append(username, rhs.username)
+ .append(name, rhs.name)
+ .append(partition, rhs.partition)
+ .append(password, rhs.password)
+ .append(transportType, rhs.transportType)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return "Topic [name=" + name + ", host=" + host + ", username=" + username + ", password=" + password + ", partition="
+ + partition + ", consumerGroup=" + consumerGroup + ", consumerId=" + consumerId
+ + ", transportType =" + transportType + "]";
+ }
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/config/TopicPropertiesConfig.java b/src/main/java/org/onap/aai/validation/config/TopicPropertiesConfig.java
new file mode 100644
index 0000000..4742cef
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/TopicPropertiesConfig.java
@@ -0,0 +1,75 @@
+/*
+ * ============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.config;
+
+import org.onap.aai.validation.logging.LogHelper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.PropertiesFactoryBean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+@Configuration
+public class TopicPropertiesConfig {
+
+ private static LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ @Value("${topics.properties.location}")
+ private String topicsPropertiesLocation;
+
+ private static final String[] TOPICS_PROPERTIES_LOCATION_TPL = { "file:./%s/*.properties", "classpath:/%s/*.properties" };
+
+ @Bean(name="topicProperties")
+ public Properties topicProperties() throws IOException {
+ PropertiesFactoryBean config = new PropertiesFactoryBean();
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ List<Resource> resouceList = new ArrayList<Resource>();
+ try {
+ for (String p : bldrsPropLoc2Path(topicsPropertiesLocation)) {
+ Resource[] resources = resolver.getResources(p);
+ if (resources != null && resources.length > 0) {
+ for (Resource resource : resources) {
+ resouceList.add(resource);
+ }
+ break;
+ }
+ }
+ } catch (Exception e) {
+ applicationLogger.logAuditError(e);
+ }
+ config.setLocations(resouceList.toArray(new Resource[]{}));
+ config.afterPropertiesSet();
+ return config.getObject();
+ }
+
+ private static String[] bldrsPropLoc2Path(String topicsPropertiesLocation) {
+ String[] res = new String[TOPICS_PROPERTIES_LOCATION_TPL.length];
+ int indx = 0;
+ for (String tmpl : TOPICS_PROPERTIES_LOCATION_TPL) {
+ res[indx++] = String.format(tmpl, topicsPropertiesLocation).replace("//", "/");
+ }
+ return res;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/config/ValidationControllerConfig.java b/src/main/java/org/onap/aai/validation/config/ValidationControllerConfig.java
new file mode 100644
index 0000000..4110175
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/ValidationControllerConfig.java
@@ -0,0 +1,82 @@
+/*
+ * ============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.config;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Loads the properties needed by the controller using spring.
+ */
+public class ValidationControllerConfig extends PropertiesConfig {
+
+ @Value("${event.domain}")
+ private String eventDomain;
+
+ @Value("#{'${event.action.exclude}'.split(',')}")
+ private List<String> excludedEventActions;
+
+ @Value("#{'${event.type.rule}'.split(',')}")
+ private List<String> eventTypeRule;
+
+ @Value("#{'${event.type.model}'.split(',')}")
+ private List<String> eventTypeModel;
+
+ @Value("${event.type.end:END-EVENT}")
+ private String eventTypeEnd;
+
+ public String getEventDomain() {
+ return eventDomain;
+ }
+
+ public void setEventDomain(String eventDomain) {
+ this.eventDomain = eventDomain;
+ }
+
+ public List<String> getExcludedEventActions() {
+ return excludedEventActions;
+ }
+
+ public void setExcludedEventActions(List<String> excludedEventActions) {
+ this.excludedEventActions = excludedEventActions;
+ }
+
+ public List<String> getEventTypeRule() {
+ return eventTypeRule;
+ }
+
+ public void setEventTypeRule(List<String> eventTypeRule) {
+ this.eventTypeRule = eventTypeRule;
+ }
+
+ public List<String> getEventTypeModel() {
+ return eventTypeModel;
+ }
+
+ public void setEventTypeModel(List<String> eventTypeModel) {
+ this.eventTypeModel = eventTypeModel;
+ }
+
+ public String getEventTypeEnd() {
+ return eventTypeEnd;
+ }
+
+ public void setEventTypeEnd(String eventTypeEnd) {
+ this.eventTypeEnd = eventTypeEnd;
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/config/ValidationServiceAuthConfig.java b/src/main/java/org/onap/aai/validation/config/ValidationServiceAuthConfig.java
new file mode 100644
index 0000000..62280fa
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/config/ValidationServiceAuthConfig.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.config;
+
+import org.springframework.beans.factory.annotation.Value;
+
+public class ValidationServiceAuthConfig {
+
+ @Value("${auth.authentication.disable}")
+ private boolean authenticationDisable;
+
+ @Value("${auth.policy.file}")
+ private String authPolicyFile;
+
+ public boolean isAuthenticationDisable() {
+ return authenticationDisable;
+ }
+
+ public void setAuthenticationDisable(boolean authenticationDisable) {
+ this.authenticationDisable = authenticationDisable;
+ }
+
+ public String getAuthPolicyFile() {
+ return authPolicyFile;
+ }
+
+ public void setAuthPolicyFile(String authPolicyFile) {
+ this.authPolicyFile = authPolicyFile;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/controller/ValidationController.java b/src/main/java/org/onap/aai/validation/controller/ValidationController.java
new file mode 100644
index 0000000..60d1c1c
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/controller/ValidationController.java
@@ -0,0 +1,380 @@
+/*
+ * ============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.controller;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import javax.inject.Inject;
+import org.onap.aai.validation.Validator;
+import org.onap.aai.validation.config.ValidationControllerConfig;
+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.publisher.MessagePublisher;
+import org.onap.aai.validation.reader.EventReader;
+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.util.JsonUtil;
+
+/**
+ * Controls the execution (of validation of an event) for the various validation service components.
+ */
+public class ValidationController {
+
+ private static LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ public static final String VALIDATION_ERROR_SEVERITY = "CRITICAL";
+
+ private static final String VALIDATION_ERROR_CATEGORY = "CANNOT_VALIDATE";
+ private static final String VALIDATION_ERROR_VIOLATIONTYPE = "NONE";
+
+ /**
+ * Result of the Controller executing validation of a single event. Either there is a set of ValidationResults or
+ * instead an exception was handled.
+ */
+ public class Result {
+ /**
+ * A successful validation will produce a set of results.
+ */
+ Optional<List<ValidationResult>> validationResults = Optional.empty();
+
+ /**
+ * For an unsuccessful validation, we will record the error details.
+ */
+ private String errorText;
+
+ /**
+ * @return whether or not we have a set of validation results
+ */
+ public boolean validationSuccessful() {
+ return validationResults.isPresent();
+ }
+
+ public List<ValidationResult> getValidationResults() {
+ return validationResults.orElse(Collections.emptyList());
+ }
+
+ /**
+ * @return a JSON string representing the first ValidationResult, or an empty string when there are no results
+ */
+ public String getValidationResultAsJson() {
+ List<ValidationResult> resultsList = getValidationResults();
+ if (resultsList.isEmpty()) {
+ return "";
+ } else {
+ // Only one Validation Result is returned (as only one is expected)
+ return JsonUtil.toJson(resultsList.get(0));
+ }
+ }
+
+ public Optional<String> getErrorText() {
+ return Optional.ofNullable(errorText);
+ }
+
+ private void handleException(String event, Exception rootException) {
+ try {
+ Entity entity = eventReader.getEntity(event);
+ if (!entity.getIds().isEmpty() && eventReader.getEntityType(event).isPresent()
+ && entity.getResourceVersion().isPresent()) {
+ ValidationResult validationResult = new ValidationResult(entity);
+ // @formatter:off
+ validationResult.addViolation(new Violation.Builder(entity)
+ .category(VALIDATION_ERROR_CATEGORY)
+ .severity(VALIDATION_ERROR_SEVERITY)
+ .violationType(VALIDATION_ERROR_VIOLATIONTYPE)
+ .errorMessage(rootException.getMessage())
+ .build());
+ // @formatter:on
+
+ validationResults = Optional.of(Collections.singletonList(validationResult));
+ publishValidationResults(validationResults);
+ }
+ } catch (Exception e) {
+ errorText = e.getMessage();
+ applicationLogger.error(ApplicationMsgs.CANNOT_VALIDATE_HANDLE_EXCEPTION_ERROR, e, event);
+ }
+ }
+ }
+
+ /**
+ * Status Report for the Controller
+ *
+ */
+ public class StatusReport {
+
+ private Temporal reportTime = LocalDateTime.now();
+ private long upTime = ChronoUnit.SECONDS.between(startTime, reportTime);
+ private long upTimeDays = ChronoUnit.DAYS.between(startTime, reportTime);
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Started at ");
+ sb.append(startTime).append('\n').append("Up time ");
+ if (upTimeDays > 0) {
+ sb.append(upTimeDays).append(" days ");
+ }
+ sb.append(LocalTime.MIDNIGHT.plusSeconds(upTime).format(DateTimeFormatter.ofPattern("HH:mm:ss")));
+ sb.append('\n').append(formatStats(stats));
+ return sb.toString();
+ }
+
+ /**
+ * @return formatted statistics
+ */
+ private String formatStats(Statistics stats) {
+ StringBuilder sb = new StringBuilder();
+ formatStats(stats, "info", sb, "Info Service");
+ formatStats(stats, "http", sb, "Validation REST API");
+ formatStats(stats, "topic", sb, "Events Consumed");
+ if (stats.reportedThrowable != null) {
+ StringWriter sw = new StringWriter();
+ stats.reportedThrowable.printStackTrace(new PrintWriter(sw));
+ sb.append("Exception reported: ").append(sw.toString());
+ }
+ return sb.toString();
+ }
+
+ private void formatStats(Statistics stats, String eventSource, StringBuilder sb, String heading) {
+ sb.append("\n").append(heading).append("\n");
+ if (stats.keyValues(eventSource).isEmpty()) {
+ sb.append("total=0\n");
+ } else {
+ for (String key : stats.keyValues(eventSource)) {
+ sb.append(key).append("=").append(stats.messageCount(eventSource, key)).append("\n");
+ }
+ }
+ }
+ }
+
+ private ValidationControllerConfig validationControllerConfig;
+ private EventReader eventReader;
+ private Validator ruleDrivenValidator;
+ private Validator modelDrivenValidator;
+ private MessagePublisher messagePublisher;
+ private LocalDateTime startTime;
+ private Statistics stats;
+
+ /**
+ * Record of actions taken by the Controller
+ *
+ */
+ private class Statistics {
+
+ private Map<String, SortedMap<String, Integer>> sourceMap = new HashMap<>();
+ private Throwable reportedThrowable;
+
+ /**
+ * Increment the message count for the composite key <eventSource, key>
+ *
+ * @param eventSource the source of the event - used for statistics reporting purposes
+ * @param key the statistic to increment by one
+ */
+ private void incrementEventCount(String eventSource, String key) {
+ Map<String, Integer> messagesConsumed = getMessageCountsMap(eventSource);
+ int count = messagesConsumed.getOrDefault(key, 0);
+ messagesConsumed.put(key, count + 1);
+ }
+
+ private Map<String, Integer> getMessageCountsMap(String eventSource) {
+ return sourceMap.computeIfAbsent(eventSource, k -> new TreeMap<>());
+ }
+
+ /**
+ * @param eventSource the source of the event
+ * @return List<String> the keys for the specified eventSource
+ */
+ private List<String> keyValues(String eventSource) {
+ Map<String, Integer> messagesConsumed = getMessageCountsMap(eventSource);
+ return new ArrayList<>(messagesConsumed.keySet());
+ }
+
+ /*
+ * return the count for the supplied event source and statistic key
+ */
+ private int messageCount(String eventSource, String key) {
+ Map<String, Integer> messagesConsumed = getMessageCountsMap(eventSource);
+ return messagesConsumed.getOrDefault(key, 0);
+ }
+
+ }
+
+ /**
+ * Constructs a new validation controller with the injected parameters.
+ *
+ * @param validationControllerConfig the configuration parameters for validation controllers
+ * @param eventReader an object that can read events
+ * @param ruleDrivenValidator a validator for validating rules
+ * @param modelDrivenValidator a validator for validating model
+ * @param messagePublisher an instance of a publisher for messages
+ */
+ @Inject
+ public ValidationController(ValidationControllerConfig validationControllerConfig, EventReader eventReader,
+ Validator ruleDrivenValidator, Validator modelDrivenValidator, MessagePublisher messagePublisher) {
+ this.startTime = LocalDateTime.now();
+ this.validationControllerConfig = validationControllerConfig;
+ this.eventReader = eventReader;
+ this.ruleDrivenValidator = ruleDrivenValidator;
+ this.modelDrivenValidator = modelDrivenValidator;
+ this.messagePublisher = messagePublisher;
+ this.stats = new Statistics();
+ }
+
+ /**
+ * @throws ValidationServiceException if an error occurs initialising the controller
+ */
+ public void initialise() throws ValidationServiceException {
+ ruleDrivenValidator.initialise();
+ modelDrivenValidator.initialise();
+ }
+
+ /**
+ * Validates the event and publishes the results of the validation onto the topic configured in the message
+ * publisher.
+ *
+ * @param event the event to be validated
+ * @param eventSource the source of the event
+ * @return Result a result containing either the set of ValidationResults or an error message
+ */
+ public Result execute(String event, String eventSource) {
+ Result result = new Result();
+ try {
+ stats.incrementEventCount(eventSource, "total");
+ if (isEndEvent(event)) {
+ applicationLogger.debug("Event has not been processed. End event type was detected. Event :" + event);
+ stats.incrementEventCount(eventSource, "end");
+ } else if (isValidationCandidate(event)) {
+ result.validationResults = dispatchEvent(event, eventSource);
+ publishValidationResults(result.validationResults);
+ } else {
+ stats.incrementEventCount(eventSource, "filtered");
+ }
+ } catch (Exception e) {
+ applicationLogger.error(ApplicationMsgs.CANNOT_VALIDATE_ERROR, e, event);
+ stats.incrementEventCount(eventSource, "errored");
+ result.handleException(event, e);
+ }
+ return result;
+ }
+
+ private void publishValidationResults(Optional<List<ValidationResult>> validationResults) {
+ if (validationResults.isPresent()) {
+ for (ValidationResult validationResult : validationResults.get()) {
+ try {
+ messagePublisher.publishMessage(validationResult.toJson());
+ } catch (ValidationServiceException e) {
+ applicationLogger.error(ApplicationMsgs.MESSAGE_PUBLISH_ERROR, e, validationResult.toString());
+ }
+ }
+ }
+ }
+
+ private Optional<List<ValidationResult>> dispatchEvent(String event, String eventSource)
+ throws ValidationServiceException {
+ List<ValidationResult> validationResults = null;
+ Optional<String> eventType = eventReader.getEventType(event);
+
+ applicationLogger.debug("Event consumed: " + event);
+
+ if (eventType.isPresent()) {
+ if (isRuleDriven(eventType.get())) {
+ validationResults = ruleDrivenValidator.validate(event);
+ stats.incrementEventCount(eventSource, "rule");
+ } else if (isModelDriven(eventType.get())) {
+ validationResults = modelDrivenValidator.validate(event);
+ stats.incrementEventCount(eventSource, "model");
+ } else {
+ applicationLogger.debug("Event has not been validated. Invalid event type. Event :" + event);
+ stats.incrementEventCount(eventSource, "invalid");
+ }
+ } else {
+ stats.incrementEventCount(eventSource, "missing event type");
+ }
+
+ return Optional.ofNullable(validationResults);
+ }
+
+ private Boolean isRuleDriven(String eventType) {
+ return validationControllerConfig.getEventTypeRule().contains(eventType);
+ }
+
+ private Boolean isModelDriven(String eventType) {
+ return validationControllerConfig.getEventTypeModel().contains(eventType);
+ }
+
+ private boolean isEndEvent(String event) throws ValidationServiceException {
+ Optional<String> eventType = eventReader.getEventType(event);
+
+ return eventType.isPresent() && "END-EVENT".equalsIgnoreCase(eventType.get());
+ }
+
+ private Boolean isDomainValid(String event) throws ValidationServiceException {
+ Optional<String> eventDomain = eventReader.getEventDomain(event);
+
+ // Domain is optional in Event Header
+ return !eventDomain.isPresent()
+ || validationControllerConfig.getEventDomain().equalsIgnoreCase(eventDomain.get());
+ }
+
+ private Boolean isNotExcludedAction(String event) throws ValidationServiceException {
+ Optional<String> eventAction = eventReader.getEventAction(event);
+
+ // Action is optional in Event Header
+ return !eventAction.isPresent()
+ || !validationControllerConfig.getExcludedEventActions().contains(eventAction.get());
+ }
+
+
+ private Boolean isValidationCandidate(String event) throws ValidationServiceException {
+ return isDomainValid(event) && isNotExcludedAction(event);
+ }
+
+ /**
+ * @return a formatted string containing status information
+ */
+ public StatusReport statusReport() {
+ return new StatusReport();
+ }
+
+ /**
+ * Record a Throwable which will then be added to the status reporting text.
+ *
+ * @param t a Throwable to be reported (on demand)
+ */
+ public void recordThrowable(Throwable t) {
+ stats.reportedThrowable = t;
+ }
+
+ public void incrementInfoCount() {
+ stats.incrementEventCount("info", "total");
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/data/client/RestClient.java b/src/main/java/org/onap/aai/validation/data/client/RestClient.java
new file mode 100644
index 0000000..d2d1196
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/data/client/RestClient.java
@@ -0,0 +1,121 @@
+/*
+ * ============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.data.client;
+
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+import java.util.Arrays;
+import java.util.UUID;
+import javax.inject.Inject;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import org.onap.aai.restclient.client.OperationResult;
+import org.onap.aai.validation.config.RestConfig;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+
+/**
+ * REST client capable of establishing secured requests.
+ */
+public class RestClient {
+ private RestConfig restConfig;
+ private org.onap.aai.restclient.client.RestClient aaiRestClient;
+ private MultivaluedMap<String, String> headers;
+
+ private static final String ACCEPT = "application/json";
+ private static final String HEADER_X_FROM_APP_ID = "validation-service";
+ private static final String APP_CONFIG_HOME = System.getProperty("CONFIG_HOME");
+
+ /**
+ * Constructs a new rest client with the injected parameters.
+ *
+ * @param restConfig
+ */
+ @Inject
+ public RestClient(RestConfig restConfig) {
+ this.restConfig = restConfig;
+ initialiseRestClient();
+ }
+
+ /**
+ * Initialises the REST client
+ *
+ */
+ private void initialiseRestClient() {
+ // @formatter:off
+ aaiRestClient = new org.onap.aai.restclient.client.RestClient()
+ .validateServerHostname(false)
+ .validateServerCertChain(true)
+ .clientCertFile(APP_CONFIG_HOME + restConfig.getKeyStorePath())
+ .clientCertPassword(restConfig.getKeyStorePassword())
+ .trustStore(APP_CONFIG_HOME + restConfig.getTrustStorePath())
+ .connectTimeoutMs(restConfig.getConnectionTimeout())
+ .readTimeoutMs(restConfig.getReadTimeout());
+ // @formatter:on
+
+ headers = new MultivaluedMapImpl();
+ headers.put("Accept", Arrays.asList(ACCEPT));
+ headers.put("X-FromAppId", Arrays.asList(HEADER_X_FROM_APP_ID));
+ headers.put("X-TransactionId", Arrays.asList(UUID.randomUUID().toString()));
+ }
+
+ /**
+ * Invokes the REST URL and returns the payload string.
+ *
+ * @param uriPath
+ * @param mediaType
+ * @return The payload of the REST URL call as a string.
+ * @throws ValidationServiceException
+ */
+ public String get(String uriPath, String mediaType) throws ValidationServiceException {
+ // Construct URI
+ String uri = restConfig.getProtocol() + "://" + restConfig.getHost() + ":" + restConfig.getPort() + uriPath;
+
+ OperationResult result = aaiRestClient.get(uri, headers, MediaType.valueOf(mediaType));
+
+ if (result.getResultCode() == 200) {
+ return result.getResult();
+ } else if (result.getResultCode() == 404) {
+ throw new ValidationServiceException(ValidationServiceError.REST_CLIENT_RESPONSE_NOT_FOUND,
+ result.getResultCode(), result.getFailureCause());
+ } else {
+ throw new ValidationServiceException(ValidationServiceError.REST_CLIENT_RESPONSE_ERROR,
+ result.getResultCode(), result.getFailureCause());
+ }
+ }
+
+ /**
+ * POSTs a request.
+ *
+ * @param url
+ * @param payload
+ *
+ * @return The payload of the REST URL call as a string.
+ * @throws GapServiceException
+ */
+ public String post(String url, String payload) throws ValidationServiceException {
+ OperationResult result = aaiRestClient.post(url, payload, headers, MediaType.APPLICATION_JSON_TYPE,
+ MediaType.APPLICATION_JSON_TYPE);
+ if (result.getResultCode() == 200) {
+ return result.getResult();
+ } else {
+ throw new ValidationServiceException(ValidationServiceError.REST_CLIENT_RESPONSE_ERROR,
+ result.getResultCode(), result.getFailureCause());
+ }
+
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/exception/BaseValidationServiceException.java b/src/main/java/org/onap/aai/validation/exception/BaseValidationServiceException.java
new file mode 100644
index 0000000..59fdb72
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/exception/BaseValidationServiceException.java
@@ -0,0 +1,66 @@
+/*
+ * ============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.exception;
+
+import java.util.Locale;
+
+/**
+ * Validation service exception base class
+ *
+ */
+public class BaseValidationServiceException extends Exception {
+
+ private static final long serialVersionUID = -6663403070792969748L;
+
+ public static final Locale LOCALE = Locale.US;
+
+ private final String id;
+
+ /**
+ * Default constructor.
+ *
+ * @param id
+ */
+ public BaseValidationServiceException(String id) {
+ super();
+ this.id = id;
+ }
+
+ /**
+ * @param id
+ * @param message
+ */
+ public BaseValidationServiceException(String id, String message) {
+ super(message);
+ this.id = id;
+ }
+
+ /**
+ * @param id
+ * @param message
+ * @param cause
+ */
+ public BaseValidationServiceException(String id, String message, Throwable cause) {
+ super(message, cause);
+ this.id = id;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/exception/ValidationServiceError.java b/src/main/java/org/onap/aai/validation/exception/ValidationServiceError.java
new file mode 100644
index 0000000..45d79d4
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/exception/ValidationServiceError.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.exception;
+
+import java.text.MessageFormat;
+
+/**
+ * Error text formatting
+ *
+ */
+public enum ValidationServiceError {
+
+ //@formatter:off
+
+ // Rule Configuration exceptions. Range 100..199
+ RULES_FILE_ERROR("VS-100", "Error reading rules configuration file(s) {0}"),
+ RULE_UNEXPECTED_TOKEN("VS-101", "Token {0} unexpected in rules configuration file."),
+ RULES_NOT_DEFINED("VS-102", "Event type {0} has no rule definitions."),
+
+ // Rule exceptions. Range 200..299
+ RULE_EXECUTION_ERROR("VS-201", "Error executing rule {0} with arguments {1}"),
+
+ // Validation service processing exceptions. Range 300..399
+ OMX_LOAD_ERROR("VS-300", "Validation service failed to load the OXM file."),
+ OXM_MISSING_KEY("VS-301", "Validation service failed to retrieve primary key for object of type {0}."),
+ MESSAGE_DIGEST_ERROR("VS-302", "Java platform security error. Failed to create Message Digest hashing object {0}."),
+ MODEL_RETRIEVAL_ERROR("VS-303", "Validator failed to get the model from external system."),
+ MODEL_CACHE_ERROR("VS-304", "Validator failed to retrieve the model from the cache. {0}"),
+ MODEL_NOT_FOUND("VS-305", "Model with UUID {0} not found."),
+
+ // Event publishing exceptions. Range 400..499
+ EVENT_CLIENT_PUBLISHER_INIT_ERROR("VS-400", "Error while initialising the Event Publisher Client."),
+ EVENT_CLIENT_SEND_ERROR("VS-401", "Error while sending a message to the event bus."),
+ EVENT_CLIENT_INCORRECT_NUMBER_OF_MESSAGES_SENT("VS-402", "Publisher client returned a result of {0} messages sent."),
+ EVENT_CLIENT_CLOSE_ERROR("VS-403", "Error while closing the Event Publisher Client."),
+ EVENT_CLIENT_CLOSE_UNSENT_MESSAGE("VS-404", "Failed to publish message. Error while closing the Event Publisher Client. " +
+ "The following message is unsent: {0}. Please check the logs for more information."),
+ EVENT_CLIENT_CONSUMER_INIT_ERROR("VS-405", "Error while initialising the Event Consumer Client."),
+
+ // Reader exceptions. Range 500..599
+ JSON_READER_PARSE_ERROR("VS-500", "JSON could not be parsed."),
+ EVENT_READER_MISSING_PROPERTY("VS-501", "Missing property: {0}"),
+ EVENT_READER_TOO_MANY_ENTITIES("VS-502", "Unexpected number or entities."),
+ INSTANCE_READER_NO_INSTANCE("VS-503", "Failed to extract instance under path: {0}. JSON payload: {1}"),
+ EVENT_READER_PROPERTY_READ_ERROR("VS-504", "Failed to read entity link property. Check event reader configuration properties."),
+
+ // Model-instance mapping exceptions. Range 600..649
+ MODEL_INSTANCE_MAPPING_RETRIEVAL_ERROR("VS-600", "Error retrieving model-instance mappings."),
+ MODEL_INSTANCE_MAPPING_FILE_IO_ERROR("VS-601", "IO error when reading from the model-instance mapping file {0}."),
+ MODEL_PARSE_ERROR("VS-602", "Validator failed to parse the model provided. Source:\n{0}"),
+ MODEL_VALUE_ERROR("VS-603", "Validator failed to access the model value {0} in {1}"),
+ INSTANCE_MAPPING_ROOT_ERROR("VS-604", "Missing 'root' property in instance mapping."),
+
+ // REST client exceptions. Range 650..699
+ REST_CLIENT_RESPONSE_ERROR("VS-650", "REST client response error: Response Code: {0}, Failure Cause: {1}."),
+ REST_CLIENT_RESPONSE_NOT_FOUND("VS-651", "REST client response error: Response Code: {0}, Failure Cause: {1}."),
+
+ // Validation Service configuration exceptions. Range 700..799
+ VS_PROPERTIES_LOAD_ERROR("VS-700", "Failed to read property {0} in the validation service properties file."),
+
+ // Miscellaneous exceptions. Range 800..849
+ STRING_UTILS_INVALID_REGEX("VS-800", "Invalid regular expression: {0}");
+
+ //@formatter:on
+
+ private String id;
+ private String message;
+
+ private ValidationServiceError(String id, String message) {
+ this.id = id;
+ this.message = message;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ /**
+ * @param args
+ * to be formatted
+ * @return the formatted error message
+ */
+ public String getMessage(Object... args) {
+ MessageFormat formatter = new MessageFormat("");
+ formatter.applyPattern(this.message);
+ return formatter.format(args);
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/exception/ValidationServiceException.java b/src/main/java/org/onap/aai/validation/exception/ValidationServiceException.java
new file mode 100644
index 0000000..2606ce9
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/exception/ValidationServiceException.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.exception;
+
+/**
+ * Validation service exception
+ *
+ */
+public class ValidationServiceException extends BaseValidationServiceException { // NOSONAR
+
+ private static final long serialVersionUID = 883498159309797607L;
+
+ /**
+ * Constructs an exception defined by the Error.
+ *
+ * @param error
+ * {@link ValidationServiceError} with the error id.
+ */
+ public ValidationServiceException(ValidationServiceError error) {
+ super(error.getId(), error.getId() + ", " + error.getMessage());
+ }
+
+ /**
+ * Constructs an exception defined by the Error. The message is parameterised with the arguments.
+ *
+ * @param error
+ * {@link ValidationServiceError} with the error id.
+ * @param args
+ * Arguments for the exception message.
+ */
+ public ValidationServiceException(ValidationServiceError error, Object... args) {
+ super(error.getId(), error.getId() + ", " + error.getMessage(args));
+ }
+
+ /**
+ * Constructs an exception defined by the Error and the underlying Exception. The message is parameterised with the
+ * arguments and enhanced with the underlying Exception message.
+ *
+ * @param error
+ * {@link ValidationServiceError} with the error id.
+ * @param exception
+ * Exception thrown by an underlying API.
+ * @param args
+ * Arguments for the exception message.
+ */
+ public ValidationServiceException(ValidationServiceError error, Exception exception, Object... args) {
+ super(error.getId(), error.getId() + ", " + error.getMessage(args) + "; Caused by: " + exception.getMessage(), exception);
+ }
+
+ /**
+ * Constructs an exception defined by the Error and the underlying Exception. The message is enhanced with the
+ * underlying Exception message.
+ *
+ * @param error
+ * {@link ValidationServiceError} with the error id.
+ * @param exception
+ * Exception thrown by an underlying API.
+ */
+ public ValidationServiceException(ValidationServiceError error, Exception exception) {
+ super(error.getId(), error.getId() + ", " + error.getMessage() + "; Caused by: " + exception.getMessage(), exception);
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/factory/DMaaPEventPublisherFactory.java b/src/main/java/org/onap/aai/validation/factory/DMaaPEventPublisherFactory.java
new file mode 100644
index 0000000..fa4af74
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/factory/DMaaPEventPublisherFactory.java
@@ -0,0 +1,34 @@
+/*
+ * ============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.factory;
+
+import org.onap.aai.event.client.DMaaPEventPublisher;
+
+public class DMaaPEventPublisherFactory {
+
+
+ public DMaaPEventPublisher createEventPublisher(String topicHost, String topicName, String topicUsername,
+ String topicPassword, String topicTransportType) {
+ int defaultBatchSize = DMaaPEventPublisher.DEFAULT_BATCH_SIZE;
+ long defaultBatchAge = DMaaPEventPublisher.DEFAULT_BATCH_AGE;
+ int defaultBatchDelay = DMaaPEventPublisher.DEFAULT_BATCH_DELAY;
+ return new DMaaPEventPublisher(topicHost, topicName, topicUsername, topicPassword, defaultBatchSize,
+ defaultBatchAge, defaultBatchDelay, topicTransportType);
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/logging/ApplicationMsgs.java b/src/main/java/org/onap/aai/validation/logging/ApplicationMsgs.java
new file mode 100644
index 0000000..6066a20
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/logging/ApplicationMsgs.java
@@ -0,0 +1,74 @@
+/*
+ * ============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.logging;
+
+import com.att.eelf.i18n.EELFResourceManager;
+import org.onap.aai.cl.eelf.LogMessageEnum;
+
+/**
+ * Application messages
+ */
+public enum ApplicationMsgs implements LogMessageEnum {
+
+ /**
+ * Add message keys here.
+ */
+ // @formatter:off
+ MESSAGE_VALIDATION_REQUEST,
+ MESSAGE_AUDIT,
+ MESSAGE_METRIC,
+ MESSAGE_PUBLISH_ERROR,
+ OXM_LOAD_ERROR,
+ OXM_MISSING_KEY_ERROR,
+ MISSING_REQUEST_ID,
+ CANNOT_VALIDATE_ERROR,
+ CANNOT_VALIDATE_HANDLE_EXCEPTION_ERROR,
+ POLL_EVENTS,
+ NUMBER_OF_MESSAGES_CONSUMED,
+ INVOKE_EVENT_CONSUMER_ERROR,
+ READ_FILE_ERROR,
+ STARTUP_SERVLET_INIT,
+ POLLING_INTERVAL_CONFIG_NOT_PRESENT,
+ POLLING_FOR_EVENTS,
+ POLLING_DISABLED,
+ STARTUP_SERVLET_INIT_SUCCESS,
+ UNSENT_MESSAGE_WARN,
+ UNSENT_MESSAGE_ERROR,
+ EVENT_CLIENT_CLOSE_UNSENT_MESSAGE,
+ SEND_MESSAGE_ABORT_WARN,
+ SEND_MESSAGE_RETRY_WARN,
+ FILE_ARG_NULL_ERROR,
+ LOAD_PROPERTIES,
+ FILE_LOAD_INTO_MAP,
+ FILE_LOAD_INTO_MAP_ERROR,
+ CREATE_PROPERTY_MAP_ERROR,
+ FILE_MONITOR_BLOCK_ERROR,
+ READ_FILE_STREAM_ERROR,
+ STRING_UTILS_INVALID_REGEX,
+ MALFORMED_REQUEST_ERROR,
+ PROCESS_REQUEST_ERROR;
+ // @formatter:on
+
+ /**
+ * Static initializer to ensure the resource bundles for this class are loaded... Here this application loads
+ * messages from three bundles
+ */
+ static {
+ EELFResourceManager.loadMessageBundle("validation-service-logging-resources");
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/logging/LogHelper.java b/src/main/java/org/onap/aai/validation/logging/LogHelper.java
new file mode 100644
index 0000000..4abf5fd
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/logging/LogHelper.java
@@ -0,0 +1,548 @@
+/*
+ * ============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.logging;
+
+import static com.att.eelf.configuration.Configuration.MDC_SERVICE_NAME;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.core.FileAppender;
+import com.att.eelf.configuration.EELFLogger;
+import com.att.eelf.configuration.EELFManager;
+import com.att.eelf.i18n.EELFResolvableErrorEnum;
+import java.io.File;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import javax.servlet.ServletRequest;
+import javax.ws.rs.core.Response.Status;
+import org.apache.commons.lang.time.StopWatch;
+import org.onap.aai.cl.api.LogFields;
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.cl.mdc.MdcContext;
+import org.onap.aai.cl.mdc.MdcOverride;
+import org.onap.aai.restclient.client.Headers;
+import org.onap.aai.validation.services.RequestHeaders;
+import org.slf4j.MDC;
+import org.springframework.http.HttpHeaders;
+
+/*-
+ * This Log Helper mimics the interface of a Common Logging Logger
+ * but adds helper methods for audit and metrics logging requirements.
+ *
+ * Messages are logged to the appropriate EELF functional logger as described below.
+ *
+ * Error Log: INFO/WARN/ERROR/FATAL
+ * Debug Log: DEBUG/TRACE
+ * Audit Log: summary view of transaction processing
+ * Metrics Log: detailed timings of transaction processing interactions
+ *
+ * Audit and Metrics log messages record the following fields:
+ *
+ * RequestID - an RFC4122 UUID for the transaction request
+ * ServiceName - the API provided by this service
+ * PartnerName - invoker of the API
+ * ClassName - name of the class creating the log record
+ *
+ * The above list is not exhaustive.
+ */
+public enum LogHelper implements Logger {
+ INSTANCE; // Joshua Bloch's singleton pattern
+
+ @FunctionalInterface
+ public interface TriConsumer<T, U, V> {
+ public void accept(T t, U u, V v);
+ }
+
+ /**
+ * Audit log message status code values. See {@code MdcParameter.STATUS_CODE}
+ */
+ public enum StatusCode {
+ COMPLETE,
+ ERROR;
+ }
+
+ /**
+ * Mapped Diagnostic Context parameter names.
+ *
+ * Note that MdcContext.MDC_START_TIME is used for audit messages, and indicates the start of a transaction.
+ * Messages in the metrics log record sub-operations of a transaction and thus use different timestamps.
+ */
+ public enum MdcParameter {
+ REQUEST_ID(MdcContext.MDC_REQUEST_ID),
+ CLASS_NAME("ClassName"),
+ BEGIN_TIMESTAMP("BeginTimestamp"),
+ END_TIMESTAMP("EndTimestamp"),
+ ELAPSED_TIME("ElapsedTime"),
+ STATUS_CODE("StatusCode"),
+ RESPONSE_CODE("ResponseCode"),
+ RESPONSE_DESCRIPTION("ResponseDescription"),
+ TARGET_ENTITY("TargetEntity"),
+ TARGET_SERVICE_NAME("TargetServiceName"),
+ USER("User");
+
+ private final String parameterName;
+
+ MdcParameter(String parameterName) {
+ this.parameterName = parameterName;
+ }
+
+ /**
+ * Get the MDC logging context parameter name as referenced by the logback configuration
+ *
+ * @return the MDC parameter name
+ */
+ public String value() {
+ return parameterName;
+ }
+ }
+
+ /**
+ * Our externally advertised service API
+ */
+ private static final String SERVICE_NAME_VALUE = "AAI-VS";
+
+ private static final EELFLogger errorLogger = EELFManager.getInstance().getErrorLogger();
+ private static final EELFLogger debugLogger = EELFManager.getInstance().getDebugLogger();
+ private static final EELFLogger auditLogger = EELFManager.getInstance().getAuditLogger();
+ private static final EELFLogger metricsLogger = EELFManager.getInstance().getMetricsLogger();
+
+ /**
+ * Formatting for timestamps logged as Strings (from the MDC)
+ */
+ private DateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+
+ // Records the elapsed time (since the start of servicing a request) for audit logging
+ private StopWatch auditStopwatch;
+
+ /**
+ * Initialises the MDC (logging context) with default values, to support any logging of messages BEFORE the
+ * startAudit() method is invoked.
+ */
+ private LogHelper() {
+ setContextValue(MDC_SERVICE_NAME, SERVICE_NAME_VALUE);
+ // This value is not expected to be used in the default logging configuration
+ setContextValue(MdcContext.MDC_START_TIME, timestampFormat.format(new Date()));
+ }
+
+ /**
+ * Begin recording transaction information for a new request. This data is intended for logging purposes. This
+ * method does not actually write any messages to the log(s).
+ *
+ * Initialise the MDC logging context for auditing and metrics, using the HTTP request headers. This information
+ * includes: the correlation ID, local application service name/ID, calling host details and authentication data.
+ *
+ * The request object is used to find the client details (e.g. IP address)
+ *
+ * @param headers raw HTTP headers
+ * @param servletRequest the request
+ */
+ public void startAudit(final HttpHeaders headers, ServletRequest servletRequest) {
+ auditStopwatch = new StopWatch();
+ auditStopwatch.start();
+
+ String requestId = null;
+ String serviceInstanceId = null;
+ String partnerName = "<UNKNOWN_PARTNER>";
+
+ if (headers != null) {
+ requestId = setRequestId(headers);
+ serviceInstanceId = headers.getFirst(RequestHeaders.HEADER_SERVICE_INSTANCE_ID);
+ partnerName = headers.getFirst(Headers.FROM_APP_ID);
+ }
+
+ String clientHost = null;
+ String clientIPAddress = null;
+ String user = "<UNKNOWN_USER>";
+
+ if (servletRequest != null) {
+ clientHost = servletRequest.getRemoteHost();
+ clientIPAddress = servletRequest.getRemoteAddr();
+
+ if (partnerName == null) {
+ partnerName = clientHost;
+ }
+ }
+
+ // Populate standard MDC keys - note that the initialize method calls MDC.clear()
+ MdcContext.initialize(requestId, SERVICE_NAME_VALUE, serviceInstanceId, partnerName, clientIPAddress);
+
+ setContextValue(MdcParameter.USER, user);
+ setContextValue(MdcContext.MDC_REMOTE_HOST, clientHost);
+ }
+
+ /**
+ * Store a value in the current thread's logging context.
+ *
+ * @param key non-null parameter name
+ * @param value the value to store against the key
+ */
+ public void setContextValue(String key, String value) {
+ debug(key + "=" + value);
+ MDC.put(key, value);
+ }
+
+ /**
+ * Store a value in the current thread's logging context.
+ *
+ * @param param identifier of the context parameter
+ * @param value the value to store for this parameter
+ */
+ public void setContextValue(MdcParameter param, String value) {
+ setContextValue(param.value(), value);
+ }
+
+ /**
+ * Set a value in the current thread's logging context, only if this is not already set.
+ *
+ * @param key non-null parameter name
+ * @param value the value to store against the key (only if the current value is null)
+ */
+ public void setDefaultContextValue(String key, String value) {
+ if (MDC.get(key) == null) {
+ setContextValue(key, value);
+ }
+ }
+
+ /**
+ * Set a value in the current thread's logging context, only if this is not already set.
+ *
+ * @param param identifier of the context parameter
+ * @param value the value to store for this parameter (only if the current value is null)
+ */
+ public void setDefaultContextValue(MdcParameter param, String value) {
+ setContextValue(param.value(), value);
+ }
+
+ /**
+ * Clear all logging context values. This should be called at start-up only.
+ */
+ public void clearContext() {
+ debug("Clearing MDC context");
+ MDC.clear();
+ }
+
+ /**
+ * Clear the specified logging context value.
+ */
+ public void clearContextValue(MdcParameter param) {
+ MDC.remove(param.value());
+ }
+
+ /**
+ * Log an audit message to the audit logger. This method is expected to be called when a response is returned to the
+ * caller and/or when the processing of the request completes.
+ *
+ * @param status status of the service request: COMPLETE/ERROR
+ * @param responseCode optional application error code
+ * @param responseDescription human-readable description of the response code
+ * @param args the argument(s) required to populate the Audit Message log content
+ */
+ public void logAudit(StatusCode status, String responseCode, String responseDescription, final String... args) {
+ if (auditStopwatch == null) {
+ debug("Unexpected program state: audit stopwatch not started");
+ auditStopwatch = new StopWatch();
+ auditStopwatch.start();
+ }
+
+ if (auditLogger.isInfoEnabled()) {
+ setMdcElapsedTime(auditStopwatch);
+ setContextValue(MdcParameter.STATUS_CODE, status.toString());
+ setContextValue(MdcParameter.RESPONSE_CODE, responseCode);
+ setContextValue(MdcParameter.RESPONSE_DESCRIPTION, responseDescription);
+ invokeLogger(auditLogger::info, ApplicationMsgs.MESSAGE_AUDIT, args);
+ }
+ }
+
+ /**
+ * Log an audit message to the audit logger representing a non-specific processing success message.
+ *
+ * @param msg
+ */
+ public void logAuditSuccess(String msg) {
+ Status status = Status.OK;
+ logAudit(StatusCode.COMPLETE, Integer.toString(status.getStatusCode()), status.getReasonPhrase(), msg);
+ }
+
+ /**
+ * Log an audit message to the audit logger representing an internal error (e.g. for an exception thrown by the
+ * implementation). This method is expected to be called when a generic error response is returned to the caller to
+ * indicate a processing failure.
+ *
+ * @param e Exception
+ */
+ public void logAuditError(Exception e) {
+ Status status = Status.INTERNAL_SERVER_ERROR;
+ logAudit(StatusCode.ERROR, Integer.toString(status.getStatusCode()), status.getReasonPhrase(), e.getMessage());
+ }
+
+ /**
+ * Log a message to the metrics log.
+ *
+ * @param error the log code
+ * @param args the info messages
+ */
+ public void logMetrics(final String... args) {
+ if (metricsLogger.isInfoEnabled()) {
+ invokeLogger(metricsLogger::info, ApplicationMsgs.MESSAGE_METRIC, args);
+ }
+ }
+
+ /**
+ * @param stopwatch
+ * @param args
+ */
+ public void logMetrics(final StopWatch stopwatch, String... args) {
+ setMdcElapsedTime(stopwatch);
+ logMetrics(args);
+ }
+
+ @Override
+ public String formatMsg(@SuppressWarnings("rawtypes") Enum arg0, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return debugLogger != null && debugLogger.isDebugEnabled();
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return errorLogger.isErrorEnabled();
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return errorLogger.isInfoEnabled();
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return debugLogger.isTraceEnabled();
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return errorLogger.isWarnEnabled();
+ }
+
+ /**
+ * Log a DEBUG level message to the debug logger.
+ *
+ * @param message the debug message
+ */
+ @Override
+ public void debug(String message) {
+ if (isDebugEnabled()) {
+ invokeLogger(debugLogger::debug, message);
+ }
+ }
+
+ @Override
+ public void debug(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
+ if (isDebugEnabled()) {
+ invokeErrorCodeLogger(debugLogger::debug, (EELFResolvableErrorEnum) errorCode, args);
+ }
+ }
+
+ @Override
+ public void debug(@SuppressWarnings("rawtypes") Enum errorCode, LogFields arg1, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void error(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
+ if (isErrorEnabled()) {
+ invokeErrorCodeLogger(errorLogger::error, (EELFResolvableErrorEnum) errorCode, args);
+ }
+ }
+
+ @Override
+ public void error(@SuppressWarnings("rawtypes") Enum errorCode, Throwable t, String... args) {
+ if (isErrorEnabled()) {
+ invokeErrorCodeLogger(errorLogger::error, (EELFResolvableErrorEnum) errorCode, t, args);
+ }
+ }
+
+ @Override
+ public void error(@SuppressWarnings("rawtypes") Enum errorCode, LogFields arg1, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void error(@SuppressWarnings("rawtypes") Enum errorCode, LogFields arg1, Throwable arg2, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void info(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
+ if (isInfoEnabled()) {
+ invokeErrorCodeLogger(errorLogger::info, (EELFResolvableErrorEnum) errorCode, args);
+ }
+ }
+
+ @Override
+ public void info(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void info(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, MdcOverride arg2, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void trace(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
+ if (isTraceEnabled()) {
+ invokeErrorCodeLogger(debugLogger::trace, (EELFResolvableErrorEnum) errorCode, args);
+ }
+ }
+
+ @Override
+ public void trace(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void warn(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
+ if (isWarnEnabled()) {
+ invokeErrorCodeLogger(errorLogger::warn, (EELFResolvableErrorEnum) errorCode, args);
+ }
+ }
+
+ @Override
+ public void warn(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, String... args) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the method name for a calling method (from the current stack trace)
+ *
+ * @param level number of levels for the caller (not including this method)
+ * @return the class and name of the calling method in the form "class#method"
+ */
+ public static String getCallerMethodName(int level) {
+ StackTraceElement callingMethod = Thread.currentThread().getStackTrace()[level + 2];
+ return callingMethod.getClassName() + "#" + callingMethod.getMethodName();
+ }
+
+
+ /**
+ * Convenience method to be used only for testing purposes.
+ *
+ * @return the directory storing the log files
+ */
+ public static String getLogDirectory() {
+ ch.qos.logback.classic.Logger logger =
+ (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger("com.att.eelf");
+ AsyncAppender appender = (AsyncAppender) logger.getAppender("asyncEELF");
+ FileAppender<?> fileAppender = (FileAppender<?>) appender.getAppender("EELF");
+ return new File(fileAppender.getFile()).getParent();
+ }
+
+ /**
+ * @param headers
+ * @return the request ID from the HTTP headers
+ */
+ private String setRequestId(final HttpHeaders headers) {
+ String requestId = headers.getFirst(RequestHeaders.HEADER_REQUEST_ID);
+
+ // If the request ID is missing, set it from the transaction ID
+ if (requestId == null) {
+ requestId = headers.getFirst(Headers.TRANSACTION_ID);
+ }
+
+ // Normalize the incoming ID by removing any suffix
+ if (requestId != null && requestId.contains(":")) {
+ requestId = requestId.split(":")[0];
+ }
+
+ return requestId == null ? "missing-request-id" : requestId;
+ }
+
+ private void setMdcClassName() {
+ MDC.put(MdcParameter.CLASS_NAME.value(), getCallerMethodName(3));
+ }
+
+ private void unsetMdcClassName() {
+ MDC.put(MdcParameter.CLASS_NAME.value(), null);
+ }
+
+ /**
+ * Set the Begin, End, and Elapsed time values in the MDC context.
+ *
+ * @param stopwatch
+ */
+ private void setMdcElapsedTime(final StopWatch stopwatch) {
+ long startTime = stopwatch.getStartTime();
+ long elapsedTime = stopwatch.getTime();
+
+ setContextValue(MdcParameter.BEGIN_TIMESTAMP, timestampFormat.format(startTime));
+ setContextValue(MdcParameter.END_TIMESTAMP, timestampFormat.format(startTime + elapsedTime));
+ setContextValue(MdcParameter.ELAPSED_TIME, Long.toString(elapsedTime)); // Milliseconds
+ }
+
+ /**
+ * @param logMethod the logger method to invoke
+ * @param message
+ */
+ private void invokeLogger(Consumer<String> logMethod, String message) {
+ setMdcClassName();
+ logMethod.accept(message);
+ unsetMdcClassName();
+ }
+
+ /**
+ * @param logMethod
+ * @param msg
+ * @param args
+ */
+ private void invokeLogger(BiConsumer<ApplicationMsgs, String[]> logMethod, ApplicationMsgs msg, String[] args) {
+ setMdcClassName();
+ logMethod.accept(msg, args);
+ unsetMdcClassName();
+ }
+
+ /**
+ * @param logMethod
+ * @param errorEnum
+ * @param args
+ */
+ private void invokeErrorCodeLogger(BiConsumer<EELFResolvableErrorEnum, String[]> logMethod,
+ EELFResolvableErrorEnum errorEnum, String[] args) {
+ setMdcClassName();
+ logMethod.accept(errorEnum, args);
+ unsetMdcClassName();
+ }
+
+ /**
+ * @param logMethod
+ * @param errorEnum
+ * @param t a Throwable
+ * @param args
+ */
+ private void invokeErrorCodeLogger(TriConsumer<EELFResolvableErrorEnum, Throwable, String[]> logMethod,
+ EELFResolvableErrorEnum errorEnum, Throwable t, String[] args) {
+ setMdcClassName();
+ logMethod.accept(errorEnum, t, args);
+ unsetMdcClassName();
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/ModelCacheManager.java b/src/main/java/org/onap/aai/validation/modeldriven/ModelCacheManager.java
new file mode 100644
index 0000000..5c741fb
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/ModelCacheManager.java
@@ -0,0 +1,182 @@
+/*
+ * ============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.modeldriven;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+import javax.ws.rs.core.MediaType;
+import org.apache.http.client.utils.URIBuilder;
+import org.dom4j.Element;
+import org.onap.aai.validation.config.ModelConfig;
+import org.onap.aai.validation.config.RestConfig;
+import org.onap.aai.validation.data.client.RestClient;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.modeldriven.parser.XMLModelParser;
+
+/**
+ * A cache of model UUID against the parsed model Element, using Guava's CacheBuilder.
+ */
+public class ModelCacheManager {
+
+ public static final String FILE_MODEL_PROTOCOL = "file";
+
+ private LoadingCache<ModelId, Element> modelCache;
+ private RestConfig restConfig;
+
+ /**
+ * Initialises the instance by loading validator properties from config.
+ *
+ * @param modelConfig
+ * @param restConfig
+ * @throws ValidationServiceException
+ */
+ @Inject
+ public ModelCacheManager(ModelConfig modelConfig, RestConfig restConfig) {
+ this.restConfig = restConfig;
+
+ // Create an expiring cache with a load implementation which is executed when a key value is not cached.
+ modelCache = CacheBuilder.newBuilder().maximumSize(1000)
+ .expireAfterWrite(modelConfig.getModelCacheExpirySeconds(), TimeUnit.SECONDS)
+ .build(new CacheLoader<ModelId, Element>() {
+ @Override
+ public Element load(ModelId key) throws ValidationServiceException {
+ return retrieveModelElement(key);
+ }
+ });
+ }
+
+ /**
+ * Gets the model with specified uuid from the model cache. If model is not cached, retrieve the model from the
+ * external system.
+ *
+ * @param uuid
+ * The model UUID.
+ * @return The DOM Element representing the model's root element.
+ * @throws ValidationServiceException
+ */
+ public Element get(ModelId uuid) throws ValidationServiceException {
+ if (uuid == null || uuid.isEmpty()) {
+ return null;
+ }
+
+ return getCachedModel(uuid);
+ }
+
+ private Element getCachedModel(ModelId uuid) throws ValidationServiceException {
+ Element element = null;
+ try {
+ element = modelCache.get(uuid);
+ } catch (ExecutionException e) {
+ // If the wrapped exception is a model validation error, return null.
+ Throwable cause = e.getCause();
+ if (cause != null && cause.getClass().equals(ValidationServiceException.class)
+ && (((ValidationServiceException) cause).getId().equals(ValidationServiceError.MODEL_NOT_FOUND.getId())
+ || ((ValidationServiceException) cause).getId().equals(ValidationServiceError.REST_CLIENT_RESPONSE_NOT_FOUND.getId()))) {
+ return null;
+ }
+ throw new ValidationServiceException(ValidationServiceError.MODEL_CACHE_ERROR, e, "");
+ }
+ return element;
+ }
+
+ /**
+ * Puts an item into the model cache.
+ *
+ * @param uuid
+ * The key.
+ * @param modelElement
+ * The value.
+ */
+ public void put(ModelId uuid, Element modelElement) {
+ modelCache.put(uuid, modelElement);
+ }
+
+ /**
+ * Constructs and invokes the configured REST URL. The XML payload is then parsed into a model Element.
+ *
+ * @param uuid
+ * The model UUID to retrieve.
+ * @return The payload of the REST URL call as a model Element.
+ * @throws ValidationServiceException
+ * if the payload is null
+ */
+ private Element retrieveModelElement(ModelId uuid) throws ValidationServiceException {
+ String protocol = restConfig.getProtocol();
+ Element modelElement;
+ if (FILE_MODEL_PROTOCOL.equals(protocol)) {
+ File modelFile = new File(restConfig.getBaseModelURI());
+ modelElement = retrieveModelElementFromFile(modelFile, uuid);
+ } else {
+ modelElement = retrieveModelElementFromREST(restConfig, uuid);
+ }
+
+ // Do not store a null value in the CacheBuilder
+ if (modelElement != null) {
+ return modelElement;
+ } else {
+ throw new ValidationServiceException(ValidationServiceError.MODEL_NOT_FOUND, uuid);
+ }
+ }
+
+ /**
+ * Constructs and invokes the configured REST URL to retrieve the model for the supplied UUID. The XML payload is
+ * then parsed into a model Element.
+ *
+ * @param uuid
+ * The model UUID to retrieve.
+ * @return The payload of the REST URL call as a model Element, or null of no model could be found.
+ * @throws ValidationServiceException
+ */
+ private Element retrieveModelElementFromREST(RestConfig restConfig, ModelId uuid)
+ throws ValidationServiceException {
+ try {
+ URI restURI = new URIBuilder(restConfig.getBaseModelURI())
+ .addParameter(uuid.getModelIdAttribute(), uuid.getModelId()).build();
+ String restPayload = new RestClient(restConfig).get(restURI.toString(), MediaType.APPLICATION_XML);
+ return XMLModelParser.parse(restPayload, true);
+ } catch (URISyntaxException e) {
+ throw new ValidationServiceException(ValidationServiceError.MODEL_RETRIEVAL_ERROR, e);
+ }
+ }
+
+ /**
+ * Retrieves the model with supplied model UUID from the supplied model file. The XML payload is then parsed into a
+ * model Element.
+ *
+ * @param uuid
+ * The model UUID to retrieve.
+ * @return The model Element, or null if no model could be found.
+ * @throws ValidationServiceException
+ */
+ private Element retrieveModelElementFromFile(File modelFile, ModelId uuid) throws ValidationServiceException {
+ Element rootElement = XMLModelParser.parse(modelFile, false);
+ // Check that the root element of the model file is correct.
+ if (!XMLModelParser.MODELS_ROOT_ELEMENT.equals(rootElement.getName())) {
+ return null;
+ }
+ return XMLModelParser.getModelElementWithId(rootElement, uuid.getModelIdAttribute(), uuid.getModelId());
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/ModelId.java b/src/main/java/org/onap/aai/validation/modeldriven/ModelId.java
new file mode 100644
index 0000000..25fbc22
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/ModelId.java
@@ -0,0 +1,86 @@
+/*
+ * ============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.modeldriven;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * Bean representing a model ID.
+ *
+ * The modelIdAttribute field defines the attribute in the model that holds the modelId
+ */
+public class ModelId {
+
+ public static final String ATTR_MODEL_NAME_VERSION_ID = "model-name-version-id";
+ public static final String ATTR_MODEL_ID = "model-id";
+
+ private String modelIdAttribute;
+ private String id;
+
+ /**
+ * @param modelIdAttribute The name of the attribute that holds the model ID.
+ * @param modelId The model ID.
+ */
+ public ModelId(String modelIdAttribute, String modelId) {
+ super();
+ this.modelIdAttribute = modelIdAttribute;
+ this.id = modelId;
+ }
+
+ public String getModelIdAttribute() {
+ return modelIdAttribute;
+ }
+
+ public void setModelIdAttribute(String modelIdAttribute) {
+ this.modelIdAttribute = modelIdAttribute;
+ }
+
+ public String getModelId() {
+ return id;
+ }
+
+ public void setModelId(String modelId) {
+ this.id = modelId;
+ }
+
+ public boolean isEmpty() {
+ return modelIdAttribute == null || id == null || modelIdAttribute.isEmpty() || id.isEmpty();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.id, this.modelIdAttribute);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ModelId)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ ModelId rhs = (ModelId) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(id, rhs.id)
+ .append(modelIdAttribute, rhs.modelIdAttribute)
+ .isEquals();
+ // @formatter:on
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/Filter.java b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/Filter.java
new file mode 100644
index 0000000..a251d54
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/Filter.java
@@ -0,0 +1,75 @@
+/*
+ * ============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.modeldriven.configuration.mapping;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * Defines a path and value that will be used to validate a particular model.
+ * @see ValueConfiguration and ModelInstanceMapper
+ */
+public class Filter {
+
+ private String path;
+ private List<String> valid = new ArrayList<>();
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public List<String> getValid() {
+ return valid;
+ }
+
+ public void setValid(List<String> valid) {
+ this.valid = valid;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path, valid);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Filter)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ Filter rhs = (Filter) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(path, rhs.path)
+ .append(valid, rhs.valid)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return "Filter [path=" + path + ", valid=" + valid + "]";
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMapper.java b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMapper.java
new file mode 100644
index 0000000..9ebe03c
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMapper.java
@@ -0,0 +1,89 @@
+/*
+ * ============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.modeldriven.configuration.mapping;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * Maps the configuration of model and instance values to be compared.
+ */
+public class ModelInstanceMapper {
+
+ /**
+ * Types of mappings.
+ */
+ public enum MappingType {
+ RELATIONSHIP, ATTRIBUTE
+ }
+
+ private MappingType mappingType;
+ private ValueConfiguration model;
+ private ValueConfiguration instance;
+
+ public MappingType getMappingType() {
+ return mappingType;
+ }
+
+ public void setMappingType(String mappingType) {
+ this.mappingType = MappingType.valueOf(mappingType);
+ }
+
+ public ValueConfiguration getModel() {
+ return model;
+ }
+
+ public void setModel(ValueConfiguration model) {
+ this.model = model;
+ }
+
+ public ValueConfiguration getInstance() {
+ return instance;
+ }
+
+ public void setInstance(ValueConfiguration instance) {
+ this.instance = instance;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(instance, mappingType, model);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ModelInstanceMapper)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ ModelInstanceMapper rhs = (ModelInstanceMapper) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(instance, rhs.instance)
+ .append(mappingType, rhs.mappingType)
+ .append(model, rhs.model)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return "ModelInstanceMapper [mappingType=" + mappingType + ", model=" + model + ", instance=" + instance + "]";
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMappingReader.java b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMappingReader.java
new file mode 100644
index 0000000..7be2d76
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMappingReader.java
@@ -0,0 +1,69 @@
+/*
+ * ============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.modeldriven.configuration.mapping;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.util.JsonUtil;
+
+/**
+ * Read mapping files
+ */
+public class ModelInstanceMappingReader {
+
+ private String mappingFile;
+
+ /**
+ * @param mappingFile
+ */
+ public ModelInstanceMappingReader(String mappingFile) {
+ this.mappingFile = mappingFile;
+ }
+
+ /**
+ * Returns a list of model and object instance paths that will be used for comparing the corresponding model and
+ * instance elements. The mappings are defined in the configuration file model-instance-mapping.json_conf and follows the
+ * JSON notation.
+ *
+ * @return a List of {@link ModelInstanceMapper} beans which represents all the mappings defined in the file
+ * model-instance-mapping.json_conf.
+ * @throws ValidationServiceException
+ */
+ public List<ModelInstanceMapper> getMappings() throws ValidationServiceException {
+ List<ModelInstanceMapper> mappings = new ArrayList<>();
+
+ try {
+ JSONArray mappingsArray = new JSONArray(mappingFile);
+
+ for (int i = 0; i < mappingsArray.length(); i++) {
+ JSONObject jsonObject = mappingsArray.getJSONObject(i);
+ ModelInstanceMapper mapping = JsonUtil.fromJson(jsonObject.toString(), ModelInstanceMapper.class);
+ mappings.add(mapping);
+ }
+ } catch (JSONException e) {
+ throw new ValidationServiceException(ValidationServiceError.MODEL_INSTANCE_MAPPING_RETRIEVAL_ERROR, e);
+ }
+
+ return mappings;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ValueConfiguration.java b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ValueConfiguration.java
new file mode 100644
index 0000000..230bbd9
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ValueConfiguration.java
@@ -0,0 +1,124 @@
+/*
+ * ============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.modeldriven.configuration.mapping;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * Describes a model or instance value that is used in model to instance comparison.
+ *
+ * @see ModelInstanceMapper
+ */
+public class ValueConfiguration {
+
+ /** Top level element. From which all navigation starts. */
+ private String origin;
+
+ /**
+ * Root element from which the value will be extracted. Can be used to recursively navigate the model/instance
+ * hierarchy.
+ */
+ private String root;
+
+ /**
+ * Provides validation on the model before the value is extracted. If the filter is not satisfied other root models
+ * will be searched.
+ */
+ private Filter filter;
+
+ /**
+ * Path to the model id
+ */
+ private String id;
+
+ /**
+ * Path to a model or instance value
+ */
+ private String value;
+
+ public String getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(String origin) {
+ this.origin = origin;
+ }
+
+ public String getRoot() {
+ return root;
+ }
+
+ public void setRoot(String root) {
+ this.root = root;
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(Filter filter) {
+ this.filter = filter;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.filter, this.origin, this.root, this.value, this.id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ValueConfiguration)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ ValueConfiguration rhs = (ValueConfiguration) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(filter, rhs.filter)
+ .append(origin, rhs.origin)
+ .append(root, rhs.root)
+ .append(value, rhs.value)
+ .append(id, rhs.id)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return "ValueConfiguration [origin=" + origin + ", root=" + root + ", id=" + id + ", filter=" + filter
+ + ", value=" + value + "]";
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/parser/XMLModelParser.java b/src/main/java/org/onap/aai/validation/modeldriven/parser/XMLModelParser.java
new file mode 100644
index 0000000..cf7aa38
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/parser/XMLModelParser.java
@@ -0,0 +1,152 @@
+/*
+ * ============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.modeldriven.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.List;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Node;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+
+/**
+ * Read models from XML file
+ *
+ */
+public class XMLModelParser {
+
+ public static final String MODEL_ROOT_ELEMENT = "model";
+ public static final String MODELS_ROOT_ELEMENT = "models";
+
+ private XMLModelParser() {
+ // Do nothing
+ }
+
+ /**
+ * Parses an xml file and returns the root Element.
+ *
+ * @param modelFile
+ * The XML File.
+ * @param validateModel
+ * If true the model will be validated.
+ * @return The root Element of the document.
+ * @throws DocumentException
+ * @throws ValidationServiceException
+ */
+ public static Element parse(File modelFile, boolean validateModel) throws ValidationServiceException {
+ try {
+ byte[] encoded = Files.readAllBytes(modelFile.toPath());
+ String modelString = new String(encoded, Charset.defaultCharset());
+
+ return parse(modelString, validateModel);
+ } catch (IOException e) {
+ throw new ValidationServiceException(ValidationServiceError.MODEL_PARSE_ERROR, e, modelFile.toString());
+ }
+ }
+
+ /**
+ * Parses an xml String and returns the root Element.
+ *
+ * @param modelString
+ * The XML String.
+ * @param validateModel
+ * If true the model will be validated.
+ * @return The root Element of the document, or null if no valid model can be parsed.
+ * @throws DocumentException
+ */
+ public static Element parse(String modelString, boolean validateModel) throws ValidationServiceException {
+ // Strip namespace information first.
+ String model = removeXmlStringNamespaceAndPreamble(modelString);
+
+ try {
+ Element modelElement = DocumentHelper.parseText(model).getRootElement();
+ if (validateModel && (modelElement == null || !isValidModel(modelElement))) {
+ return null;
+ }
+ return modelElement;
+ } catch (DocumentException e) {
+ throw new ValidationServiceException(ValidationServiceError.MODEL_PARSE_ERROR, e, model);
+ }
+ }
+
+ /**
+ * Returns the result of running the XPath expression on the DOM Node.
+ *
+ * @param currentNode
+ * The current Node being processed.
+ * @param xPath
+ * The XPath expression to run on the Node.
+ * @return A List of Nodes representing the result of the XPath expression.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Node> getObjectsFromXPath(Node currentNode, String xPath) {
+ return currentNode.selectNodes(xPath);
+ }
+
+ /**
+ * Returns the child model Element that corresponds to the provided root Element and model ID.
+ *
+ * @param rootElement
+ * The root Element to search.
+ * @param attributeName
+ * The name of the attribute that holds the model ID.
+ * @param modelId
+ * The model ID to search for.
+ * @return The model Element that matches the model ID supplied.
+ */
+ public static Element getModelElementWithId(Element rootElement, String attributeName, String modelId) {
+ Element modelElement = null;
+ List<Node> modelsList = getObjectsFromXPath(rootElement, MODEL_ROOT_ELEMENT + "/" + attributeName);
+ for (Node model : modelsList) {
+ if (model.getText().equals(modelId)) {
+ modelElement = model.getParent();
+ }
+ }
+ return modelElement;
+ }
+
+ /**
+ * Determines the validity of the supplied model element by checking if the name of the root element is correct.
+ *
+ * @param modelElement
+ * The model element to check.
+ * @return True if the root element matches the expected name.
+ */
+ private static boolean isValidModel(Element modelElement) {
+ return MODEL_ROOT_ELEMENT.equals(modelElement.getName());
+ }
+
+ /**
+ * Strips all namespace information from the model XML.
+ *
+ * @param modelXML
+ * The model XML as a String.
+ * @return The model XML String minus the namespace information.
+ */
+ private static String removeXmlStringNamespaceAndPreamble(String xmlString) {
+ return xmlString.replaceAll("(<\\?[^<]*\\?>)?", "") /* remove preamble */
+ .replaceAll("xmlns.*?(\"|\').*?(\"|\')", "") /* remove xmlns declaration */
+ .replaceAll("(<)(\\w+:)(.*?>)", "$1$3") /* remove opening tag prefix */
+ .replaceAll("(</)(\\w+:)(.*?>)", "$1$3"); /* remove closing tags prefix */
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/validator/InstanceReader.java b/src/main/java/org/onap/aai/validation/modeldriven/validator/InstanceReader.java
new file mode 100644
index 0000000..7832e51
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/validator/InstanceReader.java
@@ -0,0 +1,316 @@
+/*
+ * ============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.modeldriven.validator;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.jayway.jsonpath.DocumentContext;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.inject.Inject;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMapper;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMapper.MappingType;
+import org.onap.aai.validation.reader.JsonReader;
+import org.onap.aai.validation.reader.OxmReader;
+
+/**
+ * Reads values from an instance object.
+ */
+public class InstanceReader {
+
+ private static final String MODEL_NAME = "model-name";
+ private static final String[] INVALID_ENTRIES = { "inventory-response-items", "extra-properties", MODEL_NAME };
+ private static final String RESOURCE_VERSION = "resource-version";
+ private static final String JSON_PATH_MODEL_ID = "$.*.persona-model-id";
+
+ private JsonReader jsonReader;
+ private OxmReader oxmReader;
+ private JsonParser jsonParser = new JsonParser();
+
+ /**
+ * @param jsonReader
+ * @param oxmReader
+ */
+ @Inject
+ public InstanceReader(JsonReader jsonReader, OxmReader oxmReader) {
+ this.jsonReader = jsonReader;
+ this.oxmReader = oxmReader;
+ }
+
+ public OxmReader getOxmReader() {
+ return oxmReader;
+ }
+
+ /**
+ * Gets object instance values.
+ *
+ * @param json
+ * a Named Query JSON payload
+ * @param mapping
+ * defines the paths that allow the extraction of values from the object instance. This includes:
+ * <ul>
+ * <li>origin: path that serves as the starting point for the instance search</li>
+ * <li>root: path to underlying instance objects that can be examined by recursively calling the
+ * getValues method</li>
+ * </ul>
+ *
+ * @return a {@link Multimap} of instances keyed by their model id.
+ * @throws ValidationServiceException
+ */
+ public Multimap<String, String> getValues(String json, ModelInstanceMapper mapping) throws ValidationServiceException {
+ Multimap<String, String> values = HashMultimap.create();
+
+ DocumentContext document = jsonReader.parse(json);
+
+ if (MappingType.RELATIONSHIP.equals(mapping.getMappingType())) {
+ String rootPath = mapping.getInstance().getRoot();
+ if (rootPath == null || rootPath.isEmpty()) {
+ throw new ValidationServiceException(ValidationServiceError.INSTANCE_MAPPING_ROOT_ERROR);
+ }
+
+ JsonElement jsonElement = jsonReader.getJsonElement(document, rootPath);
+
+ if (jsonElement instanceof JsonArray) {
+ JsonArray jsonArray = jsonElement.getAsJsonArray();
+
+ processRelatedObjects(values, jsonArray);
+ }
+ } else {
+ // We are dealing with attributes.
+ String valuePath = mapping.getInstance().getValue();
+ if (valuePath != null && !valuePath.isEmpty()) {
+ List<String> attributes = jsonReader.get(json, valuePath);
+ for (String attribute : attributes) {
+ values.put(attribute, null);
+ }
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Gets the instance type, e.g. connector, pserver, etc.
+ *
+ * @param json
+ * a Named Query JSON payload
+ * @return the type of the entity
+ */
+ public String getInstanceType(String json) {
+ return getNamedQueryEntity(json).getEntityType();
+ }
+
+ /**
+ * Gets the id of the instance. Uses the {@link OxmReader} to identify the property holding the primary key.<br>
+ *
+ * WARNING: Some types of object appear to have more than one primary key. This method uses the first primary key.
+ *
+ * @param json
+ * a Named Query JSON payload
+ * @return the identifier of the object instance
+ * @throws ValidationServiceException
+ */
+ public String getInstanceId(String json) throws ValidationServiceException {
+ String instanceId = null;
+
+ InstanceEntity entity = getNamedQueryEntity(json);
+
+ List<String> primaryKeys = oxmReader.getPrimaryKeys(entity.getEntityType());
+
+ if (primaryKeys != null && !primaryKeys.isEmpty()) {
+ JsonObject instance = entity.getObject().getAsJsonObject();
+ JsonElement primaryKey = instance.get(primaryKeys.get(0));
+ instanceId = primaryKey == null ? null : primaryKey.getAsString();
+ }
+
+ return instanceId;
+ }
+
+ /**
+ * Strips the instance out of its payload wrapping.
+ *
+ * @param json
+ * a Named Query JSON payload
+ * @param mappings
+ * the definition of the paths that allow the extraction of the instance from the JSON payload
+ * @return
+ * @throws ValidationServiceException
+ */
+ public String getInstance(String json, List<ModelInstanceMapper> mappings) throws ValidationServiceException {
+ String origin = mappings.iterator().next().getInstance().getOrigin();
+ List<String> jsonList = jsonReader.get(json, origin);
+
+ if (!jsonList.isEmpty()) {
+ return jsonList.get(0);
+ } else {
+ throw new ValidationServiceException(ValidationServiceError.INSTANCE_READER_NO_INSTANCE, origin, json);
+ }
+ }
+
+ /**
+ * Extracts the entity from a Named Query JSON payload.
+ *
+ * @param json
+ * a Named Query JSON payload
+ * @return an {@link InstanceEntity} object
+ */
+ public InstanceEntity getNamedQueryEntity(String json) {
+ return getNamedQueryEntity(jsonParser.parse(json).getAsJsonObject());
+ }
+
+ /**
+ * Gets the model identifier of a given entity.
+ *
+ * @param entity
+ * a JSON entity
+ * @return a model identifier attribute value if the attribute exists else a null is returned.
+ * @throws ValidationServiceException
+ */
+ public String getModelId(String entity) throws ValidationServiceException {
+ String modelId = null;
+ List<String> readResult = jsonReader.get(entity, JSON_PATH_MODEL_ID);
+ if (!readResult.isEmpty()) {
+ modelId = readResult.get(0);
+ }
+ return modelId;
+ }
+
+ /**
+ * Gets the resource version of the instance.
+ *
+ * @param json
+ * a Named Query JSON payload
+ * @return the resource version of the object instance
+ */
+ public String getResourceVersion(String json) {
+ String resourceVersion = null;
+
+ InstanceEntity entity = getNamedQueryEntity(json);
+
+ if (entity != null && entity.getObject() != null && entity.getObject().getAsJsonObject().has(RESOURCE_VERSION)) {
+ resourceVersion = entity.getObject().getAsJsonObject().get(RESOURCE_VERSION).getAsString();
+ }
+ return resourceVersion;
+ }
+
+ /**
+ * Gets the model name of the instance.
+ *
+ * @param jsonString
+ * a Named Query JSON payload
+ * @return the model name of the object instance
+ * @throws ValidationServiceException
+ */
+ public String getModelName(String jsonString) {
+ JsonObject jsonObject = jsonParser.parse(jsonString).getAsJsonObject();
+ return getModelName(jsonObject);
+ }
+
+ /**
+ * @param jsonObject
+ * @return
+ */
+ private String getModelName(JsonObject jsonObject) {
+ for (Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+ if (MODEL_NAME.equals(entry.getKey())) {
+ return entry.getValue().getAsString();
+ }
+ }
+ return null;
+ }
+
+ private void processRelatedObjects(Multimap<String, String> values, JsonArray jsonArray) {
+ for (JsonElement relatedObject : jsonArray) {
+ JsonObject jsonObject = relatedObject.getAsJsonObject();
+
+ InstanceEntity entity = getNamedQueryEntity(jsonObject);
+ if (entity != null) {
+ values.put(entity.getModelName() == null ? entity.getEntityType() : entity.getModelName(), jsonObject.toString());
+ }
+ }
+ }
+
+ private InstanceEntity getNamedQueryEntity(JsonObject jsonObject) {
+ Set<Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
+
+ String modelName = getModelName(jsonObject);
+
+ for (Entry<String, JsonElement> entry : entrySet) {
+ if (!Arrays.asList(INVALID_ENTRIES).contains(entry.getKey())) {
+ return new InstanceEntity(entry.getKey(), modelName, entry.getValue().getAsJsonObject(), jsonObject);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * An Entity bean for the InstanceReader
+ *
+ */
+ public class InstanceEntity {
+
+ private String entityType;
+ private String modelName;
+ private JsonObject object;
+ private JsonObject objectAndGraph;
+
+ /**
+ * @param entityType
+ * @param modelName
+ * @param object
+ * @param objectAndGraph
+ */
+ public InstanceEntity(String entityType, String modelName, JsonObject object, JsonObject objectAndGraph) {
+ this.entityType = entityType;
+ this.modelName = modelName;
+ this.object = object;
+ this.objectAndGraph = objectAndGraph;
+ }
+
+ public String getEntityType() {
+ return entityType;
+ }
+
+ public String getModelName() {
+ return modelName;
+ }
+
+ public JsonObject getObject() {
+ return object;
+ }
+
+ public JsonObject getObjectAndGraph() {
+ return objectAndGraph;
+ }
+
+ @Override
+ public String toString() {
+ return "Entity [entityType=" + entityType + ", modelName=" + modelName + ", object=" + object.toString() + ", fullObject="
+ + objectAndGraph.toString() + "]";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/validator/ModelDrivenValidator.java b/src/main/java/org/onap/aai/validation/modeldriven/validator/ModelDrivenValidator.java
new file mode 100644
index 0000000..1b8ab00
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/validator/ModelDrivenValidator.java
@@ -0,0 +1,306 @@
+/*
+ * ============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.modeldriven.validator;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.gson.JsonObject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.inject.Inject;
+import org.apache.commons.collections.CollectionUtils;
+import org.dom4j.Node;
+import org.onap.aai.validation.Validator;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.modeldriven.ModelCacheManager;
+import org.onap.aai.validation.modeldriven.ModelId;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMapper;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMappingReader;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMapper.MappingType;
+import org.onap.aai.validation.reader.EntityReader;
+import org.onap.aai.validation.reader.EventReader;
+import org.onap.aai.validation.reader.InstanceEntityReader;
+import org.onap.aai.validation.reader.data.Entity;
+import org.onap.aai.validation.reader.data.EntityId;
+import org.onap.aai.validation.result.ValidationResult;
+import org.onap.aai.validation.result.Violation;
+import org.onap.aai.validation.result.Violation.Builder;
+import org.onap.aai.validation.result.Violation.ViolationType;
+
+/**
+ * Validates object instances against the ONAP model.
+ *
+ */
+public class ModelDrivenValidator implements Validator {
+
+ /**
+ * Types of validation rules.
+ */
+ public enum RuleType {
+ REL, ATTR
+ }
+
+ /**
+ * Types of validation outcomes.
+ */
+ public enum ValidationOutcomeType {
+ NO_MODEL, MISSING, UNEXPECTED;
+ }
+
+ private ModelCacheManager modelCacheManager;
+ private List<ModelInstanceMapper> mappings;
+ private InstanceReader instanceReader;
+ private EventReader eventReader;
+
+ /**
+ * Constructor defining injected dependencies.
+ *
+ * @param modelCacheManager
+ * a cache manager for the models
+ * @param modelInstanceMappingReader
+ * a configuration reader to provide model and instance mapping
+ * @param instanceReader
+ * a reader of A&AI instances
+ * @param eventReader
+ * @throws ValidationServiceException
+ */
+ @Inject
+ public ModelDrivenValidator(ModelCacheManager modelCacheManager, ModelInstanceMappingReader modelInstanceMappingReader, InstanceReader instanceReader,
+ EventReader eventReader) throws ValidationServiceException {
+ this.modelCacheManager = modelCacheManager;
+ this.mappings = modelInstanceMappingReader.getMappings();
+ this.instanceReader = instanceReader;
+ this.eventReader = eventReader;
+ }
+
+ @Override
+ public void initialise() throws ValidationServiceException {
+ // Deliberately empty
+ }
+
+ /**
+ * Validates the given event instance against the ECOMP model.
+ *
+ * @param eventInstance
+ * the event instance to be validated
+ * @return {@link Violation} with the results of the object validation
+ * @throws ValidationServiceException
+ */
+ @Override
+ public List<ValidationResult> validate(String eventInstance) throws ValidationServiceException {
+ // Read event json into Entity bean
+ Entity eventEntity = eventReader.getEntity(eventInstance);
+
+ EntityReader reader = new InstanceEntityReader(instanceReader);
+ String entityJson = instanceReader.getInstance(eventEntity.getJson(), mappings);
+ Entity instanceEntity = new Entity(entityJson, instanceReader.getInstanceType(entityJson), eventEntity.getEntityLink(), reader);
+
+ // Get model ID from object instance and retrieve corresponding model.
+ ModelId modelId = new ModelId(ModelId.ATTR_MODEL_ID, instanceReader.getModelId(instanceEntity.getJson()));
+ Node modelElement = modelCacheManager.get(modelId);
+
+ List<Violation> violations = new ArrayList<>();
+
+ if (modelElement == null) {
+ ViolationInfo info = ViolationInfo.valueOf(ModelDrivenValidator.ValidationOutcomeType.NO_MODEL.toString());
+ Map<String, Object> details = new HashMap<>();
+ details.put("No model ID", modelId.getModelId());
+ Violation.Builder builder = new Violation.Builder(instanceEntity).violationType(ViolationType.MODEL);
+ builder.category(info.getCategory()).severity(info.getSeverity()).violationDetails(details)
+ .errorMessage(info.getErrorMessage(modelId.getModelId()));
+ violations.add(builder.build());
+ } else {
+ // Validate model with instance according to mappings.
+ for (ModelInstanceMapper mapping : mappings) {
+ List<Violation> currentViolations = new ArrayList<>();
+
+ // If we are validating related objects, find the first valid child object to begin validating from.
+ Node validModelElement = modelElement;
+ if (MappingType.RELATIONSHIP.equals(mapping.getMappingType()) && !ModelReader.isValidModelType(modelElement, mapping)) {
+ Multimap<String, Node> models = HashMultimap.create();
+ ModelReader.getValuesAndModels(modelElement, mapping, modelCacheManager, models);
+ validModelElement = models.isEmpty() ? modelElement : models.values().iterator().next();
+ }
+
+ validateAllRecursive(validModelElement, instanceEntity, mapping, currentViolations, reader);
+ violations.addAll(currentViolations);
+ }
+ }
+
+ ValidationResult validationResult = new ValidationResult(instanceEntity);
+
+ // This is a shortcut to passing the parent model name all the way down.
+ populateViolationModelNames(violations, instanceEntity);
+
+ validationResult.addViolations(violations);
+
+ return Arrays.asList(validationResult);
+ }
+
+ /*
+ * Recursively validates all child entities starting with the crown widget.
+ */
+ private void validateAllRecursive(Node currentModelNode, Entity entity, ModelInstanceMapper mapping, List<Violation> validations, EntityReader reader)
+ throws ValidationServiceException {
+ String entityLink = null;
+ Multimap<String, Node> modelMap = ModelReader.getValues(currentModelNode, mapping, modelCacheManager);
+ Multimap<String, String> instanceMap = instanceReader.getValues(entity.getJson(), mapping);
+
+ // Validate model with instance according to mappings.
+ // Note: Currently the cardinality of instances are not validated.
+ List<Violation> currentViolations = validateModelInstanceValues(modelMap.keySet(), instanceMap.keySet(), entity, mapping);
+ validations.addAll(currentViolations);
+
+ // Remove erroring objects from the maps so that we don't validate their children.
+ for (Violation currentViolation : currentViolations) {
+ String entityType = (String) currentViolation.getViolationDetails().get(Violation.ENTITY_TYPE_PROPERTY);
+ if (entityType != null) {
+ modelMap.removeAll(entityType);
+ instanceMap.removeAll(entityType);
+ }
+ }
+
+ // Continue down the model hierarchy for objects that did not error in the current layer.
+ for (Entry<String, Node> modelEntry : modelMap.entries()) {
+ // Get the child model.
+ Node childModelNode = modelEntry.getValue();
+ if (childModelNode != null) {
+ // Validate all child instance objects with current child model.
+ Collection<String> childInstanceObjects = instanceMap.get(modelEntry.getKey());
+ for (String childInstanceObject : childInstanceObjects) {
+ Entity childEntity = new Entity(childInstanceObject, instanceReader.getInstanceType(childInstanceObject), entityLink, reader);
+ validateAllRecursive(childModelNode, childEntity, mapping, validations, reader);
+ }
+ }
+ }
+ }
+
+ /**
+ * Compares the List of values found in the model with the values found in the Json and generates validation errors
+ * based on the differences.
+ *
+ * @param modelValues
+ * the values found in the model
+ * @param instanceValues
+ * the values found in the Json.
+ * @param entity
+ * @param modelInstanceMapper
+ * the mappings used to to find the model and instance values
+ * @return List of Validation objects representing the errors found during validation.
+ * @throws ValidationServiceException
+ */
+ private List<Violation> validateModelInstanceValues(Set<String> modelValues, Set<String> instanceValues, Entity entity,
+ ModelInstanceMapper modelInstanceMapper) throws ValidationServiceException {
+ List<Violation> violations = new ArrayList<>();
+
+ Collection<?> missingValues = CollectionUtils.subtract(modelValues, instanceValues);
+ violations.addAll(setViolationDetails(ValidationOutcomeType.MISSING, missingValues, entity, modelInstanceMapper));
+
+ Collection<?> unexpectedValues = CollectionUtils.subtract(instanceValues, modelValues);
+ violations.addAll(setViolationDetails(ValidationOutcomeType.UNEXPECTED, unexpectedValues, entity, modelInstanceMapper));
+
+ return violations;
+ }
+
+ /*
+ * Sets the list of {@link Violation} objects with all the violation details.
+ *
+ * @param outcome
+ *
+ * @param values the values that are causing the violation
+ *
+ * @param entity the entity being validated
+ *
+ * @param modelInstanceMapper the mappings used to to find the model and instance values
+ *
+ * @return list of {@link Violation} objects set by this method
+ *
+ * @throws ValidationServiceException
+ */
+ private List<Violation> setViolationDetails(ValidationOutcomeType outcome, Collection<?> values, Entity entity, ModelInstanceMapper modelInstanceMapper)
+ throws ValidationServiceException {
+
+ List<Violation> violations = new ArrayList<>();
+ Builder builder = new Builder(entity).violationType("Model");
+
+ for (Object value : values) {
+ RuleType ruleType = modelInstanceMapper.getMappingType().equals(MappingType.RELATIONSHIP) ? RuleType.REL : RuleType.ATTR;
+ String category = outcome.toString() + "_" + ruleType;
+
+ ViolationInfo info = ViolationInfo.valueOf(category);
+ builder.category(info.getCategory());
+ builder.severity(info.getSeverity());
+
+ Map<String, Object> details = new HashMap<>();
+ details.put(outcome.toString() + " " + ruleType.toString(), value);
+
+ switch (ruleType) {
+ case ATTR:
+ builder.errorMessage(info.getErrorMessage(value));
+ break;
+ case REL:
+ buildEntityIdDetails(details, entity);
+ builder.errorMessage(info.getErrorMessage(entity.getIds().toString(), entity.getType(), value));
+ break;
+ default:
+ // Do nothing
+ break;
+ }
+ builder.violationDetails(details);
+ violations.add(builder.build());
+ }
+
+ return violations;
+ }
+
+ /*
+ * Add entity IDs to the violation details
+ *
+ * @param details the violation details to populate
+ *
+ * @param entity
+ *
+ * @throws ValidationServiceException
+ */
+ private void buildEntityIdDetails(Map<String, Object> details, Entity entity) throws ValidationServiceException {
+ JsonObject entityIdsObject = new JsonObject();
+ for (EntityId entityId : entity.getIds()) {
+ entityIdsObject.addProperty(entityId.getPrimaryKey(), entityId.getValue());
+ }
+ details.put(Violation.ENTITY_ID_PROPERTY, entityIdsObject);
+ details.put(Violation.ENTITY_TYPE_PROPERTY, entity.getType());
+ details.put(Violation.ENTITY_MODELNAME_PROPERTY, instanceReader.getModelName(entity.getJson()));
+ }
+
+ /*
+ * Sets the model name attribute on all violations in the list. The model name is retrieved from the entity
+ * provided.
+ */
+ private void populateViolationModelNames(List<Violation> violations, Entity entity) {
+ String modelName = instanceReader.getModelName(entity.getJson());
+ for (Violation violation : violations) {
+ violation.setModelName(modelName);
+ }
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/validator/ModelReader.java b/src/main/java/org/onap/aai/validation/modeldriven/validator/ModelReader.java
new file mode 100644
index 0000000..04728a3
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/validator/ModelReader.java
@@ -0,0 +1,236 @@
+/*
+ * ============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.modeldriven.validator;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.dom4j.Node;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.modeldriven.ModelCacheManager;
+import org.onap.aai.validation.modeldriven.ModelId;
+import org.onap.aai.validation.modeldriven.configuration.mapping.Filter;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMapper;
+import org.onap.aai.validation.modeldriven.configuration.mapping.ModelInstanceMapper.MappingType;
+import org.onap.aai.validation.modeldriven.parser.XMLModelParser;
+
+/**
+ * Reads values from the model.
+ */
+public class ModelReader {
+
+ private static final String ATTRIBUTE_MODELTYPE = "model-type";
+
+ /**
+ * Do not instantiate an object of this class
+ */
+ private ModelReader() {
+ // Deliberately empty
+ }
+
+ /**
+ * Gets the values of a model element as defined by the model-instance mapping configuration. When the mapping
+ * type is "attribute", the multimap will be returned with a null value.
+ *
+ * @param modelElement
+ * the model element from which the values will be extracted
+ * @param mapping
+ * the model-instance mapping object defining the path to the model values
+ * @param modelCacheManager
+ * the model cache manager used to retrieve further models
+ * @return a {@link Multimap} of model values.
+ * @throws ValidationServiceException
+ */
+ public static Multimap<String, Node> getValues(Node modelElement, ModelInstanceMapper mapping, ModelCacheManager modelCacheManager)
+ throws ValidationServiceException {
+ Multimap<String, Node> values = HashMultimap.create();
+
+ if (MappingType.ATTRIBUTE.equals(mapping.getMappingType())) {
+ // Get attributes on current model element.
+ Multimap<String, Node> modelValues = getModelValues(modelElement, mapping, false);
+ if (modelValues.isEmpty()) {
+ throw new ValidationServiceException(ValidationServiceError.MODEL_VALUE_ERROR,
+ mapping.getModel().getValue(), modelElement.asXML());
+ }
+ values.putAll(modelValues);
+ } else {
+ // Get related objects.
+ getValuesAndModels(modelElement, mapping, modelCacheManager, values);
+ }
+
+
+ return values;
+ }
+
+ /**
+ * Returns the model type property of the current model element.
+ *
+ * @param model
+ * The current model element.
+ * @return the model type of the current element or null if not found.
+ */
+ public static String getModelType(Node model) {
+ String modelType = null;
+ List<Node> modelTypeElements = XMLModelParser.getObjectsFromXPath(model, ATTRIBUTE_MODELTYPE);
+ if (!modelTypeElements.isEmpty()) {
+ modelType = modelTypeElements.iterator().next().getText();
+ }
+ return modelType;
+ }
+
+ /**
+ * @param model
+ * @param mapping
+ * @return True if supplied model is of type widget.
+ */
+ public static boolean isValidModelType(Node model, ModelInstanceMapper mapping) {
+ Collection<String> validTypes = mapping.getModel().getFilter().getValid();
+ return validTypes.isEmpty() ? false :validTypes.contains(getModelType(model));
+ }
+
+ /**
+ * Populates a Multimap of models. If a root and filter are defined in the mapping it will navigate the model to find a
+ * valid models according to the filter. If the root property is not defined a model is not returned.
+ *
+ * @param model
+ * the model to be inspected
+ * @param mapping
+ * the model-instance mapping object defining the root model
+ * @param modelCacheManager
+ * the model cache manager used to retrieve further models
+ * @param models
+ * a Multimap of models that will be populated with further models
+ * @throws ValidationServiceException
+ */
+ public static void getValuesAndModels(Node model, ModelInstanceMapper mapping, ModelCacheManager modelCacheManager, Multimap<String, Node> models) throws ValidationServiceException {
+ String root = mapping.getModel().getRoot();
+
+ if (root == null) {
+ return;
+ }
+
+ List<Node> childModelElements = XMLModelParser.getObjectsFromXPath(model, root);
+ for (Node childModel : childModelElements) {
+ // If the child element is a leaf, this could either mean the end of the hierarchy, or that we have
+ // encountered a resource and need to retrieve a separate model to continue the model traversal.
+ List<String> modelNames = getModelValuesList(childModel, mapping);
+ if (!hasChildren(childModel, root) && !isValidModel(childModel, mapping) && mapping.getModel().getId() != null) {
+ childModel = getChildModelNode(modelCacheManager, childModel, mapping);
+ }
+
+ if (isValidModel(childModel, mapping)) {
+ for (String modelName : modelNames) {
+ models.put(modelName, childModel);
+ }
+ } else {
+ getValuesAndModels(childModel, mapping, modelCacheManager, models);
+ }
+ }
+ }
+
+ /**
+ * Find the next child model given a specific node.
+ *
+ * @param modelCacheManager
+ * the model cache manager used to retrieve further models
+ * @param node
+ * the top-level node under which child model nodes are searched
+ * @param rootXPath
+ * the path expression to apply to the node to find child elements
+ * @param modelIdPath
+ * the path expression to apply to the node to find the child model IDs
+ * @return either or the {@code node} if there were no matches for {@code id}
+ * @throws ValidationServiceException
+ */
+ private static Node getChildModelNode(ModelCacheManager modelCacheManager, Node node, ModelInstanceMapper mapping) throws ValidationServiceException {
+ Node childModel = node;
+
+ // Get the model for the specified node to check its type.
+ // Only one model ID is expected, although the API returns a list.
+ List<Node> childModelIds = XMLModelParser.getObjectsFromXPath(node, mapping.getModel().getId());
+
+ if (!childModelIds.isEmpty()) {
+ // Found the child model ID, so retrieve the child model from cache.
+ ModelId modelId = new ModelId(ModelId.ATTR_MODEL_NAME_VERSION_ID, childModelIds.iterator().next().getText());
+ Node fullChildModel = modelCacheManager.get(modelId);
+
+ if (fullChildModel != null && !isValidModelType(fullChildModel, mapping)) {
+ // Child model is not a widget so replace current child model with the full child model
+ // retrieved from the cache.
+ List<Node> fullChildModelElements = XMLModelParser.getObjectsFromXPath(fullChildModel, mapping.getModel().getRoot());
+ // Only one crown widget is expected, although the API returns a list.
+ childModel = fullChildModelElements.isEmpty() ? node : fullChildModelElements.iterator().next();
+ }
+ }
+
+ return childModel;
+ }
+
+ private static Multimap<String, Node> getModelValues(Node model, ModelInstanceMapper mapping, boolean addModel) {
+ Multimap<String, Node> values = HashMultimap.create();
+ List<String> valueStrings = getModelValuesList(model, mapping);
+ for (String value : valueStrings) {
+ values.put(value, addModel ? model : null);
+ }
+ return values;
+ }
+
+ private static List<String> getModelValuesList(Node model, ModelInstanceMapper mapping) {
+ List<String> values = new ArrayList<>();
+ List<Node> valueElements = XMLModelParser.getObjectsFromXPath(model, mapping.getModel().getValue());
+ for (Node node : valueElements) {
+ values.add(node.getText());
+ }
+ return values;
+ }
+
+ private static boolean isValidModel(Node node, ModelInstanceMapper mapping) {
+ Filter filter = mapping.getModel().getFilter();
+ if (filter == null) {
+ return true;
+ }
+
+ List<String> valid = filter.getValid();
+ // If there are no valid values, return false.
+ if (valid.isEmpty()) {
+ return false;
+ }
+
+ String filterXPath = filter.getPath();
+ if (filterXPath == null) {
+ return false;
+ }
+
+ List<Node> filterNodes = XMLModelParser.getObjectsFromXPath(node, filterXPath);
+ for (Node filterNode : filterNodes) {
+ String text = filterNode.getText();
+ if (valid.contains(text)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean hasChildren(Node parent, String rootXPath) {
+ return !XMLModelParser.getObjectsFromXPath(parent, rootXPath).isEmpty();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/modeldriven/validator/ViolationInfo.java b/src/main/java/org/onap/aai/validation/modeldriven/validator/ViolationInfo.java
new file mode 100644
index 0000000..e452d73
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/modeldriven/validator/ViolationInfo.java
@@ -0,0 +1,88 @@
+/*
+ * ============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.modeldriven.validator;
+
+import java.text.MessageFormat;
+import org.onap.aai.validation.controller.ValidationController;
+
+/**
+ * Defines and formats the violation information.
+ */
+public enum ViolationInfo {
+ //@formatter:off
+ NO_MODEL ("NO_MODEL", ValidationController.VALIDATION_ERROR_SEVERITY, "No model ID=[{0}]", "The model [{0}] could not be found"),
+ MISSING_ATTR ("MISSING_ATTR", ValidationController.VALIDATION_ERROR_SEVERITY, "{0} {1}=[{2}]", "Attribute [{0}] is missing in the object instance"),
+ UNEXPECTED_ATTR ("UNEXPECTED_ATTR", ValidationController.VALIDATION_ERROR_SEVERITY, "{0} {1}=[{2}]", "Attribute [{0}] should not be present in the object instance"),
+ MISSING_REL ("MISSING_REL", ValidationController.VALIDATION_ERROR_SEVERITY, "entityId=[{0}] entityType=[{1}], {2} {3}=[{4}]", "Entity {0} of type [{1}] must be related to [{2}]"),
+ UNEXPECTED_REL ("UNEXPECTED_REL", ValidationController.VALIDATION_ERROR_SEVERITY, "entityId=[{0}] entityType=[{1}], {2} {3}=[{4}]", "Entity {0} of type [{1}] must not be related to [{2}]");
+ //@formatter:on
+
+ private String category;
+ private String severity;
+ private String violationDetails;
+ private String errorMessage;
+
+ /**
+ * @param category
+ * @param severity
+ * @param violationDetails
+ * @param errorMessage
+ */
+ private ViolationInfo(String category, String severity, String violationDetails, String errorMessage) {
+ this.category = category;
+ this.severity = severity;
+ this.violationDetails = violationDetails;
+ this.errorMessage = errorMessage;
+ }
+
+ /**
+ * @return
+ */
+ public String getCategory() {
+ return this.category;
+ }
+
+ /**
+ * @return
+ */
+ public String getSeverity() {
+ return this.severity;
+ }
+
+ /**
+ * @param args
+ * @return
+ */
+ public String getViolationDetails(Object... args) {
+ return formatter(this.violationDetails, args);
+ }
+
+ /**
+ * @param args
+ * @return
+ */
+ public String getErrorMessage(Object... args) {
+ return formatter(this.errorMessage, args);
+ }
+
+ private String formatter(String violationInfo, Object... args) {
+ MessageFormat formatter = new MessageFormat("");
+ formatter.applyPattern(violationInfo);
+ return formatter.format(args);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/publisher/MessagePublisher.java b/src/main/java/org/onap/aai/validation/publisher/MessagePublisher.java
new file mode 100644
index 0000000..94e1f29
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/publisher/MessagePublisher.java
@@ -0,0 +1,47 @@
+/*
+ * ============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.publisher;
+
+import java.util.Collection;
+import org.onap.aai.validation.exception.ValidationServiceException;
+
+/**
+ * A Publisher of messages.
+ *
+ */
+public interface MessagePublisher {
+
+ /**
+ * Sends a message somewhere.
+ *
+ * @param message
+ * The String message to send.
+ * @throws ValidationServiceException
+ */
+ void publishMessage(String message) throws ValidationServiceException;
+
+ /**
+ * Sends a Collection of messages somewhere.
+ *
+ * @param messages
+ * The String messages to send.
+ * @throws ValidationServiceException
+ */
+ void publishMessages(Collection<String> messages) throws ValidationServiceException;
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/publisher/ValidationEventPublisher.java b/src/main/java/org/onap/aai/validation/publisher/ValidationEventPublisher.java
new file mode 100644
index 0000000..0cebbf9
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/publisher/ValidationEventPublisher.java
@@ -0,0 +1,164 @@
+/*
+ * ============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.publisher;
+
+import org.onap.aai.event.client.DMaaPEventPublisher;
+import org.onap.aai.validation.config.TopicAdminConfig;
+import org.onap.aai.validation.config.TopicConfig;
+import org.onap.aai.validation.config.TopicConfig.Topic;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.factory.DMaaPEventPublisherFactory;
+import org.onap.aai.validation.logging.ApplicationMsgs;
+import org.onap.aai.validation.logging.LogHelper;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.inject.Inject;
+
+/**
+ * Event Publisher
+ *
+ */
+public class ValidationEventPublisher implements MessagePublisher {
+
+ private static LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ private List<Topic> publisherTopics;
+
+ private boolean enablePublishing;
+
+ private long retries;
+
+ private long retriesRemaining;
+
+ private DMaaPEventPublisherFactory dMaapFactory;
+
+
+ /**
+ * Instantiates an Event Publisher instance using properties from config file.
+ *
+ * @param topicConfig
+ * @param topicAdminConfig
+ */
+ @Inject
+ public ValidationEventPublisher(TopicConfig topicConfig, TopicAdminConfig topicAdminConfig) {
+ enablePublishing = topicAdminConfig.isPublishEnable();
+ if (enablePublishing) {
+ publisherTopics = topicConfig.getPublisherTopics();
+ retries = topicAdminConfig.getPublishRetries();
+ }
+ dMaapFactory = new DMaaPEventPublisherFactory();
+ }
+
+ /**
+ * Connect to the event publisher, add the message, and then publish it by closing the publisher.
+ */
+ @Override
+ public void publishMessage(String message) throws ValidationServiceException {
+ Collection<String> messages = new ArrayList<>();
+ messages.add(message);
+ publishMessages(messages);
+ }
+
+ /**
+ * Connect to the event publisher, adds the messages, and then publish them by closing the publisher.
+ */
+ @Override
+ public void publishMessages(Collection<String> messages) throws ValidationServiceException {
+ if (!enablePublishing) {
+ return;
+ } else {
+ applicationLogger.debug("Publishing messages: " + messages);
+ for (Topic topic : publisherTopics) {
+ retriesRemaining = retries;
+ publishMessages(messages, topic);
+ }
+ }
+ }
+
+ private void publishMessages(Collection<String> messages, Topic topic) throws ValidationServiceException {
+
+ DMaaPEventPublisher dMaapEventPublisher = dMaapFactory.createEventPublisher(topic.getHost(), topic.getName(), topic.getUsername(),
+ topic.getPassword(), topic.getTransportType());
+
+ try {
+ // Add our message to the publisher's queue/bus
+ int result = dMaapEventPublisher.sendSync(topic.getPartition(), messages);
+ if (result != messages.size()) {
+ applicationLogger.warn(ApplicationMsgs.UNSENT_MESSAGE_WARN);
+ closeEventPublisher(dMaapEventPublisher);
+ retryOrThrow(messages, topic, new ValidationServiceException(
+ ValidationServiceError.EVENT_CLIENT_INCORRECT_NUMBER_OF_MESSAGES_SENT, result));
+ }
+ } catch (Exception e) {
+ applicationLogger.error(ApplicationMsgs.UNSENT_MESSAGE_ERROR);
+ closeEventPublisher(dMaapEventPublisher);
+ retryOrThrow(messages, topic,
+ new ValidationServiceException(ValidationServiceError.EVENT_CLIENT_SEND_ERROR, e));
+ }
+
+ completeMessageSending(dMaapEventPublisher, topic);
+ }
+
+ /**
+ * Publish the queued messages by closing the publisher.
+ *
+ * @param eventPublisher the publisher to close
+ * @throws AuditException
+ */
+ private void completeMessageSending(DMaaPEventPublisher eventPublisher, Topic topic)
+ throws ValidationServiceException {
+ List<String> unsentMsgs = closeEventPublisher(eventPublisher);
+
+ if (unsentMsgs != null && !unsentMsgs.isEmpty()) {
+ // Log the error, as the exception will not be propagated due to the fact that the Cambria Client throws
+ // an exception first in a separate thread.
+ applicationLogger.error(ApplicationMsgs.EVENT_CLIENT_CLOSE_UNSENT_MESSAGE,
+ ValidationServiceError.EVENT_CLIENT_CLOSE_UNSENT_MESSAGE.getMessage(unsentMsgs));
+
+ retryOrThrow(unsentMsgs, topic, new ValidationServiceException(
+ ValidationServiceError.EVENT_CLIENT_CLOSE_UNSENT_MESSAGE, unsentMsgs));
+ }
+ }
+
+ private void retryOrThrow(Collection<String> messages, Topic topic, ValidationServiceException exceptionToThrow)
+ throws ValidationServiceException {
+ if (retriesRemaining <= 0) {
+ applicationLogger.warn(ApplicationMsgs.SEND_MESSAGE_ABORT_WARN);
+ throw exceptionToThrow;
+ } else {
+ applicationLogger.warn(ApplicationMsgs.SEND_MESSAGE_RETRY_WARN);
+ retriesRemaining--;
+ publishMessages(messages, topic);
+ }
+ }
+
+ private List<String> closeEventPublisher(DMaaPEventPublisher eventPublisher) throws ValidationServiceException {
+ try {
+ return eventPublisher.closeWithUnsent();
+ } catch (Exception e) {
+ throw new ValidationServiceException(ValidationServiceError.EVENT_CLIENT_CLOSE_ERROR, e);
+ }
+ }
+
+ public void setEventPublisherFactory(DMaaPEventPublisherFactory dMaapFactory) {
+ this.dMaapFactory = dMaapFactory;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/reader/EntityReader.java b/src/main/java/org/onap/aai/validation/reader/EntityReader.java
new file mode 100644
index 0000000..f55af2f
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/EntityReader.java
@@ -0,0 +1,61 @@
+/*
+ * ============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.reader;
+
+import java.util.List;
+import java.util.Optional;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.reader.data.EntityId;
+
+/**
+ * Interface for extracting values from an entity (in JSON format).
+ *
+ */
+public interface EntityReader {
+
+ /**
+ * Return the value found at the supplied path.
+ *
+ * @param json
+ * the JSON representation of the entity
+ * @param path
+ * specifier of the path to the value within the JSON entity
+ * @return either a primitive object (e.g. String, Integer) or a JSON element
+ * @throws ValidationServiceException
+ */
+ Object getObject(String json, String path) throws ValidationServiceException;
+
+ /**
+ * @param json
+ * the JSON representation of the entity
+ * @param type
+ * the type of the entity
+ * @return the key value(s) identifying the entity
+ * @throws ValidationServiceException
+ */
+ List<EntityId> getIds(String json, String type) throws ValidationServiceException;
+
+ /**
+ * @param json
+ * the JSON representation of the entity
+ * @return the resource version of the entity (if present)
+ * @throws ValidationServiceException
+ */
+ Optional<String> getResourceVersion(String json) throws ValidationServiceException;
+
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/EventEntityReader.java b/src/main/java/org/onap/aai/validation/reader/EventEntityReader.java
new file mode 100644
index 0000000..7f285f8
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/EventEntityReader.java
@@ -0,0 +1,118 @@
+/*
+ * ============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.reader;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.onap.aai.validation.config.EventReaderConfig;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.reader.data.EntityId;
+
+/**
+ * An entity reader for reading A&AI events. This implementation is used by the EventReader
+ *
+ */
+public class EventEntityReader implements EntityReader {
+
+ private JsonReader jsonReader;
+ private OxmReader oxmReader;
+
+ private EventReaderConfig config;
+
+ /**
+ * @param eventReaderConfig
+ * @param jsonReader
+ * @param oxmReader
+ */
+ public EventEntityReader(final EventReaderConfig eventReaderConfig, final JsonReader jsonReader,
+ final OxmReader oxmReader) {
+ this.jsonReader = jsonReader;
+ this.oxmReader = oxmReader;
+ this.config = eventReaderConfig;
+ }
+
+ /**
+ * Parse the supplied json and return the content (values) specified by the supplied path
+ *
+ * @param json
+ * @param path
+ * @return either a String or an Array of objects
+ * @throws ValidationServiceException
+ */
+ @Override
+ public Object getObject(String json, String path) throws ValidationServiceException {
+ return jsonReader.getObject(jsonReader.parse(json), path);
+ }
+
+ public String getEntityResourceVersionPath() {
+ return config.getEntityResourceVersionPath();
+ }
+
+ @Override
+ public List<EntityId> getIds(String json, String type) throws ValidationServiceException {
+ List<EntityId> ids = new ArrayList<>();
+ for (String pk : oxmReader.getPrimaryKeys(type)) {
+ String pkPaths = config.getEntityIdPath(pk);
+ String pkValue = getPropertyForMultiplePaths(json, pkPaths).orElseThrow(
+ () -> new ValidationServiceException(ValidationServiceError.EVENT_READER_MISSING_PROPERTY,
+ pkPaths));
+ ids.add(new EntityId(pk, pkValue));
+ }
+ return ids;
+ }
+
+ /**
+ * Get an entity property from an entity in JSON format.
+ *
+ * @param json the entity
+ * @param path the JSON path to the property
+ * @return an optional property value
+ * @throws ValidationServiceException
+ */
+ public Optional<String> getProperty(String json, String path) throws ValidationServiceException {
+ return jsonReader.get(json, path).stream().findFirst();
+ }
+
+ @Override
+ public Optional<String> getResourceVersion(String json) throws ValidationServiceException {
+ return getPropertyForMultiplePaths(json, getEntityResourceVersionPath());
+ }
+
+ /**
+ * Takes a comma separated list of jsonpaths and applies each one in turn until a value is found.
+ *
+ * @param json The json to search
+ * @param multiplePaths Comma separated list of jsonpath strings
+ * @return The value of the first jsonpath string that returns a value
+ * @throws ValidationServiceException
+ */
+ private Optional<String> getPropertyForMultiplePaths(String json, String multiplePaths)
+ throws ValidationServiceException {
+ Optional<String> propertyValue = Optional.empty();
+ for (String path : multiplePaths.split(",")) {
+ propertyValue = getProperty(json, path);
+ if (propertyValue.isPresent()) {
+ break;
+ }
+ }
+ return propertyValue;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/EventReader.java b/src/main/java/org/onap/aai/validation/reader/EventReader.java
new file mode 100644
index 0000000..614ccbf
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/EventReader.java
@@ -0,0 +1,215 @@
+/*
+ * ============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.reader;
+
+import com.jayway.jsonpath.DocumentContext;
+import java.util.List;
+import java.util.Optional;
+import org.onap.aai.validation.config.EventReaderConfig;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.reader.data.Entity;
+import org.onap.aai.validation.util.StringUtils;
+
+/**
+ * Reads event objects.
+ *
+ */
+public class EventReader {
+
+ private EventReaderConfig eventReaderConfig;
+
+ private JsonReader jsonReader;
+
+ private EntityReader entityReader;
+
+ /**
+ *
+ * @param eventReaderConfig the event reader configuration including paths to event properties
+ * @param jsonReader a JSON reader
+ * @param oxmReader an OXM reader to retrieve the primary key names for the entity
+ */
+ public EventReader(final EventReaderConfig eventReaderConfig, final JsonReader jsonReader,
+ final OxmReader oxmReader) {
+ this.eventReaderConfig = eventReaderConfig;
+ this.jsonReader = jsonReader;
+ this.entityReader = new EventEntityReader(eventReaderConfig, jsonReader, oxmReader);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // PUBLIC METHODS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the domain of the event.
+ *
+ * @param event a JSON String with the event contents
+ * @return the domain of the event
+ * @throws ValidationServiceException
+ */
+ public Optional<String> getEventDomain(String event) throws ValidationServiceException {
+ List<String> readerResult = jsonReader.get(event, eventReaderConfig.getEventDomainPath());
+ return getFirst(readerResult);
+ }
+
+ /**
+ * Get the action of the event.
+ *
+ * @param event a JSON String with the event contents
+ * @return the action of the event
+ * @throws ValidationServiceException
+ */
+ public Optional<String> getEventAction(String event) throws ValidationServiceException {
+ List<String> readerResult = jsonReader.get(event, eventReaderConfig.getEventActionPath());
+ return getFirst(readerResult);
+ }
+
+ /**
+ * Get the type of the event.
+ *
+ * @param event a JSON String with the event contents
+ * @return the type of the event
+ * @throws ValidationServiceException
+ */
+ public Optional<String> getEventType(String event) throws ValidationServiceException {
+ List<String> readerResult = jsonReader.get(event, eventReaderConfig.getEventTypePath());
+ return getFirst(readerResult);
+ }
+
+ /**
+ * Get the entity type of the entity in the event.
+ *
+ * @param event a JSON String with the event contents
+ * @return the type of the entity
+ * @throws ValidationServiceException
+ */
+ public Optional<String> getEntityType(String event) throws ValidationServiceException {
+ List<String> readerResult = jsonReader.get(event, eventReaderConfig.getEntityTypePath());
+ return getFirst(readerResult);
+ }
+
+ /**
+ * Get the entity contained in the event.
+ *
+ * @param event a JSON String with the event contents
+ * @return the entity
+ */
+ public Entity getEntity(String event) throws ValidationServiceException {
+ DocumentContext document = jsonReader.parse(event);
+
+ String entityType = getValue(document, eventReaderConfig.getEntityTypePath())
+ .orElseThrow(() -> new ValidationServiceException(ValidationServiceError.EVENT_READER_MISSING_PROPERTY,
+ eventReaderConfig.getEntityTypePath()));
+ String topEntityType = getValue(document, eventReaderConfig.getTopEntityTypePath()).orElse(entityType);
+ String entityLink = getEntityLink(document);
+ String json = findEntity(event, topEntityType, entityType);
+
+ return new Entity(json, entityType, entityLink, entityReader);
+ }
+
+ /**
+ * Get the value of the JSON property defined by the path.
+ *
+ * @param json a JSON string
+ * @param path the path to a property
+ * @return the value
+ * @throws ValidationServiceException if the value is not present
+ */
+ public String getValue(final String json, final String path) throws ValidationServiceException {
+ return getFirst(jsonReader.get(json, path)).orElseThrow(
+ () -> new ValidationServiceException(ValidationServiceError.EVENT_READER_MISSING_PROPERTY, path));
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // PRIVATE METHODS
+ ///////////////////////////////////////////////////////////////////////////
+
+ private String findEntity(String event, String topEntityType, String entityType) throws ValidationServiceException {
+ String json;
+ if (entityType.equals(topEntityType)) {
+ json = getValue(event, eventReaderConfig.getEntityPath());
+ } else {
+ json = findNestedEntity(event, eventReaderConfig.getNestedEntityPath(entityType));
+ }
+ return json;
+ }
+
+ /**
+ * @param event
+ * @param path
+ * @return
+ * @throws ValidationServiceException
+ */
+ private String findNestedEntity(String event, String path) throws ValidationServiceException {
+ List<String> entities = jsonReader.get(event, path);
+ if (entities.isEmpty()) {
+ throw new ValidationServiceException(ValidationServiceError.EVENT_READER_MISSING_PROPERTY, path);
+ } else if (entities.size() > 1) {
+ throw new ValidationServiceException(ValidationServiceError.EVENT_READER_TOO_MANY_ENTITIES);
+ }
+ return entities.get(0);
+ }
+
+ private Optional<String> getFirst(List<String> l) {
+ return l.stream().findFirst();
+ }
+
+ private Optional<String> getValue(final DocumentContext document, final String path) {
+ return getFirst(jsonReader.getAsList(document, path));
+ }
+
+ /**
+ * Gets the entity link from the event but altered by stripping a prefix identified by a delimiter configured in the
+ * event reader properties configuration.
+ *
+ * @param document the parsed JSON event
+ * @return the entity link
+ * @throws ValidationServiceException
+ */
+ private String getEntityLink(DocumentContext document) throws ValidationServiceException {
+ String entityLink = getValue(document, eventReaderConfig.getEntityLinkPath()).orElse("");
+ String strippedEntityLink = null;
+ try {
+ strippedEntityLink = StringUtils.stripPrefixRegex(entityLink, eventReaderConfig.getEntityLinkDelimiter());
+ } catch (ValidationServiceException e) {
+ throw new ValidationServiceException(ValidationServiceError.EVENT_READER_PROPERTY_READ_ERROR, e);
+ }
+ return strippedEntityLink;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // GETTERS AND SETTERS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ *
+ * @return event configuration
+ */
+ public EventReaderConfig getEventReaderConfig() {
+ return eventReaderConfig;
+ }
+
+ /**
+ *
+ * @param eventReaderConfig event configuration
+ */
+ public void setEventReaderConfig(EventReaderConfig eventReaderConfig) {
+ this.eventReaderConfig = eventReaderConfig;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/InstanceEntityReader.java b/src/main/java/org/onap/aai/validation/reader/InstanceEntityReader.java
new file mode 100644
index 0000000..0b9fbed
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/InstanceEntityReader.java
@@ -0,0 +1,75 @@
+/*
+ * ============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.reader;
+
+import com.google.gson.JsonElement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.modeldriven.validator.InstanceReader;
+import org.onap.aai.validation.modeldriven.validator.InstanceReader.InstanceEntity;
+import org.onap.aai.validation.reader.data.EntityId;
+
+/**
+ * Entity reader implemented using the model-driven instance reader.
+ *
+ */
+public class InstanceEntityReader implements EntityReader {
+
+ private InstanceReader reader;
+
+ /**
+ * @param instanceReader
+ */
+ public InstanceEntityReader(InstanceReader instanceReader) {
+ this.reader = instanceReader;
+ }
+
+ @Override
+ public Object getObject(String json, String attribute) throws ValidationServiceException {
+ throw new ValidationServiceException(ValidationServiceError.INSTANCE_READER_NO_INSTANCE, "Not implemented");
+ }
+
+ @Override
+ public List<EntityId> getIds(String json, String type) throws ValidationServiceException {
+ List<EntityId> ids = new ArrayList<>();
+
+ InstanceEntity entity = reader.getNamedQueryEntity(json);
+
+ List<String> primaryKeys = reader.getOxmReader().getPrimaryKeys(entity.getEntityType());
+
+ if (primaryKeys != null && !primaryKeys.isEmpty()) {
+ for (String pk : primaryKeys) {
+ JsonElement jsonValue = entity.getObject().getAsJsonObject().get(pk);
+ if (jsonValue != null) {
+ ids.add(new EntityId(pk, jsonValue.getAsString()));
+ }
+ }
+ }
+
+ return ids;
+ }
+
+ @Override
+ public Optional<String> getResourceVersion(String json) throws ValidationServiceException {
+ return Optional.of(reader.getResourceVersion(json));
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/reader/JsonReader.java b/src/main/java/org/onap/aai/validation/reader/JsonReader.java
new file mode 100644
index 0000000..9d349ac
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/JsonReader.java
@@ -0,0 +1,186 @@
+/*
+ * ============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.reader;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.DocumentContext;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.Option;
+import com.jayway.jsonpath.ReadContext;
+import com.jayway.jsonpath.spi.json.GsonJsonProvider;
+import com.jayway.jsonpath.spi.json.JsonProvider;
+import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
+import com.jayway.jsonpath.spi.mapper.MappingProvider;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+
+/**
+ * Reads JSON objects. Supported by the JayWay JsonPath library.
+ */
+public class JsonReader {
+
+ private Configuration jsonPathConfig;
+
+ /**
+ * Initialise the JSON reader.
+ */
+ public JsonReader() {
+ setJsonProvider();
+ this.jsonPathConfig = Configuration.builder().options(Option.SUPPRESS_EXCEPTIONS).build();
+ }
+
+ /**
+ * Parse the JSON.
+ *
+ * @param json
+ * the JSON object
+ * @return a {@link ReadContext} the parsed JSON.
+ * @throws ValidationServiceException
+ */
+ public DocumentContext parse(String json) throws ValidationServiceException {
+ DocumentContext document = null;
+ try {
+ document = JsonPath.using(jsonPathConfig).parse(json);
+ } catch (Exception e) {
+ throw new ValidationServiceException(ValidationServiceError.JSON_READER_PARSE_ERROR, e);
+ }
+ return document;
+ }
+
+ /**
+ * Gets values from JSON objects.
+ *
+ * @param json
+ * the JSON object
+ * @param path
+ * the path to property values. The format must comply with the JayWay JsonPath definition.
+ * @return a List of values found by evaluating the path.
+ * @throws ValidationServiceException
+ */
+ public List<String> get(String json, String path) throws ValidationServiceException {
+ return getAsList(parse(json), path);
+ }
+
+ /**
+ * Gets values from JSON objects. Used in combination with {@link JsonReader#parse(String)} it reduces the number of
+ * times the JSON document is parsed.
+ *
+ * @param document
+ * a {@link DocumentContext} object with the parsed JSON
+ * @param path
+ * the path to property values. The format must comply with the JayWay JsonPath definition.
+ * @return a List of values found by evaluating the path, or an empty list if no values were found
+ */
+ public List<String> getAsList(DocumentContext document, String path) {
+ List<String> result = new ArrayList<>();
+ JsonElement jsonElement = document.read(path);
+ if (jsonElement != null) {
+ if (jsonElement.isJsonPrimitive()) {
+ result.add(jsonElement.getAsString());
+ } else if (jsonElement.isJsonObject()) {
+ result.add(jsonElement.getAsJsonObject().toString());
+ } else if (jsonElement.isJsonArray()) {
+ for (JsonElement obj : jsonElement.getAsJsonArray()) {
+ Object object = jsonElementToObject(obj);
+ result.add(object == null ? null : object.toString());
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get the value(s) from the specified JSON document
+ *
+ * @param document
+ * a {@link DocumentContext} object with the parsed JSON
+ * @param path
+ * the path to property value(s). The format must comply with the JayWay JsonPath definition.
+ * @return either all the values found by evaluating the path (e.g. as an array), or a String object (only) where
+ * the path evaluates to a single primitive value
+ */
+ public Object getObject(DocumentContext document, String path) {
+ return jsonElementToObject(document.read(path));
+ }
+
+ /**
+ * Convert the JSON element to a String or Array where possible, otherwise return the JSON object.
+ *
+ * @param jsonElement
+ * @return the jsonElement converted to a Java Object
+ */
+ private Object jsonElementToObject(JsonElement jsonElement) {
+ if (jsonElement == null) {
+ return null;
+ } else if (jsonElement.isJsonPrimitive()) {
+ return jsonElement.getAsString();
+ } else if (jsonElement.isJsonObject()) {
+ return jsonElement.getAsJsonObject();
+ } else if (jsonElement.isJsonArray()) {
+ // Convert to a List for simplified handling within rules
+ return jsonArrayToList(jsonElement.getAsJsonArray());
+ } else {
+ return jsonElement;
+ }
+ }
+
+ private List<Object> jsonArrayToList(JsonArray jsonArray) {
+ List<Object> result = new ArrayList<>();
+ for (JsonElement obj : jsonArray) {
+ result.add(jsonElementToObject(obj));
+ }
+ return result;
+ }
+
+ /**
+ * @param document
+ * @param path
+ * @return a JsonElement from the document
+ */
+ public JsonElement getJsonElement(DocumentContext document, String path) {
+ return document.read(path);
+ }
+
+ private void setJsonProvider() {
+ Configuration.setDefaults(new Configuration.Defaults() {
+ private final JsonProvider jsonProvider = new GsonJsonProvider();
+ private final MappingProvider mappingProvider = new GsonMappingProvider();
+
+ @Override
+ public JsonProvider jsonProvider() {
+ return jsonProvider;
+ }
+
+ @Override
+ public MappingProvider mappingProvider() {
+ return mappingProvider;
+ }
+
+ @Override
+ public Set<Option> options() {
+ return EnumSet.noneOf(Option.class);
+ }
+ });
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/OxmConfigTranslator.java b/src/main/java/org/onap/aai/validation/reader/OxmConfigTranslator.java
new file mode 100644
index 0000000..53be6dd
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/OxmConfigTranslator.java
@@ -0,0 +1,100 @@
+/*
+ * ============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.reader;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceConfigurationError;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.onap.aai.setup.ConfigTranslator;
+import org.onap.aai.setup.SchemaLocationsBean;
+import org.onap.aai.setup.Version;
+
+/**
+ * Determine which OXM and edge rules files to return based on the latest Version
+ *
+ */
+public class OxmConfigTranslator extends ConfigTranslator {
+
+ public OxmConfigTranslator(SchemaLocationsBean bean) {
+ super(bean);
+ }
+
+ @Override
+ public Map<Version, List<String>> getNodeFiles() {
+ String nodeDirectory = bean.getNodeDirectory();
+ if (nodeDirectory == null) {
+ throw new ServiceConfigurationError(
+ "Node(s) directory is empty in the schema location bean (" + bean.getSchemaConfigLocation() + ")");
+ }
+ try {
+ return getVersionMap(Paths.get(nodeDirectory), "*_v*.xml");
+ } catch (IOException e) {
+ throw new ServiceConfigurationError("Failed to read node(s) directory " + getPath(nodeDirectory), e);
+ }
+ }
+
+ @Override
+ public Map<Version, List<String>> getEdgeFiles() {
+ String edgeDirectory = bean.getEdgeDirectory();
+ if (edgeDirectory == null) {
+ throw new ServiceConfigurationError(
+ "Edge(s) directory is empty in the schema location bean (" + bean.getSchemaConfigLocation() + ")");
+ }
+ try {
+ return getVersionMap(Paths.get(edgeDirectory), "*_v*.json");
+ } catch (IOException e) {
+ throw new ServiceConfigurationError("Failed to read edge(s) directory " + getPath(edgeDirectory), e);
+ }
+ }
+
+ private String getPath(String nodeDirectory) {
+ return Paths.get(nodeDirectory).toAbsolutePath().toString();
+ }
+
+ /**
+ * Creates a map containing each OXM Version and the matching OXM file path(s)
+ *
+ * @param folderPath the folder/directory containing the OXM files
+ * @param fileSuffix
+ * @return a new Map object (may be empty)
+ * @throws IOException if there is a problem reading the specified directory path
+ */
+ private Map<Version, List<String>> getVersionMap(Path folderPath, String globPattern) throws IOException {
+ final PathMatcher filter = folderPath.getFileSystem().getPathMatcher("glob:**/" + globPattern);
+ try (final Stream<Path> stream = Files.list(folderPath)) {
+ return stream.filter(filter::matches).map(Path::toString).filter(p -> getVersionFromPath(p) != null)
+ .collect(Collectors.groupingBy(this::getVersionFromPath));
+ }
+ }
+
+ private Version getVersionFromPath(String pathName) {
+ String version = "V" + pathName.replaceAll("\\D+", "");
+ try {
+ return Version.valueOf(version);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/OxmReader.java b/src/main/java/org/onap/aai/validation/reader/OxmReader.java
new file mode 100644
index 0000000..b8343f1
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/OxmReader.java
@@ -0,0 +1,99 @@
+/*
+ * ============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.reader;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceConfigurationError;
+import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
+import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
+import org.onap.aai.nodes.NodeIngestor;
+import org.onap.aai.setup.Version;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.util.StringUtils;
+
+/**
+ * Reads the primary keys of a specific OXM resource (identified by version).
+ */
+public class OxmReader {
+
+ private NodeIngestor nodeIngestor;
+ private Version version;
+ private Map<String, List<String>> primaryKeysMap = new HashMap<>();
+
+ public OxmReader(NodeIngestor nodeIngestor, Version version) {
+ this.nodeIngestor = nodeIngestor;
+ this.version = version;
+ }
+
+ public OxmReader(NodeIngestor nodeIngestor) {
+ this(nodeIngestor, Version.getLatest());
+ }
+
+ /**
+ * Get the primary keys for a given entity type.
+ *
+ * @param entityType the name of the entity type
+ * @return the primary keys for the entity type
+ * @throws ValidationServiceException
+ */
+ public List<String> getPrimaryKeys(String entityType) throws ValidationServiceException {
+ if (primaryKeysMap.isEmpty()) {
+ throw new ValidationServiceException(ValidationServiceError.OXM_MISSING_KEY, entityType);
+ }
+
+ List<String> primaryKeys = primaryKeysMap.get(entityType);
+ return primaryKeys != null ? primaryKeys : Collections.emptyList();
+ }
+
+ /**
+ * Populate a Map of primary keys for the entity types defined in the OXM.<br>
+ * The primary keys are keyed by the Default Root Element.
+ */
+ public void init() {
+ for (Descriptor<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> descriptor : getObjectDescriptors()) {
+ if (!descriptor.getPrimaryKeyFieldNames().isEmpty()) {
+ primaryKeysMap.put(descriptor.getDefaultRootElement(),
+ StringUtils.stripSuffix(descriptor.getPrimaryKeyFieldNames(), "/text()"));
+ }
+ }
+ }
+
+ /**
+ * Gets a list of descriptors from the OXM. These descriptor objects contain property information about each entity
+ * type.
+ *
+ * @return list of descriptors
+ */
+ @SuppressWarnings("rawtypes")
+ private List<Descriptor> getObjectDescriptors() {
+ DynamicJAXBContext dynamicJaxbContext = nodeIngestor.getContextForVersion(version);
+ if (dynamicJaxbContext == null) {
+ throw new ServiceConfigurationError("OXM Version " + version + " was not ingested.");
+ }
+ return dynamicJaxbContext.getXMLContext().getDescriptors();
+ }
+
+ public Map<String, List<String>> getPrimaryKeysMap() {
+ return primaryKeysMap;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/data/AttributeValues.java b/src/main/java/org/onap/aai/validation/reader/data/AttributeValues.java
new file mode 100644
index 0000000..78665ef
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/data/AttributeValues.java
@@ -0,0 +1,139 @@
+/*
+ * ============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.reader.data;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stores a collection of attribute values (retrievable by name).
+ */
+public class AttributeValues {
+
+ private Map<String, Object> map;
+
+ /**
+ * Instantiates a new empty set of attribute values.
+ */
+ public AttributeValues() {
+ this.map = new HashMap<>();
+ }
+
+ /**
+ * Instantiates a new set of attribute values using the provided properties map.
+ *
+ * @param map
+ * the attribute name/value pairs
+ */
+ public AttributeValues(Map<String, Object> map) {
+ this.map = map;
+ }
+
+ /**
+ * Instantiates a new set of attribute values comprising one name/value pair.
+ *
+ * @param key
+ * the attribute name
+ * @param value
+ * the attribute value
+ */
+ public AttributeValues(String key, String value) {
+ this();
+ this.map.put(key, value);
+ }
+
+ @Override
+ public String toString() {
+ return map.toString();
+ }
+
+ /**
+ *
+ * @return the number of attributes stored
+ */
+ public int size() {
+ return this.map.size();
+ }
+
+ /**
+ * Add an attribute name/value pair.
+ *
+ * @param key
+ * the attribute name
+ * @param value
+ * the attribute value
+ */
+ public void put(String key, Object value) {
+ this.map.put(key, value);
+ }
+
+ /**
+ * Add an attribute name with a collection of values.
+ *
+ * @param key
+ * the attribute name
+ * @param valueList
+ * the collection of attribute values
+ */
+ public void put(String key, List<String> valueList) {
+ this.map.put(key, valueList);
+ }
+
+ /**
+ * Removes the attribute
+ *
+ * @param key
+ * the attribute name
+ */
+ public void remove(String key) {
+ this.map.remove(key);
+
+ }
+
+ /**
+ * Overwrites the attribute values, replacing them with the specified attribute to attribute values mappings
+ *
+ * @param map
+ * the name/values to store
+ */
+ public void setMap(Map<String, Object> map) {
+ this.map = map;
+ }
+
+ /**
+ * Gets an attribute value
+ *
+ * @param attrName
+ * the attribute name
+ * @return the object
+ */
+ public Object get(String attrName) {
+ return this.map.get(attrName);
+ }
+
+ /**
+ * Converts the attribute values into a report-friendly format
+ *
+ * @return a Map which is a copy of the AttributeValues, formatted for reporting
+ */
+ public Map<String, Object> generateReport() {
+ return map;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/reader/data/Entity.java b/src/main/java/org/onap/aai/validation/reader/data/Entity.java
new file mode 100644
index 0000000..415298f
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/data/Entity.java
@@ -0,0 +1,135 @@
+/*
+ * ============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.reader.data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.reader.EntityReader;
+
+/**
+ * An A&AI entity.
+ */
+public class Entity {
+
+ private String json;
+ private String type;
+ private EntityReader reader;
+ private List<EntityId> ids = new ArrayList<>();
+ private Optional<String> resourceVersion = Optional.empty();
+ private String entityLink;
+
+ /**
+ *
+ * @param json
+ * the full entity JSON supplied in the event
+ * @param entityType
+ * the entity type
+ * @param entityLink
+ * @param entityReader
+ * an {@link EntityReader}
+ */
+ public Entity(final String json, final String entityType, final String entityLink, final EntityReader entityReader) {
+ this.json = json;
+ this.type = entityType;
+ this.entityLink = entityLink;
+ this.reader = entityReader;
+ }
+
+ /**
+ * Get the full entity JSON.
+ *
+ * @return the entity JSON
+ */
+ public String getJson() {
+ return json;
+ }
+
+ /**
+ * Get the entity type.
+ *
+ * @return the entity type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Get the entity identifiers. More than one identifier may be provided for composite keys.
+ *
+ * @return a list of entity identifiers
+ * @throws ValidationServiceException
+ */
+ public List<EntityId> getIds() throws ValidationServiceException {
+ if (ids.isEmpty()) {
+ ids = reader.getIds(getJson(), getType());
+ }
+ return ids;
+ }
+
+ /**
+ * Get the resource version.
+ *
+ * @return the resource version
+ * @throws ValidationServiceException
+ */
+ public Optional<String> getResourceVersion() throws ValidationServiceException {
+ if (!resourceVersion.isPresent()) {
+ resourceVersion = reader.getResourceVersion(getJson());
+ }
+ return resourceVersion;
+ }
+
+ /**
+ * Get the event entity link.
+ *
+ * @return the event entity link
+ */
+ public String getEntityLink() {
+ return entityLink;
+ }
+
+ /**
+ * Get collection of attributes
+ *
+ * @param attributes
+ * the names of the attributes
+ * @return an {@link AttributeValues} object containing the attribute values
+ * @throws ValidationServiceException
+ */
+ public AttributeValues getAttributeValues(List<String> attributes) throws ValidationServiceException {
+ AttributeValues attributeValues = new AttributeValues();
+
+ if (attributes == null || attributes.isEmpty()) {
+ return attributeValues;
+ }
+
+ for (String attribute : attributes) {
+ attributeValues.put(attribute, reader.getObject(getJson(), attribute));
+ }
+
+ return attributeValues;
+ }
+
+ @Override
+ public String toString() {
+ return json;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/aai/validation/reader/data/EntityId.java b/src/main/java/org/onap/aai/validation/reader/data/EntityId.java
new file mode 100644
index 0000000..83c1bf6
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/reader/data/EntityId.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.reader.data;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * Describes an identifier for an entity.
+ */
+public class EntityId {
+
+ private String primaryKey;
+ private String value;
+
+ /**
+ * Construct an entity Id
+ */
+ public EntityId() {
+ // Deliberately empty
+ }
+
+ /**
+ * Construct an entity Id (key-value pair)
+ *
+ * @param primaryKey
+ * @param value
+ */
+ public EntityId(String primaryKey, String value) {
+ this.primaryKey = primaryKey;
+ this.value = value;
+ }
+
+ public String getPrimaryKey() {
+ return primaryKey;
+ }
+
+ public void setPrimaryKey(String primaryKey) {
+ this.primaryKey = primaryKey;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.primaryKey, this.value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof EntityId)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ EntityId rhs = (EntityId) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(primaryKey, rhs.primaryKey)
+ .append(value, rhs.value)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return primaryKey + "=" + value;
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/result/ValidationResult.java b/src/main/java/org/onap/aai/validation/result/ValidationResult.java
new file mode 100644
index 0000000..b632a0f
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/result/ValidationResult.java
@@ -0,0 +1,244 @@
+/*
+ * ============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.result;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.annotations.Expose;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.reader.data.Entity;
+import org.onap.aai.validation.reader.data.EntityId;
+import org.onap.aai.validation.util.JsonUtil;
+
+/**
+ * The result of an instance validation. This can include zero or more {@link Violation} objects.
+ */
+public class ValidationResult {
+
+ @Expose
+ private String validationId;
+
+ @Expose
+ private String validationTimestamp;
+
+ @Expose
+ private JsonElement entityId;
+
+ @Expose
+ private String entityType;
+
+ @Expose
+ private String entityLink;
+
+ @Expose
+ private String resourceVersion;
+
+ @Expose
+ private JsonElement entity;
+
+
+ @Expose
+ private List<Violation> violations = new ArrayList<>();
+
+ /**
+ * Create the validation payload initialised with an event identifier and a timestamp.
+ *
+ * @param entity
+ * @throws ValidationServiceException
+ */
+ public ValidationResult(Entity entity) throws ValidationServiceException {
+ this.validationId = UUID.randomUUID().toString();
+ this.validationTimestamp =
+ DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmssX").withZone(ZoneOffset.UTC).format(Instant.now());
+ this.entityId = new JsonObject();
+ for (EntityId id : entity.getIds()) {
+ this.entityId.getAsJsonObject().addProperty(id.getPrimaryKey(), id.getValue());
+ }
+ this.entityType = entity.getType();
+ this.entityLink = entity.getEntityLink();
+ this.resourceVersion = entity.getResourceVersion().orElse(null);
+ this.entity = entity.getJson()!=null ?new JsonParser().parse(entity.getJson()): new JsonObject();
+ }
+
+
+ /**
+ * Add a validation violation.
+ *
+ * @param violation a single {@link Violation} to add to the validation result
+ */
+ public void addViolation(Violation violation) {
+ this.violations.add(violation);
+ }
+
+ /**
+ * Add a list of validation violations.
+ *
+ * @param violations a List of {@link Violation} objects to add to the validation result
+ */
+ public void addViolations(List<Violation> violations) {
+ this.violations.addAll(violations);
+ }
+
+ public String getValidationId() {
+ return validationId;
+ }
+
+ public void setValidationId(String eventId) {
+ this.validationId = eventId;
+ }
+
+ public String getValidationTimestamp() {
+ return validationTimestamp;
+ }
+
+ public void setValidationTimestamp(String timestamp) {
+ this.validationTimestamp = timestamp;
+ }
+
+ public JsonElement getEntityId() {
+ return entityId;
+ }
+
+ public void setEntityId(JsonElement entityId) {
+ this.entityId = entityId;
+ }
+
+ public String getEntityType() {
+ return entityType;
+ }
+
+ public void setEntityType(String entityType) {
+ this.entityType = entityType;
+ }
+
+ public String getEntityLink() {
+ return entityLink;
+ }
+
+ public void setEntityLink(String uri) {
+ this.entityLink = uri;
+ }
+
+ public String getResourceVersion() {
+ return resourceVersion;
+ }
+
+ public void setResourceVersion(String resourceVersion) {
+ this.resourceVersion = resourceVersion;
+ }
+
+ public JsonElement getEntity() {
+ return entity;
+ }
+
+ public void setEntity(JsonElement entity) {
+ this.entity = entity;
+ }
+
+ public List<Violation> getViolations() {
+ return violations;
+ }
+
+ public void setViolations(List<Violation> violations) {
+ this.violations = violations;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.entityId, this.entityLink, this.entityType, this.resourceVersion, this.validationId,
+ this.validationTimestamp, this.violations, this.entity);
+ }
+
+ /*
+ * validationId is checked first, as this is meant to be unique
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ValidationResult)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ ValidationResult rhs = (ValidationResult) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(entityId, rhs.entityId)
+ .append(entityLink, rhs.entityLink)
+ .append(entityType, rhs.entityType)
+ .append(resourceVersion, rhs.resourceVersion)
+ .append(validationId, rhs.validationId)
+ .append(validationTimestamp, rhs.validationTimestamp)
+ .append(violations, rhs.violations)
+ .append(entity, rhs.entity)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return JsonUtil.toJson(this);
+ }
+
+ /**
+ * Create a JSON representation of the object, with each violation's validationRule omitted when it has a null value
+ *
+ * @return this object formatted as a JSON string ready for publishing
+ */
+ public String toJson() {
+ return toString();
+ }
+
+ /**
+ * Create a new object from the JSON representation
+ *
+ * @param json representation of the Validation Result
+ * @return a ValidationResult object
+ */
+ public static ValidationResult fromJson(String json) {
+ ValidationResult validationResult = JsonUtil.toAnnotatedClassfromJson(json, ValidationResult.class);
+ if (validationResult != null) {
+ validationResult.initialiseValues();
+ }
+ return validationResult;
+ }
+
+ /**
+ * Ensure that any unset fields are properly initialised. This is particularly useful when the object has been
+ * deserialised from a JSON string, as any missing/undefined values will not be read by the deserialiser and thus
+ * the corresponding fields will not be set.
+ */
+ private void initialiseValues() {
+ List<Violation> violationList = getViolations();
+ if (violationList != null) {
+ for (Violation violation : violationList) {
+ violation.initialiseValues();
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/result/Violation.java b/src/main/java/org/onap/aai/validation/result/Violation.java
new file mode 100644
index 0000000..29baed3
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/result/Violation.java
@@ -0,0 +1,418 @@
+/*
+ * ============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.result;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.Expose;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.onap.aai.validation.exception.ValidationServiceError;
+import org.onap.aai.validation.exception.ValidationServiceException;
+import org.onap.aai.validation.reader.data.Entity;
+import org.onap.aai.validation.reader.data.EntityId;
+import org.onap.aai.validation.util.JsonUtil;
+
+/**
+ * A validation violation.
+ */
+public class Violation {
+
+ public static final String ENTITY_TYPE_PROPERTY = "entityType";
+ public static final String ENTITY_ID_PROPERTY = "entityId";
+ public static final String ENTITY_MODELNAME_PROPERTY = "modelName";
+
+ @Expose
+ private final String violationId;
+
+ @Expose
+ private String modelName;
+
+ @Expose
+ private final String category;
+
+ @Expose
+ private final String severity;
+
+ @Expose
+ private final String violationType;
+
+ /**
+ * The rule name (not applicable for model-driven) is not final as the value may be set to null via deserialisation
+ * and we will need to update the field.
+ */
+ @Expose
+ private Optional<String> validationRule;
+
+ @Expose
+ private final JsonElement violationDetails;
+
+ @Expose
+ private String errorMessage;
+
+ /**
+ * rule-based or model-driven?
+ */
+ public enum ViolationType {
+ NONE, RULE, MODEL
+ }
+
+ /**
+ * Builder for a Violation.
+ */
+ public static class Builder {
+
+ private final MessageDigest messageDigest;
+ private final JsonElement entityId;
+ private final String entityType;
+ private final String entityLink;
+ private final String resourceVersion;
+ private String category = null;
+ private String severity = null;
+ private String violationType = null;
+ private Optional<String> validationRule = Optional.empty();
+ private JsonElement violationDetails = new JsonObject();
+ private String errorMessage = null;
+
+ /**
+ * Create a Violation Builder for the supplied entity.
+ *
+ * @param entity
+ * the entity
+ * @throws ValidationServiceException
+ * the validation service exception
+ */
+ public Builder(Entity entity) throws ValidationServiceException {
+ this.entityId = new JsonObject();
+ for (EntityId id : entity.getIds()) {
+ this.entityId.getAsJsonObject().addProperty(id.getPrimaryKey(), id.getValue());
+ }
+ this.entityType = entity.getType();
+ this.entityLink = entity.getEntityLink();
+ this.resourceVersion = entity.getResourceVersion().orElse(null);
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new ValidationServiceException(ValidationServiceError.MESSAGE_DIGEST_ERROR, e);
+ }
+ }
+
+ /**
+ * Category.
+ *
+ * @param val
+ * the val
+ * @return the builder
+ */
+ public Builder category(String val) {
+ category = val;
+ return this;
+ }
+
+ /**
+ * Severity.
+ *
+ * @param val
+ * the val
+ * @return the builder
+ */
+ public Builder severity(String val) {
+ severity = val;
+ return this;
+ }
+
+ /**
+ * Violation type.
+ *
+ * @param val
+ * the val
+ * @return the builder
+ */
+ public Builder violationType(String val) {
+ violationType = val;
+ return this;
+ }
+
+ /**
+ * Violation type.
+ *
+ * @param type
+ * the type
+ * @return the builder
+ */
+ public Builder violationType(ViolationType type) {
+ String name = type.name();
+ // Convert to Camel Case
+ return violationType(name.substring(0, 1).toUpperCase() + name.substring(1, name.length()).toLowerCase());
+ }
+
+ /**
+ * Validation rule.
+ *
+ * @param val
+ * the val
+ * @return the builder
+ */
+ public Builder validationRule(String val) {
+ validationRule = Optional.ofNullable(val);
+ return this;
+ }
+
+ /**
+ * Violation details.
+ *
+ * @param map
+ * the map
+ * @return the builder
+ */
+ public Builder violationDetails(Map<String, Object> map) {
+ violationDetails = JsonUtil.toJsonElement(map);
+ return this;
+ }
+
+ /**
+ * Error message.
+ *
+ * @param val
+ * the val
+ * @return the builder
+ */
+ public Builder errorMessage(String val) {
+ errorMessage = val;
+ return this;
+ }
+
+ /**
+ * Generate violation id.
+ *
+ * @return a deterministic identifier of the violation (from its details) which may be used to compare the
+ * equality of violations which are created at different times
+ * @throws ValidationServiceException
+ * the validation service exception
+ */
+ public String generateViolationId() throws ValidationServiceException {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ StringBuilder result = new StringBuilder();
+ writeObjectToStream(baos);
+ messageDigest.reset(); // Not strictly needed as digest() will cause a reset
+ for (byte byt : messageDigest.digest(baos.toByteArray())) {
+ result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1));
+ }
+ return result.toString();
+ } catch (IOException e) {
+ throw new ValidationServiceException(ValidationServiceError.JSON_READER_PARSE_ERROR, e);
+ }
+ }
+
+ /**
+ * Builds the.
+ *
+ * @return a new Violation object
+ * @throws ValidationServiceException
+ * the validation service exception
+ */
+ public Violation build() throws ValidationServiceException {
+ return new Violation(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Builder [entityId=" + entityId + ", entityType=" + entityType + ", entityLink=" + entityLink + ", resourceVersion=" + resourceVersion
+ + ", category=" + category + ", severity=" + severity + ", violationType=" + violationType + ", validationRule=" + validationRule
+ + ", violationDetails=" + violationDetails + ", errorMessage=" + errorMessage + "]";
+ }
+
+ /**
+ * Stream all fields that are required for generating a deterministic violation ID field. Note that
+ * resourceVersion is not included as this is allowed to vary
+ *
+ * @param baos
+ * the byte array output stream
+ * @throws IOException
+ * Signals that an I/O exception has occurred.
+ */
+ private void writeObjectToStream(ByteArrayOutputStream baos) throws IOException {
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(this.category);
+ oos.writeObject(this.entityId.toString());
+ oos.writeObject(this.entityType);
+ oos.writeObject(this.entityLink);
+ oos.writeObject(this.severity);
+ oos.writeObject(this.validationRule.toString());
+ oos.writeObject(this.violationDetails.toString());
+ oos.writeObject(this.violationType);
+ oos.close();
+ }
+
+ }
+
+ /**
+ * Instantiates a new Violation object via a builder.
+ *
+ * @param builder
+ * the builder storing the Violation values
+ * @throws ValidationServiceException
+ * the validation service exception
+ */
+ private Violation(Builder builder) throws ValidationServiceException {
+ violationId = builder.generateViolationId();
+ category = builder.category;
+ severity = builder.severity;
+ violationType = builder.violationType;
+ validationRule = builder.validationRule;
+ // Clone by serialising and deserialising!
+ violationDetails = JsonUtil.fromJson(JsonUtil.toJson(builder.violationDetails), JsonElement.class);
+ errorMessage = builder.errorMessage;
+ }
+
+ /**
+ * Gets the violation id.
+ *
+ * @return the violation id
+ */
+ public String getViolationId() {
+ return violationId;
+ }
+
+ /**
+ * Gets the model name.
+ *
+ * @return the model name
+ */
+ public String getModelName() {
+ return modelName;
+ }
+
+ /**
+ * Sets the model name.
+ *
+ * @param modelName
+ * the new model name
+ */
+ // Naughty and breaks the builder pattern but it saves a lot of faffing about.
+ public void setModelName(String modelName) {
+ this.modelName = modelName;
+ }
+
+ /**
+ * Gets the category.
+ *
+ * @return the category
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * Gets the severity.
+ *
+ * @return the severity
+ */
+ public String getSeverity() {
+ return severity;
+ }
+
+ /**
+ * Gets the violation type.
+ *
+ * @return the violation type
+ */
+ public String getViolationType() {
+ return violationType;
+ }
+
+ /**
+ * Gets the violation details.
+ *
+ * @return the violation details
+ */
+ @SuppressWarnings("unchecked")
+ public Map<String, Object> getViolationDetails() {
+ return JsonUtil.toAnnotatedClassfromJson(violationDetails.getAsJsonObject().toString(), Map.class);
+ }
+
+ /**
+ * Gets the error message.
+ *
+ * @return the error message
+ */
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.modelName, this.category, this.errorMessage, this.severity, this.validationRule, this.violationDetails, this.violationId, this.violationType);
+ }
+
+ /*
+ * A hand-crafted equivalence relation to compare two Violation objects. Note that the violationId is compared
+ * first, because this value is deterministically generated from the majority of the object's fields.
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Violation)) {
+ return false;
+ } else if (obj == this) {
+ return true;
+ }
+ Violation rhs = (Violation) obj;
+ // @formatter:off
+ return new EqualsBuilder()
+ .append(modelName, rhs.modelName)
+ .append(category, rhs.category)
+ .append(errorMessage, rhs.errorMessage)
+ .append(severity, rhs.severity)
+ .append(validationRule, rhs.validationRule)
+ .append(violationDetails, rhs.violationDetails)
+ .append(violationId, rhs.violationId)
+ .append(violationType, rhs.violationType)
+ .isEquals();
+ // @formatter:on
+ }
+
+ @Override
+ public String toString() {
+ return JsonUtil.toJson(this);
+ }
+
+ /**
+ * Ensure that any unset fields are properly initialised. This is particularly useful when the object has been
+ * deserialised from a JSON string, as any missing/undefined values will not be read by the deserialiser and thus
+ * the corresponding fields will not be set.
+ */
+ public void initialiseValues() {
+ if (validationRule == null) { // NOSONAR
+ validationRule = Optional.empty();
+ }
+ }
+}
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);
+
+}
diff --git a/src/main/java/org/onap/aai/validation/services/EventPollingService.java b/src/main/java/org/onap/aai/validation/services/EventPollingService.java
new file mode 100644
index 0000000..8b38031
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/services/EventPollingService.java
@@ -0,0 +1,121 @@
+/*
+ * ============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.services;
+
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.springframework.stereotype.Service;
+
+import org.onap.aai.event.client.DMaaPEventConsumer;
+import org.onap.aai.validation.config.TopicConfig;
+import org.onap.aai.validation.config.TopicConfig.Topic;
+import org.onap.aai.validation.controller.ValidationController;
+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.logging.LogHelper.MdcParameter;
+import com.google.common.collect.Iterables;
+
+/**
+ * Event Polling Service
+ *
+ */
+@Service
+public class EventPollingService implements Runnable {
+
+ private static final LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ private List<DMaaPEventConsumer> consumers;
+
+ private ValidationController validationController;
+
+ /**
+ * Instantiates an EventPollingService instance using the supplied configuration.
+ *
+ * @param topicConfig
+ * @throws ValidationServiceException
+ */
+ @Inject
+ public EventPollingService(TopicConfig topicConfig) throws ValidationServiceException {
+ consumers = new ArrayList<>();
+ for (Topic topic : topicConfig.getConsumerTopics()) {
+ try {
+ consumers.add(new DMaaPEventConsumer(topic.getHost(), topic.getName(), topic.getUsername(),
+ topic.getPassword(), topic.getConsumerGroup(), topic.getConsumerId(),
+ DMaaPEventConsumer.DEFAULT_MESSAGE_WAIT_TIMEOUT, DMaaPEventConsumer.DEFAULT_MESSAGE_LIMIT,
+ topic.getTransportType()));
+ } catch (MalformedURLException e) {
+ throw new ValidationServiceException(ValidationServiceError.EVENT_CLIENT_CONSUMER_INIT_ERROR, e);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ applicationLogger.info(ApplicationMsgs.POLL_EVENTS);
+ try {
+ for (DMaaPEventConsumer consumer : consumers) {
+ for (String event : consumeEvents(consumer)) {
+ // The event does not have a transaction ID so create one for logging purposes
+ applicationLogger.setContextValue(MdcParameter.REQUEST_ID, UUID.randomUUID().toString());
+ validationController.execute(event, "topic");
+ }
+ }
+ } catch (Exception e) {
+ // This could be a temporary issue, so the exception is swallowed
+ applicationLogger.error(ApplicationMsgs.INVOKE_EVENT_CONSUMER_ERROR, e);
+ } catch (Throwable t) { // NOSONAR
+ // E.g. We may catch an IllegalArgumentException caused by invalid configuration
+ applicationLogger.error(ApplicationMsgs.INVOKE_EVENT_CONSUMER_ERROR, t);
+
+ // Add these details to the status report available via the controller
+ validationController.recordThrowable(t);
+
+ // For non IO exceptions, rethrow in order to cause the executor to stop scheduling
+ throw t;
+ } finally {
+ applicationLogger.clearContextValue(MdcParameter.REQUEST_ID);
+ }
+ }
+
+ /* Getters and Setters for Spring injection */
+
+ public ValidationController getValidationController() {
+ return validationController;
+ }
+
+ public void setValidationController(ValidationController validationController) throws ValidationServiceException {
+ this.validationController = validationController;
+ // Instruct the validation controller to load and validate its configuration
+ this.validationController.initialise();
+ }
+
+ private Iterable<String> consumeEvents(DMaaPEventConsumer consumer) throws Exception {
+ applicationLogger.clearContextValue(MdcParameter.REQUEST_ID);
+ applicationLogger.debug("Querying consumer " + consumer);
+ Iterable<String> events = consumer.consume();
+ applicationLogger.info(ApplicationMsgs.NUMBER_OF_MESSAGES_CONSUMED, Integer.toString(Iterables.size(events)));
+ return events;
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/services/InfoService.java b/src/main/java/org/onap/aai/validation/services/InfoService.java
new file mode 100644
index 0000000..5f2b625
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/services/InfoService.java
@@ -0,0 +1,57 @@
+/*
+ * ============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.services;
+
+import org.onap.aai.validation.controller.ValidationController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Information Service for the Validation Controller. Return status details to the caller.
+ *
+ */
+@RestController
+@RequestMapping("/services/validation-service/v1/core/core-service")
+public class InfoService {
+
+ @Autowired
+ private ValidationController validationController;
+
+ public ValidationController getValidationController() {
+ return validationController;
+ }
+
+ public void setValidationController(ValidationController validationController) {
+ this.validationController = validationController;
+ }
+
+ /**
+ * @param format is an optional setting - html requests an HTML format
+ * @return a formatted status report
+ */
+ @RequestMapping(value = "/info", method = RequestMethod.GET, produces = "text/plain")
+ @ResponseBody
+ public String getInfo() {
+ validationController.incrementInfoCount();
+ return "Status: Up\n" + validationController.statusReport().toString() + "\n";
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/services/RequestHeaders.java b/src/main/java/org/onap/aai/validation/services/RequestHeaders.java
new file mode 100644
index 0000000..1d12600
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/services/RequestHeaders.java
@@ -0,0 +1,53 @@
+/*
+ * ============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.services;
+
+import org.springframework.http.HttpHeaders;
+
+/**
+ * Bean to represent the ONAP request/transaction IDs required for EELF logging.
+ *
+ */
+public class RequestHeaders {
+
+ // ONAP request ID a.k.a. transaction ID or correlation ID
+ public static final String HEADER_REQUEST_ID = "X-ECOMP-RequestID";
+ public static final String HEADER_SERVICE_INSTANCE_ID = "X-ECOMP-ServiceInstanceID";
+
+ private String requestId;
+ private String instanceId;
+
+ public RequestHeaders(HttpHeaders headers) {
+ requestId = headers.getFirst(RequestHeaders.HEADER_REQUEST_ID);
+ instanceId = headers.getFirst(RequestHeaders.HEADER_SERVICE_INSTANCE_ID);
+ }
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public String getInstanceId() {
+ return instanceId;
+ }
+
+ @Override
+ public String toString() {
+ return "RequestHeaders [requestId=" + requestId + ", instanceId=" + instanceId + "]";
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/services/ValidateService.java b/src/main/java/org/onap/aai/validation/services/ValidateService.java
new file mode 100644
index 0000000..4ef5e7f
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/services/ValidateService.java
@@ -0,0 +1,77 @@
+/*
+ * ============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.services;
+
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Validates an event (containing details of an entity) and synchronously returns the details of the validation
+ * result(s) including any violations that were detected. Note that in the current version there is expected to be one
+ * and only one validation result per event.
+ */
+@RestController
+@RequestMapping("/services/validation-service/v1/app")
+public interface ValidateService { // NOSONAR
+ /**
+ * Validate an event and, if successful, return the result(s) in JSON format. Note that not every event is validated
+ * and so the set of results may be empty. In the event of an error/exception then plain text is returned containing
+ * the exception details.
+ *
+ * <h4>The HTTP message body must be comprised of a single JSON object containing the following members (nested JSON
+ * elements)</h4> <B>event-header</B> a JSON object which contains the following members:
+ * <ul>
+ * <li><B>domain</B> the value must match with the event domain expected by the Validation Service
+ * <li><B>action</B> the value must not be present in the list of excluded event actions (default list: DELETE)</li>
+ * <li><B>event-type</B> the value must match with one of the expected event types (either for rule-driven or for
+ * model-driven validation)</li>
+ * <li><B>top-entity-type</B> indicating the type of the entity member (see below)</li>
+ * <li><B>entity-type</B> the value must match with an A&AI entity defined by the OXM model. This value identifies
+ * the object to be validated</li>
+ * <li><B>entity-link</B> the value indicates the source of the entity. This is expected to begin with the text
+ * https://host/aai/vX/</li>
+ * </ul>
+ * <B> entity</B> a JSON object representing the top-level entity. This object must contain the single entity
+ * instance to be validated, either
+ * <ul>
+ * <li>contained by a parent entity of a different type (indicated by top-entity-type)</li>
+ * <li>or as a stand-alone JSON object (entity-type will have the same value as top-entity-type)</li>
+ * </ul>
+ *
+ * @param event a JSON object representing an event
+ * @return an HTTP Response containing either a JSON array of ValidationResult objects or a plain-text error message
+ *
+ * @responseMessage 200 Success
+ * @responseMessage 400 Bad Request
+ * @responseMessage 500 Internal Server Error
+ *
+ */
+ @RequestMapping(value = "/validate", method = RequestMethod.POST, produces = {"application/json", "text/plain"})
+ @ResponseBody
+ public ResponseEntity<String> validate(@RequestHeader HttpHeaders headers, HttpServletRequest servletRequest,
+ @RequestBody String event);
+
+ public ResponseEntity<String> validate(String event);
+}
diff --git a/src/main/java/org/onap/aai/validation/services/ValidateServiceImpl.java b/src/main/java/org/onap/aai/validation/services/ValidateServiceImpl.java
new file mode 100644
index 0000000..54f8b5a
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/services/ValidateServiceImpl.java
@@ -0,0 +1,160 @@
+/*
+ * ============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.services;
+
+import java.util.Optional;
+import java.util.UUID;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.Response;
+import org.onap.aai.auth.AAIMicroServiceAuth;
+import org.onap.aai.validation.controller.ValidationController;
+import org.onap.aai.validation.controller.ValidationController.Result;
+import org.onap.aai.validation.logging.ApplicationMsgs;
+import org.onap.aai.validation.logging.LogHelper;
+import org.onap.aai.validation.logging.LogHelper.MdcParameter;
+import org.onap.aai.validation.logging.LogHelper.StatusCode;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Validation Service HTTP interface implementation.
+ *
+ */
+public class ValidateServiceImpl implements ValidateService {
+
+ private static final LogHelper applicationLogger = LogHelper.INSTANCE;
+
+ /**
+ * This message is returned in the HTTP Response when an event is filtered (deliberately not validated).
+ */
+ public static final String DEFAULT_MESSAGE_FOR_FILTERED_EVENTS =
+ "No validation results available. The action value may have caused the event to be filtered. Otherwise the event type or domain may be invalid.";
+
+ /**
+ * Events are passed to the controller which will execute the validation(s).
+ */
+ private ValidationController controller;
+ private AAIMicroServiceAuth aaiMicroServiceAuth;
+
+ /**
+ * @param controller
+ */
+ @Inject
+ public ValidateServiceImpl(final ValidationController controller, final AAIMicroServiceAuth aaiMicroServiceAuth) {
+ this.controller = controller;
+ this.aaiMicroServiceAuth = aaiMicroServiceAuth;
+ }
+
+ @Override
+ public ResponseEntity<String> validate(@RequestHeader HttpHeaders headers, HttpServletRequest servletRequest,
+ @RequestBody String event) {
+ applicationLogger.startAudit(headers, servletRequest);
+ applicationLogger.info(ApplicationMsgs.MESSAGE_VALIDATION_REQUEST, headers + event);
+
+ String path = (String) servletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
+
+ if (applicationLogger.isDebugEnabled()) {
+ applicationLogger.debug(String.format(
+ "Received request. Path \"%s\", HttpHeaders \"%s\", ServletRequest \"%s\", Request Body \"%s\"",
+ path, headers, servletRequest.getMethod(), event));
+ }
+
+ // Additional name/value pairs according to EELF guidelines
+ applicationLogger.setContextValue("Protocol", "https");
+ applicationLogger.setContextValue("Method", "POST");
+ applicationLogger.setContextValue("Path", path);
+ applicationLogger.setContextValue("Query", servletRequest.getQueryString());
+
+ RequestHeaders requestHeaders = new RequestHeaders(headers);
+ String requestId = requestHeaders.getRequestId();
+ if (requestId == null) {
+ requestId = UUID.randomUUID().toString();
+ applicationLogger.info(ApplicationMsgs.MISSING_REQUEST_ID, requestId);
+ applicationLogger.setContextValue(MdcParameter.REQUEST_ID, requestId);
+ }
+
+ ResponseEntity<String> response;
+
+ try {
+ boolean authorized =
+ aaiMicroServiceAuth.validateRequest(servletRequest, servletRequest.getMethod(), "validate");
+
+ if (!authorized) {
+ response = ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+ .body("User not authorized to perform the operation.");
+ } else {
+ response = validate(event);
+ }
+ } catch (Exception e) {
+ applicationLogger.error(ApplicationMsgs.PROCESS_REQUEST_ERROR, e);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
+ "Error while processing request. Please check the validation service logs for more details.\n");
+ }
+
+ StatusCode statusDescription;
+ int statusCode = response.getStatusCodeValue();
+ if (Response.Status.Family.familyOf(statusCode).equals(Response.Status.Family.SUCCESSFUL)) {
+ statusDescription = StatusCode.COMPLETE;
+ } else {
+ statusDescription = StatusCode.ERROR;
+ }
+ applicationLogger.logAudit(statusDescription, Integer.toString(statusCode),
+ response.getStatusCode().getReasonPhrase(), response.getBody());
+
+ return response;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.onap.aai.validation.services.ValidateService#validate(java.lang.String)
+ */
+ @Override
+ public ResponseEntity<String> validate(String event) {
+
+ try {
+ // Attempt to validate the event
+ Result result = controller.execute(event, "http");
+
+ if (result.validationSuccessful()) {
+ return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
+ .body(result.getValidationResultAsJson());
+ } else {
+ Optional<String> errorText = result.getErrorText();
+ if (!errorText.isPresent() || errorText.get().isEmpty()) {
+ errorText = Optional.of(DEFAULT_MESSAGE_FOR_FILTERED_EVENTS);
+ } else {
+ applicationLogger.error(ApplicationMsgs.MALFORMED_REQUEST_ERROR, event);
+ }
+ return ResponseEntity.badRequest().body(errorText.orElse(""));
+ }
+ } catch (Exception e) {
+ // Unchecked runtime exception - this is intended to catch potential programming errors.
+ applicationLogger.error(ApplicationMsgs.PROCESS_REQUEST_ERROR, e);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
+ "Error while processing request. Please check the validation service logs for more details.\n" + e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/servlet/StartupServlet.java b/src/main/java/org/onap/aai/validation/servlet/StartupServlet.java
new file mode 100644
index 0000000..01fdc31
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/servlet/StartupServlet.java
@@ -0,0 +1,95 @@
+/*
+ * ============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.servlet;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.onap.aai.cl.api.Logger;
+import org.onap.aai.validation.config.TopicAdminConfig;
+import org.onap.aai.validation.logging.ApplicationMsgs;
+import org.onap.aai.validation.logging.LogHelper;
+import org.onap.aai.validation.services.EventPollingService;
+import org.springframework.stereotype.Service;
+
+/**
+ * Main application service
+ *
+ */
+@Service
+public class StartupServlet {
+
+ private static final Logger applicationLogger = LogHelper.INSTANCE;
+
+ private static final long DEFAULT_POLLING_INTERVAL = 10;
+
+ private final EventPollingService eventPollingService;
+ private final TopicAdminConfig topicAdminConfig;
+
+ /**
+ * @param eventPollingService
+ * @param topicAdminConfig
+ */
+ @Inject
+ public StartupServlet(EventPollingService eventPollingService, TopicAdminConfig topicAdminConfig) {
+ this.eventPollingService = eventPollingService;
+ this.topicAdminConfig = topicAdminConfig;
+ }
+
+ /**
+ * Called from Spring
+ */
+ @PostConstruct
+ public void init() {
+ applicationLogger.info(ApplicationMsgs.STARTUP_SERVLET_INIT);
+
+ // Schedule a polling service if consuming is enabled.
+ boolean consumingEnabled = topicAdminConfig.isConsumeEnable();
+ if (consumingEnabled) {
+ Long consumerPollingIntervalSeconds = topicAdminConfig.getConsumePollingIntervalSeconds();
+
+ long interval;
+ if (consumerPollingIntervalSeconds == null) {
+ applicationLogger.info(ApplicationMsgs.POLLING_INTERVAL_CONFIG_NOT_PRESENT,
+ Long.toString(DEFAULT_POLLING_INTERVAL));
+ interval = DEFAULT_POLLING_INTERVAL;
+ } else {
+ interval = consumerPollingIntervalSeconds;
+ }
+
+ applicationLogger.info(ApplicationMsgs.POLLING_FOR_EVENTS, Long.toString(interval));
+ Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(eventPollingService, 0, interval,
+ TimeUnit.SECONDS);
+ } else {
+ applicationLogger.info(ApplicationMsgs.POLLING_DISABLED);
+ }
+
+ applicationLogger.info(ApplicationMsgs.STARTUP_SERVLET_INIT_SUCCESS);
+ }
+
+ public EventPollingService getEventPollingService() {
+ return eventPollingService;
+ }
+
+ public TopicAdminConfig getTopicAdminConfig() {
+ return topicAdminConfig;
+ }
+}
diff --git a/src/main/java/org/onap/aai/validation/util/GsonUtil.java b/src/main/java/org/onap/aai/validation/util/GsonUtil.java
new file mode 100644
index 0000000..f6525c8
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/util/GsonUtil.java
@@ -0,0 +1,124 @@
+/*
+ * ============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.util;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Optional;
+
+/**
+ * Static utility class
+ *
+ */
+public class GsonUtil {
+
+ /**
+ * All methods are static.
+ */
+ private GsonUtil() {
+ // Do not instantiate
+ }
+
+ /**
+ * Tell Gson how to handle Optional fields. This factory builds a Type Adapter for a class wrapped with Optional
+ *
+ */
+ public static class OptionalTypeAdapterFactory implements TypeAdapterFactory {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ Class<T> rawType = (Class<T>) type.getRawType();
+ if (rawType != Optional.class) {
+ return null;
+ }
+ final ParameterizedType parameterizedType = (ParameterizedType) type.getType();
+ final Type actualType = parameterizedType.getActualTypeArguments()[0];
+ return new OptionalTypeAdapter(gson.getAdapter(TypeToken.get(actualType)));
+ }
+ }
+
+ /**
+ * Implementation of the Optional Type Adapter
+ *
+ * @param <E>
+ */
+ public static class OptionalTypeAdapter<E> extends TypeAdapter<Optional<E>> {
+
+ private final TypeAdapter<E> adapter;
+
+ public static final TypeAdapterFactory FACTORY = new OptionalTypeAdapterFactory();
+
+ /**
+ * @param adapter
+ */
+ public OptionalTypeAdapter(TypeAdapter<E> adapter) {
+ this.adapter = adapter;
+ }
+
+ @Override
+ public void write(JsonWriter out, Optional<E> value) throws IOException {
+ if (value != null && value.isPresent()) { // NOSONAR
+ adapter.write(out, value.get());
+ } else {
+ boolean nullsAllowed = out.getSerializeNulls();
+ if (nullsAllowed) {
+ out.setSerializeNulls(false);
+ }
+ out.nullValue();
+ if (nullsAllowed) {
+ out.setSerializeNulls(true);
+ }
+ }
+ }
+
+ @Override
+ public Optional<E> read(JsonReader in) throws IOException {
+ final JsonToken peek = in.peek();
+ if (peek != JsonToken.NULL) {
+ return Optional.ofNullable(adapter.read(in));
+ }
+ return Optional.empty();
+ }
+
+ }
+
+ /**
+ * @return a new GsonBuilder with standard settings
+ */
+ public static GsonBuilder createGsonBuilder() {
+ return new GsonBuilder().disableHtmlEscaping().serializeNulls().excludeFieldsWithoutExposeAnnotation()
+ .registerTypeAdapterFactory(OptionalTypeAdapter.FACTORY);
+ }
+
+ /**
+ * @return a new Gson instance
+ */
+ public static Gson createGson() {
+ return createGsonBuilder().create();
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/util/JsonUtil.java b/src/main/java/org/onap/aai/validation/util/JsonUtil.java
new file mode 100644
index 0000000..910eb63
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/util/JsonUtil.java
@@ -0,0 +1,94 @@
+/*
+ * ============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.util;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import java.util.Map;
+
+/**
+ * Wrapper to hide the actual JSON API.
+ */
+public class JsonUtil {
+
+ // General purpose. Excludes null values.
+ private static final Gson gsonDefault = new Gson();
+
+ // Only serialises fields with the Expose annotation
+ private static final Gson gsonForAnnotatedClasses = GsonUtil.createGson();
+
+ /**
+ * Purely a static class.
+ */
+ private JsonUtil() {
+ // Deliberately empty
+ }
+
+ /**
+ * Serialise the annotated object to a JSON string.
+ *
+ * @param obj
+ * the object to serialise
+ * @return the JSON representation of the object
+ */
+ public static String toJson(Object obj) {
+ return gsonForAnnotatedClasses.toJson(obj);
+ }
+
+ /**
+ * Deserialise the annotated object from a JSON string.
+ *
+ * @param <T>
+ * the type of the object
+ * @param json
+ * the JSON string
+ * @param classOfT
+ * the class of type T
+ * @return a new object instance
+ */
+ public static <T> T toAnnotatedClassfromJson(String json, Class<T> classOfT) {
+ return gsonForAnnotatedClasses.fromJson(json, classOfT);
+ }
+
+ /**
+ * Deserialise the object from a JSON string.
+ *
+ * @param <T>
+ * the type of the object
+ * @param json
+ * the JSON string
+ * @param classOfT
+ * the class of type T
+ * @return a new object instance
+ */
+ public static <T> T fromJson(String json, Class<T> classOfT) {
+ return gsonDefault.fromJson(json, classOfT);
+ }
+
+ /**
+ * To json element.
+ *
+ * @param map
+ * the map
+ * @return the json element
+ */
+ public static JsonElement toJsonElement(Map<String, Object> map) {
+ return gsonForAnnotatedClasses.toJsonTree(map).getAsJsonObject();
+ }
+
+}
diff --git a/src/main/java/org/onap/aai/validation/util/StringUtils.java b/src/main/java/org/onap/aai/validation/util/StringUtils.java
new file mode 100644
index 0000000..d50b5bf
--- /dev/null
+++ b/src/main/java/org/onap/aai/validation/util/StringUtils.java
@@ -0,0 +1,100 @@
+/*
+ * ============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.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.onap.aai.cl.api.Logger;
+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;
+
+/**
+ * Utility class for String manipulation.
+ */
+public class StringUtils {
+
+ private static final Logger applicationLogger = LogHelper.INSTANCE;
+
+ /**
+ * All methods are static.
+ */
+ private StringUtils() {
+ // Do not instantiate
+ }
+
+ /**
+ * Utility method to strip a prefix or set of prefixes (identified by a delimiter sequence) from the string. This is
+ * achieved by finding the index of the last prefix delimiter in the string and removing all characters before and
+ * including this index.
+ *
+ * @param string the String to strip prefixes from
+ * @param prefixDelimiter the String that acts as the delimiter for the prefix(es)
+ * @return the String minus the prefixes
+ */
+ public static String stripPrefix(String string, String prefixDelimiter) {
+ return string.contains(prefixDelimiter)
+ ? string.substring(string.lastIndexOf(prefixDelimiter) + prefixDelimiter.length()) : string;
+ }
+
+ /**
+ * Strips a prefix identified by a delimiter. This is achieved by splitting the string in two around matches of the
+ * first occurrence of the given regular expression.
+ *
+ * @param string a String from which to strip a prefix
+ * @param regex the delimiting regular expression
+ * @return
+ * @throws ValidationServiceException If there is a problem with the provided regular expression.
+ */
+ public static String stripPrefixRegex(String string, String regex) throws ValidationServiceException {
+ String[] strings = validParameters(string, regex) ? string.split(regex, 2) : new String[0];
+ return strings.length == 2 ? strings[1] : string;
+ }
+
+ /**
+ * Process a list of strings and strip the given suffix from each string in the list.
+ *
+ * @param stringList a list of strings
+ * @param suffix a suffix to be removed from the strings
+ * @return stripped list of strings.
+ */
+ public static List<String> stripSuffix(List<String> stringList, String suffix) {
+ List<String> result = new ArrayList<>();
+ for (String str : stringList) {
+ String stripped = str.endsWith(suffix) ? str.substring(0, str.indexOf(suffix)) : str;
+ result.add(stripped);
+ }
+ return result;
+ }
+
+ private static boolean validParameters(String string, String regex) throws ValidationServiceException {
+ if (string != null && !string.isEmpty() && regex != null && !regex.isEmpty()) {
+ try {
+ Pattern.compile(regex);
+ return true;
+ } catch (PatternSyntaxException e) {
+ applicationLogger.error(ApplicationMsgs.STRING_UTILS_INVALID_REGEX, regex);
+ throw new ValidationServiceException(ValidationServiceError.STRING_UTILS_INVALID_REGEX, e, regex);
+ }
+ }
+ return false;
+ }
+}