X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=runtime%2Forg.argeo.slc.support.simple%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fslc%2Fcore%2Fexecution%2Ftasks%2FSystemCall.java;h=e45ae540d12d74c36d38d4890621da660cd5e32f;hb=e7ddf0b3a271a2f22108ee5b9fba69888f5c78ec;hp=c0316128549e1be498836f9e08ed0c34bd2fe597;hpb=043edae6e81b1ef82dbc7c0990610e8658186ac3;p=gpl%2Fargeo-slc.git diff --git a/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java b/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java index c03161285..e45ae540d 100644 --- a/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java +++ b/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java @@ -1,43 +1,87 @@ package org.argeo.slc.core.execution.tasks; -import java.io.BufferedReader; import java.io.File; +import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.ExecuteResultHandler; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.Executor; +import org.apache.commons.exec.LogOutputStream; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.exec.ShutdownHookProcessDestroyer; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.NotImplementedException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.slc.SlcException; -import org.argeo.slc.execution.Executable; - -public class SystemCall implements Executable { - // TODO: specify environment variables - +import org.argeo.slc.core.structure.tree.TreeSPath; +import org.argeo.slc.core.structure.tree.TreeSRelatedHelper; +import org.argeo.slc.core.test.SimpleResultPart; +import org.argeo.slc.structure.StructureAware; +import org.argeo.slc.test.TestResult; +import org.argeo.slc.test.TestStatus; +import org.springframework.core.io.Resource; + +/** Execute and OS system call. */ +public class SystemCall extends TreeSRelatedHelper implements Runnable, + StructureAware { private final Log log = LogFactory.getLog(getClass()); private String execDir; private String cmd = null; - private List command = null; + private List command = null; private Boolean synchronous = true; - private Boolean captureStdIn = false; private String stdErrLogLevel = "ERROR"; private String stdOutLogLevel = "INFO"; - private Map> osCommands = new HashMap>(); + private Resource stdOutFile = null; + private Resource stdErrFile = null; + + private Map> osCommands = new HashMap>(); private Map osCmds = new HashMap(); + private Map environmentVariables = new HashMap(); + + private Long watchdogTimeout = 24 * 60 * 60 * 1000l; + + private TestResult testResult; + + public SystemCall() { + + } + + public SystemCall(List command) { + super(); + this.command = command; + } + + public void run() { + // Log writers + final Writer stdOutWriter; + final Writer stdErrWriter; + if (stdOutFile != null) { + stdOutWriter = createWriter(stdOutFile); + } else + stdOutWriter = null; + if (stdErrFile != null) { + stdErrWriter = createWriter(stdErrFile); + } else { + if (stdOutFile != null) { + stdErrWriter = createWriter(stdOutFile); + } else + stdErrWriter = null; + } - public void execute() { try { if (log.isTraceEnabled()) { log.debug("os.name=" + System.getProperty("os.name")); @@ -48,16 +92,14 @@ public class SystemCall implements Executable { // Execution directory File dir = null; if (execDir != null) { - // Replace '/' by local file separator, for portabiliy + // Replace '/' by local file separator, for portability execDir.replace('/', File.separatorChar); dir = new File(execDir).getCanonicalFile(); } - Process process = null; - // Check if an OS specific command overrides String osName = System.getProperty("os.name"); - List commandToUse = null; + List commandToUse = null; if (osCommands.containsKey(osName)) commandToUse = osCommands.get(osName); else @@ -68,6 +110,34 @@ public class SystemCall implements Executable { else cmdToUse = cmd; + // Prepare executor + if (dir == null) + dir = new File(getUsedDir(dir)); + if (!dir.exists()) + dir.mkdirs(); + + Executor executor = new DefaultExecutor(); + executor.setWatchdog(new ExecuteWatchdog(watchdogTimeout)); + + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( + new LogOutputStream() { + protected void processLine(String line, int level) { + log(stdOutLogLevel, line); + if (stdOutWriter != null) + appendLineToFile(stdOutWriter, line); + } + }, new LogOutputStream() { + protected void processLine(String line, int level) { + log(stdErrLogLevel, line); + if (stdErrWriter != null) + appendLineToFile(stdErrWriter, line); + } + }, null); + executor.setStreamHandler(pumpStreamHandler); + executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); + executor.setWorkingDirectory(dir); + final CommandLine commandLine; + // Which command definition to use if (commandToUse == null && cmdToUse == null) throw new SlcException("Please specify a command."); @@ -78,52 +148,77 @@ public class SystemCall implements Executable { if (log.isTraceEnabled()) log.trace("Execute '" + cmdToUse + "' in " + getUsedDir(dir)); - process = Runtime.getRuntime().exec(cmdToUse, null, dir); + + commandLine = CommandLine.parse(cmdToUse); } else if (commandToUse != null) { if (log.isTraceEnabled()) log.trace("Execute '" + commandToUse + "' in " + getUsedDir(dir)); - ProcessBuilder processBuilder = new ProcessBuilder(commandToUse); - processBuilder.directory(dir); - process = processBuilder.start(); + if (commandToUse.size() == 0) + throw new SlcException("Command line is empty."); + + commandLine = new CommandLine(commandToUse.get(0).toString()); + for (int i = 1; i < commandToUse.size(); i++) + commandLine.addArgument(commandToUse.get(i).toString()); } else { // all cases covered previously + throw new NotImplementedException(); } - // Manage standard streams - StreamReaderThread stdOutThread = new StreamReaderThread(process - .getInputStream()) { - protected void callback(String line) { - stdOutCallback(line); + // Env variables + Map environmentVariablesToUse = environmentVariables + .size() > 0 ? environmentVariables : null; + + // Execute + ExecuteResultHandler executeResultHandler = new ExecuteResultHandler() { + + public void onProcessComplete(int exitValue) { + if (log.isDebugEnabled()) + log.debug("Process " + commandLine + + " properly completed."); + if (testResult != null) { + forwardPath(testResult, null); + testResult.addResultPart(new SimpleResultPart( + TestStatus.PASSED, "Process " + commandLine + + " properly completed.")); + } } - }; - stdOutThread.start(); - StreamReaderThread stdErrThread = new StreamReaderThread(process - .getInputStream()) { - protected void callback(String line) { - stdErrCallback(line); + + public void onProcessFailed(ExecuteException e) { + if (testResult != null) { + forwardPath(testResult, null); + testResult.addResultPart(new SimpleResultPart( + TestStatus.ERROR, "Process " + commandLine + + " failed.", e)); + } else { + throw new SlcException("Process " + commandLine + + " failed.", e); + } } }; - stdErrThread.start(); - if (captureStdIn) - new StdInThread(process.getOutputStream()).start(); - - // Wait for the end of the process - if (synchronous) { - Integer exitCode = process.waitFor(); - if (exitCode != 0) - log.warn("Process return exit code " + exitCode); - } else { - // asynchronous: return - } + + if (synchronous) + try { + int exitValue = executor.execute(commandLine, + environmentVariablesToUse); + executeResultHandler.onProcessComplete(exitValue); + } catch (ExecuteException e1) { + executeResultHandler.onProcessFailed(e1); + } + else + executor.execute(commandLine, environmentVariablesToUse, + executeResultHandler); } catch (Exception e) { throw new SlcException("Could not execute command " + cmd, e); + } finally { + IOUtils.closeQuietly(stdOutWriter); + IOUtils.closeQuietly(stdErrWriter); } } /** - * Shortcut method returning teh current exec dir if the specified one is + * Shortcut method returning the current exec dir if the specified one is * null. */ private String getUsedDir(File dir) { @@ -133,14 +228,6 @@ public class SystemCall implements Executable { return dir.getPath(); } - protected void stdOutCallback(String line) { - log(stdOutLogLevel, line); - } - - protected void stdErrCallback(String line) { - log(stdErrLogLevel, line); - } - protected void log(String logLevel, String line) { if ("ERROR".equals(logLevel)) log.error(line); @@ -156,6 +243,26 @@ public class SystemCall implements Executable { throw new SlcException("Unknown log level " + logLevel); } + protected void appendLineToFile(Writer writer, String line) { + try { + writer.append(line).append('\n'); + } catch (IOException e) { + log.error("Cannot write to log file", e); + } + } + + protected Writer createWriter(Resource target) { + FileWriter writer = null; + try { + File file = target.getFile(); + writer = new FileWriter(file, true); + } catch (IOException e) { + log.error("Cannot create log file " + target, e); + IOUtils.closeQuietly(writer); + } + return writer; + } + public void setCmd(String command) { this.cmd = command; } @@ -176,15 +283,11 @@ public class SystemCall implements Executable { this.synchronous = synchronous; } - public void setCaptureStdIn(Boolean captureStdIn) { - this.captureStdIn = captureStdIn; - } - - public void setCommand(List command) { + public void setCommand(List command) { this.command = command; } - public void setOsCommands(Map> osCommands) { + public void setOsCommands(Map> osCommands) { this.osCommands = osCommands; } @@ -192,69 +295,24 @@ public class SystemCall implements Executable { this.osCmds = osCmds; } - protected abstract class StreamReaderThread extends Thread { - private final InputStream stream; - - public StreamReaderThread(InputStream stream) { - this.stream = stream; - } - - @Override - public void run() { - BufferedReader in = null; - try { - in = new BufferedReader(new InputStreamReader(stream)); - String line = null; - while ((line = in.readLine()) != null) { - stdOutCallback(line); - } - } catch (IOException e) { - if (log.isTraceEnabled()) { - log.trace("Could not read stream", e); - // catch silently - // because the other methods - // to check whether the stream - // is closed would probably - // be to costly - } - } finally { - if (synchronous) - IOUtils.closeQuietly(in); - } - } - - protected abstract void callback(String line); + public void setEnvironmentVariables(Map environmentVariables) { + this.environmentVariables = environmentVariables; } - protected class StdInThread extends Thread { - private final OutputStream stream; + public void setWatchdogTimeout(Long watchdogTimeout) { + this.watchdogTimeout = watchdogTimeout; + } - public StdInThread(OutputStream stream) { - this.stream = stream; - } + public void setStdOutFile(Resource stdOutFile) { + this.stdOutFile = stdOutFile; + } - @Override - public void run() { - BufferedReader in = null; - Writer out = null; - try { - out = new OutputStreamWriter(stream); - in = new BufferedReader(new InputStreamReader(System.in)); - String line = null; - while ((line = in.readLine()) != null) { - out.write(line); - out.write("\n"); - out.flush(); - } - } catch (IOException e) { - throw new SlcException("Could not write to stdin stream", e); - } finally { - if (synchronous) { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(out); - } - } - } + public void setStdErrFile(Resource stdErrFile) { + this.stdErrFile = stdErrFile; + } + public void setTestResult(TestResult testResult) { + this.testResult = testResult; } + }