2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Modifications Copyright © 2018 IBM.
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
23 package org.onap.aai.prevalidation;
25 import com.google.gson.Gson;
26 import com.google.gson.JsonSyntaxException;
28 import java.net.ConnectException;
29 import java.net.SocketTimeoutException;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.List;
36 import java.util.UUID;
37 import java.util.regex.Pattern;
38 import java.util.stream.Collectors;
40 import javax.annotation.PostConstruct;
42 import org.apache.http.conn.ConnectTimeoutException;
43 import org.onap.aai.exceptions.AAIException;
44 import org.onap.aai.introspection.Introspector;
45 import org.onap.aai.rest.ueb.NotificationEvent;
46 import org.onap.aai.restclient.RestClient;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import org.springframework.beans.factory.annotation.Autowired;
50 import org.springframework.beans.factory.annotation.Qualifier;
51 import org.springframework.beans.factory.annotation.Value;
52 import org.springframework.context.annotation.Profile;
53 import org.springframework.http.HttpMethod;
54 import org.springframework.http.ResponseEntity;
55 import org.springframework.stereotype.Service;
58 * <b>ValidationService</b> routes all the writes to the database
59 * excluding deletes for now to the validation service to verify
60 * that the request is an valid one before committing to the database
63 @Profile("pre-validation")
64 public class ValidationService {
67 * Error indicating that the service trying to connect is down
69 static final String CONNECTION_REFUSED_STRING =
70 "Connection refused to the validation microservice due to service unreachable";
73 * Error indicating that the server is unable to reach the port
74 * Could be server related connectivity issue
76 static final String CONNECTION_TIMEOUT_STRING = "Connection timeout to the validation microservice as this could "
77 + "indicate the server is unable to reach port, "
78 + "please check on server by running: nc -w10 -z -v ${VALIDATION_HOST} ${VALIDATION_PORT}";
81 * Error indicating that the request exceeded the allowed time
83 * Note: This means that the service could be active its
84 * just taking some time to process our request
86 static final String REQUEST_TIMEOUT_STRING =
87 "Request to validation service took longer than the currently set timeout";
89 static final String VALIDATION_ENDPOINT = "/v1/validate";
90 static final String VALIDATION_HEALTH_ENDPOINT = "/v1/info";
92 private static final String ENTITY_TYPE = "entity-type";
93 private static final String ACTION = "action";
94 private static final String SOURCE_NAME = "source-name";
96 private static final String DELETE = "DELETE";
98 private static final Logger LOGGER = LoggerFactory.getLogger(ValidationService.class);
100 private final RestClient validationRestClient;
102 private final String appName;
104 private final Set<String> validationNodeTypes;
106 private List<Pattern> exclusionList;
108 private final Gson gson;
111 public ValidationService(@Qualifier("validationRestClient") RestClient validationRestClient,
112 @Value("${spring.application.name}") String appName,
113 @Value("${validation.service.node-types}") String validationNodes,
114 @Value("${validation.service.exclusion-regexes}") String exclusionRegexes) {
115 this.validationRestClient = validationRestClient;
116 this.appName = appName;
118 this.validationNodeTypes = Arrays.stream(validationNodes.split(",")).collect(Collectors.toSet());
120 if (exclusionRegexes == null || exclusionRegexes.isEmpty()) {
121 this.exclusionList = new ArrayList<>();
124 Arrays.stream(exclusionRegexes.split(",")).map(Pattern::compile).collect(Collectors.toList());
126 this.gson = new Gson();
127 LOGGER.info("Successfully initialized the pre validation service");
131 public void initialize() throws AAIException {
133 Map<String, String> httpHeaders = new HashMap<>();
135 httpHeaders.put("X-FromAppId", appName);
136 httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
137 httpHeaders.put("Content-Type", "application/json");
139 ResponseEntity<String> healthCheckResponse = null;
143 healthCheckResponse =
144 validationRestClient.execute(VALIDATION_HEALTH_ENDPOINT, HttpMethod.GET, httpHeaders, null);
146 } catch (Exception ex) {
147 AAIException validationException = new AAIException("AAI_4021", ex);
148 throw validationException;
151 if (!isSuccess(healthCheckResponse)) {
152 throw new AAIException("AAI_4021");
155 LOGGER.info("Successfully connected to the validation service endpoint");
158 public boolean shouldValidate(String nodeType) {
159 return this.validationNodeTypes.contains(nodeType);
162 public void validate(List<NotificationEvent> notificationEvents) throws AAIException {
164 if (notificationEvents == null || notificationEvents.isEmpty()) {
169 // Get the first notification and if the source of that notification
170 // is in one of the regexes then we skip sending it to validation
171 NotificationEvent notification = notificationEvents.get(0);
172 Introspector eventHeader = notification.getEventHeader();
173 if (eventHeader != null) {
174 String source = eventHeader.getValue(SOURCE_NAME);
175 for (Pattern pattern : exclusionList) {
176 if (pattern.matcher(source).matches()) {
184 for (NotificationEvent event : notificationEvents) {
186 Introspector eventHeader = event.getEventHeader();
188 if (eventHeader == null) {
189 // Should I skip processing the request and let it continue
190 // or fail the request and cause client impact
194 String entityType = eventHeader.getValue(ENTITY_TYPE);
195 String action = eventHeader.getValue(ACTION);
198 * Skipping the delete events for now
199 * Note: Might revisit this later when validation supports DELETE events
201 if (DELETE.equalsIgnoreCase(action)) {
205 if (this.shouldValidate(entityType)) {
206 List<String> violations = this.preValidate(event.getNotificationEvent());
207 if (!violations.isEmpty()) {
208 AAIException aaiException = new AAIException("AAI_4019");
209 aaiException.getTemplateVars().addAll(violations);
216 List<String> preValidate(String body) throws AAIException {
218 Map<String, String> httpHeaders = new HashMap<>();
220 httpHeaders.put("X-FromAppId", appName);
221 httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
222 httpHeaders.put("Content-Type", "application/json");
224 List<String> violations = new ArrayList<>();
225 ResponseEntity responseEntity;
228 responseEntity = validationRestClient.execute(VALIDATION_ENDPOINT, HttpMethod.POST, httpHeaders, body);
230 Object responseBody = responseEntity.getBody();
231 if (isSuccess(responseEntity)) {
232 LOGGER.debug("Validation Service returned following response status code {} and body {}",
233 responseEntity.getStatusCodeValue(), responseEntity.getBody());
234 } else if (responseBody != null) {
235 Validation validation = null;
237 validation = gson.fromJson(responseBody.toString(), Validation.class);
238 } catch (JsonSyntaxException jsonException) {
239 LOGGER.warn("Unable to convert the response body {}", jsonException.getMessage());
242 if (validation == null) {
243 LOGGER.debug("Validation Service following status code {} with body {}",
244 responseEntity.getStatusCodeValue(), responseEntity.getBody());
246 violations.addAll(extractViolations(validation));
249 LOGGER.warn("Unable to convert the response body null");
251 } catch (Exception e) {
252 // If the exception cause is client side timeout
253 // then proceed as if it passed validation
254 // resources microservice shouldn't be blocked because of validation service
255 // is taking too long or if the validation service is down
256 // Any other exception it should block the request from passing?
257 if (e.getCause() instanceof SocketTimeoutException) {
258 LOGGER.error(REQUEST_TIMEOUT_STRING, e.getCause());
259 } else if (e.getCause() instanceof ConnectException) {
260 LOGGER.error(CONNECTION_REFUSED_STRING, e.getCause());
261 } else if (e.getCause() instanceof ConnectTimeoutException) {
262 LOGGER.error(CONNECTION_TIMEOUT_STRING, e.getCause());
264 LOGGER.error("Unknown exception thrown please investigate", e.getCause());
270 boolean isSuccess(ResponseEntity responseEntity) {
271 return responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful();
274 List<String> extractViolations(Validation validation) {
276 List<String> errorMessages = new ArrayList<>();
278 if (validation == null) {
279 return errorMessages;
282 List<Violation> violations = validation.getViolations();
284 if (violations != null && !violations.isEmpty()) {
285 for (Violation violation : validation.getViolations()) {
286 LOGGER.info(violation.getErrorMessage());
287 errorMessages.add(violation.getErrorMessage());
291 return errorMessages;