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