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