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 */
017package org.apache.commons.exec;
018
019import java.io.Closeable;
020import java.io.File;
021import java.io.IOException;
022import java.util.Map;
023import java.util.concurrent.Executors;
024import java.util.concurrent.ThreadFactory;
025import java.util.function.Supplier;
026
027import org.apache.commons.exec.launcher.CommandLauncher;
028import org.apache.commons.exec.launcher.CommandLauncherFactory;
029
030/**
031 * The default class to start a subprocess. The implementation allows to
032 * <ul>
033 * <li>set a current working directory for the subprocess</li>
034 * <li>provide a set of environment variables passed to the subprocess</li>
035 * <li>capture the subprocess output of stdout and stderr using an ExecuteStreamHandler</li>
036 * <li>kill long-running processes using an ExecuteWatchdog</li>
037 * <li>define a set of expected exit values</li>
038 * <li>terminate any started processes when the main process is terminating using a ProcessDestroyer</li>
039 * </ul>
040 *
041 * The following example shows the basic usage:
042 *
043 * <pre>
044 * Executor exec = DefaultExecutor.builder().get();
045 * CommandLine cl = new CommandLine("ls -l");
046 * int exitvalue = exec.execute(cl);
047 * </pre>
048 */
049public class DefaultExecutor implements Executor {
050
051    /**
052     * Constructs a new builder.
053     *
054     * @param <T> The builder type.
055     * @since 1.4.0
056     */
057    public static class Builder<T extends Builder<T>> implements Supplier<DefaultExecutor> {
058
059        private ThreadFactory threadFactory;
060        private ExecuteStreamHandler executeStreamHandler;
061        private File workingDirectory;
062
063        @SuppressWarnings("unchecked")
064        T asThis() {
065            return (T) this;
066        }
067
068        /**
069         * Creates a new configured DefaultExecutor.
070         *
071         * @return a new configured DefaultExecutor.
072         */
073        @Override
074        public DefaultExecutor get() {
075            return new DefaultExecutor(threadFactory, executeStreamHandler, workingDirectory);
076        }
077
078        ExecuteStreamHandler getExecuteStreamHandler() {
079            return executeStreamHandler;
080        }
081
082        ThreadFactory getThreadFactory() {
083            return threadFactory;
084        }
085
086        File getWorkingDirectory() {
087            return workingDirectory;
088        }
089
090        /**
091         * Sets the PumpStreamHandler.
092         *
093         * @param executeStreamHandler the ExecuteStreamHandler, null resets to the default.
094         * @return this.
095         */
096        public T setExecuteStreamHandler(final ExecuteStreamHandler executeStreamHandler) {
097            this.executeStreamHandler = executeStreamHandler;
098            return asThis();
099        }
100
101        /**
102         * Sets the ThreadFactory.
103         *
104         * @param threadFactory the ThreadFactory, null resets to the default.
105         * @return this.
106         */
107        public T setThreadFactory(final ThreadFactory threadFactory) {
108            this.threadFactory = threadFactory;
109            return asThis();
110        }
111
112        /**
113         * Sets the working directory.
114         *
115         * @param workingDirectory the working directory., null resets to the default.
116         * @return this.
117         */
118        public T setWorkingDirectory(final File workingDirectory) {
119            this.workingDirectory = workingDirectory;
120            return asThis();
121        }
122
123    }
124
125    /**
126     * Creates a new builder.
127     *
128     * @return a new builder.
129     * @since 1.4.0
130     */
131    public static Builder<?> builder() {
132        return new Builder<>();
133    }
134
135    /** Taking care of output and error stream. */
136    private ExecuteStreamHandler executeStreamHandler;
137
138    /** The working directory of the process. */
139    private File workingDirectory;
140
141    /** Monitoring of long running processes. */
142    private ExecuteWatchdog watchdog;
143
144    /** The exit values considered to be successful. */
145    private int[] exitValues;
146
147    /** Launches the command in a new process. */
148    private final CommandLauncher launcher;
149
150    /** Optional cleanup of started processes. */
151    private ProcessDestroyer processDestroyer;
152
153    /** Worker thread for asynchronous execution. */
154    private Thread executorThread;
155
156    /** The first exception being caught to be thrown to the caller. */
157    private IOException exceptionCaught;
158
159    /**
160     * The thread factory.
161     */
162    private final ThreadFactory threadFactory;
163
164    /**
165     * Constructs a default {@code PumpStreamHandler} and sets the working directory of the subprocess to the current working directory.
166     *
167     * The {@code PumpStreamHandler} pumps the output of the subprocess into our {@code System.out} and {@code System.err} to avoid into our {@code System.out}
168     * and {@code System.err} to avoid a blocked or deadlocked subprocess (see {@link Process Process}).
169     *
170     * @deprecated Use {@link Builder#get()}.
171     */
172    @Deprecated
173    public DefaultExecutor() {
174        this(Executors.defaultThreadFactory(), new PumpStreamHandler(), new File("."));
175    }
176
177    DefaultExecutor(final ThreadFactory threadFactory, final ExecuteStreamHandler executeStreamHandler, final File workingDirectory) {
178        this.threadFactory = threadFactory != null ? threadFactory : Executors.defaultThreadFactory();
179        this.executeStreamHandler = executeStreamHandler != null ? executeStreamHandler : new PumpStreamHandler();
180        this.workingDirectory = workingDirectory != null ? workingDirectory : new File(".");
181        this.launcher = CommandLauncherFactory.createVMLauncher();
182        this.exitValues = new int[0];
183    }
184
185    private void checkWorkingDirectory() throws IOException {
186        checkWorkingDirectory(workingDirectory);
187    }
188
189    private void checkWorkingDirectory(final File directory) throws IOException {
190        if (directory != null && !directory.exists()) {
191            throw new IOException(directory + " doesn't exist.");
192        }
193    }
194
195    /**
196     * Closes the Closeable, remembering any exception.
197     *
198     * @param closeable the {@link Closeable} to close.
199     */
200    private void closeCatch(final Closeable closeable) {
201        try {
202            closeable.close();
203        } catch (final IOException e) {
204            setExceptionCaught(e);
205        }
206    }
207
208    /**
209     * Closes the streams belonging to the given Process.
210     *
211     * @param process the {@link Process}.
212     */
213    @SuppressWarnings("resource")
214    private void closeProcessStreams(final Process process) {
215        closeCatch(process.getInputStream());
216        closeCatch(process.getOutputStream());
217        closeCatch(process.getErrorStream());
218    }
219
220    /**
221     * Creates a thread waiting for the result of an asynchronous execution.
222     *
223     * @param runnable the runnable passed to the thread.
224     * @param name     the name of the thread.
225     * @return the thread
226     */
227    protected Thread createThread(final Runnable runnable, final String name) {
228        return ThreadUtil.newThread(threadFactory, runnable, name, false);
229    }
230
231    /**
232     * @see org.apache.commons.exec.Executor#execute(CommandLine)
233     */
234    @Override
235    public int execute(final CommandLine command) throws ExecuteException, IOException {
236        return execute(command, (Map<String, String>) null);
237    }
238
239    /**
240     * @see org.apache.commons.exec.Executor#execute(CommandLine, org.apache.commons.exec.ExecuteResultHandler)
241     */
242    @Override
243    public void execute(final CommandLine command, final ExecuteResultHandler handler) throws ExecuteException, IOException {
244        execute(command, null, handler);
245    }
246
247    /**
248     * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map)
249     */
250    @Override
251    public int execute(final CommandLine command, final Map<String, String> environment) throws ExecuteException, IOException {
252        checkWorkingDirectory();
253        return executeInternal(command, environment, workingDirectory, executeStreamHandler);
254    }
255
256    /**
257     * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map, org.apache.commons.exec.ExecuteResultHandler)
258     */
259    @Override
260    public void execute(final CommandLine command, final Map<String, String> environment, final ExecuteResultHandler handler)
261            throws ExecuteException, IOException {
262        checkWorkingDirectory();
263        if (watchdog != null) {
264            watchdog.setProcessNotStarted();
265        }
266        executorThread = createThread(() -> {
267            int exitValue = Executor.INVALID_EXITVALUE;
268            try {
269                exitValue = executeInternal(command, environment, workingDirectory, executeStreamHandler);
270                handler.onProcessComplete(exitValue);
271            } catch (final ExecuteException e) {
272                handler.onProcessFailed(e);
273            } catch (final Exception e) {
274                handler.onProcessFailed(new ExecuteException("Execution failed", exitValue, e));
275            }
276        }, "CommonsExecDefaultExecutor");
277        getExecutorThread().start();
278    }
279
280    /**
281     * Execute an internal process. If the executing thread is interrupted while waiting for the child process to return the child process will be killed.
282     *
283     * @param command          the command to execute.
284     * @param environment      the execution environment.
285     * @param workingDirectory the working directory.
286     * @param streams          process the streams (in, out, err) of the process.
287     * @return the exit code of the process.
288     * @throws IOException executing the process failed.
289     */
290    private int executeInternal(final CommandLine command, final Map<String, String> environment, final File workingDirectory,
291            final ExecuteStreamHandler streams) throws IOException {
292        final Process process;
293        exceptionCaught = null;
294        try {
295            process = launch(command, environment, workingDirectory);
296        } catch (final IOException e) {
297            if (watchdog != null) {
298                watchdog.failedToStart(e);
299            }
300            throw e;
301        }
302        try {
303            setStreams(streams, process);
304        } catch (final IOException e) {
305            process.destroy();
306            if (watchdog != null) {
307                watchdog.failedToStart(e);
308            }
309            throw e;
310        }
311        streams.start();
312        try {
313            // add the process to the list of those to destroy if the VM exits
314            if (getProcessDestroyer() != null) {
315                getProcessDestroyer().add(process);
316            }
317            // associate the watchdog with the newly created process
318            if (watchdog != null) {
319                watchdog.start(process);
320            }
321            int exitValue = Executor.INVALID_EXITVALUE;
322            try {
323                exitValue = process.waitFor();
324            } catch (final InterruptedException e) {
325                process.destroy();
326            } finally {
327                // see http://bugs.sun.com/view_bug.do?bug_id=6420270
328                // see https://issues.apache.org/jira/browse/EXEC-46
329                // Process.waitFor should clear interrupt status when throwing InterruptedException
330                // but we have to do that manually
331                Thread.interrupted();
332            }
333            if (watchdog != null) {
334                watchdog.stop();
335            }
336            try {
337                streams.stop();
338            } catch (final IOException e) {
339                setExceptionCaught(e);
340            }
341            closeProcessStreams(process);
342            if (getExceptionCaught() != null) {
343                throw getExceptionCaught();
344            }
345            if (watchdog != null) {
346                try {
347                    watchdog.checkException();
348                } catch (final IOException e) {
349                    throw e;
350                } catch (final Exception e) {
351                    throw new IOException(e);
352                }
353            }
354            if (isFailure(exitValue)) {
355                throw new ExecuteException("Process exited with an error: " + exitValue, exitValue);
356            }
357            return exitValue;
358        } finally {
359            // remove the process to the list of those to destroy if the VM exits
360            if (getProcessDestroyer() != null) {
361                getProcessDestroyer().remove(process);
362            }
363        }
364    }
365
366    /**
367     * Gets the first IOException being thrown.
368     *
369     * @return the first IOException being caught.
370     */
371    private IOException getExceptionCaught() {
372        return exceptionCaught;
373    }
374
375    /**
376     * Gets the worker thread being used for asynchronous execution.
377     *
378     * @return the worker thread.
379     */
380    protected Thread getExecutorThread() {
381        return executorThread;
382    }
383
384    /**
385     * @see org.apache.commons.exec.Executor#getProcessDestroyer()
386     */
387    @Override
388    public ProcessDestroyer getProcessDestroyer() {
389        return processDestroyer;
390    }
391
392    /**
393     * @see org.apache.commons.exec.Executor#getStreamHandler()
394     */
395    @Override
396    public ExecuteStreamHandler getStreamHandler() {
397        return executeStreamHandler;
398    }
399
400    /**
401     * Gets the thread factory. Z
402     *
403     * @return the thread factory.
404     */
405    ThreadFactory getThreadFactory() {
406        return threadFactory;
407    }
408
409    /**
410     * @see org.apache.commons.exec.Executor#getWatchdog()
411     */
412    @Override
413    public ExecuteWatchdog getWatchdog() {
414        return watchdog;
415    }
416
417    /**
418     * @see org.apache.commons.exec.Executor#getWorkingDirectory()
419     */
420    @Override
421    public File getWorkingDirectory() {
422        return workingDirectory;
423    }
424
425    /** @see org.apache.commons.exec.Executor#isFailure(int) */
426    @Override
427    public boolean isFailure(final int exitValue) {
428        if (exitValues == null) {
429            return false;
430        }
431        if (exitValues.length == 0) {
432            return launcher.isFailure(exitValue);
433        }
434        for (final int exitValue2 : exitValues) {
435            if (exitValue2 == exitValue) {
436                return false;
437            }
438        }
439        return true;
440    }
441
442    /**
443     * Creates a process that runs a command.
444     *
445     * @param command          the command to run.
446     * @param env              the environment for the command.
447     * @param workingDirectory the working directory for the command.
448     * @return the process started.
449     * @throws IOException forwarded from the particular launcher used.
450     */
451    protected Process launch(final CommandLine command, final Map<String, String> env, final File workingDirectory) throws IOException {
452        if (launcher == null) {
453            throw new IllegalStateException("CommandLauncher can not be null");
454        }
455        checkWorkingDirectory(workingDirectory);
456        return launcher.exec(command, env, workingDirectory);
457    }
458
459    /**
460     * Sets the first IOException thrown.
461     *
462     * @param e the IOException.
463     */
464    private void setExceptionCaught(final IOException e) {
465        if (exceptionCaught == null) {
466            exceptionCaught = e;
467        }
468    }
469
470    /** @see org.apache.commons.exec.Executor#setExitValue(int) */
471    @Override
472    public void setExitValue(final int value) {
473        setExitValues(new int[] { value });
474    }
475
476    /** @see org.apache.commons.exec.Executor#setExitValues(int[]) */
477    @Override
478    public void setExitValues(final int[] values) {
479        exitValues = values == null ? null : (int[]) values.clone();
480    }
481
482    /**
483     * @see org.apache.commons.exec.Executor#setProcessDestroyer(ProcessDestroyer)
484     */
485    @Override
486    public void setProcessDestroyer(final ProcessDestroyer processDestroyer) {
487        this.processDestroyer = processDestroyer;
488    }
489
490    /**
491     * @see org.apache.commons.exec.Executor#setStreamHandler(org.apache.commons.exec.ExecuteStreamHandler)
492     */
493    @Override
494    public void setStreamHandler(final ExecuteStreamHandler streamHandler) {
495        this.executeStreamHandler = streamHandler;
496    }
497
498    @SuppressWarnings("resource")
499    private void setStreams(final ExecuteStreamHandler streams, final Process process) throws IOException {
500        streams.setProcessInputStream(process.getOutputStream());
501        streams.setProcessOutputStream(process.getInputStream());
502        streams.setProcessErrorStream(process.getErrorStream());
503    }
504
505    /**
506     * @see org.apache.commons.exec.Executor#setWatchdog(org.apache.commons.exec.ExecuteWatchdog)
507     */
508    @Override
509    public void setWatchdog(final ExecuteWatchdog watchdog) {
510        this.watchdog = watchdog;
511    }
512
513    /**
514     * Sets the working directory.
515     *
516     * @see org.apache.commons.exec.Executor#setWorkingDirectory(java.io.File)
517     * @deprecated Use {@link Builder#setWorkingDirectory(File)}.
518     */
519    @Deprecated
520    @Override
521    public void setWorkingDirectory(final File workingDirectory) {
522        this.workingDirectory = workingDirectory;
523    }
524
525}