Java exec - execute system processes with Java ProcessBuilder and Process (part 3)

<< Back to "Java exec with ProcessBuilder and Process, part 2"

Our ThreadedStreamHandler class

Based on an old-but-good article at JavaWorld, I'm developing the following Java ThreadedStreamHandler class. (It's still a work-in-progress, but as mentioned, commands that don't have to be run using sudo seem to work just fine right now.)

As you have seen from the way this code is used, this class is used to read the system command's standard error (stderr) and standard output (stdout) streams in standard Java Thread objects. Let me share the code here, and then I'll discuss how it works.

package com.devdaily.system;

import java.io.*;

/**
 * ThreadedStreamHandler.java
 * @version 0.1
 *
 * This class is intended to be used with the SystemCommandExecutor
 * class to let users execute system commands from Java applications.
 * 
 * This class is based on work that was shared in a JavaWorld article
 * named "When System.exec() won't". That article is available at this
 * url:
 * 
 * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
 * 
 * Copyright 2010 alvin j. alexander, devdaily.com.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.

 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Please ee the following page for the LGPL license:
 * http://www.gnu.org/licenses/lgpl.txt
 * 
 */
class ThreadedStreamHandler extends Thread
{
  InputStream inputStream;
  String adminPassword;
  OutputStream outputStream;
  PrintWriter printWriter;
  StringBuilder outputBuffer = new StringBuilder();
  private boolean sudoIsRequested = false;
  
  /**
   * A simple constructor for when the sudo command is not necessary.
   * This constructor will just run the command you provide, without
   * running sudo before the command, and without expecting a password.
   * 
   * @param inputStream
   * @param streamType
   */
  ThreadedStreamHandler(InputStream inputStream)
  {
    this.inputStream = inputStream;
  }

  /**
   * Use this constructor when you want to invoke the 'sudo' command.
   * The outputStream must not be null. If it is, you'll regret it. :)
   * 
   * TODO this currently hangs if the admin password given for the sudo command is wrong.
   * 
   * @param inputStream
   * @param streamType
   * @param outputStream
   * @param adminPassword
   */
  ThreadedStreamHandler(InputStream inputStream, OutputStream outputStream, String adminPassword)
  {
    this.inputStream = inputStream;
    this.outputStream = outputStream;
    this.printWriter = new PrintWriter(outputStream);
    this.adminPassword = adminPassword;
    this.sudoIsRequested = true;
  }
  
  public void run()
  {
    // on mac os x 10.5.x, when i run a 'sudo' command, i need to write
    // the admin password out immediately; that's why this code is
    // here.
    if (sudoIsRequested)
    {
      //doSleep(500);
      printWriter.println(adminPassword);
      printWriter.flush();
    }

    BufferedReader bufferedReader = null;
    try
    {
      bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
      String line = null;
      while ((line = bufferedReader.readLine()) != null)
      {
        outputBuffer.append(line + "\n");
      }
    }
    catch (IOException ioe)
    {
      // TODO handle this better; users won't want the code doing this
      ioe.printStackTrace();
    }
    catch (Throwable t)
    {
      // TODO handle this better; users won't want the code doing this
      t.printStackTrace();
    }
    finally
    {
      try
      {
        bufferedReader.close();
      }
      catch (IOException e)
      {
        // ignore this one
      }
    }
  }
  
  private void doSleep(long millis)
  {
    try
    {
      Thread.sleep(millis);
    }
    catch (InterruptedException e)
    {
      // ignore
    }
  }
  
  public StringBuilder getOutputBuffer()
  {
    return outputBuffer;
  }

}

As you can see, that's a lot of wrapper and error-handling code wrapped around some more basic code that looks like this:

BufferedReader bufferedReader = null;
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
while ((line = bufferedReader.readLine()) != null)
{
  outputBuffer.append(line + "\n");
}

Putting everything together, this code, running in a Java thread, lets your program read the stdout and stderr streams from the command you're running. As mentioned in that JavaWorld program, this is very important, because if you don't read these streams in Java Threads, your system call may block. This is eloquently stated in the Process class Javadoc this way:

Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

I've found this to be especially true when running a sudo command with this code on Mac OS X; if I don't immediately write my admin password to the process, the code will hang.

This "java exec" article and class updates

As I mentioned above, this code is a work in progress, and the API will continue to change for the foreseeable future. If you just want to run a simple Unix ls or ps command, or even a command pipeline, this code seems to work fine on my Mac OS X 10.5 systems.

But if you want to run something like a sudo command, I still need to fix a few problems, and to that end, I've even removed a special constructor from the SystemCommandExecutor class that should be used when you want to run a sudo command.

I hope to resolve the sudo "wrong password" problem shortly, but other than that, I'm not aware of any other bugs (just the TODO items shown).

If you have any comments, opinions, or suggestions on how this API should evolve, feel free to make a comment below, or send me an email using our contact form.

Download our Java exec classes

You are welcome to download these classes and use them in your own programs. Here are direct links to the three classes discussed above:

I hope this article and source code help you understand the process of executing system commands from a Java application.

 

<< Back to "Java exec with ProcessBuilder and Process, part 2"