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<TreeSPath> {
private final Log log = LogFactory.getLog(getClass());
private String execDir;
private String cmd = null;
- private List<String> command = null;
+ private List<Object> command = null;
private Boolean synchronous = true;
- private Boolean captureStdIn = false;
private String stdErrLogLevel = "ERROR";
private String stdOutLogLevel = "INFO";
- private Map<String, List<String>> osCommands = new HashMap<String, List<String>>();
+ private Resource stdOutFile = null;
+ private Resource stdErrFile = null;
+
+ private Map<String, List<Object>> osCommands = new HashMap<String, List<Object>>();
private Map<String, String> osCmds = new HashMap<String, String>();
+ private Map<String, String> environmentVariables = new HashMap<String, String>();
+
+ private Long watchdogTimeout = 24 * 60 * 60 * 1000l;
+
+ private TestResult testResult;
+
+ public SystemCall() {
+
+ }
+
+ public SystemCall(List<Object> 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"));
// 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<String> commandToUse = null;
+ List<Object> commandToUse = null;
if (osCommands.containsKey(osName))
commandToUse = osCommands.get(osName);
else
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.");
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<String, String> 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) {
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);
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;
}
this.synchronous = synchronous;
}
- public void setCaptureStdIn(Boolean captureStdIn) {
- this.captureStdIn = captureStdIn;
- }
-
- public void setCommand(List<String> command) {
+ public void setCommand(List<Object> command) {
this.command = command;
}
- public void setOsCommands(Map<String, List<String>> osCommands) {
+ public void setOsCommands(Map<String, List<Object>> osCommands) {
this.osCommands = osCommands;
}
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<String, String> 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;
}
+
}