package com.cadrlife.devsearch.agent.service; import com.cadrlife.devsearch.domain.Project; import com.google.inject.Inject; import groovy.lang.Binding; import groovy.lang.GroovyShell; import groovy.lang.Script; import org.codehaus.groovy.control.CompilerConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Named; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; public class ScriptService { private static final Logger LOG = LoggerFactory.getLogger(ScriptService.class); CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); private static final String SHELL_PATH_VAR = "shellPath"; private static final String PROJECT_VAR = "project"; private static final String CHECKOUT_PATH_VAR = "checkoutPath"; @Inject(optional = true) public void setShell(@Named("shell.path") String shell) { this.shell = shell; } private String shell = "/bin/sh"; public ScriptService() { compilerConfiguration.setScriptBaseClass(BaseScript.class.getName()); } public void executeOnProject(File scriptFile, Project project) { LOG.info("Using Checkout Path {}", project.getCheckoutPath()); Binding binding = new Binding(); binding.setVariable(CHECKOUT_PATH_VAR, project.getCheckoutPath()); binding.setVariable(SHELL_PATH_VAR, shell); binding.setVariable(PROJECT_VAR, project); GroovyShell groovyShell = new GroovyShell(this.getClass().getClassLoader(), binding, compilerConfiguration); try { groovyShell.evaluate(scriptFile); project.setUpdateComplete(true); } catch (IOException e) { throw new RuntimeException(e); } } public abstract static class BaseScript extends Script { public File file(String fileName) { String varName = CHECKOUT_PATH_VAR; String checkoutPathString = getStringVariable(varName); return new File(checkoutPathString).toPath().resolve(fileName).toFile(); } private String getStringVariable(String varName) { return getBinding().getVariable(varName).toString(); } private Project getProject() { return (Project) getBinding().getVariable(PROJECT_VAR); } public String lineSep() { return System.getProperty("line.separator"); } public void info(Object msg) { LOG.info("{}", msg); } public Map<String,Object> lineInFile(Map<String,String> params) throws IOException { String path = params.get("path"); String regexp = params.get("regexp"); String replacementLine = params.get("line"); File file = file(path); StringBuilder sb = new StringBuilder(); String lineSep = lineSep(); boolean changed = false; BufferedReader br = new BufferedReader(new FileReader(file)); String line; Pattern pattern = Pattern.compile(regexp); String projectName = getProjectName(); while ((line = br.readLine()) != null) { if (pattern.matcher(line).find() && !replacementLine.equals(line)) { LOG.info("{}: Changing '{}' to '{}' in '{}'.", projectName, line, replacementLine, path); sb.append(replacementLine).append(lineSep); changed = true; } else { sb.append(line).append(lineSep); } } br.close(); if (changed) { LOG.info("{}: Writing new '{}'.", projectName, path); Files.write(file.toPath(), sb.toString().getBytes()); getProject().addFileUpdate(path); } Map<String, Object> result = new HashMap<>(); result.put("changed", changed); return result; } public Map<String, Object> command(String cmd) throws IOException, InterruptedException { String shell = getStringVariable(SHELL_PATH_VAR); ProcessBuilder builder = new ProcessBuilder(shell, "-c", cmd); File workingDir = file(""); LOG.info("{}: Running command '{}' with shell '{}', working dir '{}'.", getProjectName(), cmd, shell, workingDir); builder.directory(workingDir); builder.redirectErrorStream(true); // builder. StringBuffer output = new StringBuffer(); int readInt; Process process = builder.start(); waitFor(process, 600); InputStream inStream = process.getInputStream(); while ((readInt = inStream.read()) != -1) { output.append((char) readInt); } Map<String, Object> result = new HashMap<>(); output.toString(); result.put("exitCode", process.exitValue()); result.put("success", process.exitValue() == 0); result.put("output", output.toString()); result.put("changed", true); return checkSuccess(result); } private int waitFor(Process process, int timeoutInSeconds) throws InterruptedException { long now = System.currentTimeMillis(); long timeoutInMillis = 1000L * timeoutInSeconds; long finish = now + timeoutInMillis; while (isAlive( process ) && (System.currentTimeMillis() < finish)) { Thread.sleep(100); } if (isAlive(process)) { throw new InterruptedException( "Process timeout out after " + timeoutInSeconds + " seconds" ); } return process.waitFor(); } public boolean isAlive(Process process) { try { process.exitValue(); return false; } catch (IllegalThreadStateException e) { return true; } } private Map<String, Object> checkSuccess(Map<String, Object> result) { Object success = result.get("success"); if (Boolean.TRUE != success) { LOG.error("{}: Failed result {}", getProjectName(), result); throw new RuntimeException("Result had success value of " + success + ", not true"); } return result; } public String getProjectName() { return getProject().getName(); } } }