All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.util.ProcessRunner Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*************************************************************************
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2020 Adobe
 *  All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains
 * the property of Adobe and its suppliers, if any. The intellectual
 * and technical concepts contained herein are proprietary to Adobe
 * and its suppliers and are protected by all applicable intellectual
 * property laws, including trade secret and copyright laws.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe.
 **************************************************************************/
package com.day.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The ProcessRunner class helps running external processes. This
 * encompasses redirection of stdin, stdout and stderr as well as optionally
 * waiting for the completion of the process. This implementation is based on
 * the Runtime.exec(String) method and does not yet support passing
 * specific environments or decomposed comand lines.
 * 

* This class can be used in two ways. If you don't care about waiting for the * processes completion and return code, you might do it like this : *

* * Runnable pr = new ProcessRunner("command", null, null, null);
* new Thread(pr).start(); *
*
*

* If on the other hand you want to capture all output and also keep an eye on * the time the process takes to execute (or wait indefinitely), you might * use the class like this : *

* * ProcessRunner pr = new ProcessRunner("command", in, out, err);
* pr.run(0);
* int rc = pr.getReturnCode(); *
*
* * @version $Revision: 1.6 $ * @author fmeschbe * @since coati * Audience wad */ public class ProcessRunner implements Runnable { /** * Implementation note : The programm execution is implemented based on * the Runtime.exec(String) method. This method returns a * Process object, from which the streams for stdin, stdout * and stderr can be got. These are redirected streams, that is writing * to the OutputStream for input provides the input to the process, * while the output from the process can be got from either the * output stream - attached to stdout - or the error stream - attached * to stderr. * *

* Capturing stdout and stderr *

* As Java versions prior to Java 2 V 1.3 only support synchronous blocking * IO, the streams must be polled with the help of the * available() method to read from the stdout and stderr * streams. Therefor capturing stdout and stderr is implemented in a polling * loop. Between two consecutive polls of the streams, the loop is delayed * for {@link #POLL_OUTPUT_DELY} ms. In each poll as much bytes are * captured as are available from the respective stream. * *

* <Ending the process *

* There seems to be no other possibility to notice the end of the process * than to constantly check the Process.exitValue(), which * throws an IllegalThreadStateException as long as the process * is running. When no exception is thrown, the process has ended and * the polling loop can be left, as there is no more output to be expected. * *

* Waiting and aborting *

* The {@link #run(long)} method implements waiting for the termination * of the process. This method can either wait for ever or for a number * of milliseconds for the process to end. If the time to wait passes * without the process terminating, the process should be considered running * to long and will be killed. To support this, the run * methods use a {@link #lock} object and a {@link #running} flag. *

* The waiting method {@link #run(long)} waits on the lock * object for the specified number of milliseconds. Waiting is ended when * either the time has passed or the process has ended. The flag is needed * to differentiate between the timeout and the process termination. In the * case of timeout, the process must be terminated and we have to wait for * the real termination to settle down for getting the process return code. *

* The {@link #run()} method notifies all waiters of the lock * object and also clears the running flag to indicated, the process * has really terminated. By checking this flag the {@link #run(long)} * method can determine whether to wait for the real process termination * or not. *

* See also the notes in the code of {@link #run(long)} method for more * information on this issue. */ /** Constant to indicate the process is running */ public static final int PROCESS_RUNNING = -1; /** Constant to indicate the process has been aborted */ public static final int PROCESS_ABORTED = -2; /** default log */ private static final Logger log = LoggerFactory.getLogger(ProcessRunner.class); /** The delay in ms between two consecutive polls for process output */ private static final int POLL_OUTPUT_DELAY = 100; /** The cmdline to execute. */ private final String cmdLine; /** The input data stream - where to get stdin data from */ private final InputStream stdin; /** The output data stream - were to send stdout to */ private final OutputStream stdout; /** The error output data stream - were to send stderr to */ private final OutputStream stderr; /** The lock object used by the run methods to signal process termination */ private final Object lock = new Object(); /** * set to false, after having called lock.notifyAll() but while still * holding the monitor no lock. */ private boolean running = true; /** * The return code from running the command line or one of the * PROCESS_* constants. * * @see #PROCESS_RUNNING * @see #PROCESS_ABORTED */ private int rc = PROCESS_RUNNING; /** * Creates a new ProcessRunner to execute the given command * line containing the command to execute and all relevant command * arguments. The stdin InputStream can be used to feed * input data to the command executed, while the output (stdout and stderr) * are redirected to the given OutputStreams or the process * defaults (System.out and System.err, resp.). *

* None of the streams is closed after running the command line. This is * the sole duty of the client of this class. * * @param cmdLine This commandline is given to the * Runtime.exec(String) contains all the command * arguments and is split up with a StringTokenizer. * See the API docs on the Runtime.exec(String) method * for details. * @param stdin The InputStream containing data to be handed * to the process as stdin. Set this to null if the * process should not get any input. This stream is completely * read after the process is started and sent to the process. * Therefor the stream should be available. * @param stdout The OutputStream to send the stdout output * of the process to. If this is null, stdout output * is written to System.out. * @param stderr The OuputStream to send the stderr output * of the process to. If this is null, stderr output * is written to System.err. */ public ProcessRunner(String cmdLine, InputStream stdin, OutputStream stdout, OutputStream stderr) { this.cmdLine = cmdLine; this.stdin = stdin; this.stdout = (stdout == null) ? System.out : stdout; this.stderr = (stderr == null) ? System.err : stderr; } /** * Creates a new ProcessRunner to execute the given command * line containing the command to execute and all relevant command * arguments with no redirection of stdin, stdout and stderr. * * @param cmdLine This commandline is given to the * Runtime.exec(String) contains all the command * arguments and is split up with a StringTokenizer. * See the API docs on the Runtime.exec(String) method * for details. */ public ProcessRunner(String cmdLine) { this(cmdLine, null, null, null); } /** * Executes the command line and waits for the completion of the prcoess. * The process runs in its own thread which is marked as daemon thread. That * is as soon as the Java VM is about to end, the process will also forcibly * stopped and does not run to completion. * * @param waitTime The number of milliseconds to wait for the completion of * the process. If the time has ellapsed before the process has * terminated, the process will be forcibly terminated. If this * value is negative, the process runs completely detached, while * a value of zero indicates to wait indeterminate for the * completion of the process. */ public void run(long waitTime) { Thread t = new Thread(this); t.setDaemon(true); t.start(); if (waitTime >= 0) { synchronized(lock) { while (running) { try { // wait for the waitTime (forever if waitTime==0) lock.wait(waitTime); /** * Two possibilities here : timeout or notifyall. * * (1) after lock.notifyAll(), the process has ended and * running == false. all is well. * * (2) after timeout, the process may have ended or not. * the problem is, that when waking up and contending * for the lock monitor, the process might come first * and lock.notifyAll(). if this happens, that signal * is lost but i'm sure that running==false, as i only * get the monitor, when the process releases it. * if i win - that is running==true - i know, that i * can catch a lock.notifyAll() later. * * that is : if i get to run again, i do not know * whether i was waken up by a timeout or a notifyAll() * and i do not directly know whether the notifyAll() * has been sent. thanks to the running flag, i can be * made sure of this : if i get to run, then either the * notifyAll() has been sent (whether or not i directly * caught it) or will not be sent as long as i do not * release the monitor. */ // no notifyAll, yet if (running) { // send an interrupt to be sure t.interrupt(); // force no timeout at next wait, notifyAll() comes waitTime = 0; } } catch (InterruptedException ie) { // ignore } } } } } /** * Returns the return code from running the command line. As long as the * command is running, the method returns {@link #PROCESS_RUNNING}. After * the process has terminated, the method returns the return code from the * process or {@link #PROCESS_ABORTED} if the command has been terminated * due to a timeout. * * @return The return code from the process or either * PROCESS_RUNNING or PROCESS_ABORTED. */ public int getReturnCode() { return rc; } //---------- Runnable interface -------------------------------------------- /** * Runs the process sending the input data and capturing output data. *

* Do not directly call this method. Instead either use * new Thread(new ProcessRunner(cmd)).start() or use the * {@link #run(long)} method. */ public void run() { // need to close them in the finally block InputStream procOut = null; InputStream procErr = null; Process p = null; try { // start the process and get the output streams p = Runtime.getRuntime().exec(cmdLine); procOut = p.getInputStream(); procErr = p.getErrorStream(); // Send input data to the process writeStdIn(p, stdin); // now try and catch the output of the process while (p != null) { // let the process some time to do some work Thread.sleep(POLL_OUTPUT_DELAY); // read stdout and stderr spoolAvailable(procOut, stdout); spoolAvailable(procErr, stderr); try { // IllegalThreadStateException is thrown if the process // is still running, else we get the exit code and // end waiting rc = p.exitValue(); // terminate the loop p = null; } catch (IllegalThreadStateException e) {} } log.info("run: Process ended with rc={}", new Integer(rc)); } catch (IOException ioe) { log.info("run: IO problem during execution: {}", ioe.toString()); } catch (InterruptedException ie) { // we were interrupted for some reason, destroy the proces before // continuing. if (p != null) { p.destroy(); try { rc = p.exitValue(); } catch (IllegalThreadStateException itse) { log.info("run: Exit value of destroyed process unavailable"); } p = null; } } finally { tryClose(procOut); tryClose(procErr); // inform waiters on the termination synchronized (lock){ lock.notifyAll(); running = false; } } } //---------- internal ------------------------------------------------------ /** * Sends the input data from the client to the process. The method copies * all data available from the InputStream to the process, * that is, all data up to an error or the end of file is copied. *

* The stream to write the data to is retrieved from the process and closed * at the end regardless of whether an error occurred sending the data. * * @param proc The Process to send the data to * @param stdin The InputStream to read the data from. * * @throws IOException if reading or writing the data throws an error. */ private void writeStdIn(Process proc, InputStream stdin) throws IOException { if (stdin != null) { final int bufLen = 1024; final byte[] b = new byte[bufLen]; OutputStream procIn = null; try { procIn = proc.getOutputStream(); int numRead; while ( (numRead = stdin.read(b)) > 0 ) { procIn.write(b, 0, numRead); } } finally { tryClose(procIn); } } } /** * Spools available input data from the source InputStream to * the sink OutputStream. * * @param source The InputStream providing data. * @param sink The OutputStream getting the data. * * @throws IOException if reading or writing the data has problems. */ private void spoolAvailable(InputStream source, OutputStream sink) throws IOException { final int bufLen = 1024; final byte[] buf = new byte[bufLen]; for (int avail=source.available(); avail > 0; ) { int num = (avail > bufLen) ? bufLen : avail; num = source.read(buf, 0, num); // assume available is correct ;-) sink.write(buf, 0, num); avail -= num; } } /** * Closes the OutputStream if not null and eats * (ignores) any IOException occurrring. * * @param out The OutputStream to close ignoring any * IOException occurring. */ private void tryClose(OutputStream out) { if (out != null) { try { out.close(); } catch (IOException ioe) {} } } /** * Closes the InputStream if not null and eats * (ignores) any IOException occurrring. * * @param in The InputStream to close ignoring any * IOException occurring. */ private void tryClose(InputStream in) { if (in != null) { try { in.close(); } catch (IOException ioe) {} } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy