]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java
Revert change breaking path retro-compatibility.
[gpl/argeo-slc.git] / runtime / org.argeo.slc.core / src / main / java / org / argeo / slc / core / execution / tasks / SystemCall.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16 package org.argeo.slc.core.execution.tasks;
17
18 import java.io.File;
19 import java.io.FileOutputStream;
20 import java.io.FileWriter;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.PipedInputStream;
25 import java.io.PipedOutputStream;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import org.apache.commons.exec.CommandLine;
34 import org.apache.commons.exec.DefaultExecutor;
35 import org.apache.commons.exec.ExecuteException;
36 import org.apache.commons.exec.ExecuteResultHandler;
37 import org.apache.commons.exec.ExecuteStreamHandler;
38 import org.apache.commons.exec.ExecuteWatchdog;
39 import org.apache.commons.exec.Executor;
40 import org.apache.commons.exec.LogOutputStream;
41 import org.apache.commons.exec.PumpStreamHandler;
42 import org.apache.commons.exec.ShutdownHookProcessDestroyer;
43 import org.apache.commons.io.FileUtils;
44 import org.apache.commons.io.IOUtils;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47 import org.argeo.slc.SlcException;
48 import org.argeo.slc.UnsupportedException;
49 import org.argeo.slc.core.execution.ExecutionResources;
50 import org.argeo.slc.core.test.SimpleResultPart;
51 import org.argeo.slc.test.TestResult;
52 import org.argeo.slc.test.TestStatus;
53 import org.springframework.core.io.Resource;
54
55 /** Execute an OS specific system call. */
56 public class SystemCall implements Runnable {
57 public final static String LOG_STDOUT = "System.out";
58
59 private final Log log = LogFactory.getLog(getClass());
60
61 private String execDir;
62
63 private String cmd = null;
64 private List<Object> command = null;
65
66 private Executor executor = new DefaultExecutor();
67 private Boolean synchronous = true;
68
69 private String stdErrLogLevel = "ERROR";
70 private String stdOutLogLevel = "INFO";
71
72 private Resource stdOutFile = null;
73 private Resource stdErrFile = null;
74
75 private Resource stdInFile = null;
76 /**
77 * If no {@link #stdInFile} provided, writing to this stream will write to
78 * the stdin of the process.
79 */
80 private OutputStream stdInSink = null;
81
82 private Boolean redirectStdOut = false;
83
84 private List<SystemCallOutputListener> outputListeners = Collections
85 .synchronizedList(new ArrayList<SystemCallOutputListener>());
86
87 private Map<String, List<Object>> osCommands = new HashMap<String, List<Object>>();
88 private Map<String, String> osCmds = new HashMap<String, String>();
89 private Map<String, String> environmentVariables = new HashMap<String, String>();
90
91 private Boolean logCommand = false;
92 private Boolean redirectStreams = true;
93 private Boolean exceptionOnFailed = true;
94 private Boolean mergeEnvironmentVariables = true;
95
96 private String osConsole = null;
97 private String generateScript = null;
98
99 /** 24 hours */
100 private Long watchdogTimeout = 24 * 60 * 60 * 1000l;
101
102 private TestResult testResult;
103
104 private ExecutionResources executionResources;
105
106 /** Empty constructor */
107 public SystemCall() {
108
109 }
110
111 /**
112 * Constructor based on the provided command list.
113 *
114 * @param command
115 * the command list
116 */
117 public SystemCall(List<Object> command) {
118 this.command = command;
119 }
120
121 /**
122 * Constructor based on the provided command.
123 *
124 * @param cmd
125 * the command. If the provided string contains no space a
126 * command list is initialized with the argument as first
127 * component (useful for chained construction)
128 */
129 public SystemCall(String cmd) {
130 if (cmd.indexOf(' ') < 0) {
131 command = new ArrayList<Object>();
132 command.add(cmd);
133 } else {
134 this.cmd = cmd;
135 }
136 }
137
138 /** Executes the system call. */
139 public void run() {
140 // Manage streams
141 Writer stdOutWriter = null;
142 OutputStream stdOutputStream = null;
143 Writer stdErrWriter = null;
144 InputStream stdInStream = null;
145 if (stdOutFile != null)
146 if (redirectStdOut)
147 stdOutputStream = createOutputStream(stdOutFile);
148 else
149 stdOutWriter = createWriter(stdOutFile, true);
150
151 if (stdErrFile != null) {
152 stdErrWriter = createWriter(stdErrFile, true);
153 } else {
154 if (stdOutFile != null && !redirectStdOut)
155 stdErrWriter = createWriter(stdOutFile, true);
156 }
157
158 try {
159 if (stdInFile != null)
160 stdInStream = stdInFile.getInputStream();
161 else {
162 stdInStream = new PipedInputStream();
163 stdInSink = new PipedOutputStream(
164 (PipedInputStream) stdInStream);
165 }
166 } catch (IOException e2) {
167 throw new SlcException("Cannot open a stream for " + stdInFile, e2);
168 }
169
170 if (log.isTraceEnabled()) {
171 log.debug("os.name=" + System.getProperty("os.name"));
172 log.debug("os.arch=" + System.getProperty("os.arch"));
173 log.debug("os.version=" + System.getProperty("os.version"));
174 }
175
176 // Execution directory
177 File dir = new File(getExecDirToUse());
178 // if (!dir.exists())
179 // dir.mkdirs();
180
181 // Watchdog to check for lost processes
182 Executor executorToUse;
183 if (executor != null)
184 executorToUse = executor;
185 else
186 executorToUse = new DefaultExecutor();
187 executorToUse.setWatchdog(new ExecuteWatchdog(watchdogTimeout));
188
189 if (redirectStreams) {
190 // Redirect standard streams
191 executorToUse.setStreamHandler(createExecuteStreamHandler(
192 stdOutWriter, stdOutputStream, stdErrWriter, stdInStream));
193 } else {
194 // Dummy stream handler (otherwise pump is used)
195 executorToUse.setStreamHandler(new DummyexecuteStreamHandler());
196 }
197
198 executorToUse.setProcessDestroyer(new ShutdownHookProcessDestroyer());
199 executorToUse.setWorkingDirectory(dir);
200
201 // Command line to use
202 final CommandLine commandLine = createCommandLine();
203 if (logCommand)
204 log.info("Execute command:\n" + commandLine
205 + "\n in working directory: \n" + dir + "\n");
206
207 // Env variables
208 Map<String, String> environmentVariablesToUse = null;
209 environmentVariablesToUse = new HashMap<String, String>();
210 if (mergeEnvironmentVariables)
211 environmentVariablesToUse.putAll(System.getenv());
212 if (environmentVariables.size() > 0)
213 environmentVariablesToUse.putAll(environmentVariables);
214
215 // Execute
216 ExecuteResultHandler executeResultHandler = createExecuteResultHandler(commandLine);
217
218 //
219 // THE EXECUTION PROPER
220 //
221 try {
222 if (synchronous)
223 try {
224 int exitValue = executorToUse.execute(commandLine,
225 environmentVariablesToUse);
226 executeResultHandler.onProcessComplete(exitValue);
227 } catch (ExecuteException e1) {
228 if (e1.getExitValue() == Executor.INVALID_EXITVALUE) {
229 Thread.currentThread().interrupt();
230 return;
231 }
232 executeResultHandler.onProcessFailed(e1);
233 }
234 else
235 executorToUse.execute(commandLine, environmentVariablesToUse,
236 executeResultHandler);
237 } catch (SlcException e) {
238 throw e;
239 } catch (Exception e) {
240 throw new SlcException("Could not execute command " + commandLine,
241 e);
242 } finally {
243 IOUtils.closeQuietly(stdOutWriter);
244 IOUtils.closeQuietly(stdErrWriter);
245 IOUtils.closeQuietly(stdInStream);
246 IOUtils.closeQuietly(stdInSink);
247 }
248
249 }
250
251 public synchronized String function() {
252 final StringBuffer buf = new StringBuffer("");
253 SystemCallOutputListener tempOutputListener = new SystemCallOutputListener() {
254 public void newLine(SystemCall systemCall, String line,
255 Boolean isError) {
256 if (!isError)
257 buf.append(line);
258 }
259 };
260 addOutputListener(tempOutputListener);
261 run();
262 removeOutputListener(tempOutputListener);
263 return buf.toString();
264 }
265
266 public String asCommand() {
267 return createCommandLine().toString();
268 }
269
270 @Override
271 public String toString() {
272 return asCommand();
273 }
274
275 /**
276 * Build a command line based on the properties. Can be overridden by
277 * specific command wrappers.
278 */
279 protected CommandLine createCommandLine() {
280 // Check if an OS specific command overrides
281 String osName = System.getProperty("os.name");
282 List<Object> commandToUse = null;
283 if (osCommands.containsKey(osName))
284 commandToUse = osCommands.get(osName);
285 else
286 commandToUse = command;
287 String cmdToUse = null;
288 if (osCmds.containsKey(osName))
289 cmdToUse = osCmds.get(osName);
290 else
291 cmdToUse = cmd;
292
293 CommandLine commandLine = null;
294
295 // Which command definition to use
296 if (commandToUse == null && cmdToUse == null)
297 throw new SlcException("Please specify a command.");
298 else if (commandToUse != null && cmdToUse != null)
299 throw new SlcException(
300 "Specify the command either as a line or as a list.");
301 else if (cmdToUse != null) {
302 commandLine = CommandLine.parse(cmdToUse);
303 } else if (commandToUse != null) {
304 if (commandToUse.size() == 0)
305 throw new SlcException("Command line is empty.");
306
307 commandLine = new CommandLine(commandToUse.get(0).toString());
308
309 for (int i = 1; i < commandToUse.size(); i++) {
310 if (log.isTraceEnabled())
311 log.debug(commandToUse.get(i));
312 commandLine.addArgument(commandToUse.get(i).toString());
313 }
314 } else {
315 // all cases covered previously
316 throw new UnsupportedException();
317 }
318
319 if (generateScript != null) {
320 File scriptFile = new File(getExecDirToUse() + File.separator
321 + generateScript);
322 try {
323 FileUtils.writeStringToFile(scriptFile,
324 (osConsole != null ? osConsole + " " : "")
325 + commandLine.toString());
326 } catch (IOException e) {
327 throw new SlcException("Could not generate script "
328 + scriptFile, e);
329 }
330 commandLine = new CommandLine(scriptFile);
331 } else {
332 if (osConsole != null)
333 commandLine = CommandLine.parse(osConsole + " "
334 + commandLine.toString());
335 }
336
337 return commandLine;
338 }
339
340 /**
341 * Creates a {@link PumpStreamHandler} which redirects streams to the custom
342 * logging mechanism.
343 */
344 protected ExecuteStreamHandler createExecuteStreamHandler(
345 final Writer stdOutWriter, final OutputStream stdOutputStream,
346 final Writer stdErrWriter, final InputStream stdInStream) {
347
348 // Log writers
349
350 PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(
351 stdOutputStream != null ? stdOutputStream
352 : new LogOutputStream() {
353 protected void processLine(String line, int level) {
354 if (line != null && !line.trim().equals(""))
355 logStdOut(line);
356 if (stdOutWriter != null)
357 appendLineToFile(stdOutWriter, line);
358 }
359 }, new LogOutputStream() {
360 protected void processLine(String line, int level) {
361 if (line != null && !line.trim().equals(""))
362 logStdErr(line);
363 if (stdErrWriter != null)
364 appendLineToFile(stdErrWriter, line);
365 }
366 }, stdInStream) {
367
368 @Override
369 public void stop() {
370 // prevents the method to block when joining stdin
371 if (stdInSink != null)
372 IOUtils.closeQuietly(stdInSink);
373
374 super.stop();
375 }
376 };
377 return pumpStreamHandler;
378 }
379
380 /** Creates the default {@link ExecuteResultHandler}. */
381 protected ExecuteResultHandler createExecuteResultHandler(
382 final CommandLine commandLine) {
383 return new ExecuteResultHandler() {
384
385 public void onProcessComplete(int exitValue) {
386 String msg = "System call '" + commandLine
387 + "' properly completed.";
388 if (log.isTraceEnabled())
389 log.trace(msg);
390 if (testResult != null) {
391 forwardPath(testResult);
392 testResult.addResultPart(new SimpleResultPart(
393 TestStatus.PASSED, msg));
394 }
395 }
396
397 public void onProcessFailed(ExecuteException e) {
398 String msg = "System call '" + commandLine + "' failed.";
399 if (testResult != null) {
400 forwardPath(testResult);
401 testResult.addResultPart(new SimpleResultPart(
402 TestStatus.ERROR, msg, e));
403 } else {
404 if (exceptionOnFailed)
405 throw new SlcException(msg, e);
406 else
407 log.error(msg, e);
408 }
409 }
410 };
411 }
412
413 protected void forwardPath(TestResult testResult) {
414 // TODO: allocate a TreeSPath
415 }
416
417 /**
418 * Shortcut method getting the execDir to use
419 */
420 protected String getExecDirToUse() {
421 try {
422 if (execDir != null) {
423 return execDir;
424 }
425 return System.getProperty("user.dir");
426 } catch (Exception e) {
427 throw new SlcException("Cannot find exec dir", e);
428 }
429 }
430
431 protected void logStdOut(String line) {
432 for (SystemCallOutputListener outputListener : outputListeners)
433 outputListener.newLine(this, line, false);
434 log(stdOutLogLevel, line);
435 }
436
437 protected void logStdErr(String line) {
438 for (SystemCallOutputListener outputListener : outputListeners)
439 outputListener.newLine(this, line, true);
440 log(stdErrLogLevel, line);
441 }
442
443 /** Log from the underlying streams. */
444 protected void log(String logLevel, String line) {
445 if ("ERROR".equals(logLevel))
446 log.error(line);
447 else if ("WARN".equals(logLevel))
448 log.warn(line);
449 else if ("INFO".equals(logLevel))
450 log.info(line);
451 else if ("DEBUG".equals(logLevel))
452 log.debug(line);
453 else if ("TRACE".equals(logLevel))
454 log.trace(line);
455 else if (LOG_STDOUT.equals(logLevel))
456 System.out.println(line);
457 else if ("System.err".equals(logLevel))
458 System.err.println(line);
459 else
460 throw new SlcException("Unknown log level " + logLevel);
461 }
462
463 /** Append line to a log file. */
464 protected void appendLineToFile(Writer writer, String line) {
465 try {
466 writer.append(line).append('\n');
467 } catch (IOException e) {
468 log.error("Cannot write to log file", e);
469 }
470 }
471
472 /** Creates the writer for the output/err files. */
473 protected Writer createWriter(Resource target, Boolean append) {
474 FileWriter writer = null;
475 try {
476
477 final File file;
478 if (executionResources != null)
479 file = new File(executionResources.getAsOsPath(target, true));
480 else
481 file = target.getFile();
482 writer = new FileWriter(file, append);
483 } catch (IOException e) {
484 log.error("Cannot get file for " + target, e);
485 IOUtils.closeQuietly(writer);
486 }
487 return writer;
488 }
489
490 /** Creates an outputstream for the output/err files. */
491 protected OutputStream createOutputStream(Resource target) {
492 FileOutputStream out = null;
493 try {
494
495 final File file;
496 if (executionResources != null)
497 file = new File(executionResources.getAsOsPath(target, true));
498 else
499 file = target.getFile();
500 out = new FileOutputStream(file, false);
501 } catch (IOException e) {
502 log.error("Cannot get file for " + target, e);
503 IOUtils.closeQuietly(out);
504 }
505 return out;
506 }
507
508 /** Append the argument (for chaining) */
509 public SystemCall arg(String arg) {
510 if (command == null)
511 command = new ArrayList<Object>();
512 command.add(arg);
513 return this;
514 }
515
516 /** Append the argument (for chaining) */
517 public SystemCall arg(String arg, String value) {
518 if (command == null)
519 command = new ArrayList<Object>();
520 command.add(arg);
521 command.add(value);
522 return this;
523 }
524
525 /** */
526 public void setCmd(String command) {
527 this.cmd = command;
528 }
529
530 public void setCommand(List<Object> command) {
531 this.command = command;
532 }
533
534 public void setExecDir(String execdir) {
535 this.execDir = execdir;
536 }
537
538 public void setStdErrLogLevel(String stdErrLogLevel) {
539 this.stdErrLogLevel = stdErrLogLevel;
540 }
541
542 public void setStdOutLogLevel(String stdOutLogLevel) {
543 this.stdOutLogLevel = stdOutLogLevel;
544 }
545
546 public void setSynchronous(Boolean synchronous) {
547 this.synchronous = synchronous;
548 }
549
550 public void setOsCommands(Map<String, List<Object>> osCommands) {
551 this.osCommands = osCommands;
552 }
553
554 public void setOsCmds(Map<String, String> osCmds) {
555 this.osCmds = osCmds;
556 }
557
558 public void setEnvironmentVariables(Map<String, String> environmentVariables) {
559 this.environmentVariables = environmentVariables;
560 }
561
562 public void setWatchdogTimeout(Long watchdogTimeout) {
563 this.watchdogTimeout = watchdogTimeout;
564 }
565
566 public void setStdOutFile(Resource stdOutFile) {
567 this.stdOutFile = stdOutFile;
568 }
569
570 public void setStdErrFile(Resource stdErrFile) {
571 this.stdErrFile = stdErrFile;
572 }
573
574 public void setStdInFile(Resource stdInFile) {
575 this.stdInFile = stdInFile;
576 }
577
578 public void setTestResult(TestResult testResult) {
579 this.testResult = testResult;
580 }
581
582 public void setLogCommand(Boolean logCommand) {
583 this.logCommand = logCommand;
584 }
585
586 public void setRedirectStreams(Boolean redirectStreams) {
587 this.redirectStreams = redirectStreams;
588 }
589
590 public void setExceptionOnFailed(Boolean exceptionOnFailed) {
591 this.exceptionOnFailed = exceptionOnFailed;
592 }
593
594 public void setMergeEnvironmentVariables(Boolean mergeEnvironmentVariables) {
595 this.mergeEnvironmentVariables = mergeEnvironmentVariables;
596 }
597
598 public void setOsConsole(String osConsole) {
599 this.osConsole = osConsole;
600 }
601
602 public void setGenerateScript(String generateScript) {
603 this.generateScript = generateScript;
604 }
605
606 public void setExecutionResources(ExecutionResources executionResources) {
607 this.executionResources = executionResources;
608 }
609
610 public void setRedirectStdOut(Boolean redirectStdOut) {
611 this.redirectStdOut = redirectStdOut;
612 }
613
614 public void addOutputListener(SystemCallOutputListener outputListener) {
615 outputListeners.add(outputListener);
616 }
617
618 public void removeOutputListener(SystemCallOutputListener outputListener) {
619 outputListeners.remove(outputListener);
620 }
621
622 public void setOutputListeners(
623 List<SystemCallOutputListener> outputListeners) {
624 this.outputListeners = outputListeners;
625 }
626
627 public void setExecutor(Executor executor) {
628 this.executor = executor;
629 }
630
631 private class DummyexecuteStreamHandler implements ExecuteStreamHandler {
632
633 public void setProcessErrorStream(InputStream is) throws IOException {
634 }
635
636 public void setProcessInputStream(OutputStream os) throws IOException {
637 }
638
639 public void setProcessOutputStream(InputStream is) throws IOException {
640 }
641
642 public void start() throws IOException {
643 }
644
645 public void stop() {
646 }
647
648 }
649 }