/*
* Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
* Copyright [2016-2017] EMBL-European Bioinformatics Institute
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* File: ProcessExec.java
* Created by: dstaines
* Created on: Oct 25, 2006
* CVS: $Id$
*/
package org.ensembl.healthcheck.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import org.apache.commons.io.IOUtils;
/**
* Class to consume a stream from an executing process to avoid lockups. Code
* derived but extensively modified from <a
* href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html">JavaWorld
* article</a>
*
* @author dstaines
*
*/
class StreamGobbler extends Thread {
InputStream is;
boolean discard = false;
Appendable out;
StreamGobbler(InputStream is, Appendable out, boolean discard) {
this.is = is;
this.out = out;
this.discard = discard;
}
public void run() {
try {
if (discard) {
while (is.read() != -1) {
}
} else {
streamToBuffer(is, out);
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} finally {
InputOutputUtils.closeQuietly(is);
}
}
public static void streamToBuffer(InputStream is, Appendable out)
throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = null;
while ((line = reader.readLine()) != null) {
out.append(line);
out.append('\n');
}
reader.close();
}
}
/**
* Class to execute a process safely, avoiding hangups by consuming the output
*
* @author dstaines
*
*/
public class ProcessExec {
private static final String SHELL_FLAGS = "-c";
private static final String SHELL = "/bin/sh";
/**
* Time to wait in milliseconds for stream gobblers to finish
*/
private static final int TIMEOUT = 10000;
/**
* Execute the command, capturing the output and error streams to
* Appendables
*
* @param command
* @param out
* @param err
* @return exit code
* @throws IOException
*/
public static int exec(String command, Appendable out, Appendable err)
throws IOException {
return exec(command, out, err, false);
}
public static int exec(String command, Appendable out, Appendable err, String[] environment)
throws IOException {
return exec(command, out, err, false, environment);
}
/**
* Execute the command, capturing the output and error streams to
* Appendables
*
* @param commandarray
* @param out
* @param err
* @return exit code
* @throws IOException
*/
public static int exec(String[] commandarray, Appendable out, Appendable err)
throws IOException {
return exec(commandarray, out, err, false);
}
/**
* Execute the command, discarding output and error
*
* @param command
* @return exit code
* @throws IOException
*/
public static int exec(String command) throws IOException {
return exec(command, null, null, true);
}
/**
* Execute the command, discarding output and error
*
* @param commandarray
* @return exit code
* @throws IOException
*/
public static int exec(String[] commandarray) throws IOException {
return exec(commandarray, null, null, true);
}
/**
* Execute the command, capturing output/error into the supplied streams if
* required
*
* @param command
* @param out
* @param err
* @return
* @throws Exception
*/
private static int exec(String command, Appendable out, Appendable err,
boolean discard) throws IOException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(command);
return waitForProcess(out, err, discard, proc);
}
private static int exec(
String command,
Appendable out,
Appendable err,
boolean discard,
String[] environment
) throws IOException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(
command,
environment
);
return waitForProcess(out, err, discard, proc);
}
public static int exec(
String[] command,
Appendable out,
Appendable err,
boolean discard,
String[] environment
) throws IOException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(
command,
environment
);
return waitForProcess(out, err, discard, proc);
}
public static int exec(
String[] command,
Appendable out,
Appendable err,
boolean discard,
Map<String,String> environmentVars
) throws IOException {
return exec(
command,
out,
err,
discard,
environmentMapToString(environmentVars)
);
}
/**
*
* <p>
* Converts the map representing the environment to be set to the
* array of "key=value" representation that the Runtime.exec method
* wants.
* </p>
*
* @return k-v array
*/
protected static String[] environmentMapToString(Map<String,String> environmentVars) {
String[] environment = new String[environmentVars.keySet().size()];
int currentIndex=0;
for (String currentKey : environmentVars.keySet()) {
environment[currentIndex]=currentKey + "=" + environmentVars.get(currentKey);
currentIndex++;
}
return environment;
}
/**
* Execute the command, capturing output/error into the supplied streams if
* required
*
* @param commandarray
* @param out
* @param err
* @return
* @throws Exception
*/
private static int exec(String[] commandarray, Appendable out, Appendable err,
boolean discard) throws IOException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(commandarray);
return waitForProcess(out, err, discard, proc);
}
/**
* Execute the specified shell command, capturing output and error
*
* @param command
* @return exit code
* @throws IOException
*/
public static int execShell(
String command,
Appendable out,
Appendable err
) throws IOException {
return execShell(command, out, err, false);
}
/**
* Execute the specified shell command, discarding output and error
*
* @param command
* @return exit code
* @throws IOException
*/
public static int execShell(String command) throws IOException {
return execShell(command, (Appendable) null, (Appendable) null, true);
}
private static Process createShellProcessObject(String command) throws IOException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[] { SHELL, SHELL_FLAGS, command });
return proc;
}
private static int execShell(
String command,
Appendable out,
Appendable err,
boolean discard
) throws IOException {
return waitForProcess(out, err, discard, createShellProcessObject(command));
}
private static int execShell(
String command,
Thread outputGobbler,
Thread errorGobbler,
boolean discard
) throws IOException {
return waitForProcess(outputGobbler, errorGobbler, discard, createShellProcessObject(command));
}
private static int waitForProcess(
Thread outputGobbler,
Thread errorGobbler,
boolean discard,
Process proc
) {
// kick them off
errorGobbler.start();
outputGobbler.start();
// return exit status
try {
// get the exit status of the command once finished
//
int exit = proc.waitFor();
// now wait for the output and error to be read with a suitable
// timeout
//
outputGobbler.join(TIMEOUT);
errorGobbler.join(TIMEOUT);
return exit;
} catch (InterruptedException e) {
// While the Process is running, the Thread is in the state
// called "WAITING". If an interrupt has been requested and caught
// within the thread, reinterrupting is requested.
//
// See
// http://download.oracle.com/javase/1,5.0/docs/guide/misc/threadPrimitiveDeprecation.html
// chapter
// Section "How do I stop a thread that waits for long periods (e.g., for input)?"
// for more details on this.
//
Thread.currentThread().interrupt();
return -1;
} finally {
// to make sure the all streams are closed to avoid open file handles
//
IOUtils.closeQuietly(proc.getErrorStream());
IOUtils.closeQuietly(proc.getInputStream());
IOUtils.closeQuietly(proc.getOutputStream());
// Otherwise we will be waiting for the process to terminate on
// its own instead of interrupting as the user has requested.
//
proc.destroy();
}
}
private static int waitForProcess(
Appendable out,
Appendable err,
boolean discard,
Process proc
) {
// any error message?
StreamGobbler errorGobbler = new StreamGobbler(
proc.getErrorStream(),
err,
discard
);
// any output?
StreamGobbler outputGobbler = new StreamGobbler(
proc.getInputStream(),
out,
discard
);
return waitForProcess(
outputGobbler,
errorGobbler,
discard,
proc
);
}
/**
* Execute the command and discard the output and error. If the process is
* timeconsuming, please use
* {@link #exec(String, Appendable, Appendable)} to avoid hangups
*
* @param command
* @return exit code
* @throws IOException
*/
public static int execDirect(String command) throws IOException {
return execDirect(command, null, null, true);
}
/**
* Execute the command and capture the output and error. If the process is
* timeconsuming, please use
* {@link #exec(String, Appendable, Appendable)} to avoid hangups
*
* @param command
* @param out
* @param err
* @return exit code
* @throws IOException
*/
public static int execDirect(String command, Appendable out,
Appendable err) throws IOException {
return execDirect(command, out, err, true);
}
/**
* @param command
* @param out
* @param err
* @param discard
* @return
* @throws IOException
*/
private static int execDirect(String command, Appendable out,
Appendable err, boolean discard) throws IOException {
// return exit status
try {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(command);
// get the exit status of the command once finished
int exit = proc.waitFor();
if (!discard) {
StreamGobbler.streamToBuffer(proc.getInputStream(), out);
StreamGobbler.streamToBuffer(proc.getErrorStream(), out);
}
return exit;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return -1;
}
}
}