net.leanix.dropkit.util.ProcessWrapper Maven / Gradle / Ivy
package net.leanix.dropkit.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProcessWrapper {
private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class);
public static abstract class StdXXXLineListener {
/**
* Callback for new received line from stdout
*
* @param line
* the line from stdout
* @return true
if more lines will be received, false
if no more lines will be read any more from process
*/
public abstract boolean fireNewLine(String line);
}
private final ProcessBuilder _processBuilder;
private Process _process;
private Thread stdOutListenerThread;
public ProcessWrapper(ProcessBuilder processBuilder) {
super();
_processBuilder = processBuilder;
}
/**
* Start the wrapped process now.
*
* @param newStdOutLineListener
* optional a listener to stdout and stderr
* @throws IOException
* if program can not be started. (Eg: the program does not exist on file system or location)
*/
public ProcessWrapper start(final StdXXXLineListener newStdOutLineListener) throws IOException {
_processBuilder.redirectErrorStream(true);
LOG.info("Start external Process: {}", StringUtils.join(_processBuilder.command(), " "));
_process = _processBuilder.start();
if (newStdOutLineListener != null) {
Runnable runnable = new Runnable() {
@Override
public void run() {
BufferedReader stdOutReader = new BufferedReader(new InputStreamReader(_process.getInputStream()));
try {
String line;
try {
while ((line = stdOutReader.readLine()) != null) {
boolean needMoreLines = newStdOutLineListener.fireNewLine(line);
if (!needMoreLines) {
break;
}
}
LOG.debug("stdout stream is dead so termitate stdout listener thread clearly.");
} catch (IOException e) {
}
} finally {
IOUtils.closeQuietly(stdOutReader);
}
}
};
stdOutListenerThread = new Thread(runnable);
stdOutListenerThread.start();
}
return this;
}
public boolean isRunning() {
if (_process == null) {
return false;
}
try {
_process.exitValue();
return false;
} catch (IllegalThreadStateException e) {
return true;
}
}
/**
* @param timeOut
* @param timeUnit
* @return true
if process ends before given timeout. Otherwise false
if the process is stopped due to running
* out of timeout.
*/
public boolean waitUntilFinished(int timeOut, TimeUnit timeUnit) {
long timeOutInMillis = System.currentTimeMillis() + timeUnit.toMillis(timeOut);
while (isRunning()) {
if (timeOutInMillis <= System.currentTimeMillis()) {
return false;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
destroy();
}
}
return true;
}
public int getExitValue() {
try {
_process.waitFor();
} catch (InterruptedException e) {
}
return _process.exitValue();
}
public void destroy() {
// send a normal kill to process
int pid = getUnixPID(_process);
executeCommand("kill", "" + pid);
new TimeoutExecutor(5000, 10).checkUntilConditionIsTrue(new Callable() {
@Override
public Boolean call() throws Exception {
boolean running = isRunning();
return !running;
}
});
if (isRunning()) {
// send kill -9 if process is not dead
executeCommand("kill", "-9", "" + pid);
new TimeoutExecutor(5000, 10).checkUntilConditionIsTrue(new Callable() {
@Override
public Boolean call() throws Exception {
return !isRunning();
}
});
}
// destroy the java process object
_process.destroy();
if (stdOutListenerThread != null) {
stdOutListenerThread.interrupt();
}
}
private void executeCommand(String... cmd) {
ProcessBuilder pBuilder = new ProcessBuilder(cmd);
try {
LOG.debug("start command '" + Arrays.asList(cmd) + "'");
Process myProcess = pBuilder.start();
IOUtils.closeQuietly(myProcess.getOutputStream());
IOUtils.closeQuietly(myProcess.getInputStream());
IOUtils.closeQuietly(myProcess.getErrorStream());
} catch (IOException e) {
LOG.warn("Can not execute " + Arrays.asList(cmd), e);
return;
}
}
private Integer getUnixPID(Process process) {
if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
try {
Class> cl = process.getClass();
Field field = cl.getDeclaredField("pid");
field.setAccessible(true);
Object pidObject = field.get(process);
return (Integer) pidObject;
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new IllegalArgumentException("Needs to be a UNIXProcess");
}
}
/**
* Causes the current thread to wait, if necessary, until the process represented by this {@code Process} object has terminated. This
* method returns immediately if the subprocess has already terminated. If the subprocess has not yet terminated, the calling thread
* will be blocked until the subprocess exits.
*
* @return the exit value of the subprocess represented by this {@code Process} object. By convention, the value {@code 0} indicates
* normal termination.
* @throws InterruptedException
* if the current thread is {@linkplain Thread#interrupt() interrupted} by another thread while it is waiting, then the wait
* is ended and an {@link InterruptedException} is thrown.
*/
public int waitFor() throws InterruptedException {
return _process.waitFor();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy