Retry Simulator's DB queries upon failure
[vid.git] / vid-ext-services-simulator / src / main / java / org / onap / simulator / controller / SimulatorController.java
1 package org.onap.simulator.controller;
2
3 import static org.apache.commons.lang3.StringUtils.isEmpty;
4 import static org.mockserver.integration.ClientAndServer.startClientAndServer;
5 import static org.mockserver.matchers.Times.exactly;
6 import static org.mockserver.model.JsonBody.json;
7
8 import com.fasterxml.jackson.databind.DeserializationFeature;
9 import com.fasterxml.jackson.databind.ObjectMapper;
10 import com.google.common.collect.ImmutableMap;
11 import com.google.gson.Gson;
12 import java.io.BufferedInputStream;
13 import java.io.DataInputStream;
14 import java.io.File;
15 import java.io.FileInputStream;
16 import java.io.FileNotFoundException;
17 import java.io.IOException;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.net.URLEncoder;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.nio.file.Paths;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Enumeration;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.Scanner;
32 import java.util.function.Supplier;
33 import java.util.stream.Collectors;
34 import java.util.stream.Stream;
35 import javax.annotation.PostConstruct;
36 import javax.annotation.PreDestroy;
37 import javax.persistence.EntityManager;
38 import javax.persistence.EntityManagerFactory;
39 import javax.persistence.Persistence;
40 import javax.persistence.TypedQuery;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43 import org.mockserver.integration.ClientAndServer;
44 import org.mockserver.matchers.MatchType;
45 import org.mockserver.matchers.Times;
46 import org.mockserver.model.HttpRequest;
47 import org.mockserver.model.HttpResponse;
48 import org.mockserver.model.JsonBody;
49 import org.onap.simulator.db.entities.Function;
50 import org.onap.simulator.db.entities.User;
51 import org.onap.simulator.errorHandling.VidSimulatorException;
52 import org.onap.simulator.model.SimulatorRequestResponseExpectation;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.springframework.core.io.ClassPathResource;
56 import org.springframework.core.io.Resource;
57 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
58 import org.springframework.core.io.support.PropertiesLoaderUtils;
59 import org.springframework.core.io.support.ResourcePatternResolver;
60 import org.springframework.http.HttpEntity;
61 import org.springframework.http.HttpHeaders;
62 import org.springframework.http.HttpMethod;
63 import org.springframework.http.HttpStatus;
64 import org.springframework.http.ResponseEntity;
65 import org.springframework.stereotype.Component;
66 import org.springframework.web.bind.annotation.RequestBody;
67 import org.springframework.web.bind.annotation.RequestMapping;
68 import org.springframework.web.bind.annotation.RequestMethod;
69 import org.springframework.web.bind.annotation.ResponseBody;
70 import org.springframework.web.bind.annotation.ResponseStatus;
71 import org.springframework.web.bind.annotation.RestController;
72 import org.springframework.web.client.HttpClientErrorException;
73 import org.springframework.web.client.RestTemplate;
74 import org.springframework.web.servlet.View;
75
76 @RestController
77 @Component
78 public class SimulatorController {
79
80     private static final Times DEFAULT_NUMBER_OF_TIMES = Times.unlimited();
81     private ClientAndServer mockServer;
82     private String mockServerProtocol;
83     private String mockServerHost;
84     private Integer mockServerPort;
85     private Boolean enablePresetRegistration;
86     private Boolean enableJPA;
87     private volatile boolean isInitialized = false;
88
89     private EntityManager entityManager;
90     private EntityManagerFactory entityManagerFactory;
91
92
93     private static final Logger logger = LoggerFactory.getLogger(SimulatorController.class);
94
95     @PostConstruct
96     public void init(){
97         logger.info("Starting VID Simulator....");
98         setProperties();
99         mockServer = startClientAndServer(mockServerPort);
100         presetRegister();
101         try {
102             initJPA();
103         } catch (RuntimeException e) {
104             isInitialized = false;
105             logger.error("Error during the JPA initialization:", e);
106             return;
107         }
108         isInitialized = true;
109         logger.info("VID Simulator started successfully");
110     }
111
112     private void initJPA() {
113         if (enableJPA) {
114             entityManagerFactory = Persistence.createEntityManagerFactory("vid", overrideConnectionUrl());
115             entityManager = entityManagerFactory.createEntityManager();
116         }
117     }
118
119     private Map<Object, Object> overrideConnectionUrl() {
120         final String connectionUrlEnvProperty = "hibernate.connection.url";
121         if (isEmpty(System.getProperty(connectionUrlEnvProperty))) {
122             return Collections.emptyMap();
123         } else {
124             return ImmutableMap.of(connectionUrlEnvProperty, System.getProperty(connectionUrlEnvProperty));
125         }
126     }
127
128     @PreDestroy
129     public void tearDown(){
130         logger.info("Stopping VID Simulator....");
131         entityManager.close();
132         entityManagerFactory.close();
133         isInitialized = false;
134         mockServer.stop(false);
135     }
136
137
138     private void presetRegister() {
139         //Checking if set
140         if (enablePresetRegistration == null || !enablePresetRegistration){
141             logger.info("Preset registration property is false or not set - skipping preset registration...");
142             return;
143         }
144         ClassLoader cl = this.getClass().getClassLoader();
145         ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
146         List<Path> resources = new ArrayList<>();
147         try {
148             File presetDir = resolver.getResource("/preset_registration/").getFile();
149             if (presetDir.exists() && presetDir.isDirectory()) {
150                 try (Stream<Path> files = Files.walk(Paths.get(presetDir.getPath()))) {
151                     resources = files
152                             .filter(p -> p.toString().endsWith(".json"))
153                             .collect(Collectors.toList());
154                 }
155             } else {
156                 logger.error("preset_registration directory is not exists");
157             }
158         } catch (IOException e) {
159             logger.error("Error performing preset registration, error: ", e);
160             return;
161         }
162         logger.info("Starting preset registrations, number of requests: {}", resources.size());
163         for (Path resource: resources){
164             String content;
165             try (Scanner scanner = new Scanner(resource).useDelimiter("\\Z")){
166                 content = scanner.next();
167             } catch (IOException e){
168                 logger.error("Error reading preset registration file {}, skipping to next one. Error: ", resource.getFileName(), e);
169                 continue;
170             }
171             //register the preset request
172             try {
173                 register(content);
174             } catch (VidSimulatorException e) {
175                 logger.error("Error proceeding preset registration file {},skipping to next one. Check if the JSON is in correct format. Error: ", resource.getFileName(), e);
176             }
177         }
178     }
179
180
181
182     private void setProperties() {
183         Resource resource = new ClassPathResource("simulator.properties");
184         Properties props = new Properties();
185         try {
186             props = PropertiesLoaderUtils.loadProperties(resource);
187         } catch (IOException e) {
188             logger.error("Error loading simulator properties, error: ", e);
189             return;
190         }
191         logger.info("Simulator properties are {}", props);
192         mockServerProtocol = (String)props.get("simulator.mockserver.protocol");
193         mockServerHost = (String)props.get("simulator.mockserver.host");
194         mockServerPort = Integer.parseInt((String)props.get("simulator.mockserver.port"));
195         enablePresetRegistration = Boolean.parseBoolean((String)props.get("simulator.enablePresetRegistration"));
196         enableJPA = Boolean.parseBoolean((String)props.get("simulator.enableCentralizedRoleAccess"));
197     }
198
199     @RequestMapping(value = {"/registerToVidSimulator"}, method = RequestMethod.POST)
200     public @ResponseBody
201     ResponseEntity registerRequest(HttpServletRequest request, @RequestBody String expectation) {
202         try {
203             register(expectation);
204         } catch (VidSimulatorException e) {
205             return new ResponseEntity<>("Registration failure! Error: "+e.getMessage(),HttpStatus.BAD_REQUEST);
206         }
207         return new ResponseEntity<>("Registration successful!",HttpStatus.OK);
208     }
209
210     @RequestMapping(value = {"/echo"}, method = RequestMethod.GET)
211     ResponseEntity echo(HttpServletRequest request) {
212         return isInitialized ? new ResponseEntity<>("",HttpStatus.OK) : new ResponseEntity<>("",HttpStatus.SERVICE_UNAVAILABLE);
213     }
214
215     @RequestMapping(value = {"/retrieveRecordedRequests"}, method = RequestMethod.GET)
216     public List<HttpRequest> retrieveRecordedRequests() {
217         return Arrays.asList(mockServer.retrieveRecordedRequests(null));
218     }
219
220     @RequestMapping(value = {"/registerToVidSimulator"}, method = RequestMethod.DELETE)
221     @ResponseStatus(value = HttpStatus.OK)
222     public void wipeOutAllExpectations() {
223         mockServer.reset();
224     }
225
226     private void register(String expectation) throws VidSimulatorException{
227         ObjectMapper mapper = new ObjectMapper()
228                 .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
229
230         SimulatorRequestResponseExpectation[] expectationModels;
231         try {
232             expectationModels = mapper.readValue(expectation, SimulatorRequestResponseExpectation[].class);
233         } catch (IOException e) {
234             logger.error("Couldn't deserialize register expectation {}, error:", expectation, e);
235             throw new VidSimulatorException(e.getMessage());
236         }
237
238         for (SimulatorRequestResponseExpectation expectationModel : expectationModels) {
239             logger.info("Proceeding registration request: {}", expectationModel);
240             register(expectationModel);
241         }
242     }
243
244     //*******portal role access simulator (added by ag137v)
245
246     @RequestMapping(value = {"/ecompportal_att/auxapi//{ver}/user/*", "/ONAPPORTAL/auxapi//{ver}/user/*"}, method = RequestMethod.GET)
247     public @ResponseBody
248     ResponseEntity auxapiGetUser(HttpServletRequest request) {
249         if (!enableJPA) {
250             return new ResponseEntity<>("Centralized Role Access is disabled", HttpStatus.SERVICE_UNAVAILABLE);
251         }
252         entityManager.clear();
253         String reqUri = request.getRequestURI();
254         String userName = reqUri.substring(reqUri.lastIndexOf('/') + 1);
255         TypedQuery<User> userQuery = entityManager.createQuery("select u from fn_user u where u.loginId = :userName", User.class);
256         userQuery.setParameter("userName", userName);
257         User user = doWithSingleRetry(userQuery::getSingleResult);
258
259         Gson g = new Gson();
260         String jsonString = g.toJson(user);
261
262         return new ResponseEntity<>(jsonString, HttpStatus.OK);
263
264     }
265
266     @RequestMapping(value = {"/ecompportal_att/auxapi//{ver}/functions", "/ONAPPORTAL/auxapi//{ver}/functions"}, method = RequestMethod.GET)
267     public @ResponseBody
268     ResponseEntity auxapiGetFunctions(HttpServletRequest request) {
269         if (!enableJPA) {
270             return new ResponseEntity<>("Centralized Role Access is disabled", HttpStatus.SERVICE_UNAVAILABLE);
271         }
272         TypedQuery<Function> userQuery = entityManager.createQuery("select f from fn_function f", Function.class);
273         List<Function> functions = doWithSingleRetry(userQuery::getResultList);
274         Gson g = new Gson();
275         String jsonString = g.toJson(functions);
276
277         return new ResponseEntity<>(jsonString, HttpStatus.OK);
278
279     }
280
281     //*******portal role access simulator end
282
283     @RequestMapping(value = {"/ecompportal_att/auxapi//{ver}/getSessionSlotCheckInterval", "/ONAPPORTAL/auxapi//{ver}/getSessionSlotCheckInterval"}, method = RequestMethod.GET)
284     @ResponseBody
285     public String getSessionSlotCheckInterval() {
286         return "300000";
287     }
288
289     @RequestMapping(value = {"/**"})
290     public ResponseEntity redirectToMockServer(HttpServletRequest request, HttpServletResponse response) throws IOException {
291         //This is needed to allow POST redirect - see http://www.baeldung.com/spring-redirect-and-forward
292         request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.TEMPORARY_REDIRECT);
293
294         //Building the redirect URL
295 //        String restOfTheUrl = (String) request.getAttribute(
296 //                HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
297         String requestUri = URLEncoder.encode(request.getRequestURI(), "UTF-8");
298         requestUri = requestUri.replaceAll("%2F", "/");
299         String restOfTheUrl = requestUri.replaceFirst(request.getContextPath(), "");
300
301         StringBuilder sb = new StringBuilder();
302         sb.append(mockServerProtocol).append("://").append(mockServerHost).append(":").append(mockServerPort).append(restOfTheUrl);
303         String queryString = request.getQueryString();
304         if (queryString != null){
305             sb.append("?").append(queryString);
306         }
307         String redirectUrl = sb.toString();
308         logger.info("Redirecting the request to : {}", redirectUrl);
309
310         URI uri;
311         try {
312             uri = new URI("http", null, "localhost", 1080, restOfTheUrl, request.getQueryString(), null);
313         } catch (URISyntaxException e) {
314             logger.error("Error during proxying request {}, error: ", request.getRequestURI(), e.getMessage());
315             return new ResponseEntity(e.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR);
316         }
317         RestTemplate restTemplate = new RestTemplate();
318         //Preparing the headers
319         HttpHeaders headers = new HttpHeaders();
320         Enumeration<String> headerNames =  request.getHeaderNames();
321         while (headerNames.hasMoreElements()){
322             String headerToSet = headerNames.nextElement();
323             headers.set(headerToSet, request.getHeader(headerToSet));
324         }
325         HttpEntity<String> proxyRequest;
326         if ("POST".equalsIgnoreCase(request.getMethod()))
327         {
328             String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
329             proxyRequest = new HttpEntity<>(body, headers);
330         } else {
331             proxyRequest = new HttpEntity<>(headers);
332         }
333
334         ResponseEntity<String> responseEntity;
335         try {
336             responseEntity =
337                     restTemplate.exchange(uri, HttpMethod.resolve(request.getMethod()), proxyRequest, String.class);
338         } catch (HttpClientErrorException exception) {
339             responseEntity = new ResponseEntity<>(exception.getResponseBodyAsString(), exception.getStatusCode());
340         }
341
342         return responseEntity;
343     }
344
345     private <T> T doWithSingleRetry(Supplier<T> supplier) {
346         try {
347             return supplier.get();
348         } catch (Exception e) {
349             logger.error("exception was thrown; will retry the same action one more time", e);
350             // here exceptions will be thrown
351             return supplier.get();
352         }
353     }
354
355     private void register(SimulatorRequestResponseExpectation expectationModel) throws VidSimulatorException{
356         //Setting request according to what is passed
357         HttpRequest request = HttpRequest.request();
358         String id = expectationModel.getSimulatorRequest().getId();
359         if (id != null) {
360             request.withHeader("x-simulator-id", id);
361         }
362
363         if (expectationModel.getSimulatorRequest().getHeaders()!=null) {
364             expectationModel.getSimulatorRequest().getHeaders().forEach(
365                     request::withHeader);
366         }
367
368         String method = expectationModel.getSimulatorRequest().getMethod();
369         if (method != null) {
370             request.withMethod(method);
371         }
372         String path = expectationModel.getSimulatorRequest().getPath();
373         if (path != null) {
374             request.withPath(path);
375         }
376         String body = expectationModel.getSimulatorRequest().getBody();
377         if (body != null) {
378             if (expectationModel.getSimulatorRequest().getStrict()) {
379                 request.withBody(json(body, MatchType.STRICT));
380             } else {
381                 request.withBody(new JsonBody(body));
382             }
383         }
384
385         //Queryparams
386         final Map<String, List<String>> queryParams = expectationModel.getSimulatorRequest().getQueryParams();
387         if (queryParams != null){
388             String[] arr = new String[0];
389             queryParams.forEach((key, value) -> request.withQueryStringParameter(key, value.toArray(arr)));
390         }
391
392         //Setting response according to what is passed
393         HttpResponse response = HttpResponse.response();
394         Integer responseCode = expectationModel.getSimulatorResponse().getResponseCode();
395         if (responseCode != null) {
396             response.withStatusCode(responseCode);
397         } else {
398             logger.error("Invalid registration - response code cannot be empty");
399             throw new VidSimulatorException("Invalid registration - response code cannot be empty");
400         }
401
402         String respBody = expectationModel.getSimulatorResponse().getBody();
403         if (respBody != null) {
404             response.withBody(respBody);
405         }
406
407         String file = expectationModel.getSimulatorResponse().getFile();
408         if (file != null) {
409             response.withBody(loadFileString(file));
410         }
411
412         Map<String, String> responseHeaders = expectationModel.getSimulatorResponse().getResponseHeaders();
413         if (responseHeaders != null) {
414             responseHeaders.forEach(response::withHeader);
415         }
416
417         Times numberOfTimes = getExpectationNumberOfTimes(expectationModel);
418
419         if (expectationModel.getMisc().getReplace()) {
420             logger.info("Unregistering request expectation, if previously set, request: {}", expectationModel.getSimulatorRequest());
421             mockServer.clear(request);
422         }
423
424         mockServer
425                 .when(request, numberOfTimes).respond(response);
426     }
427
428
429     private byte[] loadFileString(String filePath) {
430         byte[] bytes = null;
431         File file = null;
432         try {
433             file = new ClassPathResource("download_files/" + filePath).getFile();
434             try(DataInputStream dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(file.getPath())))) {
435                 bytes = new byte[(int)file.length()];
436                 dataInputStream.readFully(bytes);
437             }
438         } catch (FileNotFoundException e) {
439             logger.error("File not found for file:" + filePath);
440             e.printStackTrace();
441         } catch (IOException e) {
442             logger.error("Error reading file:" + filePath);
443             e.printStackTrace();
444         }
445
446         return bytes;
447     }
448     private Times getExpectationNumberOfTimes(SimulatorRequestResponseExpectation expectationModel) {
449         Integer expectationModelNumberOfTimes = expectationModel.getMisc().getNumberOfTimes();
450         Times effectiveNumberOfTimes;
451         if (expectationModelNumberOfTimes == null || expectationModelNumberOfTimes < 0) {
452             effectiveNumberOfTimes = DEFAULT_NUMBER_OF_TIMES;
453         } else {
454             effectiveNumberOfTimes = exactly(expectationModelNumberOfTimes);
455         }
456         return effectiveNumberOfTimes;
457     }
458 }