001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.exec; 018 019 import java.io.File; 020 import java.io.IOException; 021 import java.util.Map; 022 023 import org.apache.commons.exec.launcher.CommandLauncher; 024 import org.apache.commons.exec.launcher.CommandLauncherFactory; 025 026 /** 027 * The default class to start a subprocess. The implementation 028 * allows to 029 * <ul> 030 * <li>set a current working directory for the subprocess</li> 031 * <li>provide a set of environment variables passed to the subprocess</li> 032 * <li>capture the subprocess output of stdout and stderr using an ExecuteStreamHandler</li> 033 * <li>kill long-running processes using an ExecuteWatchdog</li> 034 * <li>define a set of expected exit values</li> 035 * <li>terminate any started processes when the main process is terminating using a ProcessDestroyer</li> 036 * </ul> 037 * 038 * The following example shows the basic usage: 039 * 040 * <pre> 041 * Executor exec = new DefaultExecutor(); 042 * CommandLine cl = new CommandLine("ls -l"); 043 * int exitvalue = exec.execute(cl); 044 * </pre> 045 */ 046 public class DefaultExecutor implements Executor { 047 048 /** taking care of output and error stream */ 049 private ExecuteStreamHandler streamHandler; 050 051 /** the working directory of the process */ 052 private File workingDirectory; 053 054 /** monitoring of long running processes */ 055 private ExecuteWatchdog watchdog; 056 057 /** the exit values considered to be successful */ 058 private int[] exitValues; 059 060 /** launches the command in a new process */ 061 private final CommandLauncher launcher; 062 063 /** optional cleanup of started processes */ 064 private ProcessDestroyer processDestroyer; 065 066 /** worker thread for asynchronous execution */ 067 private Thread executorThread; 068 069 /** 070 * Default constructor creating a default <code>PumpStreamHandler</code> 071 * and sets the working directory of the subprocess to the current 072 * working directory. 073 * 074 * The <code>PumpStreamHandler</code> pumps the output of the subprocess 075 * into our <code>System.out</code> and <code>System.err</code> to avoid 076 * into our <code>System.out</code> and <code>System.err</code> to avoid 077 * a blocked or deadlocked subprocess (see{@link java.lang.Process Process}). 078 */ 079 public DefaultExecutor() { 080 this.streamHandler = new PumpStreamHandler(); 081 this.launcher = CommandLauncherFactory.createVMLauncher(); 082 this.exitValues = new int[0]; 083 this.workingDirectory = new File("."); 084 } 085 086 /** 087 * @see org.apache.commons.exec.Executor#getStreamHandler() 088 */ 089 public ExecuteStreamHandler getStreamHandler() { 090 return streamHandler; 091 } 092 093 /** 094 * @see org.apache.commons.exec.Executor#setStreamHandler(org.apache.commons.exec.ExecuteStreamHandler) 095 */ 096 public void setStreamHandler(ExecuteStreamHandler streamHandler) { 097 this.streamHandler = streamHandler; 098 } 099 100 /** 101 * @see org.apache.commons.exec.Executor#getWatchdog() 102 */ 103 public ExecuteWatchdog getWatchdog() { 104 return watchdog; 105 } 106 107 /** 108 * @see org.apache.commons.exec.Executor#setWatchdog(org.apache.commons.exec.ExecuteWatchdog) 109 */ 110 public void setWatchdog(ExecuteWatchdog watchDog) { 111 this.watchdog = watchDog; 112 } 113 114 /** 115 * @see org.apache.commons.exec.Executor#getProcessDestroyer() 116 */ 117 public ProcessDestroyer getProcessDestroyer() { 118 return this.processDestroyer; 119 } 120 121 /** 122 * @see org.apache.commons.exec.Executor#setProcessDestroyer(ProcessDestroyer) 123 */ 124 public void setProcessDestroyer(ProcessDestroyer processDestroyer) { 125 this.processDestroyer = processDestroyer; 126 } 127 128 /** 129 * @see org.apache.commons.exec.Executor#getWorkingDirectory() 130 */ 131 public File getWorkingDirectory() { 132 return workingDirectory; 133 } 134 135 /** 136 * @see org.apache.commons.exec.Executor#setWorkingDirectory(java.io.File) 137 */ 138 public void setWorkingDirectory(File dir) { 139 this.workingDirectory = dir; 140 } 141 142 /** 143 * @see org.apache.commons.exec.Executor#execute(CommandLine) 144 */ 145 public int execute(final CommandLine command) throws ExecuteException, 146 IOException { 147 return execute(command, (Map) null); 148 } 149 150 /** 151 * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map) 152 */ 153 public int execute(final CommandLine command, Map environment) 154 throws ExecuteException, IOException { 155 156 if (workingDirectory != null && !workingDirectory.exists()) { 157 throw new IOException(workingDirectory + " doesn't exist."); 158 } 159 160 return executeInternal(command, environment, workingDirectory, streamHandler); 161 162 } 163 164 /** 165 * @see org.apache.commons.exec.Executor#execute(CommandLine, 166 * org.apache.commons.exec.ExecuteResultHandler) 167 */ 168 public void execute(final CommandLine command, ExecuteResultHandler handler) 169 throws ExecuteException, IOException { 170 execute(command, null, handler); 171 } 172 173 /** 174 * @see org.apache.commons.exec.Executor#execute(CommandLine, 175 * java.util.Map, org.apache.commons.exec.ExecuteResultHandler) 176 */ 177 public void execute(final CommandLine command, final Map environment, 178 final ExecuteResultHandler handler) throws ExecuteException, IOException { 179 180 if (workingDirectory != null && !workingDirectory.exists()) { 181 throw new IOException(workingDirectory + " doesn't exist."); 182 } 183 184 executorThread = new Thread() { 185 public void run() { 186 int exitValue = Executor.INVALID_EXITVALUE; 187 try { 188 exitValue = executeInternal(command, environment, workingDirectory, streamHandler); 189 handler.onProcessComplete(exitValue); 190 } catch (ExecuteException e) { 191 handler.onProcessFailed(e); 192 } catch(Exception e) { 193 handler.onProcessFailed(new ExecuteException("Execution failed", exitValue, e)); 194 } 195 } 196 }; 197 198 getExecutorThread().start(); 199 } 200 201 /** @see org.apache.commons.exec.Executor#setExitValue(int) */ 202 public void setExitValue(final int value) { 203 this.setExitValues(new int[] {value}); 204 } 205 206 207 /** @see org.apache.commons.exec.Executor#setExitValues(int[]) */ 208 public void setExitValues(final int[] values) { 209 this.exitValues = (values == null ? null : (int[]) values.clone()); 210 } 211 212 /** @see org.apache.commons.exec.Executor#isFailure(int) */ 213 public boolean isFailure(final int exitValue) { 214 215 if(this.exitValues == null) { 216 return false; 217 } 218 else if(this.exitValues.length == 0) { 219 return this.launcher.isFailure(exitValue); 220 } 221 else { 222 for(int i=0; i<this.exitValues.length; i++) { 223 if(this.exitValues[i] == exitValue) { 224 return false; 225 } 226 } 227 } 228 return true; 229 } 230 231 /** 232 * Creates a process that runs a command. 233 * 234 * @param command 235 * the command to run 236 * @param env 237 * the environment for the command 238 * @param dir 239 * the working directory for the command 240 * @return the process started 241 * @throws IOException 242 * forwarded from the particular launcher used 243 */ 244 protected Process launch(final CommandLine command, final Map env, 245 final File dir) throws IOException { 246 247 if (this.launcher == null) { 248 throw new IllegalStateException("CommandLauncher can not be null"); 249 } 250 251 if (dir != null && !dir.exists()) { 252 throw new IOException(dir + " doesn't exist."); 253 } 254 return this.launcher.exec(command, env, dir); 255 } 256 257 /** 258 * Get the worker thread being used for asynchronous execution. 259 * 260 * @return the worker thread 261 */ 262 protected Thread getExecutorThread() { 263 return executorThread; 264 } 265 266 /** 267 * Close the streams belonging to the given Process. In the 268 * original implementation all exceptions were dropped which 269 * is probably not a good thing. On the other hand the signature 270 * allows throwing an IOException so the current implementation 271 * might be quite okay. 272 * 273 * @param process the <CODE>Process</CODE>. 274 * @throws IOException closing one of the three streams failed 275 */ 276 private void closeStreams(final Process process) throws IOException { 277 278 IOException caught = null; 279 280 try { 281 process.getInputStream().close(); 282 } 283 catch(IOException e) { 284 caught = e; 285 } 286 287 try { 288 process.getOutputStream().close(); 289 } 290 catch(IOException e) { 291 caught = e; 292 } 293 294 try { 295 process.getErrorStream().close(); 296 } 297 catch(IOException e) { 298 caught = e; 299 } 300 301 if(caught != null) { 302 throw caught; 303 } 304 } 305 306 /** 307 * Execute an internal process. 308 * 309 * @param command the command to execute 310 * @param environment the execution enviroment 311 * @param dir the working directory 312 * @param streams process the streams (in, out, err) of the process 313 * @return the exit code of the process 314 * @throws IOException executing the process failed 315 */ 316 private int executeInternal(final CommandLine command, final Map environment, 317 final File dir, final ExecuteStreamHandler streams) throws IOException { 318 319 final Process process = this.launch(command, environment, dir); 320 321 try { 322 streams.setProcessInputStream(process.getOutputStream()); 323 streams.setProcessOutputStream(process.getInputStream()); 324 streams.setProcessErrorStream(process.getErrorStream()); 325 } catch (IOException e) { 326 process.destroy(); 327 throw e; 328 } 329 330 streams.start(); 331 332 try { 333 334 // add the process to the list of those to destroy if the VM exits 335 if(this.getProcessDestroyer() != null) { 336 this.getProcessDestroyer().add(process); 337 } 338 339 // associate the watchdog with the newly created process 340 if (watchdog != null) { 341 watchdog.start(process); 342 } 343 344 int exitValue = Executor.INVALID_EXITVALUE; 345 346 try { 347 exitValue = process.waitFor(); 348 } catch (InterruptedException e) { 349 process.destroy(); 350 } 351 finally { 352 // see http://bugs.sun.com/view_bug.do?bug_id=6420270 353 // see https://issues.apache.org/jira/browse/EXEC-46 354 // Process.waitFor should clear interrupt status when throwing InterruptedException 355 // but we have to do that manually 356 Thread.interrupted(); 357 } 358 359 if (watchdog != null) { 360 watchdog.stop(); 361 } 362 363 streams.stop(); 364 closeStreams(process); 365 366 if (watchdog != null) { 367 try { 368 watchdog.checkException(); 369 } catch (IOException e) { 370 throw e; 371 } catch (Exception e) { 372 throw new IOException(e.getMessage()); 373 } 374 } 375 376 if(this.isFailure(exitValue)) { 377 throw new ExecuteException("Process exited with an error: " + exitValue, exitValue); 378 } 379 380 return exitValue; 381 } finally { 382 // remove the process to the list of those to destroy if the VM exits 383 if(this.getProcessDestroyer() != null) { 384 this.getProcessDestroyer().remove(process); 385 } 386 } 387 } 388 }