/*- | |
* ============LICENSE_START======================================================= | |
* OSAM | |
* ================================================================================ | |
* Copyright (C) 2018 AT&T | |
* ================================================================================ | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* ============LICENSE_END========================================================= | |
*/ | |
package org.onap.simulator.controller; | |
import com.fasterxml.jackson.databind.DeserializationFeature; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import org.mockserver.integration.ClientAndServer; | |
import org.mockserver.matchers.Times; | |
import org.mockserver.model.HttpRequest; | |
import org.mockserver.model.HttpResponse; | |
import static org.mockserver.model.HttpRequest.request; | |
import static org.mockserver.model.HttpResponse.response; | |
import org.mockserver.model.JsonBody; | |
import org.onap.simulator.errorHandling.VidSimulatorException; | |
import org.onap.simulator.model.SimulatorRequestResponseExpectation; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.core.io.ClassPathResource; | |
import org.springframework.core.io.Resource; | |
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | |
import org.springframework.core.io.support.PropertiesLoaderUtils; | |
import org.springframework.core.io.support.ResourcePatternResolver; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.stereotype.Component; | |
import org.springframework.web.bind.annotation.*; | |
import org.springframework.web.servlet.HandlerMapping; | |
import org.springframework.web.servlet.View; | |
import javax.annotation.PostConstruct; | |
import javax.annotation.PreDestroy; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import java.io.*; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.util.*; | |
import java.io.UnsupportedEncodingException; | |
import java.net.URLEncoder; | |
import java.util.stream.Collectors; | |
import static org.mockserver.integration.ClientAndServer.startClientAndServer; | |
import static org.mockserver.matchers.Times.exactly; | |
@RestController | |
@Component | |
public class SimulatorController { | |
private static final Times DEFAULT_NUMBER_OF_TIMES = Times.unlimited(); | |
private ClientAndServer mockServer; | |
private String mockServerProtocol; | |
private String mockServerHost; | |
private Integer mockServerPort; | |
private Boolean enablePresetRegistration; | |
private volatile boolean isInitialized = false; | |
Logger logger = LoggerFactory.getLogger(SimulatorController.class); | |
@PostConstruct | |
public void init(){ | |
logger.info("Starting VID Simulator...."); | |
setProperties(); | |
mockServer = startClientAndServer(mockServerPort); | |
presetRegister(); | |
isInitialized = true; | |
logger.info("VID Simulator started successfully"); | |
} | |
@PreDestroy | |
public void tearDown(){ | |
logger.info("Stopping VID Simulator...."); | |
isInitialized = false; | |
mockServer.stop(); | |
} | |
private void presetRegister() { | |
//Checking if set | |
if (enablePresetRegistration == null || !enablePresetRegistration){ | |
logger.info("Preset registration property is false or not set - skipping preset registration..."); | |
return; | |
} | |
ClassLoader cl = this.getClass().getClassLoader(); | |
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl); | |
List<Path> resources = new ArrayList<>(); | |
try { | |
File presetDir = resolver.getResource("/preset_registration/").getFile(); | |
if (presetDir.exists() && presetDir.isDirectory()) { | |
resources = Files.walk(Paths.get(presetDir.getPath())) | |
.filter(p -> p.toString().endsWith(".json")) | |
.collect(Collectors.toList()); | |
} else { | |
logger.error("preset_registration directory is not exists"); | |
} | |
} catch (IOException e) { | |
logger.error("Error performing preset registration, error: ", e); | |
return; | |
} | |
logger.info("Starting preset registrations, number of requests: {}", resources.size()); | |
for (Path resource: resources){ | |
String content; | |
try { | |
content = new Scanner(resource).useDelimiter("\\Z").next(); | |
} catch (IOException e){ | |
logger.error("Error reading preset registration file {}, skipping to next one. Error: ", resource.getFileName(), e); | |
continue; | |
} | |
//register the preset request | |
try { | |
register(content); | |
} catch (VidSimulatorException e) { | |
logger.error("Error proceeding preset registration file {},skipping to next one. Check if the JSON is in correct format. Error: ", resource.getFileName(), e); | |
} | |
} | |
} | |
private void setProperties() { | |
Resource resource = new ClassPathResource("simulator.properties"); | |
Properties props = new Properties(); | |
try { | |
props = PropertiesLoaderUtils.loadProperties(resource); | |
} catch (IOException e) { | |
logger.error("Error loading simulator properties, error: ", e); | |
return; | |
} | |
logger.info("Simulator properties are {}", props); | |
mockServerProtocol = (String)props.get("simulator.mockserver.protocol"); | |
mockServerHost = (String)props.get("simulator.mockserver.host"); | |
mockServerPort = Integer.parseInt((String)props.get("simulator.mockserver.port")); | |
enablePresetRegistration = Boolean.parseBoolean((String)props.get("simulator.enablePresetRegistration")); | |
} | |
@RequestMapping(value = {"/registerToVidSimulator"}, method = RequestMethod.POST) | |
public @ResponseBody | |
ResponseEntity registerRequest(HttpServletRequest request, @RequestBody String expectation) { | |
try { | |
register(expectation); | |
} catch (VidSimulatorException e) { | |
return new ResponseEntity<>("Registration failure! Error: "+e.getMessage(),HttpStatus.BAD_REQUEST); | |
} | |
return new ResponseEntity<>("Registration successful!",HttpStatus.OK); | |
} | |
@RequestMapping(value = {"/echo"}, method = RequestMethod.GET) | |
ResponseEntity echo(HttpServletRequest request) { | |
return isInitialized ? new ResponseEntity<>("",HttpStatus.OK) : new ResponseEntity<>("",HttpStatus.SERVICE_UNAVAILABLE); | |
} | |
// @RequestMapping(value = {"/registerToVidSimulator"}, method = RequestMethod.GET) | |
// public ResponseEntity<String> getAllRegisteredRequests() throws JsonProcessingException { | |
// final Expectation[] expectations = mockServer.retrieveExistingExpectations(null); | |
// return new ResponseEntity<>(new ObjectMapper() | |
// .configure(SerializationFeature.INDENT_OUTPUT, true) | |
// .writeValueAsString(expectations), HttpStatus.OK); | |
// } | |
@RequestMapping(value = {"/registerToVidSimulator"}, method = RequestMethod.DELETE) | |
@ResponseStatus(value = HttpStatus.OK) | |
public void wipeOutAllExpectations() { | |
mockServer.reset(); | |
} | |
private void register(String expectation) throws VidSimulatorException{ | |
ObjectMapper mapper = new ObjectMapper() | |
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); | |
SimulatorRequestResponseExpectation[] expectationModels; | |
try { | |
expectationModels = mapper.readValue(expectation, SimulatorRequestResponseExpectation[].class); | |
} catch (IOException e) { | |
logger.error("Couldn't deserialize register expectation {}, error:", expectation, e); | |
throw new VidSimulatorException(e.getMessage()); | |
} | |
for (SimulatorRequestResponseExpectation expectationModel : expectationModels) { | |
logger.info("Proceeding registration request: {}", expectationModel); | |
register(expectationModel); | |
} | |
} | |
@RequestMapping(value = {"/**"}) | |
public String redirectToMockServer(HttpServletRequest request, HttpServletResponse response) { | |
//Currently, the easiest logic is redirecting | |
//This is needed to allow POST redirect - see http://www.baeldung.com/spring-redirect-and-forward | |
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.TEMPORARY_REDIRECT); | |
//Building the redirect URL | |
String restOfTheUrl = (String) request.getAttribute( | |
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); | |
//TODO encode only characters like spaces, not slashes | |
/* try { | |
restOfTheUrl = URLEncoder.encode(restOfTheUrl, "UTF-8"); | |
restOfTheUrl = restOfTheUrl.replaceAll("%2F", "/"); | |
} catch (UnsupportedEncodingException e) { | |
e.printStackTrace(); | |
}*/ | |
StringBuilder sb = new StringBuilder(); | |
sb.append(mockServerProtocol+"://"+mockServerHost+":"+mockServerPort+"/"+restOfTheUrl); | |
String queryString = request.getQueryString(); | |
if (queryString != null){ | |
sb.append("?").append(queryString); | |
} | |
String redirectUrl = sb.toString(); | |
logger.info("Redirecting the request to : {}", redirectUrl); | |
return ("redirect:"+redirectUrl); | |
//This was a try to setup a proxy instead of redirect | |
//Abandoned this direction when trying to return the original HTTP error code which was registered to mock server, instead of wrapped up HTTP 500. | |
/* String restOfTheUrl = "/"+(String) request.getAttribute( | |
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); | |
URI uri = null; | |
try { | |
uri = new URI("http", null, "localhost", 1080, restOfTheUrl, request.getQueryString(), null); | |
} catch (URISyntaxException e) { | |
logger.error("Error during proxying request {}, error: ", request.getRequestURI(), e.getMessage()); | |
return new ResponseEntity(e.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR); | |
} | |
RestTemplate restTemplate = new RestTemplate(); | |
//Preparing the headers | |
HttpHeaders headers = new HttpHeaders(); | |
Enumeration<String> headerNames = request.getHeaderNames(); | |
while (headerNames.hasMoreElements()){ | |
String headerToSet = headerNames.nextElement(); | |
headers.set(headerToSet, request.getHeader(headerToSet)); | |
} | |
ResponseEntity<String> responseEntity = | |
restTemplate.exchange(uri, HttpMethod.resolve(request.getMethod()), new HttpEntity<String>(body, headers), String.class); | |
return responseEntity;*/ | |
} | |
private void register(SimulatorRequestResponseExpectation expectationModel) throws VidSimulatorException{ | |
//Setting request according to what is passed | |
HttpRequest request = HttpRequest.request(); | |
String id = expectationModel.getSimulatorRequest().getId(); | |
if (id != null) { | |
request.withHeader("x-simulator-id", id); | |
} | |
String method = expectationModel.getSimulatorRequest().getMethod(); | |
if (method != null) { | |
request.withMethod(method); | |
} | |
String path = expectationModel.getSimulatorRequest().getPath(); | |
if (path != null) { | |
request.withPath(path); | |
} | |
String body = expectationModel.getSimulatorRequest().getBody(); | |
if (body != null) { | |
request.withBody(new JsonBody(body)); | |
} | |
//Queryparams | |
final Map<String, List<String>> queryParams = expectationModel.getSimulatorRequest().getQueryParams(); | |
if (queryParams != null){ | |
String[] arr = new String[0]; | |
queryParams.entrySet().stream().forEach(x -> { | |
request.withQueryStringParameter(x.getKey(), x.getValue().toArray(arr)); | |
}); | |
} | |
//Setting response according to what is passed | |
HttpResponse response = HttpResponse.response(); | |
Integer responseCode = expectationModel.getSimulatorResponse().getResponseCode(); | |
if (responseCode != null) { | |
response.withStatusCode(responseCode); | |
} else { | |
logger.error("Invalid registration - response code cannot be empty"); | |
throw new VidSimulatorException("Invalid registration - response code cannot be empty"); | |
} | |
String respBody = expectationModel.getSimulatorResponse().getBody(); | |
if (respBody != null) { | |
response.withBody(respBody); | |
} | |
String file = expectationModel.getSimulatorResponse().getFile(); | |
if (file != null) { | |
response.withBody(loadFileString(file)); | |
} | |
Map<String, String> responseHeaders = expectationModel.getSimulatorResponse().getResponseHeaders(); | |
if (responseHeaders != null) { | |
responseHeaders.forEach(response::withHeader); | |
} | |
Times numberOfTimes = getExpectationNumberOfTimes(expectationModel); | |
if (expectationModel.getMisc().getReplace()) { | |
logger.info("Unregistering request expectation, if previously set, request: {}", expectationModel.getSimulatorRequest()); | |
mockServer.clear(request); | |
} | |
mockServer | |
.when(request, numberOfTimes).respond(response); | |
} | |
private byte[] loadFileString(String filePath) { | |
byte[] bytes = null; | |
try { | |
File file = new ClassPathResource("download_files/" + filePath).getFile(); | |
bytes = new byte[(int)file.length()]; | |
DataInputStream dataInputStream = null; | |
dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(file.getPath()))); | |
dataInputStream.readFully(bytes); | |
dataInputStream.close(); | |
} catch (FileNotFoundException e) { | |
logger.error("File not found for file:" + filePath); | |
e.printStackTrace(); | |
} catch (IOException e) { | |
logger.error("Error reading file:" + filePath); | |
e.printStackTrace(); | |
} | |
return bytes; | |
} | |
private Times getExpectationNumberOfTimes(SimulatorRequestResponseExpectation expectationModel) { | |
Integer expectationModelNumberOfTimes = expectationModel.getMisc().getNumberOfTimes(); | |
Times effectiveNumberOfTimes; | |
if (expectationModelNumberOfTimes == null || expectationModelNumberOfTimes < 0) { | |
effectiveNumberOfTimes = DEFAULT_NUMBER_OF_TIMES; | |
} else { | |
effectiveNumberOfTimes = exactly(expectationModelNumberOfTimes); | |
} | |
return effectiveNumberOfTimes; | |
} | |
} |