Command.java
package gov.usgs.earthquake.distribution;
import gov.usgs.util.StreamUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
public class Command {
private String[] commandArray = null;
private String[] envp = null;
private File dir = null;
private InputStream stdin = null;
private ByteArrayOutputStream stdout = new ByteArrayOutputStream();
private ByteArrayOutputStream stderr = new ByteArrayOutputStream();
private long timeout = 0;
private int exitCode = -1;
/** Empty command constructor */
public Command() {
}
/**
* @throws CommandTimeout CommandTimeout
* @throws IOException IOException
* @throws InterruptedException InterruptedException
*/
public void execute() throws CommandTimeout, IOException,
InterruptedException {
StreamTransferThread outputTransfer = null;
StreamTransferThread errorTransfer = null;
try {
final Process process = Runtime.getRuntime().exec(commandArray,
envp, dir);
final Timer timer;
if (timeout > 0) {
timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
process.destroy();
}
}, timeout);
} else {
timer = null;
}
try {
OutputStream processStdin = process.getOutputStream();
if (stdin != null) {
StreamUtils.transferStream(stdin, processStdin);
}
StreamUtils.closeStream(processStdin);
outputTransfer = new StreamTransferThread(process.getInputStream(),
stdout);
outputTransfer.start();
errorTransfer = new StreamTransferThread(process.getErrorStream(),
stderr);
errorTransfer.start();
// now wait for process to complete
exitCode = process.waitFor();
if (exitCode == 143) {
throw new CommandTimeout();
}
} finally {
// cancel destruction of process, if it hasn't already run
if (timer != null) {
timer.cancel();
}
}
} finally {
try {
outputTransfer.interrupt();
outputTransfer.join();
} catch (Exception e) {
}
try {
errorTransfer.interrupt();
errorTransfer.join();
} catch (Exception e) {
}
}
}
/** @return string[] */
public String[] getCommand() {
return commandArray;
}
/** @param command single command */
public void setCommand(final String command) {
setCommand(splitCommand(command));
}
/** @param commandArray string[] */
public void setCommand(final String[] commandArray) {
this.commandArray = commandArray;
}
/** @return envp */
public String[] getEnvp() {
return envp;
}
/** @param envp String[] */
public void setEnvp(final String[] envp) {
this.envp = envp;
}
/** @return dir */
public File getDir() {
return dir;
}
/** @param dir File */
public void setDir(final File dir) {
this.dir = dir;
}
/** @return timeout */
public long getTimeout() {
return timeout;
}
/** @param timeout long */
public void setTimeout(final long timeout) {
this.timeout = timeout;
}
/** @return exitCode */
public int getExitCode() {
return exitCode;
}
/** @param stdin InputStream */
public void setStdin(final InputStream stdin) {
this.stdin = stdin;
}
/** @return stdout byte[] */
public byte[] getStdout() {
return stdout.toByteArray();
}
/** @return stderr byte[] */
public byte[] getStderr() {
return stderr.toByteArray();
}
/**
* Split a command string into a command array.
*
* This version uses a StringTokenizer to split arguments. Quoted arguments
* are supported (single or double), with quotes removed before passing to
* runtime. Double quoting arguments will preserve quotes when passing to
* runtime.
*
* @param command
* command to run.
* @return Array of arguments suitable for passing to
* Runtime.exec(String[]).
*/
protected static String[] splitCommand(final String command) {
List<String> arguments = new LinkedList<String>();
String currentArgument = null;
// use a tokenizer because that's how Runtime.exec does it currently...
StringTokenizer tokens = new StringTokenizer(command);
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken();
if (currentArgument == null) {
currentArgument = token;
} else {
// continuing previous argument, that was split on whitespace
currentArgument = currentArgument + " " + token;
}
if (currentArgument.startsWith("\"")) {
// double quoted argument
if (currentArgument.endsWith("\"")) {
// that has balanced quotes
// remove quotes and add argument
currentArgument = currentArgument.substring(1,
currentArgument.length() - 1);
} else {
// unbalanced quotes, keep going
continue;
}
} else if (currentArgument.startsWith("'")) {
// single quoted argument
if (currentArgument.endsWith("'")) {
// that has balanced quotes
// remove quotes and add argument
currentArgument = currentArgument.substring(1,
currentArgument.length() - 1);
} else {
// unbalanced quotes, keep going
continue;
}
}
arguments.add(currentArgument);
currentArgument = null;
}
if (currentArgument != null) {
// weird, but add argument anyways
arguments.add(currentArgument);
}
return arguments.toArray(new String[] {});
}
public static class CommandTimeout extends Exception {
private static final long serialVersionUID = 1L;
}
private static class StreamTransferThread extends Thread {
private InputStream in;
private OutputStream out;
public StreamTransferThread(final InputStream in, final OutputStream out) {
this.in = in;
this.out = out;
}
@Override
public void run() {
try {
StreamUtils.transferStream(in, out);
} catch (Exception e) {
}
}
}
}