Site icon Experiences Unlimited

Execute external process from within JVM using Apache Commons Exec library

Executing external command from within JVM often causes problems- be it in terms of the code to write and manage or in the ease of implementation. I had similar requirement in my Major project for my Under Graduate Degree, where in I had to launch a C program from the Java code. I ran into different issues like- the Main thread getting blocked, the GUI freezing, or reading the output streams and so on. Finally I had to give up the idea and stick with launching the external command externally 😛 Had I found the Exec library from Apache Commons then, my work would have been lot easier. Anyways better late then never. I will quickly go through how one can use  Exec library to launch external programs from JVM- Its a wrapper over Java’s ProcessBuilder, Runtime.getRuntime().exe(). The Javadoc shows lots of classes- but important one among them- which the end user would mostly be using are:

There are other CommandLauncher classes which are internally called by when one uses DefaultExecutor, though the methods of these classes are available for one to use them directly.

Using Exec helps us to:

Lets have a look at the implementation of the library.

Building the Command-Uses CommandLine class.

//Just the Executable, No arguments
CommandLine command = new CommandLine("ls");
command.addArgument(String) //Adding a String argument
command.addArgument("${arg1}"); //Expands the arg1 value from the Map

//map used to expand the arguments like above
command.setSubstitutionMap(Map);

command.addArguments(String[]) //Adding all the arguments at one go

Now we need to set the Stream handler- PumpStreamHandler


//Takes System.out for dumping the output and System.err for Error
PumpStreamHandler streamHandler = new PumpStreamHander();

//Create a new FileOutputStream instance
//Sends the output and error to the file
streamHandler = new PumpStreamHandler(FileOutputStream);

In this way PumpStreamHandler provides overloaded Constructors. Also note that internally it creates different Threads for each Stream viz Output, Input, Error.

Now we should be executing the command- DefaultExecutor– There are lot of variations in this. Lets start with the Simple-

DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(PumpStreamHandler); //Sets the stream handler
executor.execute(CommandLine);//Executes the command

Lets apply the above to create a simple “ls” on Linux systems.


CommandLine command = new CommandLine("ls");
PumpStreamHandler streamHandler = new PumpStreamHandler();
DefaultExecutor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
executor.execute(command);

Now lets execute the same Asynchronously- uses DefaultExeuteResultHandler class. I would be using the sleep command as that will clearly show the difference


CommandLine command = new CommandLine("sleep");
command.addArgument("10");//Number of seconds to sleep
System.out.println("Before Sleep");
DefaultExecuteResultHandler resultHandler
         = new DefaultExecuteResultHandler();
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler();
executor.setStreamHandler(streamHandler);
executor.execute(command,resultHandler);
System.out.println("After Sleep");
//Use of resultHandler makes this a Asynch process

You can see that there’s now time interval between the two print statements. If we add this line after Line 09-

//Wait for the command to finish execution.
resultHandler.waitFor();
//Obtain the exit value of the command.
int exitValue = resultHandler.getExitValue();

Then there’s a delay- which means the main thread waits for the sub process to complete before proceeding- waitFor() internally uses Thread.sleep(time). Also the exitValue is captured which can be used for:


//testing if the exitValue indicates failure
if(executor.isFailure(exitValue){

System.out.println("Command execution failed");
}else{

System.out.println("Command execution Successful");

}

ifFailure() checks if the exitValue obtained after executing the command denotes a Success (exitValue=0) or a Failure (any other exitValue). One can also set exitValue for the executor using setExitValue(int) or setExitValues(int[]) in that case the exitValue for success will not be “0” but the value specified. One can use waitFor() if the command has to be executed before the program continues execution. Otherwise in cases where the user wants to execute the command and store the result in a File waitFor() will not be required.

Sometimes the command might continue executing for a long time and we need to keep a check to see that it doesnt exceed the time. In those cases ExecuteWatchdog class can be used. Whenever the process terminates due to the timeout specified in WatchDog, the exit value returned will be for that of a failure.


ExecuteWatchdog watchDog = new ExecuteWatchdog(timeout);

exeuctor.setWatchDog(watchDog);//setting the watchdog for the executor

//The time out can also be ExecuteWatchdog.INFINITE_TIMEOUT

Summarizing:

Note: Most of the classes are default implementations of their interfaces

Let me give a sample code which uses all of the above:


//Command to be executed
 CommandLine command = new CommandLine("ls");

 //Adding its arguments
 command.addArguments(new String[]{"-l","-t","-R"});

 //Infinite timeout
 ExecuteWatchdog watchDog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT);

 //Result Handler for executing the process in a Asynch way
 DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();

 //Using Std out for the output/error stream
 PumpStreamHandler streamHandler = new PumpStreamHandler();

 //This is used to end the process when the JVM exits
 ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();

 //Our main command executor
 DefaultExecutor executor = new DefaultExecutor();

 //Setting the properties
 executor.setStreamHandler(streamHandler);
 executor.setWatchdog(watchDog);
 //Setting the working directory
 //Use of recursion along with the ls makes this a long running process
 executor.setWorkingDirectory(new File("/home"));
 executor.setProcessDestroyer(processDestroyer);

 //Executing the command
 executor.execute(command,resultHandler);

 //The below section depends on your need
 //Anything after this will be executed only when the command completes the execution
 resultHandler.waitFor();
 int exitValue = resultHandler.getExitValue();
 System.out.println(exitValue);
 if(executor.isFailure(exitValue)){
    System.out.println("Execution failed");
 }else{
    System.out.println("Execution Successful");
 }

Note: The Exec library has to be in your classpath- Either add it in CLASSPATH env variable or use java -cp

Please drop in any corrections, additional information as comments. I will update the post accordingly

Exit mobile version