package io.lumify.core.util;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
public class ProcessRunner {
private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(ProcessRunner.class);
public Process execute(final String programName, final String[] programArgs, OutputStream out, final String logPrefix) throws IOException, InterruptedException {
final List<String> arguments = Lists.newArrayList(programName);
for (String programArg : programArgs) {
if (programArg == null) {
throw new NullPointerException("Argument was null in argument list [ " + Joiner.on(", ").useForNull("null").join(programArgs) + " ]");
}
arguments.add(programArg);
}
final ProcessBuilder procBuilder = new ProcessBuilder(arguments);
final Map<String, String> sortedEnv = new TreeMap<String, String>(procBuilder.environment());
LOGGER.info("%s Running: %s", logPrefix, arrayToString(arguments));
if (!sortedEnv.isEmpty()) {
LOGGER.info("%s Spawned program environment: ", logPrefix);
for (final Map.Entry<String, String> entry : sortedEnv.entrySet()) {
LOGGER.info("%s %s:%s", logPrefix, entry.getKey(), entry.getValue());
}
} else {
LOGGER.info("%s Running program environment is empty", logPrefix);
}
final Process proc = procBuilder.start();
StreamHelper errStreamHelper = new StreamHelper(proc.getErrorStream(), LOGGER, logPrefix + programName + "(stderr): ");
errStreamHelper.start();
final Exception[] pipeException = new Exception[1];
Pipe pipe = null;
StreamHelper stdoutStreamHelper = null;
if (out == null) {
stdoutStreamHelper = new StreamHelper(proc.getInputStream(), LOGGER, logPrefix + programName + "(stdout): ");
stdoutStreamHelper.start();
} else {
Pipe.StatusHandler statusHandler = new Pipe.StatusHandler() {
@Override
public void handleException(Exception e) {
pipeException[0] = e;
}
};
pipe = new Pipe().pipe(proc.getInputStream(), out, statusHandler);
}
// Pipe will ensure to some degree that we have started reading but if the process exits at
// just the right time (after threadStarted = true but before the in.read occurs) we could miss the output
// this sleep should be enough to prevent this happening.
Thread.sleep(100);
proc.waitFor();
errStreamHelper.join(10000);
if (stdoutStreamHelper != null) {
stdoutStreamHelper.join(10000);
}
if (pipe != null) {
pipe.waitForCompletion(10000, TimeUnit.MILLISECONDS);
}
proc.getOutputStream().close(); // stdin
proc.getInputStream().close(); // stdout
proc.getErrorStream().close();
LOGGER.info(logPrefix + programName + "(returncode): " + proc.exitValue());
if (proc.exitValue() != 0) {
throw new RuntimeException("unexpected return code: " + proc.exitValue() + " for command " + arrayToString(arguments));
}
if (pipeException[0] != null) {
throw new RuntimeException("pipe exception", pipeException[0]);
}
return proc;
}
private static String arrayToString(List<String> arr) {
StringBuilder result = new StringBuilder();
for (String s : arr) {
result.append(s).append(' ');
}
return result.toString();
}
}