package com.github.bjuvensjo.rsimulator.core; import com.google.inject.Singleton; import groovy.lang.Binding; import groovy.util.GroovyScriptEngine; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * SimulatorScriptInterceptor is an interceptor that supports Groovy scripts intercepting invocations of * {@link Simulator#service(String, String, String, String)}. * <p> * The scripts supported are: * <ol> * <li>GlobalRequest.groovy; Must be put in the rootPath folder and is applied before the invocation.</li> * <li><TestName>.groovy; Must be put in the same folder as test request and response and is applied first after * the invocation. The name of the groovy file must be the same as for the test request, e.g. Test1Request.txt - * Test1.groovy.</li> * <li>GlobalResponse.groovy; Must be put in the rootPath folder and is applied last after the invocation.</li> * </ol> * <p> * All script have a Map<String, Object> available through the variable vars. The keys contentType, simulatorResponse, * request, rootPath, rootRelativePath can be used to access invocation arguments and return value. In addition, the map * can be used to communicate arbitrary objects between the Groovy scripts. If a script sets a SimulatorResponse in the * vars map, this SimulatorResponse is directly returned. */ @Singleton public class SimulatorScriptInterceptor implements MethodInterceptor { private static final int CONTENT_TYPE_INDEX = 3; private static final int REQUEST_INDEX = 2; private static final int ROOT_RELATIVE_PATH_INDEX = 1; private static final int ROOT_PATH_INDEX = 0; private static final String CONTENT_TYPE = "contentType"; private static final String SIMULATOR_RESPONSE_OPTIONAL = "simulatorResponseOptional"; private static final String REQUEST = "request"; private static final String ROOT_PATH = "rootPath"; private static final String ROOT_RELATIVE_PATH = "rootRelativePath"; private static final String GROOVY_PATTERN = com.github.bjuvensjo.rsimulator.core.config.Constants.REQUEST + ".*"; private Logger log = LoggerFactory.getLogger(SimulatorScriptInterceptor.class); private enum Scope { GLOBAL_REQUEST, GLOBAL_RESPONSE, LOCAL_RESPONSE } public Object invoke(MethodInvocation invocation) throws Throwable { log.debug("Arguments are {}", invocation.getArguments()); Map<String, Object> vars = new HashMap<String, Object>(); vars.put(ROOT_PATH, invocation.getArguments()[ROOT_PATH_INDEX]); vars.put(ROOT_RELATIVE_PATH, invocation.getArguments()[ROOT_RELATIVE_PATH_INDEX]); vars.put(REQUEST, invocation.getArguments()[REQUEST_INDEX]); vars.put(CONTENT_TYPE, invocation.getArguments()[CONTENT_TYPE_INDEX]); applyScript(Scope.GLOBAL_REQUEST, vars); Optional<SimulatorResponse> simulatorResponseOptional = (Optional<SimulatorResponse>) vars.get(SIMULATOR_RESPONSE_OPTIONAL); if (simulatorResponseOptional != null && simulatorResponseOptional.isPresent()) { log.debug("Returning {}", simulatorResponseOptional.get()); return simulatorResponseOptional; } invocation.getArguments()[ROOT_PATH_INDEX] = vars.get(ROOT_PATH); invocation.getArguments()[ROOT_RELATIVE_PATH_INDEX] = vars.get(ROOT_RELATIVE_PATH); invocation.getArguments()[REQUEST_INDEX] = vars.get(REQUEST); invocation.getArguments()[CONTENT_TYPE_INDEX] = vars.get(CONTENT_TYPE); simulatorResponseOptional = (Optional<SimulatorResponse>) invocation.proceed(); vars.put(SIMULATOR_RESPONSE_OPTIONAL, simulatorResponseOptional); applyScript(Scope.LOCAL_RESPONSE, vars); applyScript(Scope.GLOBAL_RESPONSE, vars); return vars.get(SIMULATOR_RESPONSE_OPTIONAL); } private void applyScript(Scope type, Map<String, Object> vars) { try { String root = null; String script = null; switch (type) { case GLOBAL_REQUEST: root = (String) vars.get(ROOT_PATH); script = "GlobalRequest.groovy"; break; case GLOBAL_RESPONSE: root = (String) vars.get(ROOT_PATH); script = "GlobalResponse.groovy"; break; case LOCAL_RESPONSE: Optional<SimulatorResponse> simulatorResponseOptional = (Optional<SimulatorResponse>) vars.get(SIMULATOR_RESPONSE_OPTIONAL); if (simulatorResponseOptional.isPresent()) { Path matchingRequest = simulatorResponseOptional.get().getMatchingRequest(); root = matchingRequest.getParent().toAbsolutePath().toString(); script = matchingRequest.getFileName().toString().replaceAll(GROOVY_PATTERN, ".groovy"); } break; default: break; } File file = new File(String.join(File.separator, root, script)); if (file.exists()) { log.debug("Applying script {} of type: {}, with vars: {}", new Object[]{file, type, vars}); String[] roots = new String[]{root}; GroovyScriptEngine gse = new GroovyScriptEngine(roots); Binding binding = new Binding(); binding.setVariable("vars", vars); gse.run(script, binding); log.debug("Applied script {} of type: {}, and updated vars are: {}", new Object[]{file, type, vars}); } else { log.debug("When applying script of type {}, script path {} is not an existing file", type, root); } } catch (Exception e) { log.error("Script error.", e); } } }