DefaultExecutor.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.exec;

  18. import java.io.Closeable;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.nio.file.Files;
  22. import java.nio.file.Path;
  23. import java.nio.file.Paths;
  24. import java.util.Map;
  25. import java.util.concurrent.Executors;
  26. import java.util.concurrent.ThreadFactory;
  27. import java.util.function.Supplier;

  28. import org.apache.commons.exec.launcher.CommandLauncher;
  29. import org.apache.commons.exec.launcher.CommandLauncherFactory;

  30. /**
  31.  * The default class to start a subprocess. The implementation allows to
  32.  * <ul>
  33.  * <li>set a current working directory for the subprocess</li>
  34.  * <li>provide a set of environment variables passed to the subprocess</li>
  35.  * <li>capture the subprocess output of stdout and stderr using an ExecuteStreamHandler</li>
  36.  * <li>kill long-running processes using an ExecuteWatchdog</li>
  37.  * <li>define a set of expected exit values</li>
  38.  * <li>terminate any started processes when the main process is terminating using a ProcessDestroyer</li>
  39.  * </ul>
  40.  *
  41.  * The following example shows the basic usage:
  42.  *
  43.  * <pre>
  44.  * Executor exec = DefaultExecutor.builder().get();
  45.  * CommandLine cl = new CommandLine("ls -l");
  46.  * int exitvalue = exec.execute(cl);
  47.  * </pre>
  48.  */
  49. public class DefaultExecutor implements Executor {

  50.     /**
  51.      * Constructs a new {@link DefaultExecutor}.
  52.      *
  53.      * @param <T> The builder type.
  54.      * @since 1.4.0
  55.      */
  56.     public static class Builder<T extends Builder<T>> implements Supplier<DefaultExecutor> {

  57.         private ThreadFactory threadFactory;
  58.         private ExecuteStreamHandler executeStreamHandler;
  59.         private Path workingDirectory;

  60.         /**
  61.          * Constructs a new instance.
  62.          */
  63.         public Builder() {
  64.             // empty
  65.         }

  66.         /**
  67.          * Returns this instance typed as the subclass type {@code T}.
  68.          * <p>
  69.          * This is the same as the expression:
  70.          * </p>
  71.          * <pre>
  72.          * (B) this
  73.          * </pre>
  74.          *
  75.          * @return this instance typed as the subclass type {@code T}.
  76.          */
  77.         @SuppressWarnings("unchecked")
  78.         T asThis() {
  79.             return (T) this;
  80.         }

  81.         /**
  82.          * Creates a new configured DefaultExecutor.
  83.          *
  84.          * @return a new configured DefaultExecutor.
  85.          */
  86.         @Override
  87.         public DefaultExecutor get() {
  88.             return new DefaultExecutor(threadFactory, executeStreamHandler, workingDirectory);
  89.         }

  90.         ExecuteStreamHandler getExecuteStreamHandler() {
  91.             return executeStreamHandler;
  92.         }

  93.         ThreadFactory getThreadFactory() {
  94.             return threadFactory;
  95.         }

  96.         Path getWorkingDirectoryPath() {
  97.             return workingDirectory;
  98.         }

  99.         /**
  100.          * Sets the PumpStreamHandler.
  101.          *
  102.          * @param executeStreamHandler the ExecuteStreamHandler, null resets to the default.
  103.          * @return {@code this} instance.
  104.          */
  105.         public T setExecuteStreamHandler(final ExecuteStreamHandler executeStreamHandler) {
  106.             this.executeStreamHandler = executeStreamHandler;
  107.             return asThis();
  108.         }

  109.         /**
  110.          * Sets the ThreadFactory.
  111.          *
  112.          * @param threadFactory the ThreadFactory, null resets to the default.
  113.          * @return {@code this} instance.
  114.          */
  115.         public T setThreadFactory(final ThreadFactory threadFactory) {
  116.             this.threadFactory = threadFactory;
  117.             return asThis();
  118.         }

  119.         /**
  120.          * Sets the working directory.
  121.          *
  122.          * @param workingDirectory the working directory., null resets to the default.
  123.          * @return {@code this} instance.
  124.          */
  125.         public T setWorkingDirectory(final File workingDirectory) {
  126.             this.workingDirectory = workingDirectory != null ? workingDirectory.toPath() : null;
  127.             return asThis();
  128.         }

  129.         /**
  130.          * Sets the working directory.
  131.          *
  132.          * @param workingDirectory the working directory., null resets to the default.
  133.          * @return {@code this} instance.
  134.          * @since 1.5.0
  135.          */
  136.         public T setWorkingDirectory(final Path workingDirectory) {
  137.             this.workingDirectory = workingDirectory;
  138.             return asThis();
  139.         }

  140.     }

  141.     /**
  142.      * Creates a new builder.
  143.      *
  144.      * @return a new builder.
  145.      * @since 1.4.0
  146.      */
  147.     public static Builder<?> builder() {
  148.         return new Builder<>();
  149.     }

  150.     /** Taking care of output and error stream. */
  151.     private ExecuteStreamHandler executeStreamHandler;

  152.     /** The working directory of the process. */
  153.     private Path workingDirectory;

  154.     /** Monitoring of long running processes. */
  155.     private ExecuteWatchdog watchdog;

  156.     /** The exit values considered to be successful. */
  157.     private int[] exitValues;

  158.     /** Launches the command in a new process. */
  159.     private final CommandLauncher launcher;

  160.     /** Optional cleanup of started processes. */
  161.     private ProcessDestroyer processDestroyer;

  162.     /** Worker thread for asynchronous execution. */
  163.     private Thread executorThread;

  164.     /** The first exception being caught to be thrown to the caller. */
  165.     private IOException exceptionCaught;

  166.     /**
  167.      * The thread factory.
  168.      */
  169.     private final ThreadFactory threadFactory;

  170.     /**
  171.      * Constructs a default {@code PumpStreamHandler} and sets the working directory of the subprocess to the current working directory.
  172.      *
  173.      * 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}
  174.      * and {@code System.err} to avoid a blocked or deadlocked subprocess (see {@link Process Process}).
  175.      *
  176.      * @deprecated Use {@link Builder#get()}.
  177.      */
  178.     @Deprecated
  179.     public DefaultExecutor() {
  180.         this(Executors.defaultThreadFactory(), new PumpStreamHandler(), Paths.get("."));
  181.     }

  182.     DefaultExecutor(final ThreadFactory threadFactory, final ExecuteStreamHandler executeStreamHandler, final Path workingDirectory) {
  183.         this.threadFactory = threadFactory != null ? threadFactory : Executors.defaultThreadFactory();
  184.         this.executeStreamHandler = executeStreamHandler != null ? executeStreamHandler : new PumpStreamHandler();
  185.         this.workingDirectory = workingDirectory != null ? workingDirectory : Paths.get(".");
  186.         this.launcher = CommandLauncherFactory.createVMLauncher();
  187.         this.exitValues = new int[0];
  188.     }

  189.     private void checkWorkingDirectory() throws IOException {
  190.         checkWorkingDirectory(workingDirectory);
  191.     }

  192.     private void checkWorkingDirectory(final File directory) throws IOException {
  193.         if (directory != null && !directory.exists()) {
  194.             throw new IOException(directory + " doesn't exist.");
  195.         }
  196.     }

  197.     private void checkWorkingDirectory(final Path directory) throws IOException {
  198.         if (directory != null && !Files.exists(directory)) {
  199.             throw new IOException(directory + " doesn't exist.");
  200.         }
  201.     }

  202.     /**
  203.      * Closes the Closeable, remembering any exception.
  204.      *
  205.      * @param closeable the {@link Closeable} to close.
  206.      */
  207.     private void closeCatch(final Closeable closeable) {
  208.         try {
  209.             closeable.close();
  210.         } catch (final IOException e) {
  211.             setExceptionCaught(e);
  212.         }
  213.     }

  214.     /**
  215.      * Closes the streams belonging to the given Process.
  216.      *
  217.      * @param process the {@link Process}.
  218.      */
  219.     @SuppressWarnings("resource")
  220.     private void closeProcessStreams(final Process process) {
  221.         closeCatch(process.getInputStream());
  222.         closeCatch(process.getOutputStream());
  223.         closeCatch(process.getErrorStream());
  224.     }

  225.     /**
  226.      * Creates a thread waiting for the result of an asynchronous execution.
  227.      *
  228.      * @param runnable the runnable passed to the thread.
  229.      * @param name     the name of the thread.
  230.      * @return the thread
  231.      */
  232.     protected Thread createThread(final Runnable runnable, final String name) {
  233.         return ThreadUtil.newThread(threadFactory, runnable, name, false);
  234.     }

  235.     /**
  236.      * @see org.apache.commons.exec.Executor#execute(CommandLine)
  237.      */
  238.     @Override
  239.     public int execute(final CommandLine command) throws ExecuteException, IOException {
  240.         return execute(command, (Map<String, String>) null);
  241.     }

  242.     /**
  243.      * @see org.apache.commons.exec.Executor#execute(CommandLine, org.apache.commons.exec.ExecuteResultHandler)
  244.      */
  245.     @Override
  246.     public void execute(final CommandLine command, final ExecuteResultHandler handler) throws ExecuteException, IOException {
  247.         execute(command, null, handler);
  248.     }

  249.     /**
  250.      * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map)
  251.      */
  252.     @Override
  253.     public int execute(final CommandLine command, final Map<String, String> environment) throws ExecuteException, IOException {
  254.         checkWorkingDirectory();
  255.         return executeInternal(command, environment, workingDirectory, executeStreamHandler);
  256.     }

  257.     /**
  258.      * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map, org.apache.commons.exec.ExecuteResultHandler)
  259.      */
  260.     @Override
  261.     public void execute(final CommandLine command, final Map<String, String> environment, final ExecuteResultHandler handler)
  262.             throws ExecuteException, IOException {
  263.         checkWorkingDirectory();
  264.         if (watchdog != null) {
  265.             watchdog.setProcessNotStarted();
  266.         }
  267.         executorThread = createThread(() -> {
  268.             int exitValue = INVALID_EXITVALUE;
  269.             try {
  270.                 exitValue = executeInternal(command, environment, workingDirectory, executeStreamHandler);
  271.                 handler.onProcessComplete(exitValue);
  272.             } catch (final ExecuteException e) {
  273.                 handler.onProcessFailed(e);
  274.             } catch (final Exception e) {
  275.                 handler.onProcessFailed(new ExecuteException("Execution failed", exitValue, e));
  276.             }
  277.         }, "CommonsExecDefaultExecutor");
  278.         getExecutorThread().start();
  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 Path 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 = INVALID_EXITVALUE;
  322.             try {
  323.                 exitValue = process.waitFor();
  324.             } catch (final InterruptedException e) {
  325.                 process.destroy();
  326.             } finally {
  327.                 // see https://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.      * Gets the first IOException being thrown.
  367.      *
  368.      * @return the first IOException being caught.
  369.      */
  370.     private IOException getExceptionCaught() {
  371.         return exceptionCaught;
  372.     }

  373.     /**
  374.      * Gets the worker thread being used for asynchronous execution.
  375.      *
  376.      * @return the worker thread.
  377.      */
  378.     protected Thread getExecutorThread() {
  379.         return executorThread;
  380.     }

  381.     /**
  382.      * @see org.apache.commons.exec.Executor#getProcessDestroyer()
  383.      */
  384.     @Override
  385.     public ProcessDestroyer getProcessDestroyer() {
  386.         return processDestroyer;
  387.     }

  388.     /**
  389.      * @see org.apache.commons.exec.Executor#getStreamHandler()
  390.      */
  391.     @Override
  392.     public ExecuteStreamHandler getStreamHandler() {
  393.         return executeStreamHandler;
  394.     }

  395.     /**
  396.      * Gets the thread factory. Z
  397.      *
  398.      * @return the thread factory.
  399.      */
  400.     ThreadFactory getThreadFactory() {
  401.         return threadFactory;
  402.     }

  403.     /**
  404.      * @see org.apache.commons.exec.Executor#getWatchdog()
  405.      */
  406.     @Override
  407.     public ExecuteWatchdog getWatchdog() {
  408.         return watchdog;
  409.     }

  410.     /**
  411.      * @see org.apache.commons.exec.Executor#getWorkingDirectory()
  412.      */
  413.     @Override
  414.     public File getWorkingDirectory() {
  415.         return workingDirectory.toFile();
  416.     }

  417.     /** @see org.apache.commons.exec.Executor#isFailure(int) */
  418.     @Override
  419.     public boolean isFailure(final int exitValue) {
  420.         if (exitValues == null) {
  421.             return false;
  422.         }
  423.         if (exitValues.length == 0) {
  424.             return launcher.isFailure(exitValue);
  425.         }
  426.         for (final int exitValue2 : exitValues) {
  427.             if (exitValue2 == exitValue) {
  428.                 return false;
  429.             }
  430.         }
  431.         return true;
  432.     }

  433.     /**
  434.      * Creates a process that runs a command.
  435.      *
  436.      * @param command          the command to run.
  437.      * @param env              the environment for the command.
  438.      * @param workingDirectory the working directory for the command.
  439.      * @return the process started.
  440.      * @throws IOException forwarded from the particular launcher used.
  441.      */
  442.     protected Process launch(final CommandLine command, final Map<String, String> env, final File workingDirectory) throws IOException {
  443.         if (launcher == null) {
  444.             throw new IllegalStateException("CommandLauncher cannot be null");
  445.         }
  446.         checkWorkingDirectory(workingDirectory);
  447.         return launcher.exec(command, env, workingDirectory);
  448.     }

  449.     /**
  450.      * Creates a process that runs a command.
  451.      *
  452.      * @param command          the command to run.
  453.      * @param env              the environment for the command.
  454.      * @param workingDirectory the working directory for the command.
  455.      * @return the process started.
  456.      * @throws IOException forwarded from the particular launcher used.
  457.      * @since 1.5.0
  458.      */
  459.     protected Process launch(final CommandLine command, final Map<String, String> env, final Path workingDirectory) throws IOException {
  460.         if (launcher == null) {
  461.             throw new IllegalStateException("CommandLauncher cannot be null");
  462.         }
  463.         checkWorkingDirectory(workingDirectory);
  464.         return launcher.exec(command, env, workingDirectory);
  465.     }

  466.     /**
  467.      * Sets the first IOException thrown.
  468.      *
  469.      * @param e the IOException.
  470.      */
  471.     private void setExceptionCaught(final IOException e) {
  472.         if (exceptionCaught == null) {
  473.             exceptionCaught = e;
  474.         }
  475.     }

  476.     /** @see org.apache.commons.exec.Executor#setExitValue(int) */
  477.     @Override
  478.     public void setExitValue(final int value) {
  479.         setExitValues(new int[] { value });
  480.     }

  481.     /** @see org.apache.commons.exec.Executor#setExitValues(int[]) */
  482.     @Override
  483.     public void setExitValues(final int[] values) {
  484.         exitValues = values == null ? null : (int[]) values.clone();
  485.     }

  486.     /**
  487.      * @see org.apache.commons.exec.Executor#setProcessDestroyer(ProcessDestroyer)
  488.      */
  489.     @Override
  490.     public void setProcessDestroyer(final ProcessDestroyer processDestroyer) {
  491.         this.processDestroyer = processDestroyer;
  492.     }

  493.     /**
  494.      * @see org.apache.commons.exec.Executor#setStreamHandler(org.apache.commons.exec.ExecuteStreamHandler)
  495.      */
  496.     @Override
  497.     public void setStreamHandler(final ExecuteStreamHandler streamHandler) {
  498.         this.executeStreamHandler = streamHandler;
  499.     }

  500.     @SuppressWarnings("resource")
  501.     private void setStreams(final ExecuteStreamHandler streams, final Process process) throws IOException {
  502.         streams.setProcessInputStream(process.getOutputStream());
  503.         streams.setProcessOutputStream(process.getInputStream());
  504.         streams.setProcessErrorStream(process.getErrorStream());
  505.     }

  506.     /**
  507.      * @see org.apache.commons.exec.Executor#setWatchdog(org.apache.commons.exec.ExecuteWatchdog)
  508.      */
  509.     @Override
  510.     public void setWatchdog(final ExecuteWatchdog watchdog) {
  511.         this.watchdog = watchdog;
  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 != null ? workingDirectory.toPath() : null;
  523.     }

  524. }