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 }