    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    }