The requirements from such a library are:
- Execute the process asynchronously.
- Ability to abort the process execution.
- Ability to wait for process completion.
- On process output notifications.
- Ability to kill the process in case it hung.
- Get the process exit code.
Here is the method signature we expose:
public static Future<Long> runProcess(final CommandLine commandline, final ProcessExecutorHandler handler, final long watchdogTimeout) throws IOException;
- It returns a Future<Long>. This covers section 1,2,3,6.
- Instance of ProcessExecutorHandler is passed to the function. This instance is actually a listener for any process output. This covers section 4 in our requirement.
- Last but not least you supply a timeout. If the process execution takes more than said timeout you assume the process hung and you will end it. In that case the error code returned by the process will be -999.
import org.apache.commons.exec.*;
import org.apache.commons.exec.Executor;
import java.io.IOException;
import java.util.concurrent.*; public class ProcessExecutor { public static final Long WATCHDOG_EXIST_VALUE = -999L; public static Future<Long> runProcess(final CommandLine commandline, final ProcessExecutorHandler handler, final long watchdogTimeout) throws IOException{ ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Long> result = executor.submit(new ProcessCallable(watchdogTimeout, handler, commandline));
executor.shutdown();
return result; } private static class ProcessCallable implements Callable<Long>{ private long watchdogTimeout; private ProcessExecutorHandler handler; private CommandLine commandline; private ProcessCallable(long watchdogTimeout, ProcessExecutorHandler handler, CommandLine commandline) { this.watchdogTimeout = watchdogTimeout; this.handler = handler; this.commandline = commandline; } @Override public Long call() throws Exception { Executor executor = new DefaultExecutor(); executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); ExecuteWatchdog watchDog = new ExecuteWatchdog(watchdogTimeout); executor.setWatchdog(watchDog); executor.setStreamHandler(new PumpStreamHandler(new MyLogOutputStream(handler, true),new MyLogOutputStream(handler, false))); Long exitValue; try { exitValue = new Long(executor.execute(commandline)); } catch (ExecuteException e) { exitValue = new Long(e.getExitValue()); } if(watchDog.killedProcess()){ exitValue =WATCHDOG_EXIST_VALUE; } return exitValue; } } private static class MyLogOutputStream extends LogOutputStream{ private ProcessExecutorHandler handler; private boolean forewordToStandardOutput; private MyLogOutputStream(ProcessExecutorHandler handler, boolean forewordToStandardOutput) { this.handler = handler; this.forewordToStandardOutput = forewordToStandardOutput; } @Override protected void processLine(String line, int level) { if (forewordToStandardOutput){ handler.onStandardOutput(line); } else{ handler.onStandardError(line); } } } } // interface. public interface ProcessExecutorHandler { public void onStandardOutput(String msg); public void onStandardError(String msg); }
Hi Nadav,
ReplyDeleteWould this method allow me to run the following command line from a web browser via JavaScript. eg using the DeployJava.js etc?
"D:\Eclipse\eclipse.exe --launcher.openFile C:\mytest.js"
I am trying to develop a way for my JS (ExtJS) code to send the commandline to Java for execution.
I can get the JS to Java communication happening but seem to be hitting security issues when I try to use Runtime.getRuntime().exec(cmd);
I was wondering whether your approach might work better!?
Thanks,
Murray
Thanks for sharing it. This will be helpful in understanding the command line better.
ReplyDelete