X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=runtime%2Forg.argeo.slc.core%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fslc%2Fcore%2Fexecution%2Ftasks%2FSystemCall.java;h=9d8ebdcf8785920a459be223eed70596ea693e5c;hb=e875b40fba70bb76fed1e2f3ff77c9adca1be1a4;hp=93a467a1d3678970ea55698d76f626fee83cf865;hpb=0d68d3b6e070e53448164ee3b3413b548a5f7a3b;p=gpl%2Fargeo-slc.git diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java index 93a467a1d..9d8ebdcf8 100644 --- a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java @@ -1,11 +1,14 @@ package org.argeo.slc.core.execution.tasks; import java.io.File; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,22 +23,23 @@ 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.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.slc.SlcException; import org.argeo.slc.UnsupportedException; -import org.argeo.slc.core.structure.tree.TreeSPath; +import org.argeo.slc.core.execution.ExecutionResources; 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 { +/** Execute an OS specific system call. */ +public class SystemCall extends TreeSRelatedHelper implements Runnable { + public final static String LOG_STDOUT = "System.out"; + private final Log log = LogFactory.getLog(getClass()); private String execDir; @@ -50,6 +54,11 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, private Resource stdOutFile = null; private Resource stdErrFile = null; + private Resource stdInFile = null; + private Boolean redirectStdOut = false; + + private List outputListeners = Collections + .synchronizedList(new ArrayList()); private Map> osCommands = new HashMap>(); private Map osCmds = new HashMap(); @@ -57,79 +66,127 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, private Boolean logCommand = false; private Boolean redirectStreams = true; + private Boolean exceptionOnFailed = true; + private Boolean mergeEnvironmentVariables = true; + private String osConsole = null; + private String generateScript = null; private Long watchdogTimeout = 24 * 60 * 60 * 1000l; private TestResult testResult; - // Internal use + private ExecutionResources executionResources; + /** Empty constructor */ public SystemCall() { } + /** + * Constructor based on the provided command list. + * + * @param command + * the command list + */ public SystemCall(List command) { - super(); this.command = command; } + /** + * Constructor based on the provided command. + * + * @param cmd + * the command. If the provided string contains no space a + * command list is initialized with the argument as first + * component (useful for chained construction) + */ + public SystemCall(String cmd) { + if (cmd.indexOf(' ') < 0) { + command = new ArrayList(); + command.add(cmd); + } else { + this.cmd = cmd; + } + } + + /** Executes the system call. */ public void run() { - final Writer stdOutWriter; - final Writer stdErrWriter; - if (stdOutFile != null) { - stdOutWriter = createWriter(stdOutFile); - } else - stdOutWriter = null; + // Manage streams + Writer stdOutWriter = null; + OutputStream stdOutputStream = null; + Writer stdErrWriter = null; + InputStream stdInStream = null; + if (stdOutFile != null) + if (redirectStdOut) + stdOutputStream = createOutputStream(stdOutFile); + else + stdOutWriter = createWriter(stdOutFile, true); + if (stdErrFile != null) { - stdErrWriter = createWriter(stdErrFile); + stdErrWriter = createWriter(stdErrFile, true); } else { - if (stdOutFile != null) { - stdErrWriter = createWriter(stdOutFile); - } else - stdErrWriter = null; + if (stdOutFile != null && !redirectStdOut) + stdErrWriter = createWriter(stdOutFile, true); } - try { - if (log.isTraceEnabled()) { - log.debug("os.name=" + System.getProperty("os.name")); - log.debug("os.arch=" + System.getProperty("os.arch")); - log.debug("os.version=" + System.getProperty("os.version")); + if (stdInFile != null) + try { + stdInStream = stdInFile.getInputStream(); + } catch (IOException e2) { + throw new SlcException("Cannot open a stream for " + stdInFile, + e2); } - // Execution directory - File dir = new File(getExecDirToUse()); - if (!dir.exists()) - dir.mkdirs(); - - // Watchdog to check for lost processes - Executor executor = new DefaultExecutor(); - executor.setWatchdog(new ExecuteWatchdog(watchdogTimeout)); - - if (redirectStreams) { - // Redirect standard streams - executor.setStreamHandler(createExecuteStreamHandler( - stdOutWriter, stdErrWriter)); - } else { - // Dummy stream handler (otherwise pump is used) - executor.setStreamHandler(new DummyexecuteStreamHandler()); - } + if (log.isTraceEnabled()) { + log.debug("os.name=" + System.getProperty("os.name")); + log.debug("os.arch=" + System.getProperty("os.arch")); + log.debug("os.version=" + System.getProperty("os.version")); + } - executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); - executor.setWorkingDirectory(dir); + // Execution directory + File dir = new File(getExecDirToUse()); + if (!dir.exists()) + dir.mkdirs(); - // Command line to use - final CommandLine commandLine = createCommandLine(); - if (logCommand) - log.info("Execute command:\n" + commandLine + "\n"); + // Watchdog to check for lost processes + Executor executor = new DefaultExecutor(); + executor.setWatchdog(new ExecuteWatchdog(watchdogTimeout)); - // Env variables - Map environmentVariablesToUse = environmentVariables - .size() > 0 ? environmentVariables : null; + if (redirectStreams) { + // Redirect standard streams + executor.setStreamHandler(createExecuteStreamHandler(stdOutWriter, + stdOutputStream, stdErrWriter, stdInStream)); + } else { + // Dummy stream handler (otherwise pump is used) + executor.setStreamHandler(new DummyexecuteStreamHandler()); + } + + executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); + executor.setWorkingDirectory(dir); + + // Command line to use + final CommandLine commandLine = createCommandLine(); + if (logCommand) + log.info("Execute command:\n" + commandLine + + "\n in working directory: \n" + dir + "\n"); + + // Env variables + Map environmentVariablesToUse = null; + if (environmentVariables.size() > 0) { + environmentVariablesToUse = new HashMap(); + if (mergeEnvironmentVariables) + environmentVariablesToUse.putAll(System.getenv()); + environmentVariablesToUse.putAll(environmentVariables); + } - // Execute - ExecuteResultHandler executeResultHandler = createExecuteResultHandler(commandLine); + // Execute + ExecuteResultHandler executeResultHandler = createExecuteResultHandler(commandLine); + // + // THE EXECUTION PROPER + // + try { if (synchronous) try { int exitValue = executor.execute(commandLine, @@ -141,16 +198,47 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, else executor.execute(commandLine, environmentVariablesToUse, executeResultHandler); + } catch (SlcException e) { + throw e; } catch (Exception e) { - throw new SlcException("Could not execute command " + cmd, e); + throw new SlcException("Could not execute command " + commandLine, + e); } finally { IOUtils.closeQuietly(stdOutWriter); IOUtils.closeQuietly(stdErrWriter); + IOUtils.closeQuietly(stdInStream); } } - /** Can be overridden by specific command wrapper */ + public synchronized String function() { + final StringBuffer buf = new StringBuffer(""); + SystemCallOutputListener tempOutputListener = new SystemCallOutputListener() { + public void newLine(SystemCall systemCall, String line, + Boolean isError) { + if (!isError) + buf.append(line); + } + }; + addOutputListener(tempOutputListener); + run(); + removeOutputListener(tempOutputListener); + return buf.toString(); + } + + public String asCommand() { + return createCommandLine().toString(); + } + + @Override + public String toString() { + return asCommand(); + } + + /** + * Build a command line based on the properties. Can be overridden by + * specific command wrappers. + */ protected CommandLine createCommandLine() { // Check if an OS specific command overrides String osName = System.getProperty("os.name"); @@ -165,7 +253,7 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, else cmdToUse = cmd; - final CommandLine commandLine; + CommandLine commandLine = null; // Which command definition to use if (commandToUse == null && cmdToUse == null) @@ -174,77 +262,102 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, throw new SlcException( "Specify the command either as a line or as a list."); else if (cmdToUse != null) { - if (osConsole != null) - cmdToUse = osConsole + " " + cmdToUse; commandLine = CommandLine.parse(cmdToUse); } else if (commandToUse != null) { if (commandToUse.size() == 0) throw new SlcException("Command line is empty."); - if (osConsole != null) { - commandLine = CommandLine.parse(osConsole); - } else { - commandLine = new CommandLine(commandToUse.get(0).toString()); - } + commandLine = new CommandLine(commandToUse.get(0).toString()); - for (int i = (osConsole != null ? 0 : 1); i < commandToUse.size(); i++) { - log.debug(commandToUse.get(i)); + for (int i = 1; i < commandToUse.size(); i++) { + if (log.isTraceEnabled()) + log.debug(commandToUse.get(i)); commandLine.addArgument(commandToUse.get(i).toString()); } } else { // all cases covered previously throw new UnsupportedException(); } + + if (generateScript != null) { + File scriptFile = new File(getExecDirToUse() + File.separator + + generateScript); + try { + FileUtils.writeStringToFile(scriptFile, + (osConsole != null ? osConsole + " " : "") + + commandLine.toString()); + } catch (IOException e) { + throw new SlcException("Could not generate script " + + scriptFile, e); + } + commandLine = new CommandLine(scriptFile); + } else { + if (osConsole != null) + commandLine = CommandLine.parse(osConsole + " " + + commandLine.toString()); + } + return commandLine; } + /** + * Creates a {@link PumpStreamHandler} which redirects streams to the custom + * logging mechanism. + */ protected ExecuteStreamHandler createExecuteStreamHandler( - final Writer stdOutWriter, final Writer stdErrWriter) { + final Writer stdOutWriter, final OutputStream stdOutputStream, + final Writer stdErrWriter, final InputStream stdInStream) { + // Log writers PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( - new LogOutputStream() { - protected void processLine(String line, int level) { - log(stdOutLogLevel, line); - if (stdOutWriter != null) - appendLineToFile(stdOutWriter, line); - } - }, new LogOutputStream() { + stdOutputStream != null ? stdOutputStream + : new LogOutputStream() { + protected void processLine(String line, int level) { + if (line != null && !line.trim().equals("")) + logStdOut(line); + if (stdOutWriter != null) + appendLineToFile(stdOutWriter, line); + } + }, new LogOutputStream() { protected void processLine(String line, int level) { - log(stdErrLogLevel, line); + if (line != null && !line.trim().equals("")) + logStdErr(line); if (stdErrWriter != null) appendLineToFile(stdErrWriter, line); } - }, null); + }, stdInStream); return pumpStreamHandler; } + /** Creates the default {@link ExecuteResultHandler}. */ protected ExecuteResultHandler createExecuteResultHandler( final CommandLine commandLine) { return new ExecuteResultHandler() { public void onProcessComplete(int exitValue) { - if (log.isDebugEnabled()) - log - .debug("Process " + commandLine - + " properly completed."); + String msg = "System call '" + commandLine + + "' properly completed."; + if (log.isTraceEnabled()) + log.trace(msg); if (testResult != null) { forwardPath(testResult, null); testResult.addResultPart(new SimpleResultPart( - TestStatus.PASSED, "Process " + commandLine - + " properly completed.")); + TestStatus.PASSED, msg)); } } public void onProcessFailed(ExecuteException e) { + String msg = "System call '" + commandLine + "' failed."; if (testResult != null) { forwardPath(testResult, null); testResult.addResultPart(new SimpleResultPart( - TestStatus.ERROR, "Process " + commandLine - + " failed.", e)); + TestStatus.ERROR, msg, e)); } else { - throw new SlcException("Process " + commandLine - + " failed.", e); + if (exceptionOnFailed) + throw new SlcException(msg, e); + else + log.error(msg, e); } } }; @@ -271,6 +384,19 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, } } + protected void logStdOut(String line) { + for (SystemCallOutputListener outputListener : outputListeners) + outputListener.newLine(this, line, false); + log(stdOutLogLevel, line); + } + + protected void logStdErr(String line) { + for (SystemCallOutputListener outputListener : outputListeners) + outputListener.newLine(this, line, true); + log(stdErrLogLevel, line); + } + + /** Log from the underlying streams. */ protected void log(String logLevel, String line) { if ("ERROR".equals(logLevel)) log.error(line); @@ -282,10 +408,15 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, log.debug(line); else if ("TRACE".equals(logLevel)) log.trace(line); + else if (LOG_STDOUT.equals(logLevel)) + System.out.println(line); + else if ("System.err".equals(logLevel)) + System.err.println(line); else throw new SlcException("Unknown log level " + logLevel); } + /** Append line to a log file. */ protected void appendLineToFile(Writer writer, String line) { try { writer.append(line).append('\n'); @@ -294,22 +425,64 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, } } - protected Writer createWriter(Resource target) { + /** Creates the writer for the output/err files. */ + protected Writer createWriter(Resource target, Boolean append) { FileWriter writer = null; try { - File file = target.getFile(); - writer = new FileWriter(file, true); + + final File file; + if (executionResources != null) + file = new File(executionResources.getAsOsPath(target, true)); + else + file = target.getFile(); + writer = new FileWriter(file, append); } catch (IOException e) { - log.error("Cannot create log file " + target, e); + log.error("Cannot get file for " + target, e); IOUtils.closeQuietly(writer); } return writer; } + /** Creates an outputstream for the output/err files. */ + protected OutputStream createOutputStream(Resource target) { + FileOutputStream out = null; + try { + + final File file; + if (executionResources != null) + file = new File(executionResources.getAsOsPath(target, true)); + else + file = target.getFile(); + out = new FileOutputStream(file, false); + } catch (IOException e) { + log.error("Cannot get file for " + target, e); + IOUtils.closeQuietly(out); + } + return out; + } + + /** Append the argument (for chaining) */ + public SystemCall arg(String arg) { + command.add(arg); + return this; + } + + /** Append the argument (for chaining) */ + public SystemCall arg(String arg, String value) { + command.add(arg); + command.add(value); + return this; + } + + /** */ public void setCmd(String command) { this.cmd = command; } + public void setCommand(List command) { + this.command = command; + } + public void setExecDir(String execdir) { this.execDir = execdir; } @@ -326,10 +499,6 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, this.synchronous = synchronous; } - public void setCommand(List command) { - this.command = command; - } - public void setOsCommands(Map> osCommands) { this.osCommands = osCommands; } @@ -354,6 +523,10 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, this.stdErrFile = stdErrFile; } + public void setStdInFile(Resource stdInFile) { + this.stdInFile = stdInFile; + } + public void setTestResult(TestResult testResult) { this.testResult = testResult; } @@ -366,10 +539,43 @@ public class SystemCall extends TreeSRelatedHelper implements Runnable, this.redirectStreams = redirectStreams; } + public void setExceptionOnFailed(Boolean exceptionOnFailed) { + this.exceptionOnFailed = exceptionOnFailed; + } + + public void setMergeEnvironmentVariables(Boolean mergeEnvironmentVariables) { + this.mergeEnvironmentVariables = mergeEnvironmentVariables; + } + public void setOsConsole(String osConsole) { this.osConsole = osConsole; } + public void setGenerateScript(String generateScript) { + this.generateScript = generateScript; + } + + public void setExecutionResources(ExecutionResources executionResources) { + this.executionResources = executionResources; + } + + public void setRedirectStdOut(Boolean redirectStdOut) { + this.redirectStdOut = redirectStdOut; + } + + public void addOutputListener(SystemCallOutputListener outputListener) { + outputListeners.add(outputListener); + } + + public void removeOutputListener(SystemCallOutputListener outputListener) { + outputListeners.remove(outputListener); + } + + public void setOutputListeners( + List outputListeners) { + this.outputListeners = outputListeners; + } + private class DummyexecuteStreamHandler implements ExecuteStreamHandler { public void setProcessErrorStream(InputStream is) throws IOException {