2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org
.argeo
.slc
.core
.execution
.tasks
;
20 import java
.io
.FileOutputStream
;
21 import java
.io
.FileWriter
;
22 import java
.io
.IOException
;
23 import java
.io
.InputStream
;
24 import java
.io
.OutputStream
;
25 import java
.io
.PipedInputStream
;
26 import java
.io
.PipedOutputStream
;
27 import java
.io
.Writer
;
28 import java
.util
.ArrayList
;
29 import java
.util
.Collections
;
30 import java
.util
.HashMap
;
31 import java
.util
.List
;
34 import org
.apache
.commons
.exec
.CommandLine
;
35 import org
.apache
.commons
.exec
.DefaultExecutor
;
36 import org
.apache
.commons
.exec
.ExecuteException
;
37 import org
.apache
.commons
.exec
.ExecuteResultHandler
;
38 import org
.apache
.commons
.exec
.ExecuteStreamHandler
;
39 import org
.apache
.commons
.exec
.ExecuteWatchdog
;
40 import org
.apache
.commons
.exec
.Executor
;
41 import org
.apache
.commons
.exec
.LogOutputStream
;
42 import org
.apache
.commons
.exec
.PumpStreamHandler
;
43 import org
.apache
.commons
.exec
.ShutdownHookProcessDestroyer
;
44 import org
.apache
.commons
.io
.FileUtils
;
45 import org
.apache
.commons
.io
.IOUtils
;
46 import org
.apache
.commons
.logging
.Log
;
47 import org
.apache
.commons
.logging
.LogFactory
;
48 import org
.argeo
.slc
.SlcException
;
49 import org
.argeo
.slc
.UnsupportedException
;
50 import org
.argeo
.slc
.core
.execution
.ExecutionResources
;
51 import org
.argeo
.slc
.core
.test
.SimpleResultPart
;
52 import org
.argeo
.slc
.test
.TestResult
;
53 import org
.argeo
.slc
.test
.TestStatus
;
54 import org
.springframework
.core
.io
.Resource
;
56 /** Execute an OS specific system call. */
57 public class SystemCall
implements Runnable
{
58 public final static String LOG_STDOUT
= "System.out";
60 private final Log log
= LogFactory
.getLog(getClass());
62 private String execDir
;
64 private String cmd
= null;
65 private List
<Object
> command
= null;
67 private Executor executor
= new DefaultExecutor();
68 private Boolean synchronous
= true;
70 private String stdErrLogLevel
= "ERROR";
71 private String stdOutLogLevel
= "INFO";
73 private Resource stdOutFile
= null;
74 private Resource stdErrFile
= null;
76 private Resource stdInFile
= null;
78 * If no {@link #stdInFile} provided, writing to this stream will write to
79 * the stdin of the process.
81 private OutputStream stdInSink
= null;
83 private Boolean redirectStdOut
= false;
85 private List
<SystemCallOutputListener
> outputListeners
= Collections
86 .synchronizedList(new ArrayList
<SystemCallOutputListener
>());
88 private Map
<String
, List
<Object
>> osCommands
= new HashMap
<String
, List
<Object
>>();
89 private Map
<String
, String
> osCmds
= new HashMap
<String
, String
>();
90 private Map
<String
, String
> environmentVariables
= new HashMap
<String
, String
>();
92 private Boolean logCommand
= false;
93 private Boolean redirectStreams
= true;
94 private Boolean exceptionOnFailed
= true;
95 private Boolean mergeEnvironmentVariables
= true;
97 private String osConsole
= null;
98 private String generateScript
= null;
101 private Long watchdogTimeout
= 24 * 60 * 60 * 1000l;
103 private TestResult testResult
;
105 private ExecutionResources executionResources
;
107 /** Empty constructor */
108 public SystemCall() {
113 * Constructor based on the provided command list.
118 public SystemCall(List
<Object
> command
) {
119 this.command
= command
;
123 * Constructor based on the provided command.
126 * the command. If the provided string contains no space a
127 * command list is initialized with the argument as first
128 * component (useful for chained construction)
130 public SystemCall(String cmd
) {
131 if (cmd
.indexOf(' ') < 0) {
132 command
= new ArrayList
<Object
>();
139 /** Executes the system call. */
142 Writer stdOutWriter
= null;
143 OutputStream stdOutputStream
= null;
144 Writer stdErrWriter
= null;
145 InputStream stdInStream
= null;
146 if (stdOutFile
!= null)
148 stdOutputStream
= createOutputStream(stdOutFile
);
150 stdOutWriter
= createWriter(stdOutFile
, true);
152 if (stdErrFile
!= null) {
153 stdErrWriter
= createWriter(stdErrFile
, true);
155 if (stdOutFile
!= null && !redirectStdOut
)
156 stdErrWriter
= createWriter(stdOutFile
, true);
160 if (stdInFile
!= null)
161 stdInStream
= stdInFile
.getInputStream();
163 stdInStream
= new PipedInputStream();
164 stdInSink
= new PipedOutputStream(
165 (PipedInputStream
) stdInStream
);
167 } catch (IOException e2
) {
168 throw new SlcException("Cannot open a stream for " + stdInFile
, e2
);
171 if (log
.isTraceEnabled()) {
172 log
.debug("os.name=" + System
.getProperty("os.name"));
173 log
.debug("os.arch=" + System
.getProperty("os.arch"));
174 log
.debug("os.version=" + System
.getProperty("os.version"));
177 // Execution directory
178 File dir
= new File(getExecDirToUse());
179 // if (!dir.exists())
182 // Watchdog to check for lost processes
183 Executor executorToUse
;
184 if (executor
!= null)
185 executorToUse
= executor
;
187 executorToUse
= new DefaultExecutor();
188 executorToUse
.setWatchdog(new ExecuteWatchdog(watchdogTimeout
));
190 if (redirectStreams
) {
191 // Redirect standard streams
192 executorToUse
.setStreamHandler(createExecuteStreamHandler(
193 stdOutWriter
, stdOutputStream
, stdErrWriter
, stdInStream
));
195 // Dummy stream handler (otherwise pump is used)
196 executorToUse
.setStreamHandler(new DummyexecuteStreamHandler());
199 executorToUse
.setProcessDestroyer(new ShutdownHookProcessDestroyer());
200 executorToUse
.setWorkingDirectory(dir
);
202 // Command line to use
203 final CommandLine commandLine
= createCommandLine();
205 log
.info("Execute command:\n" + commandLine
206 + "\n in working directory: \n" + dir
+ "\n");
209 Map
<String
, String
> environmentVariablesToUse
= null;
210 environmentVariablesToUse
= new HashMap
<String
, String
>();
211 if (mergeEnvironmentVariables
)
212 environmentVariablesToUse
.putAll(System
.getenv());
213 if (environmentVariables
.size() > 0)
214 environmentVariablesToUse
.putAll(environmentVariables
);
217 ExecuteResultHandler executeResultHandler
= createExecuteResultHandler(commandLine
);
220 // THE EXECUTION PROPER
225 int exitValue
= executorToUse
.execute(commandLine
,
226 environmentVariablesToUse
);
227 executeResultHandler
.onProcessComplete(exitValue
);
228 } catch (ExecuteException e1
) {
229 if (e1
.getExitValue() == Executor
.INVALID_EXITVALUE
) {
230 Thread
.currentThread().interrupt();
233 executeResultHandler
.onProcessFailed(e1
);
236 executorToUse
.execute(commandLine
, environmentVariablesToUse
,
237 executeResultHandler
);
238 } catch (SlcException e
) {
240 } catch (Exception e
) {
241 throw new SlcException("Could not execute command " + commandLine
,
244 IOUtils
.closeQuietly(stdOutWriter
);
245 IOUtils
.closeQuietly(stdErrWriter
);
246 IOUtils
.closeQuietly(stdInStream
);
247 IOUtils
.closeQuietly(stdInSink
);
252 public synchronized String
function() {
253 final StringBuffer buf
= new StringBuffer("");
254 SystemCallOutputListener tempOutputListener
= new SystemCallOutputListener() {
255 public void newLine(SystemCall systemCall
, String line
,
261 addOutputListener(tempOutputListener
);
263 removeOutputListener(tempOutputListener
);
264 return buf
.toString();
267 public String
asCommand() {
268 return createCommandLine().toString();
272 public String
toString() {
277 * Build a command line based on the properties. Can be overridden by
278 * specific command wrappers.
280 protected CommandLine
createCommandLine() {
281 // Check if an OS specific command overrides
282 String osName
= System
.getProperty("os.name");
283 List
<Object
> commandToUse
= null;
284 if (osCommands
.containsKey(osName
))
285 commandToUse
= osCommands
.get(osName
);
287 commandToUse
= command
;
288 String cmdToUse
= null;
289 if (osCmds
.containsKey(osName
))
290 cmdToUse
= osCmds
.get(osName
);
294 CommandLine commandLine
= null;
296 // Which command definition to use
297 if (commandToUse
== null && cmdToUse
== null)
298 throw new SlcException("Please specify a command.");
299 else if (commandToUse
!= null && cmdToUse
!= null)
300 throw new SlcException(
301 "Specify the command either as a line or as a list.");
302 else if (cmdToUse
!= null) {
303 commandLine
= CommandLine
.parse(cmdToUse
);
304 } else if (commandToUse
!= null) {
305 if (commandToUse
.size() == 0)
306 throw new SlcException("Command line is empty.");
308 commandLine
= new CommandLine(commandToUse
.get(0).toString());
310 for (int i
= 1; i
< commandToUse
.size(); i
++) {
311 if (log
.isTraceEnabled())
312 log
.debug(commandToUse
.get(i
));
313 commandLine
.addArgument(commandToUse
.get(i
).toString());
316 // all cases covered previously
317 throw new UnsupportedException();
320 if (generateScript
!= null) {
321 File scriptFile
= new File(getExecDirToUse() + File
.separator
324 FileUtils
.writeStringToFile(scriptFile
,
325 (osConsole
!= null ? osConsole
+ " " : "")
326 + commandLine
.toString());
327 } catch (IOException e
) {
328 throw new SlcException("Could not generate script "
331 commandLine
= new CommandLine(scriptFile
);
333 if (osConsole
!= null)
334 commandLine
= CommandLine
.parse(osConsole
+ " "
335 + commandLine
.toString());
342 * Creates a {@link PumpStreamHandler} which redirects streams to the custom
345 protected ExecuteStreamHandler
createExecuteStreamHandler(
346 final Writer stdOutWriter
, final OutputStream stdOutputStream
,
347 final Writer stdErrWriter
, final InputStream stdInStream
) {
351 PumpStreamHandler pumpStreamHandler
= new PumpStreamHandler(
352 stdOutputStream
!= null ? stdOutputStream
353 : new LogOutputStream() {
354 protected void processLine(String line
, int level
) {
355 if (line
!= null && !line
.trim().equals(""))
357 if (stdOutWriter
!= null)
358 appendLineToFile(stdOutWriter
, line
);
360 }, new LogOutputStream() {
361 protected void processLine(String line
, int level
) {
362 if (line
!= null && !line
.trim().equals(""))
364 if (stdErrWriter
!= null)
365 appendLineToFile(stdErrWriter
, line
);
371 // prevents the method to block when joining stdin
372 if (stdInSink
!= null)
373 IOUtils
.closeQuietly(stdInSink
);
378 return pumpStreamHandler
;
381 /** Creates the default {@link ExecuteResultHandler}. */
382 protected ExecuteResultHandler
createExecuteResultHandler(
383 final CommandLine commandLine
) {
384 return new ExecuteResultHandler() {
386 public void onProcessComplete(int exitValue
) {
387 String msg
= "System call '" + commandLine
388 + "' properly completed.";
389 if (log
.isTraceEnabled())
391 if (testResult
!= null) {
392 forwardPath(testResult
);
393 testResult
.addResultPart(new SimpleResultPart(
394 TestStatus
.PASSED
, msg
));
398 public void onProcessFailed(ExecuteException e
) {
399 String msg
= "System call '" + commandLine
+ "' failed.";
400 if (testResult
!= null) {
401 forwardPath(testResult
);
402 testResult
.addResultPart(new SimpleResultPart(
403 TestStatus
.ERROR
, msg
, e
));
405 if (exceptionOnFailed
)
406 throw new SlcException(msg
, e
);
414 protected void forwardPath(TestResult testResult
) {
415 // TODO: allocate a TreeSPath
419 * Shortcut method getting the execDir to use
421 protected String
getExecDirToUse() {
423 if (execDir
!= null) {
426 return System
.getProperty("user.dir");
427 } catch (Exception e
) {
428 throw new SlcException("Cannot find exec dir", e
);
432 protected void logStdOut(String line
) {
433 for (SystemCallOutputListener outputListener
: outputListeners
)
434 outputListener
.newLine(this, line
, false);
435 log(stdOutLogLevel
, line
);
438 protected void logStdErr(String line
) {
439 for (SystemCallOutputListener outputListener
: outputListeners
)
440 outputListener
.newLine(this, line
, true);
441 log(stdErrLogLevel
, line
);
444 /** Log from the underlying streams. */
445 protected void log(String logLevel
, String line
) {
446 if ("ERROR".equals(logLevel
))
448 else if ("WARN".equals(logLevel
))
450 else if ("INFO".equals(logLevel
))
452 else if ("DEBUG".equals(logLevel
))
454 else if ("TRACE".equals(logLevel
))
456 else if (LOG_STDOUT
.equals(logLevel
))
457 System
.out
.println(line
);
458 else if ("System.err".equals(logLevel
))
459 System
.err
.println(line
);
461 throw new SlcException("Unknown log level " + logLevel
);
464 /** Append line to a log file. */
465 protected void appendLineToFile(Writer writer
, String line
) {
467 writer
.append(line
).append('\n');
468 } catch (IOException e
) {
469 log
.error("Cannot write to log file", e
);
473 /** Creates the writer for the output/err files. */
474 protected Writer
createWriter(Resource target
, Boolean append
) {
475 FileWriter writer
= null;
479 if (executionResources
!= null)
480 file
= new File(executionResources
.getAsOsPath(target
, true));
482 file
= target
.getFile();
483 writer
= new FileWriter(file
, append
);
484 } catch (IOException e
) {
485 log
.error("Cannot get file for " + target
, e
);
486 IOUtils
.closeQuietly(writer
);
491 /** Creates an outputstream for the output/err files. */
492 protected OutputStream
createOutputStream(Resource target
) {
493 FileOutputStream out
= null;
497 if (executionResources
!= null)
498 file
= new File(executionResources
.getAsOsPath(target
, true));
500 file
= target
.getFile();
501 out
= new FileOutputStream(file
, false);
502 } catch (IOException e
) {
503 log
.error("Cannot get file for " + target
, e
);
504 IOUtils
.closeQuietly(out
);
509 /** Append the argument (for chaining) */
510 public SystemCall
arg(String arg
) {
512 command
= new ArrayList
<Object
>();
517 /** Append the argument (for chaining) */
518 public SystemCall
arg(String arg
, String value
) {
520 command
= new ArrayList
<Object
>();
527 public void setCmd(String command
) {
531 public void setCommand(List
<Object
> command
) {
532 this.command
= command
;
535 public void setExecDir(String execdir
) {
536 this.execDir
= execdir
;
539 public void setStdErrLogLevel(String stdErrLogLevel
) {
540 this.stdErrLogLevel
= stdErrLogLevel
;
543 public void setStdOutLogLevel(String stdOutLogLevel
) {
544 this.stdOutLogLevel
= stdOutLogLevel
;
547 public void setSynchronous(Boolean synchronous
) {
548 this.synchronous
= synchronous
;
551 public void setOsCommands(Map
<String
, List
<Object
>> osCommands
) {
552 this.osCommands
= osCommands
;
555 public void setOsCmds(Map
<String
, String
> osCmds
) {
556 this.osCmds
= osCmds
;
559 public void setEnvironmentVariables(Map
<String
, String
> environmentVariables
) {
560 this.environmentVariables
= environmentVariables
;
563 public void setWatchdogTimeout(Long watchdogTimeout
) {
564 this.watchdogTimeout
= watchdogTimeout
;
567 public void setStdOutFile(Resource stdOutFile
) {
568 this.stdOutFile
= stdOutFile
;
571 public void setStdErrFile(Resource stdErrFile
) {
572 this.stdErrFile
= stdErrFile
;
575 public void setStdInFile(Resource stdInFile
) {
576 this.stdInFile
= stdInFile
;
579 public void setTestResult(TestResult testResult
) {
580 this.testResult
= testResult
;
583 public void setLogCommand(Boolean logCommand
) {
584 this.logCommand
= logCommand
;
587 public void setRedirectStreams(Boolean redirectStreams
) {
588 this.redirectStreams
= redirectStreams
;
591 public void setExceptionOnFailed(Boolean exceptionOnFailed
) {
592 this.exceptionOnFailed
= exceptionOnFailed
;
595 public void setMergeEnvironmentVariables(Boolean mergeEnvironmentVariables
) {
596 this.mergeEnvironmentVariables
= mergeEnvironmentVariables
;
599 public void setOsConsole(String osConsole
) {
600 this.osConsole
= osConsole
;
603 public void setGenerateScript(String generateScript
) {
604 this.generateScript
= generateScript
;
607 public void setExecutionResources(ExecutionResources executionResources
) {
608 this.executionResources
= executionResources
;
611 public void setRedirectStdOut(Boolean redirectStdOut
) {
612 this.redirectStdOut
= redirectStdOut
;
615 public void addOutputListener(SystemCallOutputListener outputListener
) {
616 outputListeners
.add(outputListener
);
619 public void removeOutputListener(SystemCallOutputListener outputListener
) {
620 outputListeners
.remove(outputListener
);
623 public void setOutputListeners(
624 List
<SystemCallOutputListener
> outputListeners
) {
625 this.outputListeners
= outputListeners
;
628 public void setExecutor(Executor executor
) {
629 this.executor
= executor
;
632 private class DummyexecuteStreamHandler
implements ExecuteStreamHandler
{
634 public void setProcessErrorStream(InputStream is
) throws IOException
{
637 public void setProcessInputStream(OutputStream os
) throws IOException
{
640 public void setProcessOutputStream(InputStream is
) throws IOException
{
643 public void start() throws IOException
{