Thursday, 8 April 2010

Bullet-proof Java process runner

Share
Last year I wrote a post about Validating WS-BPEL programmatically. I used external Apache ODE bpelc tool, but I invoked it using Java's ProcessBuilder and Process classes.

A friend of mine, Tomek, warned me about problem I might come across by not reading error and input streams.

He was right, some time ago while working on Nuntius 0.5 I wrote an integration test whose goal was to detect WS-BPEL errors. The error output generated by bpelc was so large that my process blocked itself and waited for the error stream to be read.

Today I present a bullet-proof version of my process runner. You can easily refactor this class to run any command line tools from Java.

ProcessBuilder

No comments, just the source code. Note the private InputStreamReaderThread class which I use for reading both error and input streams:

public class BPELCRunner {
 private static final Log log = LogFactory.getLog(BPELCRunner.class);
 private ProcessBuilder pb;
 private String message;
 public BPELCRunner(String command, String bpel) {
  pb = new ProcessBuilder(command, bpel);
 }
 public boolean compile() {
  Process p = null;
  try {
   p = pb.start();
   InputStreamReaderThread errorReader = new InputStreamReaderThread(p.getErrorStream());
   errorReader.start();
   InputStreamReaderThread inputReader = new InputStreamReaderThread(p.getInputStream());
   inputReader.start();
   p.waitFor();
   if (errorReader.getInput() != null) {
    message = errorReader.getInput();
    return false;
   }
  } catch (Throwable t) {
   log.error("Could not validate WS-BPEL process", t);
   message = "Error occurred while validating WS-BPEL process";
   return false;
  }
  return true;
 }
 public String getMessage() {
  return message;
 }
 public void setMessage(String message) {
  this.message = message;
 }
 private class InputStreamReaderThread extends Thread {
  private InputStream is;
  private String input;
  public ThreadInputStreamReader(InputStream is) {
   this.is = is;
  }
  @Override
  public void run() {
   try {
    byte[] output = IOUtils.toByteArray(is);
    input = new String(output);
   } catch (Exception e) {
    throw new RuntimeException(e);
   }
  }  
  public String getInput() {
   return input;
  }
 } 
}

I hope this simple class saves you a lot of time :)

Cheers,
Łukasz

6 comments:

Tomek Nurkiewicz said...

Have you seen http://commons.apache.org/exec library? It seems to serve the same purpose, although it's still in Commons Sandbox.

Łukasz said...

Hi Tomek,

I haven't seen that library before.
For now, I'm happy with my lightweight approach. In my case I just want to run bpelc command line tool, and detect whether it succeeded or not.

But I will keep it in my mind, maybe one day I will need more advanced/complex solution.

thanks,
Łukasz

jb said...

A w ThreadInputStreamReader nie lepiej zrobić zamiast (!(is.available() > 0)) { Thread.sleep(100); }
nie lepiej po prostu zacząć z is czytać? Wątek i tak się przecież zatrzyma, jak nie będzie inputu.

Zresztą i tak nadpisujesz w każdej iteracji input to to jest średnio przydatne. Jak chcesz po prostu opróżniać bufor to:

public void run() {
is.read(); //No i try catch
}

powinno dać radę.

Czy może ominąłem coś oczywistego?

Łukasz said...

@jb

Masz rację, kod zaktualizowałem.

dzięki,
Łukasz

Ari said...

Along those same lines, I've created and published something I call JavaInvoke that makes it easy to spawn off additional VMs from a Java process.

Take a look at: http://blog.palantirtech.com/2009/07/28/javainvoke/

Anonymous said...

Typo on line 37:

ThreadInputStreamReader should be InputStreamReaderThread

Also Thread should be a static class