So, why is making shell calls from Java so difficult? All you have to do is call Runtime.getRuntime().exec() right? Well, sort of... Let me explain... round-aboutly. First off, our shell command execution needs to have some sort of timeout (to deal with those cases when the shell command hangs waiting for input). So, we need to execute the command in another thread and have a loop that waits until the process says it's done. Something that looks kind of like this perhaps (try/catch blocks and such elided for simplicity sake):
ExternalExecutionThread thread =
new ExternalExecutionThread();
thread.setCommand(command);
long start = System.currentTimeMillis();
thread.start();
while (!thread.isFinished()) {
if (System.currentTimeMillis() - start > timeout) {
thread.interrupt();
}
}
class ExternalExecutionThread extends Thread {
private boolean finished;
private int exitCode;
private ResultListener listener;
... getters and setters
public void run() {
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
exitCode = process.exitValue();
}
}
In addition to simply executing the command you specify, exec() provides StdErr and StdOut as streams for your monitoring convenience. A nice feature, especially when dealing with a legacy application as we are here. So, first problem. What if the command you're running doesn't output anything and then hangs? You're now stuck in a read loop, with nothing coming in. So, we need to spawn some more threads to gobble up the command output as it comes, so that our timeout will continue to work. Something more or less like this:
class StreamReadingThread extends Thread {
private OutputStream outputStream;
private InputStream inputStream;
private boolean finishedWriting;
... getters and setters
public void run() {
BufferedInputStream bis =
new BufferedInputStream(inputStream);
while (!finishedWriting) {
byte[] temp = new byte[bis.available()];
int bytesRead = bis.read(temp, 0, temp.length);
if (bytesRead == -1) {
finishedWriting = true;
} else {
outputStream.write(temp, 0, bytesRead);
}
}
}
}
Now, we modify our ExternalExecutionThread to create a couple of these StreamReadingThreads, feed them the InputStreams from the Process we started and send them on their merry way. When the process ends (or times out) we call finishWriting() on the threads and they exit gracefully. Now, I can hear the clamor, "That's a whole lot of trouble to go through to read the output! What if I don't even need the output?" Well, I can't argue with you, it is a royal pain. There's a catch here though: if you don't read the output of the command, the stream buffer will eventually fill up (assuming your command has a significant amount of output) and it will deadlock your whole thread. Gross.
At the end of the day, we've got a couple of custom classes and three new threads spawned every time we want to make a shell call. Is there a simpler way? I certainly hope so, but I've been unable to find it. I'd certainly love to be proven wrong...