Merge "[AAI] Fix doc config files"
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / prevalidation / ValidationService.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
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
12  *
13  *    http://www.apache.org/licenses/LICENSE-2.0
14  *
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=========================================================
21  */
22
23 package org.onap.aai.prevalidation;
24
25 import com.google.gson.Gson;
26 import com.google.gson.JsonSyntaxException;
27
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;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.UUID;
37 import java.util.regex.Pattern;
38 import java.util.stream.Collectors;
39
40 import javax.annotation.PostConstruct;
41
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;
56
57 /**
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
61  */
62 @Service
63 @Profile("pre-validation")
64 public class ValidationService {
65
66     /**
67      * Error indicating that the service trying to connect is down
68      */
69     static final String CONNECTION_REFUSED_STRING =
70             "Connection refused to the validation microservice due to service unreachable";
71
72     /**
73      * Error indicating that the server is unable to reach the port
74      * Could be server related connectivity issue
75      */
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}";
79
80     /**
81      * Error indicating that the request exceeded the allowed time
82      *
83      * Note: This means that the service could be active its
84      * just taking some time to process our request
85      */
86     static final String REQUEST_TIMEOUT_STRING =
87             "Request to validation service took longer than the currently set timeout";
88
89     static final String VALIDATION_ENDPOINT = "/v1/validate";
90     static final String VALIDATION_HEALTH_ENDPOINT = "/v1/info";
91
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";
95
96     private static final String DELETE = "DELETE";
97
98     private static final Logger LOGGER = LoggerFactory.getLogger(ValidationService.class);
99
100     private final RestClient validationRestClient;
101
102     private final String appName;
103
104     private final Set<String> validationNodeTypes;
105
106     private List<Pattern> exclusionList;
107
108     private final Gson gson;
109
110     @Autowired
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;
117
118         this.validationNodeTypes = Arrays.stream(validationNodes.split(",")).collect(Collectors.toSet());
119
120         if (exclusionRegexes == null || exclusionRegexes.isEmpty()) {
121             this.exclusionList = new ArrayList<>();
122         } else {
123             this.exclusionList =
124                     Arrays.stream(exclusionRegexes.split(",")).map(Pattern::compile).collect(Collectors.toList());
125         }
126         this.gson = new Gson();
127         LOGGER.info("Successfully initialized the pre validation service");
128     }
129
130     @PostConstruct
131     public void initialize() throws AAIException {
132
133         Map<String, String> httpHeaders = new HashMap<>();
134
135         httpHeaders.put("X-FromAppId", appName);
136         httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
137         httpHeaders.put("Content-Type", "application/json");
138
139         ResponseEntity<String> healthCheckResponse = null;
140
141         try {
142
143             healthCheckResponse =
144                     validationRestClient.execute(VALIDATION_HEALTH_ENDPOINT, HttpMethod.GET, httpHeaders, null);
145
146         } catch (Exception ex) {
147             AAIException validationException = new AAIException("AAI_4021", ex);
148             throw validationException;
149         }
150
151         if (!isSuccess(healthCheckResponse)) {
152             throw new AAIException("AAI_4021");
153         }
154
155         LOGGER.info("Successfully connected to the validation service endpoint");
156     }
157
158     public boolean shouldValidate(String nodeType) {
159         return this.validationNodeTypes.contains(nodeType);
160     }
161
162     public void validate(List<NotificationEvent> notificationEvents) throws AAIException {
163
164         if (notificationEvents == null || notificationEvents.isEmpty()) {
165             return;
166         }
167
168         {
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()) {
177                         return;
178                     }
179                 }
180             }
181
182         }
183
184         for (NotificationEvent event : notificationEvents) {
185
186             Introspector eventHeader = event.getEventHeader();
187
188             if (eventHeader == null) {
189                 // Should I skip processing the request and let it continue
190                 // or fail the request and cause client impact
191                 continue;
192             }
193
194             String entityType = eventHeader.getValue(ENTITY_TYPE);
195             String action = eventHeader.getValue(ACTION);
196
197             /**
198              * Skipping the delete events for now
199              * Note: Might revisit this later when validation supports DELETE events
200              */
201             if (DELETE.equalsIgnoreCase(action)) {
202                 continue;
203             }
204
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);
210                     throw aaiException;
211                 }
212             }
213         }
214     }
215
216     List<String> preValidate(String body) throws AAIException {
217
218         Map<String, String> httpHeaders = new HashMap<>();
219
220         httpHeaders.put("X-FromAppId", appName);
221         httpHeaders.put("X-TransactionID", UUID.randomUUID().toString());
222         httpHeaders.put("Content-Type", "application/json");
223
224         List<String> violations = new ArrayList<>();
225         ResponseEntity responseEntity;
226         try {
227
228             responseEntity = validationRestClient.execute(VALIDATION_ENDPOINT, HttpMethod.POST, httpHeaders, body);
229
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;
236                 try {
237                     validation = gson.fromJson(responseBody.toString(), Validation.class);
238                 } catch (JsonSyntaxException jsonException) {
239                     LOGGER.warn("Unable to convert the response body {}", jsonException.getMessage());
240                 }
241
242                 if (validation == null) {
243                     LOGGER.debug("Validation Service following status code {} with body {}",
244                             responseEntity.getStatusCodeValue(), responseEntity.getBody());
245                 } else {
246                     violations.addAll(extractViolations(validation));
247                 }
248             } else {
249                 LOGGER.warn("Unable to convert the response body null");
250             }
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());
263             } else {
264                 LOGGER.error("Unknown exception thrown please investigate", e.getCause());
265             }
266         }
267         return violations;
268     }
269
270     boolean isSuccess(ResponseEntity responseEntity) {
271         return responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful();
272     }
273
274     List<String> extractViolations(Validation validation) {
275
276         List<String> errorMessages = new ArrayList<>();
277
278         if (validation == null) {
279             return errorMessages;
280         }
281
282         List<Violation> violations = validation.getViolations();
283
284         if (violations != null && !violations.isEmpty()) {
285             for (Violation violation : validation.getViolations()) {
286                 LOGGER.info(violation.getErrorMessage());
287                 errorMessages.add(violation.getErrorMessage());
288             }
289         }
290
291         return errorMessages;
292     }
293 }