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=========================================================
22 package org.onap.aai.prevalidation;
24 import com.google.gson.Gson;
25 import com.google.gson.JsonSyntaxException;
26 import org.apache.http.conn.ConnectTimeoutException;
27 import org.onap.aai.exceptions.AAIException;
28 import org.onap.aai.introspection.Introspector;
29 import org.onap.aai.rest.ueb.NotificationEvent;
30 import org.onap.aai.restclient.RestClient;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.springframework.beans.factory.annotation.Autowired;
34 import org.springframework.beans.factory.annotation.Qualifier;
35 import org.springframework.beans.factory.annotation.Value;
36 import org.springframework.context.annotation.Profile;
37 import org.springframework.http.HttpMethod;
38 import org.springframework.http.ResponseEntity;
39 import org.springframework.stereotype.Service;
41 import javax.annotation.PostConstruct;
42 import java.net.ConnectException;
43 import java.net.SocketTimeoutException;
45 import java.util.regex.Pattern;
46 import java.util.stream.Collectors;
49 * <b>ValidationService</b> routes all the writes to the database
50 * excluding deletes for now to the validation service to verify
51 * that the request is an valid one before committing to the database
54 @Profile("pre-validation")
55 public class ValidationService {
58 * Error indicating that the service trying to connect is down
60 static final String CONNECTION_REFUSED_STRING =
61 "Connection refused to the validation microservice due to service unreachable";
64 * Error indicating that the server is unable to reach the port
65 * Could be server related connectivity issue
67 static final String CONNECTION_TIMEOUT_STRING =
68 "Connection timeout to the validation microservice as this could " +
69 "indicate the server is unable to reach port, " +
70 "please check on server by running: nc -w10 -z -v ${VALIDATION_HOST} ${VALIDATION_PORT}";
73 * Error indicating that the request exceeded the allowed time
75 * Note: This means that the service could be active its
76 * just taking some time to process our request
78 static final String REQUEST_TIMEOUT_STRING =
79 "Request to validation service took longer than the currently set timeout";
81 static final String VALIDATION_ENDPOINT = "/v1/validate";
82 static final String VALIDATION_HEALTH_ENDPOINT = "/v1/info";
84 private static final String ENTITY_TYPE = "entity-type";
85 private static final String ACTION = "action";
86 private static final String SOURCE_NAME = "source-name";
88 private static final String DELETE = "DELETE";
90 private static final Logger LOGGER = LoggerFactory.getLogger(ValidationService.class);
92 private final RestClient validationRestClient;
94 private final String appName;
96 private final Set<String> validationNodeTypes;
98 private List<Pattern> exclusionList;
100 private final Gson gson;
103 public ValidationService(
104 @Qualifier("validationRestClient") RestClient validationRestClient,
105 @Value("${spring.application.name}") String appName,
106 @Value("${validation.service.node-types}") String validationNodes,
107 @Value("${validation.service.exclusion-regexes}") String exclusionRegexes
109 this.validationRestClient = validationRestClient;
110 this.appName = appName;
112 this.validationNodeTypes = Arrays
113 .stream(validationNodes.split(","))
114 .collect(Collectors.toSet());
116 if(exclusionRegexes == null || exclusionRegexes.isEmpty()){
117 this.exclusionList = new ArrayList<>();
119 this.exclusionList = Arrays
120 .stream(exclusionRegexes.split(","))
121 .map(Pattern::compile)
122 .collect(Collectors.toList());
124 this.gson = new Gson();
125 LOGGER.info("Successfully initialized the pre validation service");
129 public void initialize() throws AAIException {
131 Map<String, String> httpHeaders = new HashMap<>();
133 httpHeaders.put("X-FromAppId", appName);
134 httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
135 httpHeaders.put("Content-Type", "application/json");
137 ResponseEntity<String> healthCheckResponse = null;
141 healthCheckResponse = validationRestClient.execute(
142 VALIDATION_HEALTH_ENDPOINT,
148 } catch(Exception ex){
149 AAIException validationException = new AAIException("AAI_4021", ex);
150 throw validationException;
153 if(!isSuccess(healthCheckResponse)){
154 throw new AAIException("AAI_4021");
157 LOGGER.info("Successfully connected to the validation service endpoint");
160 public boolean shouldValidate(String nodeType){
161 return this.validationNodeTypes.contains(nodeType);
164 public void validate(List<NotificationEvent> notificationEvents) throws AAIException {
166 if(notificationEvents == null || notificationEvents.isEmpty()){
171 // Get the first notification and if the source of that notification
172 // is in one of the regexes then we skip sending it to validation
173 NotificationEvent notification = notificationEvents.get(0);
174 Introspector eventHeader = notification.getEventHeader();
175 if(eventHeader != null){
176 String source = eventHeader.getValue(SOURCE_NAME);
177 for(Pattern pattern: exclusionList){
178 if(pattern.matcher(source).matches()){
186 for (NotificationEvent event : notificationEvents) {
188 Introspector eventHeader = event.getEventHeader();
190 if(eventHeader == null){
191 // Should I skip processing the request and let it continue
192 // or fail the request and cause client impact
196 String entityType = eventHeader.getValue(ENTITY_TYPE);
197 String action = eventHeader.getValue(ACTION);
200 * Skipping the delete events for now
201 * Note: Might revisit this later when validation supports DELETE events
203 if(DELETE.equalsIgnoreCase(action)){
207 if (this.shouldValidate(entityType)) {
208 List<String> violations = this.preValidate(event.getNotificationEvent());
209 if(!violations.isEmpty()){
210 AAIException aaiException = new AAIException("AAI_4019");
211 aaiException.getTemplateVars().addAll(violations);
218 List<String> preValidate(String body) throws AAIException {
220 Map<String, String> httpHeaders = new HashMap<>();
222 httpHeaders.put("X-FromAppId", appName);
223 httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
224 httpHeaders.put("Content-Type", "application/json");
226 List<String> violations = new ArrayList<>();
227 ResponseEntity responseEntity;
230 responseEntity = validationRestClient.execute(
237 if(isSuccess(responseEntity)){
238 LOGGER.debug("Validation Service returned following response status code {} and body {}", responseEntity.getStatusCodeValue(), responseEntity.getBody());
240 Validation validation = null;
242 validation = gson.fromJson(responseEntity.getBody().toString(), Validation.class);
243 } catch(JsonSyntaxException jsonException){
244 LOGGER.warn("Unable to convert the response body {}", jsonException.getMessage());
247 if(validation == null){
249 "Validation Service following status code {} with body {}",
250 responseEntity.getStatusCodeValue(),
251 responseEntity.getBody()
254 violations.addAll(extractViolations(validation));
257 } catch(Exception e){
258 // If the exception cause is client side timeout
259 // then proceed as if it passed validation
260 // resources microservice shouldn't be blocked because of validation service
261 // is taking too long or if the validation service is down
262 // Any other exception it should block the request from passing?
263 if(e.getCause() instanceof SocketTimeoutException){
264 LOGGER.error(REQUEST_TIMEOUT_STRING, e.getCause());
265 } else if(e.getCause() instanceof ConnectException){
266 LOGGER.error(CONNECTION_REFUSED_STRING, e.getCause());
267 } else if(e.getCause() instanceof ConnectTimeoutException){
268 LOGGER.error(CONNECTION_TIMEOUT_STRING, e.getCause());
270 LOGGER.error("Unknown exception thrown please investigate", e.getCause());
276 boolean isSuccess(ResponseEntity responseEntity){
277 return responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful();
280 List<String> extractViolations(Validation validation) {
282 List<String> errorMessages = new ArrayList<>();
284 if(validation == null){
285 return errorMessages;
288 List<Violation> violations = validation.getViolations();
290 if (violations != null && !violations.isEmpty()) {
291 for (Violation violation : validation.getViolations()) {
292 LOGGER.info(violation.getErrorMessage());
293 errorMessages.add(violation.getErrorMessage());
297 return errorMessages;