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();
}
}
}