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    }