From c604f64b971491f8c9b953adce54b847d7946e26 Mon Sep 17 00:00:00 2001 From: Prudence Au Date: Mon, 13 Aug 2018 17:06:59 -0400 Subject: 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 Signed-off-by: Pierre Rioux --- .../java/org/onap/aai/auth/AAIAuthException.java | 34 ++ .../org/onap/aai/auth/AAIMicroServiceAuth.java | 103 ++++ .../org/onap/aai/auth/AAIMicroServiceAuthCore.java | 269 ++++++++++ src/main/java/org/onap/aai/auth/FileWatcher.java | 58 +++ .../validation/ValidationServiceApplication.java | 57 +++ .../java/org/onap/aai/validation/Validator.java | 45 ++ .../aai/validation/config/EventReaderConfig.java | 163 ++++++ .../onap/aai/validation/config/ModelConfig.java | 39 ++ .../aai/validation/config/PropertiesConfig.java | 41 ++ .../org/onap/aai/validation/config/RestConfig.java | 225 +++++++++ .../aai/validation/config/RuleIndexingConfig.java | 73 +++ .../aai/validation/config/TopicAdminConfig.java | 103 ++++ .../onap/aai/validation/config/TopicConfig.java | 243 +++++++++ .../validation/config/TopicPropertiesConfig.java | 75 +++ .../config/ValidationControllerConfig.java | 82 +++ .../config/ValidationServiceAuthConfig.java | 46 ++ .../controller/ValidationController.java | 380 ++++++++++++++ .../aai/validation/data/client/RestClient.java | 121 +++++ .../exception/BaseValidationServiceException.java | 66 +++ .../exception/ValidationServiceError.java | 103 ++++ .../exception/ValidationServiceException.java | 78 +++ .../factory/DMaaPEventPublisherFactory.java | 34 ++ .../aai/validation/logging/ApplicationMsgs.java | 74 +++ .../org/onap/aai/validation/logging/LogHelper.java | 548 +++++++++++++++++++++ .../validation/modeldriven/ModelCacheManager.java | 182 +++++++ .../onap/aai/validation/modeldriven/ModelId.java | 86 ++++ .../modeldriven/configuration/mapping/Filter.java | 75 +++ .../configuration/mapping/ModelInstanceMapper.java | 89 ++++ .../mapping/ModelInstanceMappingReader.java | 69 +++ .../configuration/mapping/ValueConfiguration.java | 124 +++++ .../modeldriven/parser/XMLModelParser.java | 152 ++++++ .../modeldriven/validator/InstanceReader.java | 316 ++++++++++++ .../validator/ModelDrivenValidator.java | 306 ++++++++++++ .../modeldriven/validator/ModelReader.java | 236 +++++++++ .../modeldriven/validator/ViolationInfo.java | 88 ++++ .../aai/validation/publisher/MessagePublisher.java | 47 ++ .../publisher/ValidationEventPublisher.java | 164 ++++++ .../onap/aai/validation/reader/EntityReader.java | 61 +++ .../aai/validation/reader/EventEntityReader.java | 118 +++++ .../onap/aai/validation/reader/EventReader.java | 215 ++++++++ .../validation/reader/InstanceEntityReader.java | 75 +++ .../org/onap/aai/validation/reader/JsonReader.java | 186 +++++++ .../aai/validation/reader/OxmConfigTranslator.java | 100 ++++ .../org/onap/aai/validation/reader/OxmReader.java | 99 ++++ .../validation/reader/data/AttributeValues.java | 139 ++++++ .../onap/aai/validation/reader/data/Entity.java | 135 +++++ .../onap/aai/validation/reader/data/EntityId.java | 91 ++++ .../aai/validation/result/ValidationResult.java | 244 +++++++++ .../org/onap/aai/validation/result/Violation.java | 418 ++++++++++++++++ .../validation/ruledriven/RuleDrivenValidator.java | 278 +++++++++++ .../aai/validation/ruledriven/RuleManager.java | 91 ++++ .../ruledriven/configuration/EntitySection.java | 101 ++++ .../GroovyConfigurationException.java | 103 ++++ .../ruledriven/configuration/RuleSection.java | 200 ++++++++ .../configuration/RulesConfigurationLoader.groovy | 309 ++++++++++++ .../ruledriven/configuration/SettingsSection.java | 48 ++ .../configuration/build/ContentBuilder.java | 198 ++++++++ .../configuration/build/EntityBuilder.java | 70 +++ .../configuration/build/RuleBuilder.java | 59 +++ .../configuration/build/UseRuleBuilder.java | 46 ++ .../configuration/build/ValidationBuilder.java | 78 +++ .../aai/validation/ruledriven/rule/GroovyRule.java | 323 ++++++++++++ .../onap/aai/validation/ruledriven/rule/Rule.java | 81 +++ .../validation/services/EventPollingService.java | 121 +++++ .../onap/aai/validation/services/InfoService.java | 57 +++ .../aai/validation/services/RequestHeaders.java | 53 ++ .../aai/validation/services/ValidateService.java | 77 +++ .../validation/services/ValidateServiceImpl.java | 160 ++++++ .../aai/validation/servlet/StartupServlet.java | 95 ++++ .../org/onap/aai/validation/util/GsonUtil.java | 124 +++++ .../org/onap/aai/validation/util/JsonUtil.java | 94 ++++ .../org/onap/aai/validation/util/StringUtils.java | 100 ++++ 72 files changed, 9741 insertions(+) create mode 100644 src/main/java/org/onap/aai/auth/AAIAuthException.java create mode 100644 src/main/java/org/onap/aai/auth/AAIMicroServiceAuth.java create mode 100644 src/main/java/org/onap/aai/auth/AAIMicroServiceAuthCore.java create mode 100644 src/main/java/org/onap/aai/auth/FileWatcher.java create mode 100644 src/main/java/org/onap/aai/validation/ValidationServiceApplication.java create mode 100644 src/main/java/org/onap/aai/validation/Validator.java create mode 100644 src/main/java/org/onap/aai/validation/config/EventReaderConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/ModelConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/PropertiesConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/RestConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/RuleIndexingConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/TopicAdminConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/TopicConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/TopicPropertiesConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/ValidationControllerConfig.java create mode 100644 src/main/java/org/onap/aai/validation/config/ValidationServiceAuthConfig.java create mode 100644 src/main/java/org/onap/aai/validation/controller/ValidationController.java create mode 100644 src/main/java/org/onap/aai/validation/data/client/RestClient.java create mode 100644 src/main/java/org/onap/aai/validation/exception/BaseValidationServiceException.java create mode 100644 src/main/java/org/onap/aai/validation/exception/ValidationServiceError.java create mode 100644 src/main/java/org/onap/aai/validation/exception/ValidationServiceException.java create mode 100644 src/main/java/org/onap/aai/validation/factory/DMaaPEventPublisherFactory.java create mode 100644 src/main/java/org/onap/aai/validation/logging/ApplicationMsgs.java create mode 100644 src/main/java/org/onap/aai/validation/logging/LogHelper.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/ModelCacheManager.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/ModelId.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/Filter.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMapper.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ModelInstanceMappingReader.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/configuration/mapping/ValueConfiguration.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/parser/XMLModelParser.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/validator/InstanceReader.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/validator/ModelDrivenValidator.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/validator/ModelReader.java create mode 100644 src/main/java/org/onap/aai/validation/modeldriven/validator/ViolationInfo.java create mode 100644 src/main/java/org/onap/aai/validation/publisher/MessagePublisher.java create mode 100644 src/main/java/org/onap/aai/validation/publisher/ValidationEventPublisher.java create mode 100644 src/main/java/org/onap/aai/validation/reader/EntityReader.java create mode 100644 src/main/java/org/onap/aai/validation/reader/EventEntityReader.java create mode 100644 src/main/java/org/onap/aai/validation/reader/EventReader.java create mode 100644 src/main/java/org/onap/aai/validation/reader/InstanceEntityReader.java create mode 100644 src/main/java/org/onap/aai/validation/reader/JsonReader.java create mode 100644 src/main/java/org/onap/aai/validation/reader/OxmConfigTranslator.java create mode 100644 src/main/java/org/onap/aai/validation/reader/OxmReader.java create mode 100644 src/main/java/org/onap/aai/validation/reader/data/AttributeValues.java create mode 100644 src/main/java/org/onap/aai/validation/reader/data/Entity.java create mode 100644 src/main/java/org/onap/aai/validation/reader/data/EntityId.java create mode 100644 src/main/java/org/onap/aai/validation/result/ValidationResult.java create mode 100644 src/main/java/org/onap/aai/validation/result/Violation.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/RuleDrivenValidator.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/RuleManager.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/EntitySection.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/GroovyConfigurationException.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/RuleSection.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/RulesConfigurationLoader.groovy create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/SettingsSection.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ContentBuilder.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/build/EntityBuilder.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/build/RuleBuilder.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/build/UseRuleBuilder.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/configuration/build/ValidationBuilder.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/rule/GroovyRule.java create mode 100644 src/main/java/org/onap/aai/validation/ruledriven/rule/Rule.java create mode 100644 src/main/java/org/onap/aai/validation/services/EventPollingService.java create mode 100644 src/main/java/org/onap/aai/validation/services/InfoService.java create mode 100644 src/main/java/org/onap/aai/validation/services/RequestHeaders.java create mode 100644 src/main/java/org/onap/aai/validation/services/ValidateService.java create mode 100644 src/main/java/org/onap/aai/validation/services/ValidateServiceImpl.java create mode 100644 src/main/java/org/onap/aai/validation/servlet/StartupServlet.java create mode 100644 src/main/java/org/onap/aai/validation/util/GsonUtil.java create mode 100644 src/main/java/org/onap/aai/validation/util/JsonUtil.java create mode 100644 src/main/java/org/onap/aai/validation/util/StringUtils.java (limited to 'src/main/java') 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 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 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 role_entry : roles.entrySet()) { + AAIAuthRole role = role_entry.getValue(); + if (role.hasAllowedFunction(checkFunc)) { + return true; + } + } + return false; + } + + } + + public static class AAIAuthRole { + private List 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 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 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 indexedEvents; + + private List excludedOxmValidationEvents; + + private List indexAttributes; + + private String defaultIndexKey; + + public List getIndexedEvents() { + return indexedEvents; + } + + public void setIndexedEvents(List indexedEvents) { + this.indexedEvents = indexedEvents; + } + + public List getExcludedOxmValidationEvents() { + return excludedOxmValidationEvents; + } + + public void setExcludedOxmValidationEvents(List excludedOxmValidationEvents) { + this.excludedOxmValidationEvents = excludedOxmValidationEvents; + } + + public List getIndexAttributes() { + return indexAttributes; + } + + public void setIndexAttributes(List 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 consumerTopicNames; + + private List publisherTopicNames; + + @Resource(name = "topicProperties") + private Properties topicProperties; + + List consumerTopics = new ArrayList<>(); + List 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 getConsumerTopics() + { + return populateTopics(consumerTopics, consumerTopicNames); + } + + + /** + * Gets the configuration of topics for publishing. + * + * @return a list of topic configurations. + */ + public List 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 populateTopics(List topics, List 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 getConsumerTopicNames() { + return consumerTopicNames; + } + + public void setConsumerTopicNames(List consumerTopicNames) { + this.consumerTopicNames = consumerTopicNames; + } + + public List getPublisherTopicNames() { + return publisherTopicNames; + } + + public void setPublisherTopicNames(List 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 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 resouceList = new ArrayList(); + 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 excludedEventActions; + + @Value("#{'${event.type.rule}'.split(',')}") + private List eventTypeRule; + + @Value("#{'${event.type.model}'.split(',')}") + private List 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 getExcludedEventActions() { + return excludedEventActions; + } + + public void setExcludedEventActions(List excludedEventActions) { + this.excludedEventActions = excludedEventActions; + } + + public List getEventTypeRule() { + return eventTypeRule; + } + + public void setEventTypeRule(List eventTypeRule) { + this.eventTypeRule = eventTypeRule; + } + + public List getEventTypeModel() { + return eventTypeModel; + } + + public void setEventTypeModel(List 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> 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 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 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 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> sourceMap = new HashMap<>(); + private Throwable reportedThrowable; + + /** + * Increment the message count for the composite 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 messagesConsumed = getMessageCountsMap(eventSource); + int count = messagesConsumed.getOrDefault(key, 0); + messagesConsumed.put(key, count + 1); + } + + private Map getMessageCountsMap(String eventSource) { + return sourceMap.computeIfAbsent(eventSource, k -> new TreeMap<>()); + } + + /** + * @param eventSource the source of the event + * @return List the keys for the specified eventSource + */ + private List keyValues(String eventSource) { + Map 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 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> 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> dispatchEvent(String event, String eventSource) + throws ValidationServiceException { + List validationResults = null; + Optional 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 eventType = eventReader.getEventType(event); + + return eventType.isPresent() && "END-EVENT".equalsIgnoreCase(eventType.get()); + } + + private Boolean isDomainValid(String event) throws ValidationServiceException { + Optional 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 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 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 { + 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 = ""; + + 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 = ""; + + 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 logMethod, String message) { + setMdcClassName(); + logMethod.accept(message); + unsetMdcClassName(); + } + + /** + * @param logMethod + * @param msg + * @param args + */ + private void invokeLogger(BiConsumer logMethod, ApplicationMsgs msg, String[] args) { + setMdcClassName(); + logMethod.accept(msg, args); + unsetMdcClassName(); + } + + /** + * @param logMethod + * @param errorEnum + * @param args + */ + private void invokeErrorCodeLogger(BiConsumer 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 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 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() { + @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 valid = new ArrayList<>(); + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List getValid() { + return valid; + } + + public void setValid(List 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 getMappings() throws ValidationServiceException { + List 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 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 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("()", "$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: + *
    + *
  • origin: path that serves as the starting point for the instance search
  • + *
  • root: path to underlying instance objects that can be examined by recursively calling the + * getValues method
  • + *
+ * + * @return a {@link Multimap} of instances keyed by their model id. + * @throws ValidationServiceException + */ + public Multimap getValues(String json, ModelInstanceMapper mapping) throws ValidationServiceException { + Multimap 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 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.
+ * + * 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 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 mappings) throws ValidationServiceException { + String origin = mappings.iterator().next().getInstance().getOrigin(); + List 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 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 entry : jsonObject.entrySet()) { + if (MODEL_NAME.equals(entry.getKey())) { + return entry.getValue().getAsString(); + } + } + return null; + } + + private void processRelatedObjects(Multimap 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> entrySet = jsonObject.entrySet(); + + String modelName = getModelName(jsonObject); + + for (Entry 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 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 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 violations = new ArrayList<>(); + + if (modelElement == null) { + ViolationInfo info = ViolationInfo.valueOf(ModelDrivenValidator.ValidationOutcomeType.NO_MODEL.toString()); + Map 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 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 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 validations, EntityReader reader) + throws ValidationServiceException { + String entityLink = null; + Multimap modelMap = ModelReader.getValues(currentModelNode, mapping, modelCacheManager); + Multimap instanceMap = instanceReader.getValues(entity.getJson(), mapping); + + // Validate model with instance according to mappings. + // Note: Currently the cardinality of instances are not validated. + List 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 modelEntry : modelMap.entries()) { + // Get the child model. + Node childModelNode = modelEntry.getValue(); + if (childModelNode != null) { + // Validate all child instance objects with current child model. + Collection 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 validateModelInstanceValues(Set modelValues, Set instanceValues, Entity entity, + ModelInstanceMapper modelInstanceMapper) throws ValidationServiceException { + List 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 setViolationDetails(ValidationOutcomeType outcome, Collection values, Entity entity, ModelInstanceMapper modelInstanceMapper) + throws ValidationServiceException { + + List 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 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 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 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 getValues(Node modelElement, ModelInstanceMapper mapping, ModelCacheManager modelCacheManager) + throws ValidationServiceException { + Multimap values = HashMultimap.create(); + + if (MappingType.ATTRIBUTE.equals(mapping.getMappingType())) { + // Get attributes on current model element. + Multimap 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 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 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 models) throws ValidationServiceException { + String root = mapping.getModel().getRoot(); + + if (root == null) { + return; + } + + List 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 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 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 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 getModelValues(Node model, ModelInstanceMapper mapping, boolean addModel) { + Multimap values = HashMultimap.create(); + List valueStrings = getModelValuesList(model, mapping); + for (String value : valueStrings) { + values.put(value, addModel ? model : null); + } + return values; + } + + private static List getModelValuesList(Node model, ModelInstanceMapper mapping) { + List values = new ArrayList<>(); + List 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 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 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 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 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 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 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 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 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 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 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 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 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 getIds(String json, String type) throws ValidationServiceException { + List 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 getProperty(String json, String path) throws ValidationServiceException { + return jsonReader.get(json, path).stream().findFirst(); + } + + @Override + public Optional 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 getPropertyForMultiplePaths(String json, String multiplePaths) + throws ValidationServiceException { + Optional 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 getEventDomain(String event) throws ValidationServiceException { + List 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 getEventAction(String event) throws ValidationServiceException { + List 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 getEventType(String event) throws ValidationServiceException { + List 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 getEntityType(String event) throws ValidationServiceException { + List 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 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 getFirst(List l) { + return l.stream().findFirst(); + } + + private Optional 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 getIds(String json, String type) throws ValidationServiceException { + List ids = new ArrayList<>(); + + InstanceEntity entity = reader.getNamedQueryEntity(json); + + List 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 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 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 getAsList(DocumentContext document, String path) { + List 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 jsonArrayToList(JsonArray jsonArray) { + List 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