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 considerd 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 /** 067 * Default Constrctor 068 */ 069 public DefaultExecutor() { 070 this.streamHandler = new PumpStreamHandler(); 071 this.launcher = CommandLauncherFactory.createVMLauncher(); 072 this.exitValues = new int[0]; 073 } 074 075 /** 076 * @see org.apache.commons.exec.Executor#getStreamHandler() 077 */ 078 public ExecuteStreamHandler getStreamHandler() { 079 return streamHandler; 080 } 081 082 /** 083 * @see org.apache.commons.exec.Executor#setStreamHandler(org.apache.commons.exec.ExecuteStreamHandler) 084 */ 085 public void setStreamHandler(ExecuteStreamHandler streamHandler) { 086 this.streamHandler = streamHandler; 087 } 088 089 /** 090 * @see org.apache.commons.exec.Executor#getWatchdog() 091 */ 092 public ExecuteWatchdog getWatchdog() { 093 return watchdog; 094 } 095 096 /** 097 * @see org.apache.commons.exec.Executor#setWatchdog(org.apache.commons.exec.ExecuteWatchdog) 098 */ 099 public void setWatchdog(ExecuteWatchdog watchDog) { 100 this.watchdog = watchDog; 101 } 102 103 /** 104 * @see org.apache.commons.exec.Executor#getProcessDestroyer() 105 */ 106 public ProcessDestroyer getProcessDestroyer() { 107 return this.processDestroyer; 108 } 109 110 /** 111 * @see org.apache.commons.exec.Executor#setProcessDestroyer(ProcessDestroyer) 112 */ 113 public void setProcessDestroyer(ProcessDestroyer processDestroyer) { 114 this.processDestroyer = processDestroyer; 115 } 116 117 /** 118 * @see org.apache.commons.exec.Executor#getWorkingDirectory() 119 */ 120 public File getWorkingDirectory() { 121 return workingDirectory; 122 } 123 124 /** 125 * @see org.apache.commons.exec.Executor#setWorkingDirectory(java.io.File) 126 */ 127 public void setWorkingDirectory(File dir) { 128 this.workingDirectory = dir; 129 } 130 131 /** 132 * @see org.apache.commons.exec.Executor#execute(CommandLine) 133 */ 134 public int execute(final CommandLine command) throws ExecuteException, 135 IOException { 136 return execute(command, (Map) null); 137 } 138 139 /** 140 * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map) 141 */ 142 public int execute(final CommandLine command, Map environment) 143 throws ExecuteException, IOException { 144 145 if (workingDirectory != null && !workingDirectory.exists()) { 146 throw new IOException(workingDirectory + " doesn't exist."); 147 } 148 149 return executeInternal(command, environment, workingDirectory, streamHandler); 150 151 } 152 153 /** 154 * @see org.apache.commons.exec.Executor#execute(CommandLine, 155 * org.apache.commons.exec.ExecuteResultHandler) 156 */ 157 public void execute(final CommandLine command, ExecuteResultHandler handler) 158 throws ExecuteException, IOException { 159 execute(command, null, handler); 160 } 161 162 /** 163 * @see org.apache.commons.exec.Executor#execute(CommandLine, 164 * java.util.Map, org.apache.commons.exec.ExecuteResultHandler) 165 */ 166 public void execute(final CommandLine command, final Map environment, 167 final ExecuteResultHandler handler) throws ExecuteException, IOException { 168 169 if (workingDirectory != null && !workingDirectory.exists()) { 170 throw new IOException(workingDirectory + " doesn't exist."); 171 } 172 173 new Thread() { 174 175 /** 176 * @see java.lang.Thread#run() 177 */ 178 public void run() { 179 int exitValue = Executor.INVALID_EXITVALUE; 180 try { 181 exitValue = executeInternal(command, environment, workingDirectory, streamHandler); 182 handler.onProcessComplete(exitValue); 183 } catch (ExecuteException e) { 184 handler.onProcessFailed(e); 185 } catch(Exception e) { 186 handler.onProcessFailed(new ExecuteException("Execution failed", exitValue, e)); 187 } 188 } 189 }.start(); 190 } 191 192 193 /** @see org.apache.commons.exec.Executor#setExitValue(int) */ 194 public void setExitValue(final int value) { 195 this.setExitValues(new int[] {value}); 196 } 197 198 199 /** @see org.apache.commons.exec.Executor#setExitValues(int[]) */ 200 public void setExitValues(final int[] values) { 201 this.exitValues = (values == null ? null : (int[]) values.clone()); 202 } 203 204 /** @see org.apache.commons.exec.Executor#isFailure(int) */ 205 public boolean isFailure(final int exitValue) { 206 207 if(this.exitValues == null) { 208 return false; 209 } 210 else if(this.exitValues.length == 0) { 211 return this.launcher.isFailure(exitValue); 212 } 213 else { 214 for(int i=0; i<this.exitValues.length; i++) { 215 if(this.exitValues[i] == exitValue) { 216 return false; 217 } 218 } 219 } 220 return true; 221 } 222 223 /** 224 * Creates a process that runs a command. 225 * 226 * @param command 227 * the command to run 228 * @param env 229 * the environment for the command 230 * @param dir 231 * the working directory for the command 232 * @return the process started 233 * @throws IOException 234 * forwarded from the particular launcher used 235 */ 236 protected Process launch(final CommandLine command, final Map env, 237 final File dir) throws IOException { 238 239 if (this.launcher == null) { 240 throw new IllegalStateException("CommandLauncher can not be null"); 241 } 242 243 if (dir != null && !dir.exists()) { 244 throw new IOException(dir + " doesn't exist."); 245 } 246 return this.launcher.exec(command, env, dir); 247 } 248 249 /** 250 * Close the streams belonging to the given Process. In the 251 * original implementation all exceptions were dropped which 252 * is probably not a good thing. On the other hand the signature 253 * allows throwing an IOException so the curent implementation 254 * might be quite okay. 255 * 256 * @param process the <CODE>Process</CODE>. 257 * @throws IOException closing one of the three streams failed 258 */ 259 private void closeStreams(final Process process) throws IOException { 260 261 IOException caught = null; 262 263 try { 264 process.getInputStream().close(); 265 } 266 catch(IOException e) { 267 caught = e; 268 } 269 270 try { 271 process.getOutputStream().close(); 272 } 273 catch(IOException e) { 274 caught = e; 275 } 276 277 try { 278 process.getErrorStream().close(); 279 } 280 catch(IOException e) { 281 caught = e; 282 } 283 284 if(caught != null) { 285 throw caught; 286 } 287 } 288 289 /** 290 * Execute an internal process. 291 * 292 * @param command the command to execute 293 * @param environment the execution enviroment 294 * @param dir the working directory 295 * @param streams process the streams (in, out, err) of the process 296 * @return the exit code of the process 297 * @throws IOException executing the process failed 298 */ 299 private int executeInternal(final CommandLine command, final Map environment, 300 final File dir, final ExecuteStreamHandler streams) throws IOException { 301 302 final Process process = this.launch(command, environment, dir); 303 304 try { 305 streams.setProcessInputStream(process.getOutputStream()); 306 streams.setProcessOutputStream(process.getInputStream()); 307 streams.setProcessErrorStream(process.getErrorStream()); 308 } catch (IOException e) { 309 process.destroy(); 310 throw e; 311 } 312 313 streams.start(); 314 315 try { 316 // add the process to the list of those to destroy if the VM exits 317 if(this.getProcessDestroyer() != null) { 318 this.getProcessDestroyer().add(process); 319 } 320 321 if (watchdog != null) { 322 watchdog.start(process); 323 } 324 int exitValue = Executor.INVALID_EXITVALUE; 325 try { 326 exitValue = process.waitFor(); 327 } catch (InterruptedException e) { 328 process.destroy(); 329 } 330 331 if (watchdog != null) { 332 watchdog.stop(); 333 } 334 streams.stop(); 335 closeStreams(process); 336 337 if (watchdog != null) { 338 try { 339 watchdog.checkException(); 340 } catch (Exception e) { 341 throw new IOException(e.getMessage()); 342 } 343 } 344 345 if(this.isFailure(exitValue)) { 346 throw new ExecuteException("Process exited with an error: " + exitValue, exitValue); 347 } 348 349 return exitValue; 350 } finally { 351 // remove the process to the list of those to destroy if the VM exits 352 if(this.getProcessDestroyer() != null) { 353 this.getProcessDestroyer().remove(process); 354 } 355 } 356 } 357 }