DevOps Zone is brought to you in partnership with:

Nadav is a software development group leader. He gained experience in all aspects of software development including requirement analysis, software design and implementation. He has 3 kids and a fish. Nadav is a DZone MVB and is not an employee of DZone and has posted 8 posts at DZone. You can read more from them at their website. View Full User Profile

Executing a Command Line Executable From Java

02.26.2013
| 14104 views |
  • submit to reddit

In this post we'll deal with a common need for Java developers. Execute and manage an external process from within Java. Since this task is quite common we set out to find a Java library to help us accomplish it. 

The requirements from such a library are: 

  1. Execute the process asynchronously. 
  2. Ability to abort the process execution.
  3. Ability to wait for process completion.
  4. On process output notifications.
  5. Ability to kill the process in case it hung.
  6. Get the process exit code.
The native JDK does not help much. Fortunately, we have Apache Commons Exe. Indeed it is much easier but still not as straightforward as we hoped. We wrote a small wrapper on top of it. 
Here is the method signature we expose:
public static Future<Long> runProcess(final CommandLine commandline, final ProcessExecutorHandler handler, final long watchdogTimeout) throws IOException;

  1. It returns a Future<Long>. This covers section 1,2,3,6. 
  2. 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.
  3. 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. 
That's it! The implementation can be found in the code snippet below. Enjoy.
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);

}

Published at DZone with permission of Nadav Azaria, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Adrien Ferré replied on Tue, 2013/02/26 - 4:22am

Maarten Verbeek replied on Thu, 2013/03/07 - 7:53am

Nice, but in the runProcess method the shutdown method of the executor should be called before returning the future.

Adrien Ferré replied on Thu, 2013/03/07 - 8:03am in response to: Maarten Verbeek

You should post that on the page. 

I also know that when using this method it will try to allocate the amount of memory allocated to the jvm and it may cause errors like outofmemoryerror. Maybe the lib could be improved by adding a jni layer allowing to fork the process.

Nadav Azaria replied on Sat, 2013/05/25 - 9:11am in response to: Maarten Verbeek

Thanks. I updated the code accordingly. Passing an Executor as an argument might also be a good idea here..

Emanuele Casadio replied on Sat, 2014/04/12 - 2:40am

 I wonder if the code contained inside the snippet is Open Source, and if so, what is the license.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.