Author: sgoeschl Date: Thu Aug 12 16:18:02 2010 New Revision: 984849 URL: http://svn.apache.org/viewvc?rev=984849&view=rev Log: Cleaning up the code, updating javadocs, updating the tutorial and improved DefaultExecuteResultHandler (based on [EXEC-42]).
Added: commons/proper/exec/trunk/src/test/scripts/stdin.sh (with props) Modified: commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecutor.java commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/Executor.java commons/proper/exec/trunk/src/site/apt/tutorial.apt commons/proper/exec/trunk/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java Modified: commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java (original) +++ commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java Thu Aug 12 16:18:02 2010 @@ -24,6 +24,8 @@ package org.apache.commons.exec; */ public class DefaultExecuteResultHandler implements ExecuteResultHandler { + private static final int SLEEP_TIME_MS = 100; + /** Keep track if the process is still running */ private boolean hasResult; @@ -51,7 +53,10 @@ public class DefaultExecuteResultHandler } /** + * Get the <code>exception<code> causing the process execution to fail. + * * @return Returns the exception. + * @throws IllegalStateException if the process has not exited yet */ synchronized public ExecuteException getException() { if(!hasResult) throw new IllegalStateException("The process has not exited yet therefore no result is available ..."); @@ -59,7 +64,10 @@ public class DefaultExecuteResultHandler } /** + * Get the <code>exitValue<code> of the process. + * * @return Returns the exitValue. + * @throws IllegalStateException if the process has not exited yet */ synchronized public int getExitValue() { if(!hasResult) throw new IllegalStateException("The process has not exited yet therefore no result is available ..."); @@ -74,4 +82,32 @@ public class DefaultExecuteResultHandler synchronized public boolean hasResult() { return hasResult; } + + /** + * Causes the current thread to wait, if necessary, until the + * process has terminated. This method returns immediately if + * the process has already terminated. If the process has + * not yet terminated, the calling thread will be blocked until the + * process exits. + * + * @return the exit value of the process. + * @exception InterruptedException if the current thread is + * {...@linkplain Thread#interrupt() interrupted} by another + * thread while it is waiting, then the wait is ended and + * an {...@link InterruptedException} is thrown. + * @exception ExecuteException re-thrown exception if the process + * execution has failed due to ExecuteException + */ + public int waitFor() throws InterruptedException, ExecuteException { + while (!this.hasResult()) { + Thread.sleep(SLEEP_TIME_MS); + } + + if(getException() == null) { + return getExitValue(); + } + else { + throw getException(); + } + } } \ No newline at end of file Modified: commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecutor.java URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecutor.java?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecutor.java (original) +++ commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/DefaultExecutor.java Thu Aug 12 16:18:02 2010 @@ -54,7 +54,7 @@ public class DefaultExecutor implements /** monitoring of long running processes */ private ExecuteWatchdog watchdog; - /** the exit values considerd to be successful */ + /** the exit values considered to be successful */ private int[] exitValues; /** launches the command in a new process */ @@ -250,7 +250,7 @@ public class DefaultExecutor implements * Close the streams belonging to the given Process. In the * original implementation all exceptions were dropped which * is probably not a good thing. On the other hand the signature - * allows throwing an IOException so the curent implementation + * allows throwing an IOException so the current implementation * might be quite okay. * * @param process the <CODE>Process</CODE>. @@ -321,6 +321,7 @@ public class DefaultExecutor implements if (watchdog != null) { watchdog.start(process); } + int exitValue = Executor.INVALID_EXITVALUE; try { exitValue = process.waitFor(); @@ -337,8 +338,10 @@ public class DefaultExecutor implements if (watchdog != null) { try { watchdog.checkException(); + } catch (IOException e) { + throw e; } catch (Exception e) { - throw new IOException(e.getMessage()); + throw new IOException(e.getMessage(), e); } } Modified: commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java (original) +++ commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java Thu Aug 12 16:18:02 2010 @@ -19,7 +19,9 @@ package org.apache.commons.exec; /** - * The callback handlers for asynchronous execution. + * The callback handlers for the result of asynchronous process execution. When a + * process is started asynchronously the callback provides you with the result of + * the executed process, i.e. the exit value or an exception. * * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map, ExecuteResultHandler) */ Modified: commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java (original) +++ commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java Thu Aug 12 16:18:02 2010 @@ -33,7 +33,7 @@ package org.apache.commons.exec; * * When starting an asynchronous process than 'ExecuteWatchdog' is the * keeper of the process handle. In some cases it is useful not to define - * a timeout (and pass 'INFINITE_TIMEOUT') and to kill the process explicitely + * a timeout (and pass 'INFINITE_TIMEOUT') and to kill the process explicitly * using 'destroyProcess()'. * * @see org.apache.commons.exec.Executor Modified: commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/Executor.java URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/Executor.java?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/Executor.java (original) +++ commons/proper/exec/trunk/src/main/java/org/apache/commons/exec/Executor.java Thu Aug 12 16:18:02 2010 @@ -50,22 +50,28 @@ public interface Executor { int INVALID_EXITVALUE = 0xdeadbeef; /** - * Define the exit code of the process to be considered - * successful. + * Define the <code>exitValue</code> of the process to be considered + * successful. If a different exit value is returned by + * the process then {...@link org.apache.commons.exec.Executor#execute(CommandLine)} + * will throw an {...@link org.apache.commons.exec.ExecuteException} * * @param value the exit code representing successful execution */ void setExitValue(final int value); /** - * Define the exit code of the process to be considered + * Define a list of <code>exitValue</code> of the process to be considered * successful. The caller can pass one of the following values * <ul> * <li>an array of exit values to be considered successful</li> - * <li>an empty array for auto-detect of successful exit codes</li> + * <li>an empty array for auto-detect of successful exit codes relying on {...@link org.apache.commons.exec.Executor#isFailure(int)}</li> * <li>null to indicate to skip checking of exit codes</li> * </ul> * + * If a different exit value is returned by the process then + * {...@link org.apache.commons.exec.Executor#execute(CommandLine)} will + * throw an {...@link org.apache.commons.exec.ExecuteException}. + * * @param values a list of the exit codes */ void setExitValues(final int[] values); Modified: commons/proper/exec/trunk/src/site/apt/tutorial.apt URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/site/apt/tutorial.apt?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/site/apt/tutorial.apt (original) +++ commons/proper/exec/trunk/src/site/apt/tutorial.apt Thu Aug 12 16:18:02 2010 @@ -53,8 +53,9 @@ int exitValue = executor.execute(command +---------------------------------------------------------------------------- You successfuly printed your first PDF document but at the end an exception is thrown - what - happpend? Mhhmm, 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 behaviour + happpend? 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 behaviour - + we define the exit value of "1" to be considered as successful execution. +---------------------------------------------------------------------------- String line = "AcroRd32.exe /p /h " + file.getAbsolutePath(); @@ -68,8 +69,9 @@ int exitValue = executor.execute(command You happily printed for a while but now your application blocks - your printing subprocess hangs for some obvious or not so obvious reason. Starting is easy but what to do with a run-away - Acrobat Reader?! Luckily commons-exec provides a watchdog which does the work for you. - Here is the improved code which kills a run-away process after sixty seconds. + 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 which kills a + run-away process after sixty seconds. +---------------------------------------------------------------------------- String line = "AcroRd32.exe /p /h " + file.getAbsolutePath(); @@ -134,24 +136,23 @@ int exitValue = executor.execute(command In the previous example we basically hardcoded the command line to be executed. Using a different application to print PDF documents or just tinkering with command line options would require a new release - uncool. - To write better code you can look at the following code snippet (and - don't forget that using a command line string is dangerous) + To write better code you can look at the following code snippet. +---------------------------------------------------------------------------- -CommandLine commandLine; -HashMap map = new HashMap(); +Map map = new HashMap(); map.put("file", "C:\Document And Settings\documents\432432.pdf"); -commandLine = CommandLine.parse("AcroRd32.exe /p /h \"${file}\"", map); - -// Or you can use expansion with individual parameters: CommandLine commandLine = CommandLine.parse("AcroRd32.exe"); commandLine.addArgument("/p"); commandLine.addArgument("/h"); commandLine.addArgument("${file}"); -HashMap map = new HashMap(); -map.put("file", "C:\Document And Settings\documents\432432.pdf"); commandLine.setSubstitutionMap(map); + +DefaultExecutor executor = new DefaultExecutor(); +executor.setExitValue(1); +ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); +executor.setWatchdog(watchdog); +int exitValue = executor.execute(commandLine); +---------------------------------------------------------------------------- * Unblock Your Execution @@ -161,7 +162,9 @@ commandLine.setSubstitutionMap(map); At the one hand your print process might block when it tries to write to 'stdout' or 'stderr'. Avoiding this problem is done by setting a pump stream - handler which pumps the output from the print process to your own process. + handler which pumps the output from the print process to your own process. A + more detailed discussion can be found at + {{{http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Process.html}java.lang.Process}} On the other hand your own worker thread will block until the print process has exited. Therefore executing the print job asynchronously will do the trick. @@ -181,12 +184,16 @@ commandLine.setSubstitutionMap(map); DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); -ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout1); - +ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000); Executor executor = new DefaultExecutor(); executor.setExitValue(1); executor.setStreamHandler(new PumpStreamHandler()); executor.setWatchdog(watchdog); executor.execute(commandLine, resultHandler); + +// some time later the result handler callback was invoked so we +// can safely request the exit value +int exitValue = resultHandler.waitFor(); + +---------------------------------------------------------------------------- Modified: commons/proper/exec/trunk/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java?rev=984849&r1=984848&r2=984849&view=diff ============================================================================== --- commons/proper/exec/trunk/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java (original) +++ commons/proper/exec/trunk/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java Thu Aug 12 16:18:02 2010 @@ -18,6 +18,7 @@ package org.apache.commons.exec; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -41,6 +42,7 @@ public class DefaultExecutorTest extends private File exec41Script = TestUtil.resolveScriptForOS(testDir + "/exec41"); private File printArgsScript = TestUtil.resolveScriptForOS(testDir + "/printargs"); private File acroRd32Script = TestUtil.resolveScriptForOS(testDir + "/acrord32"); + private File stdinSript = TestUtil.resolveScriptForOS(testDir + "/stdin"); // Get suitable exit codes for the OS @@ -53,6 +55,7 @@ public class DefaultExecutorTest extends } protected void setUp() throws Exception { + System.out.println(">>> Executing " + getName() + " ..."); baos = new ByteArrayOutputStream(); exec.setStreamHandler(new PumpStreamHandler(baos, baos)); } @@ -126,31 +129,27 @@ public class DefaultExecutorTest extends public void testExecuteAsync() throws Exception { CommandLine cl = new CommandLine(testScript); - - DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler(); - - exec.execute(cl, handler); - - // wait for script to run - Thread.sleep(2000); - - assertFalse(exec.isFailure(handler.getExitValue())); + DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); + exec.execute(cl, resultHandler); + resultHandler.waitFor(); + assertFalse(exec.isFailure(resultHandler.getExitValue())); assertEquals("FOO..", baos.toString().trim()); } public void testExecuteAsyncWithError() throws Exception { CommandLine cl = new CommandLine(errorTestScript); - - DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler(); - - exec.execute(cl, handler); - - // wait for script to run - Thread.sleep(2000); - - assertTrue(exec.isFailure(handler.getExitValue())); - assertNotNull(handler.getException()); - assertEquals("FOO..", baos.toString().trim()); + DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); + exec.execute(cl, resultHandler); + try { + resultHandler.waitFor(); + } + catch(ExecuteException e) { + assertTrue(exec.isFailure(e.getExitValue())); + assertNotNull(resultHandler.getException()); + assertEquals("FOO..", baos.toString().trim()); + return; + } + fail("Expecting an ExecuteException"); } /** @@ -390,31 +389,17 @@ public class DefaultExecutorTest extends { FileInputStream fis = new FileInputStream("./NOTICE.txt"); CommandLine cl = new CommandLine(redirectScript); - PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.out, fis ); + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( baos, baos, fis ); DefaultExecutor executor = new DefaultExecutor(); executor.setWorkingDirectory(new File(".")); executor.setStreamHandler( pumpStreamHandler ); int exitValue = executor.execute(cl); fis.close(); + assertTrue(baos.toString().trim().endsWith("Finished reading from stdin")); assertFalse(exec.isFailure(exitValue)); } } - /** - * Start a process and connect stdin, stdout and stderr (see EXEC-33). - * - * @throws Exception the test failed - */ - public void testExecuteWithStdin() throws Exception - { - CommandLine cl = new CommandLine(testScript); - PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err, System.in ); - DefaultExecutor executor = new DefaultExecutor(); - executor.setStreamHandler( pumpStreamHandler ); - int exitValue = executor.execute(cl); - assertFalse(exec.isFailure(exitValue)); - } - /** * Start a process and connect stdout and stderr. * @@ -464,52 +449,6 @@ public class DefaultExecutorTest extends } /** - * Test the patch for EXEC-41 (https://issues.apache.org/jira/browse/EXEC-41). - * - * When a process runs longer than allowed by a configured watchdog's - * timeout, the watchdog tries to destroy it and then DefaultExecutor - * tries to clean up by joining with all installed pump stream threads. - * Problem is, that sometimes the native process doesn't die and thus - * streams aren't closed and the stream threads do not complete. - * - * The patch provides setAlwaysWaitForStreamThreads(boolean) method - * in PumpStreamHandler. By default, alwaysWaitForStreamThreads is set - * to true to preserve the current behavior. If set to false, and - * process is killed by watchdog, DefaultExecutor's call into - * ErrorStreamHandler.stop will NOT join the stream threads and - * DefaultExecutor will NOT attempt to close the streams, so the - * executor's thread won't get stuck. - * - * @throws Exception the test failed - */ - public void testExec41() throws Exception { - - CommandLine cmdLine = new CommandLine(exec41Script); - cmdLine.addArgument("10"); // sleep 10 secs - DefaultExecutor executor = new DefaultExecutor(); - ExecuteWatchdog watchdog = new ExecuteWatchdog(2*1000); // allow process no more than 2 secs - executor.setWatchdog(watchdog); - - long startTime = System.currentTimeMillis(); - - try { - executor.execute(cmdLine); - } catch (ExecuteException e) { - System.out.println(e); - } - - long duration = System.currentTimeMillis() - startTime; - - System.out.println("Process completed in " + duration +" millis; below is its output"); - - if (watchdog.killedProcess()) { - System.out.println("Process timed out and was killed."); - } - - assertTrue("The process was not killed by the watchdog", watchdog.killedProcess()); - } - - /** * A generic test case to print the command line arguments to 'printargs' script to solve * even more command line puzzles. * @@ -625,4 +564,94 @@ public class DefaultExecutorTest extends } } } + + /** + * The test script reads two arguments from stdin and prints + * the result to stdout. To make things slightly more intersting + * we are using an asynchronous process. + * + * @throws Exception the test failed + */ + public void testStdInHandling() throws Exception { + + ByteArrayInputStream bais = new ByteArrayInputStream("Foo\nBar\n".getBytes()); + CommandLine cl = new CommandLine(this.stdinSript); + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( this.baos, System.err, bais); + DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); + Executor executor = new DefaultExecutor(); + executor.setStreamHandler(pumpStreamHandler); + executor.execute(cl, resultHandler); + + resultHandler.waitFor(); + + assertTrue(resultHandler.getExitValue() == 0); + assertTrue(this.baos.toString().indexOf("Hello Foo Bar!") > 0); + } + + // === Testing bug fixes ==== + + /** + * Test the patch for EXEC-33 (https://issues.apache.org/jira/browse/EXEC-33) + * + * PumpStreamHandler hangs if System.in is redirect to process input stream . + * + * @throws Exception the test failed + */ + public void testExec33() throws Exception + { + CommandLine cl = new CommandLine(testScript); + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err, System.in ); + DefaultExecutor executor = new DefaultExecutor(); + executor.setStreamHandler( pumpStreamHandler ); + int exitValue = executor.execute(cl); + assertFalse(exec.isFailure(exitValue)); + } + + + /** + * Test the patch for EXEC-41 (https://issues.apache.org/jira/browse/EXEC-41). + * + * When a process runs longer than allowed by a configured watchdog's + * timeout, the watchdog tries to destroy it and then DefaultExecutor + * tries to clean up by joining with all installed pump stream threads. + * Problem is, that sometimes the native process doesn't die and thus + * streams aren't closed and the stream threads do not complete. + * + * The patch provides setAlwaysWaitForStreamThreads(boolean) method + * in PumpStreamHandler. By default, alwaysWaitForStreamThreads is set + * to true to preserve the current behavior. If set to false, and + * process is killed by watchdog, DefaultExecutor's call into + * ErrorStreamHandler.stop will NOT join the stream threads and + * DefaultExecutor will NOT attempt to close the streams, so the + * executor's thread won't get stuck. + * + * @throws Exception the test failed + */ + public void testExec41() throws Exception { + + CommandLine cmdLine = new CommandLine(exec41Script); + cmdLine.addArgument("10"); // sleep 10 secs + DefaultExecutor executor = new DefaultExecutor(); + ExecuteWatchdog watchdog = new ExecuteWatchdog(2*1000); // allow process no more than 2 secs + executor.setWatchdog(watchdog); + + long startTime = System.currentTimeMillis(); + + try { + executor.execute(cmdLine); + } catch (ExecuteException e) { + System.out.println(e); + } + + long duration = System.currentTimeMillis() - startTime; + + System.out.println("Process completed in " + duration +" millis; below is its output"); + + if (watchdog.killedProcess()) { + System.out.println("Process timed out and was killed."); + } + + assertTrue("The process was killed by the watchdog", watchdog.killedProcess()); + } + } Added: commons/proper/exec/trunk/src/test/scripts/stdin.sh URL: http://svn.apache.org/viewvc/commons/proper/exec/trunk/src/test/scripts/stdin.sh?rev=984849&view=auto ============================================================================== --- commons/proper/exec/trunk/src/test/scripts/stdin.sh (added) +++ commons/proper/exec/trunk/src/test/scripts/stdin.sh Thu Aug 12 16:18:02 2010 @@ -0,0 +1,25 @@ +#!/bin/sh + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +echo "What's your first name? : " +read firstName +echo "What's your surname? : " +read surname +echo "Hello $firstName $surname!" + Propchange: commons/proper/exec/trunk/src/test/scripts/stdin.sh ------------------------------------------------------------------------------ svn:executable = *