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