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
.Writer
;
26 import java
.util
.ArrayList
;
27 import java
.util
.Collections
;
28 import java
.util
.HashMap
;
29 import java
.util
.List
;
32 import org
.apache
.commons
.exec
.CommandLine
;
33 import org
.apache
.commons
.exec
.DefaultExecutor
;
34 import org
.apache
.commons
.exec
.ExecuteException
;
35 import org
.apache
.commons
.exec
.ExecuteResultHandler
;
36 import org
.apache
.commons
.exec
.ExecuteStreamHandler
;
37 import org
.apache
.commons
.exec
.ExecuteWatchdog
;
38 import org
.apache
.commons
.exec
.Executor
;
39 import org
.apache
.commons
.exec
.LogOutputStream
;
40 import org
.apache
.commons
.exec
.PumpStreamHandler
;
41 import org
.apache
.commons
.exec
.ShutdownHookProcessDestroyer
;
42 import org
.apache
.commons
.io
.FileUtils
;
43 import org
.apache
.commons
.io
.IOUtils
;
44 import org
.apache
.commons
.logging
.Log
;
45 import org
.apache
.commons
.logging
.LogFactory
;
46 import org
.argeo
.slc
.SlcException
;
47 import org
.argeo
.slc
.UnsupportedException
;
48 import org
.argeo
.slc
.core
.execution
.ExecutionResources
;
49 import org
.argeo
.slc
.core
.test
.SimpleResultPart
;
50 import org
.argeo
.slc
.test
.TestResult
;
51 import org
.argeo
.slc
.test
.TestStatus
;
52 import org
.springframework
.core
.io
.Resource
;
54 /** Execute an OS specific system call. */
55 public class SystemCall
implements Runnable
{
56 public final static String LOG_STDOUT
= "System.out";
58 private final Log log
= LogFactory
.getLog(getClass());
60 private String execDir
;
62 private String cmd
= null;
63 private List
<Object
> command
= null;
65 private Boolean synchronous
= true;
67 private String stdErrLogLevel
= "ERROR";
68 private String stdOutLogLevel
= "INFO";
70 private Resource stdOutFile
= null;
71 private Resource stdErrFile
= null;
72 private Resource stdInFile
= null;
73 private Boolean redirectStdOut
= false;
75 private List
<SystemCallOutputListener
> outputListeners
= Collections
76 .synchronizedList(new ArrayList
<SystemCallOutputListener
>());
78 private Map
<String
, List
<Object
>> osCommands
= new HashMap
<String
, List
<Object
>>();
79 private Map
<String
, String
> osCmds
= new HashMap
<String
, String
>();
80 private Map
<String
, String
> environmentVariables
= new HashMap
<String
, String
>();
82 private Boolean logCommand
= false;
83 private Boolean redirectStreams
= true;
84 private Boolean exceptionOnFailed
= true;
85 private Boolean mergeEnvironmentVariables
= true;
87 private String osConsole
= null;
88 private String generateScript
= null;
90 private Long watchdogTimeout
= 24 * 60 * 60 * 1000l;
92 private TestResult testResult
;
94 private ExecutionResources executionResources
;
96 /** Empty constructor */
102 * Constructor based on the provided command list.
107 public SystemCall(List
<Object
> command
) {
108 this.command
= command
;
112 * Constructor based on the provided command.
115 * the command. If the provided string contains no space a
116 * command list is initialized with the argument as first
117 * component (useful for chained construction)
119 public SystemCall(String cmd
) {
120 if (cmd
.indexOf(' ') < 0) {
121 command
= new ArrayList
<Object
>();
128 /** Executes the system call. */
131 Writer stdOutWriter
= null;
132 OutputStream stdOutputStream
= null;
133 Writer stdErrWriter
= null;
134 InputStream stdInStream
= null;
135 if (stdOutFile
!= null)
137 stdOutputStream
= createOutputStream(stdOutFile
);
139 stdOutWriter
= createWriter(stdOutFile
, true);
141 if (stdErrFile
!= null) {
142 stdErrWriter
= createWriter(stdErrFile
, true);
144 if (stdOutFile
!= null && !redirectStdOut
)
145 stdErrWriter
= createWriter(stdOutFile
, true);
148 if (stdInFile
!= null)
150 stdInStream
= stdInFile
.getInputStream();
151 } catch (IOException e2
) {
152 throw new SlcException("Cannot open a stream for " + stdInFile
,
156 if (log
.isTraceEnabled()) {
157 log
.debug("os.name=" + System
.getProperty("os.name"));
158 log
.debug("os.arch=" + System
.getProperty("os.arch"));
159 log
.debug("os.version=" + System
.getProperty("os.version"));
162 // Execution directory
163 File dir
= new File(getExecDirToUse());
167 // Watchdog to check for lost processes
168 Executor executor
= new DefaultExecutor();
169 executor
.setWatchdog(new ExecuteWatchdog(watchdogTimeout
));
171 if (redirectStreams
) {
172 // Redirect standard streams
173 executor
.setStreamHandler(createExecuteStreamHandler(stdOutWriter
,
174 stdOutputStream
, stdErrWriter
, stdInStream
));
176 // Dummy stream handler (otherwise pump is used)
177 executor
.setStreamHandler(new DummyexecuteStreamHandler());
180 executor
.setProcessDestroyer(new ShutdownHookProcessDestroyer());
181 executor
.setWorkingDirectory(dir
);
183 // Command line to use
184 final CommandLine commandLine
= createCommandLine();
186 log
.info("Execute command:\n" + commandLine
187 + "\n in working directory: \n" + dir
+ "\n");
190 Map
<String
, String
> environmentVariablesToUse
= null;
191 if (environmentVariables
.size() > 0) {
192 environmentVariablesToUse
= new HashMap
<String
, String
>();
193 if (mergeEnvironmentVariables
)
194 environmentVariablesToUse
.putAll(System
.getenv());
195 environmentVariablesToUse
.putAll(environmentVariables
);
199 ExecuteResultHandler executeResultHandler
= createExecuteResultHandler(commandLine
);
202 // THE EXECUTION PROPER
207 int exitValue
= executor
.execute(commandLine
,
208 environmentVariablesToUse
);
209 executeResultHandler
.onProcessComplete(exitValue
);
210 } catch (ExecuteException e1
) {
211 if (e1
.getExitValue() == Executor
.INVALID_EXITVALUE
) {
212 Thread
.currentThread().interrupt();
215 executeResultHandler
.onProcessFailed(e1
);
218 executor
.execute(commandLine
, environmentVariablesToUse
,
219 executeResultHandler
);
220 } catch (SlcException e
) {
222 } catch (Exception e
) {
223 throw new SlcException("Could not execute command " + commandLine
,
226 IOUtils
.closeQuietly(stdOutWriter
);
227 IOUtils
.closeQuietly(stdErrWriter
);
228 IOUtils
.closeQuietly(stdInStream
);
233 public synchronized String
function() {
234 final StringBuffer buf
= new StringBuffer("");
235 SystemCallOutputListener tempOutputListener
= new SystemCallOutputListener() {
236 public void newLine(SystemCall systemCall
, String line
,
242 addOutputListener(tempOutputListener
);
244 removeOutputListener(tempOutputListener
);
245 return buf
.toString();
248 public String
asCommand() {
249 return createCommandLine().toString();
253 public String
toString() {
258 * Build a command line based on the properties. Can be overridden by
259 * specific command wrappers.
261 protected CommandLine
createCommandLine() {
262 // Check if an OS specific command overrides
263 String osName
= System
.getProperty("os.name");
264 List
<Object
> commandToUse
= null;
265 if (osCommands
.containsKey(osName
))
266 commandToUse
= osCommands
.get(osName
);
268 commandToUse
= command
;
269 String cmdToUse
= null;
270 if (osCmds
.containsKey(osName
))
271 cmdToUse
= osCmds
.get(osName
);
275 CommandLine commandLine
= null;
277 // Which command definition to use
278 if (commandToUse
== null && cmdToUse
== null)
279 throw new SlcException("Please specify a command.");
280 else if (commandToUse
!= null && cmdToUse
!= null)
281 throw new SlcException(
282 "Specify the command either as a line or as a list.");
283 else if (cmdToUse
!= null) {
284 commandLine
= CommandLine
.parse(cmdToUse
);
285 } else if (commandToUse
!= null) {
286 if (commandToUse
.size() == 0)
287 throw new SlcException("Command line is empty.");
289 commandLine
= new CommandLine(commandToUse
.get(0).toString());
291 for (int i
= 1; i
< commandToUse
.size(); i
++) {
292 if (log
.isTraceEnabled())
293 log
.debug(commandToUse
.get(i
));
294 commandLine
.addArgument(commandToUse
.get(i
).toString());
297 // all cases covered previously
298 throw new UnsupportedException();
301 if (generateScript
!= null) {
302 File scriptFile
= new File(getExecDirToUse() + File
.separator
305 FileUtils
.writeStringToFile(scriptFile
,
306 (osConsole
!= null ? osConsole
+ " " : "")
307 + commandLine
.toString());
308 } catch (IOException e
) {
309 throw new SlcException("Could not generate script "
312 commandLine
= new CommandLine(scriptFile
);
314 if (osConsole
!= null)
315 commandLine
= CommandLine
.parse(osConsole
+ " "
316 + commandLine
.toString());
323 * Creates a {@link PumpStreamHandler} which redirects streams to the custom
326 protected ExecuteStreamHandler
createExecuteStreamHandler(
327 final Writer stdOutWriter
, final OutputStream stdOutputStream
,
328 final Writer stdErrWriter
, final InputStream stdInStream
) {
332 PumpStreamHandler pumpStreamHandler
= new PumpStreamHandler(
333 stdOutputStream
!= null ? stdOutputStream
334 : new LogOutputStream() {
335 protected void processLine(String line
, int level
) {
336 if (line
!= null && !line
.trim().equals(""))
338 if (stdOutWriter
!= null)
339 appendLineToFile(stdOutWriter
, line
);
341 }, new LogOutputStream() {
342 protected void processLine(String line
, int level
) {
343 if (line
!= null && !line
.trim().equals(""))
345 if (stdErrWriter
!= null)
346 appendLineToFile(stdErrWriter
, line
);
349 return pumpStreamHandler
;
352 /** Creates the default {@link ExecuteResultHandler}. */
353 protected ExecuteResultHandler
createExecuteResultHandler(
354 final CommandLine commandLine
) {
355 return new ExecuteResultHandler() {
357 public void onProcessComplete(int exitValue
) {
358 String msg
= "System call '" + commandLine
359 + "' properly completed.";
360 if (log
.isTraceEnabled())
362 if (testResult
!= null) {
363 forwardPath(testResult
);
364 testResult
.addResultPart(new SimpleResultPart(
365 TestStatus
.PASSED
, msg
));
369 public void onProcessFailed(ExecuteException e
) {
370 String msg
= "System call '" + commandLine
+ "' failed.";
371 if (testResult
!= null) {
372 forwardPath(testResult
);
373 testResult
.addResultPart(new SimpleResultPart(
374 TestStatus
.ERROR
, msg
, e
));
376 if (exceptionOnFailed
)
377 throw new SlcException(msg
, e
);
385 protected void forwardPath(TestResult testResult
) {
386 // TODO: allocate a TreeSPath
390 * Shortcut method getting the execDir to use
392 protected String
getExecDirToUse() {
395 if (execDir
!= null) {
396 // Replace '/' by local file separator, for portability
397 execDir
.replace('/', File
.separatorChar
);
398 dir
= new File(execDir
).getCanonicalFile();
402 return System
.getProperty("user.dir");
404 return dir
.getPath();
405 } catch (Exception e
) {
406 throw new SlcException("Cannot find exec dir", e
);
410 protected void logStdOut(String line
) {
411 for (SystemCallOutputListener outputListener
: outputListeners
)
412 outputListener
.newLine(this, line
, false);
413 log(stdOutLogLevel
, line
);
416 protected void logStdErr(String line
) {
417 for (SystemCallOutputListener outputListener
: outputListeners
)
418 outputListener
.newLine(this, line
, true);
419 log(stdErrLogLevel
, line
);
422 /** Log from the underlying streams. */
423 protected void log(String logLevel
, String line
) {
424 if ("ERROR".equals(logLevel
))
426 else if ("WARN".equals(logLevel
))
428 else if ("INFO".equals(logLevel
))
430 else if ("DEBUG".equals(logLevel
))
432 else if ("TRACE".equals(logLevel
))
434 else if (LOG_STDOUT
.equals(logLevel
))
435 System
.out
.println(line
);
436 else if ("System.err".equals(logLevel
))
437 System
.err
.println(line
);
439 throw new SlcException("Unknown log level " + logLevel
);
442 /** Append line to a log file. */
443 protected void appendLineToFile(Writer writer
, String line
) {
445 writer
.append(line
).append('\n');
446 } catch (IOException e
) {
447 log
.error("Cannot write to log file", e
);
451 /** Creates the writer for the output/err files. */
452 protected Writer
createWriter(Resource target
, Boolean append
) {
453 FileWriter writer
= null;
457 if (executionResources
!= null)
458 file
= new File(executionResources
.getAsOsPath(target
, true));
460 file
= target
.getFile();
461 writer
= new FileWriter(file
, append
);
462 } catch (IOException e
) {
463 log
.error("Cannot get file for " + target
, e
);
464 IOUtils
.closeQuietly(writer
);
469 /** Creates an outputstream for the output/err files. */
470 protected OutputStream
createOutputStream(Resource target
) {
471 FileOutputStream out
= null;
475 if (executionResources
!= null)
476 file
= new File(executionResources
.getAsOsPath(target
, true));
478 file
= target
.getFile();
479 out
= new FileOutputStream(file
, false);
480 } catch (IOException e
) {
481 log
.error("Cannot get file for " + target
, e
);
482 IOUtils
.closeQuietly(out
);
487 /** Append the argument (for chaining) */
488 public SystemCall
arg(String arg
) {
490 command
= new ArrayList
<Object
>();
495 /** Append the argument (for chaining) */
496 public SystemCall
arg(String arg
, String value
) {
498 command
= new ArrayList
<Object
>();
505 public void setCmd(String command
) {
509 public void setCommand(List
<Object
> command
) {
510 this.command
= command
;
513 public void setExecDir(String execdir
) {
514 this.execDir
= execdir
;
517 public void setStdErrLogLevel(String stdErrLogLevel
) {
518 this.stdErrLogLevel
= stdErrLogLevel
;
521 public void setStdOutLogLevel(String stdOutLogLevel
) {
522 this.stdOutLogLevel
= stdOutLogLevel
;
525 public void setSynchronous(Boolean synchronous
) {
526 this.synchronous
= synchronous
;
529 public void setOsCommands(Map
<String
, List
<Object
>> osCommands
) {
530 this.osCommands
= osCommands
;
533 public void setOsCmds(Map
<String
, String
> osCmds
) {
534 this.osCmds
= osCmds
;
537 public void setEnvironmentVariables(Map
<String
, String
> environmentVariables
) {
538 this.environmentVariables
= environmentVariables
;
541 public void setWatchdogTimeout(Long watchdogTimeout
) {
542 this.watchdogTimeout
= watchdogTimeout
;
545 public void setStdOutFile(Resource stdOutFile
) {
546 this.stdOutFile
= stdOutFile
;
549 public void setStdErrFile(Resource stdErrFile
) {
550 this.stdErrFile
= stdErrFile
;
553 public void setStdInFile(Resource stdInFile
) {
554 this.stdInFile
= stdInFile
;
557 public void setTestResult(TestResult testResult
) {
558 this.testResult
= testResult
;
561 public void setLogCommand(Boolean logCommand
) {
562 this.logCommand
= logCommand
;
565 public void setRedirectStreams(Boolean redirectStreams
) {
566 this.redirectStreams
= redirectStreams
;
569 public void setExceptionOnFailed(Boolean exceptionOnFailed
) {
570 this.exceptionOnFailed
= exceptionOnFailed
;
573 public void setMergeEnvironmentVariables(Boolean mergeEnvironmentVariables
) {
574 this.mergeEnvironmentVariables
= mergeEnvironmentVariables
;
577 public void setOsConsole(String osConsole
) {
578 this.osConsole
= osConsole
;
581 public void setGenerateScript(String generateScript
) {
582 this.generateScript
= generateScript
;
585 public void setExecutionResources(ExecutionResources executionResources
) {
586 this.executionResources
= executionResources
;
589 public void setRedirectStdOut(Boolean redirectStdOut
) {
590 this.redirectStdOut
= redirectStdOut
;
593 public void addOutputListener(SystemCallOutputListener outputListener
) {
594 outputListeners
.add(outputListener
);
597 public void removeOutputListener(SystemCallOutputListener outputListener
) {
598 outputListeners
.remove(outputListener
);
601 public void setOutputListeners(
602 List
<SystemCallOutputListener
> outputListeners
) {
603 this.outputListeners
= outputListeners
;
606 private class DummyexecuteStreamHandler
implements ExecuteStreamHandler
{
608 public void setProcessErrorStream(InputStream is
) throws IOException
{
611 public void setProcessInputStream(OutputStream os
) throws IOException
{
614 public void setProcessOutputStream(InputStream is
) throws IOException
{
617 public void start() throws IOException
{