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 java.net.ConnectException;
27 import java.net.SocketTimeoutException;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.List;
34 import java.util.UUID;
35 import java.util.regex.Pattern;
36 import java.util.stream.Collectors;
37 import javax.annotation.PostConstruct;
38 import org.apache.http.conn.ConnectTimeoutException;
39 import org.onap.aai.exceptions.AAIException;
40 import org.onap.aai.introspection.Introspector;
41 import org.onap.aai.rest.ueb.NotificationEvent;
42 import org.onap.aai.restclient.RestClient;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.springframework.beans.factory.annotation.Autowired;
46 import org.springframework.beans.factory.annotation.Qualifier;
47 import org.springframework.beans.factory.annotation.Value;
48 import org.springframework.context.annotation.Profile;
49 import org.springframework.http.HttpMethod;
50 import org.springframework.http.ResponseEntity;
51 import org.springframework.stereotype.Service;
54 * <b>ValidationService</b> routes all the writes to the database
55 * excluding deletes for now to the validation service to verify
56 * that the request is an valid one before committing to the database
59 @Profile("pre-validation")
60 public class ValidationService {
63 * Error indicating that the service trying to connect is down
65 static final String CONNECTION_REFUSED_STRING =
66 "Connection refused to the validation microservice due to service unreachable";
69 * Error indicating that the server is unable to reach the port
70 * Could be server related connectivity issue
72 static final String CONNECTION_TIMEOUT_STRING =
73 "Connection timeout to the validation microservice as this could " +
74 "indicate the server is unable to reach port, " +
75 "please check on server by running: nc -w10 -z -v ${VALIDATION_HOST} ${VALIDATION_PORT}";
78 * Error indicating that the request exceeded the allowed time
80 * Note: This means that the service could be active its
81 * just taking some time to process our request
83 static final String REQUEST_TIMEOUT_STRING =
84 "Request to validation service took longer than the currently set timeout";
86 static final String VALIDATION_ENDPOINT = "/v1/validate";
87 static final String VALIDATION_HEALTH_ENDPOINT = "/v1/info";
89 private static final String ENTITY_TYPE = "entity-type";
90 private static final String ACTION = "action";
91 private static final String SOURCE_NAME = "source-name";
93 private static final String DELETE = "DELETE";
95 private static final Logger LOGGER = LoggerFactory.getLogger(ValidationService.class);
97 private final RestClient validationRestClient;
99 private final String appName;
101 private final Set<String> validationNodeTypes;
103 private List<Pattern> exclusionList;
105 private final Gson gson;
108 public ValidationService(
109 @Qualifier("validationRestClient") RestClient validationRestClient,
110 @Value("${spring.application.name}") String appName,
111 @Value("${validation.service.node-types}") String validationNodes,
112 @Value("${validation.service.exclusion-regexes}") String exclusionRegexes
114 this.validationRestClient = validationRestClient;
115 this.appName = appName;
117 this.validationNodeTypes = Arrays
118 .stream(validationNodes.split(","))
119 .collect(Collectors.toSet());
121 if(exclusionRegexes == null || exclusionRegexes.isEmpty()){
122 this.exclusionList = new ArrayList<>();
124 this.exclusionList = Arrays
125 .stream(exclusionRegexes.split(","))
126 .map(Pattern::compile)
127 .collect(Collectors.toList());
129 this.gson = new Gson();
130 LOGGER.info("Successfully initialized the pre validation service");
134 public void initialize() throws AAIException {
136 Map<String, String> httpHeaders = new HashMap<>();
138 httpHeaders.put("X-FromAppId", appName);
139 httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
140 httpHeaders.put("Content-Type", "application/json");
142 ResponseEntity<String> healthCheckResponse = null;
146 healthCheckResponse = validationRestClient.execute(
147 VALIDATION_HEALTH_ENDPOINT,
153 } catch(Exception ex){
154 AAIException validationException = new AAIException("AAI_4021", ex);
155 throw validationException;
158 if(!isSuccess(healthCheckResponse)){
159 throw new AAIException("AAI_4021");
162 LOGGER.info("Successfully connected to the validation service endpoint");
165 public boolean shouldValidate(String nodeType){
166 return this.validationNodeTypes.contains(nodeType);
169 public void validate(List<NotificationEvent> notificationEvents) throws AAIException {
171 if(notificationEvents == null || notificationEvents.isEmpty()){
176 // Get the first notification and if the source of that notification
177 // is in one of the regexes then we skip sending it to validation
178 NotificationEvent notification = notificationEvents.get(0);
179 Introspector eventHeader = notification.getEventHeader();
180 if(eventHeader != null){
181 String source = eventHeader.getValue(SOURCE_NAME);
182 for(Pattern pattern: exclusionList){
183 if(pattern.matcher(source).matches()){
191 for (NotificationEvent event : notificationEvents) {
193 Introspector eventHeader = event.getEventHeader();
195 if(eventHeader == null){
196 // Should I skip processing the request and let it continue
197 // or fail the request and cause client impact
201 String entityType = eventHeader.getValue(ENTITY_TYPE);
202 String action = eventHeader.getValue(ACTION);
205 * Skipping the delete events for now
206 * Note: Might revisit this later when validation supports DELETE events
208 if(DELETE.equalsIgnoreCase(action)){
212 if (this.shouldValidate(entityType)) {
213 List<String> violations = this.preValidate(event.getNotificationEvent());
214 if(!violations.isEmpty()){
215 AAIException aaiException = new AAIException("AAI_4019");
216 aaiException.getTemplateVars().addAll(violations);
223 List<String> preValidate(String body) throws AAIException {
225 Map<String, String> httpHeaders = new HashMap<>();
227 httpHeaders.put("X-FromAppId", appName);
228 httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
229 httpHeaders.put("Content-Type", "application/json");
231 List<String> violations = new ArrayList<>();
232 ResponseEntity responseEntity;
235 responseEntity = validationRestClient.execute(
242 Object responseBody = responseEntity.getBody();
243 if(isSuccess(responseEntity)){
244 LOGGER.debug("Validation Service returned following response status code {} and body {}", responseEntity.getStatusCodeValue(), responseEntity.getBody());
245 } else if (responseBody != null) {
246 Validation validation = null;
248 validation = gson.fromJson(responseBody.toString(), Validation.class);
249 } catch(JsonSyntaxException jsonException){
250 LOGGER.warn("Unable to convert the response body {}", jsonException.getMessage());
253 if(validation == null){
255 "Validation Service following status code {} with body {}",
256 responseEntity.getStatusCodeValue(),
257 responseEntity.getBody()
260 violations.addAll(extractViolations(validation));
263 LOGGER.warn("Unable to convert the response body null");
265 } catch(Exception e){
266 // If the exception cause is client side timeout
267 // then proceed as if it passed validation
268 // resources microservice shouldn't be blocked because of validation service
269 // is taking too long or if the validation service is down
270 // Any other exception it should block the request from passing?
271 if(e.getCause() instanceof SocketTimeoutException){
272 LOGGER.error(REQUEST_TIMEOUT_STRING, e.getCause());
273 } else if(e.getCause() instanceof ConnectException){
274 LOGGER.error(CONNECTION_REFUSED_STRING, e.getCause());
275 } else if(e.getCause() instanceof ConnectTimeoutException){
276 LOGGER.error(CONNECTION_TIMEOUT_STRING, e.getCause());
278 LOGGER.error("Unknown exception thrown please investigate", e.getCause());
284 boolean isSuccess(ResponseEntity responseEntity){
285 return responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful();
288 List<String> extractViolations(Validation validation) {
290 List<String> errorMessages = new ArrayList<>();
292 if(validation == null){
293 return errorMessages;
296 List<Violation> violations = validation.getViolations();
298 if (violations != null && !violations.isEmpty()) {
299 for (Violation violation : validation.getViolations()) {
300 LOGGER.info(violation.getErrorMessage());
301 errorMessages.add(violation.getErrorMessage());
305 return errorMessages;