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