Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apache Commons exec PumpStreamHandler continuous input

I am trying to solve interaction with command line process using Apache Commons exec. I'm stuck with following code:

ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream ins = new ByteArrayOutputStream();
OutputStreamWriter ow = new OutputStreamWriter(ins);
BufferedWriter writer = new BufferedWriter(ow);
ByteArrayInputStream in = new ByteArrayInputStream(ins.toByteArray());
PumpStreamHandler psh = new PumpStreamHandler(out, null, in);
CommandLine cl = CommandLine.parse(initProcess);
DefaultExecutor exec = new DefaultExecutor();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
exec.setStreamHandler(psh);
try {
    exec.execute(cl, resultHandler);
    int i = 0;
    while (true) {
        String o = out.toString();
        if (!o.trim().isEmpty()) {
            System.out.println(o);
            out.reset();
        }
        // --- PROBLEM start ---
        if (i == 3) {
            writer.write(internalProcessCommand); 
            // string with or without trailing \n, both tested
            writer.flush();
            writer.close();
            // tested even ins.write(internalProcessCommand.getBytes())
        }
        // --- PROBLEM end ---
        Thread.sleep(3000);
        i++;
    }
} catch (ExecuteException e) {
    System.err.println(e.getMessage());
}

I hope my code is clear. I continuously read out and print it after 3 seconds while clearing stream. Problem is input to in passed to PumpStreamHandler. I need to pass process commands from code itself, continuously and dynamically as if I was interacting with process through CLI. When I simply use System.in as PumpStreamHandler argument, I can write process commands from console fine. How can I manage to have same result passing strings from code?

Edit: I also tried to connect PipedInputStream receiving data from PipedOutputStream, but it seems that data can be read only after closing PipedOutputStream which makes it un-reusable thus I can't achieve interactivity.

Edit 2: Solved myself. Solution in answer below. Howgh. :-)

like image 331
MichalVales Avatar asked Feb 21 '26 02:02

MichalVales


1 Answers

Based on the answer given by @champagniac, I have created a simple fix which introduces additional flushing by only replacing the PumpStreamHandler:

public class PumpStreamHandlerFixed extends PumpStreamHandler
{
  public PumpStreamHandlerFixed()
  {
    super();
  }

  public PumpStreamHandlerFixed(OutputStream out, OutputStream err, InputStream input)
  {
    super(out, err, input);
  }

  public PumpStreamHandlerFixed(OutputStream out, OutputStream err)
  {
    super(out, err);
  }

  public PumpStreamHandlerFixed(OutputStream outAndErr)
  {
    super(outAndErr);
  }

  @Override
  protected Thread createPump(InputStream is, OutputStream os, boolean closeWhenExhausted)
  {
    os = new AutoFlushingOutputStream(os);

    final Thread result = new Thread(new StreamPumper(is, os, closeWhenExhausted), "Exec Stream Pumper");
    result.setDaemon(true);
    return result;
  }
}

class AutoFlushingOutputStream extends OutputStream
{
  private final OutputStream decorated;

  public AutoFlushingOutputStream(OutputStream decorated)
  {
    this.decorated = decorated;
  }

  @Override
  public void write(byte[] b, int off, int len) throws IOException
  {
    this.decorated.write(b, off, len);
    this.decorated.flush();
  }

  @Override
  public void write(int b) throws IOException
  {
    this.decorated.write(b);
    this.decorated.flush();
  }

  @Override
  public void close() throws IOException
  {
    this.decorated.close();
  }

  @Override
  public void flush() throws IOException
  {
    this.decorated.flush();
  }
}
like image 155
Matthias Avatar answered Feb 22 '26 16:02

Matthias