Saturday, 14 June 2014

Executing External Programs From Java

 
   Using java, you can open external programs such as notepad, command prompt, etc. But executing external processes from java is inherently operating system dependent and requires the developer to know the specific behaviors pertaining to the platform.

       Here is the two ways to execute 
1. Using java.lang.Runtime
2. Using Apache commons exec library.

1. Using java.lang.Runtime :

                  Every Java application has a single instance of class Runtime that allows the application to interface with the environment in which the application is running. The current runtime can be obtained from the getRuntime method.

       Runtime rt = Runtime.getRuntime();

you can execute a program by supplying the command related to that program to the exec method of runtime object.

      rt.exec("notepad");
      rt.exec("cmd");

Reliably executing external processes can also require knowledge of the environment variables before or after the command is executed. 

2. Using Apache commons exec library :

      The equivalent code snippet for the above explanation follows below.

      1. Command to open notepad is "notepad"       
           String cmdStr = "notepad";
       
       2.parse the command if you want to pass options
        
         CommandLine cmdLine = CommandLine.parse(cmdStr); 
        
       3. gets the runtime object to execute command
        
         Executor exe = new DefaultExecutor();

        4. execute the command

         int exitValue = exe.execute(cmdLine);

Which one to choose ?

             you may think calling Runtime.exec() is easy and using commons exec api would waste your time with tons of code.But it gives a lot of flexibility while writing code, like setting exit value for an application (different applications will return different exit values in case of successful completions),  here are some more examples.

you would like to print some pdf documents from within your java application.  The command line under windows should look like "AcroRd32.exe /p /h file" assuming that the Acrobat Reader is found in the path.

code snippet 1:

String line = "AcroRd32.xt /p /h " + file.getAbsolutePath();
CommanLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
int exitValue = executor.execute(cmdLine);

you successfully printed your first PDF document but at the end an exception is thrown - what happend ? Oops, Acrobat Reader returned an exit value of '1' on success which is usually considered as an execution failure. So we have to tweak our code to fix this odd behavior, we define the exit value of "1" to be considered as successful execution.

Code snippet 2: 

executor.setExitValue(1);
int exitValue = executor.execute(cmdLine);

you happily printed for a while but now your application blocks for some obvious reason. Starting is easy but what to do with a run-away Acrobat Reader telling you that printing failed due to a lack of paper ? Luckily commons-exec provides a watchdog which does the work for you. Here is the improved code snippet which kills a run-away process after sixty seconds.

Code snippet 3: 

String line = "AcroRd32.xt /p /h " + file.getAbsolutePath();
CommanLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(1);
ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000);
executor.setWatchdog(watchdog);
int exitValue = executor.execute(cmdLine);


Well, the code worked for quite a while until you found no documents are printed because the following file "C:\Document And Settings\documents\print.pdf". Due to the spaces and without further quoting the command line fell literally into the following snippet 

> AcroRd32.ex /p /h C:\Document And Settings\
documents\print.pdf

As a quick fix we added double quotes which tells commons-exec to handle the file as a single command line argument instead of splitting it into parts.

Code snippet 4:

String line = "AcroRd32.xt /p /h \"" + file.getAbsolutePath() + "\""; 
CommanLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(1);
ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000);
executor.setWatchdog(watchdog);
int exitValue = executor.execute(cmdLine);

At the end of the day splitting single command line string into a string array by considering single and double quotes would be error prone. So its better to build the command line incrementally by adding arguments one by one. Below is the code snippet for this.

Code snippet 5: 

Map map = new HashMap();
map.put("file",  new File("invoice.pdf'));
CommandLine cmdLine = new CommandLine("AcroRd32.exe");
cmdLine.addArgument("/p");
cmdLine.addArgument("/h");
cmdLine.addArgument("${file}");
cmdLine.setSubstitutionMap(map);
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(1);
ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000);
executor.setWatchdog(watchdog);
int exitValue = executor.execute(cmdLine);

Up to new we have a working example but it would not be good enough for production - because it is blocking. 

Your worker thread will block until the print process has finished or was killed by the watchdog. Therefor executing the print job asynchronously will do the trick. 

To execute the process asynchronously, we need to create an instance of "ExecuteResultHandler" and pass it to the "Executor" instance. The ExecuteResultHandler instance can pick up any offending exception or the process exit code. 

Code snippet 6: 

Map map = new HashMap();
map.put("file",  new File("invoice.pdf'));
CommandLine cmdLine = new CommandLine("AcroRd32.exe");
cmdLine.addArgument("/p");
cmdLine.addArgument("/h");
cmdLine.addArgument("${file}");
cmdLine.setSubstitutionMap(map);

DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(1);
ExecuteWatchdog watchdog = new ExecuteWatchdog(60*100);
executor.setWatchdog(watchdog);
executor.execute(cmdLine,resultHandler);
int exitValue = resultHandler.watiFor();

A tutorial is nice but executing the tutorial code is even nicer. So go ahead and execute.

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete

Note: only a member of this blog may post a comment.