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

com.sun.enterprise.util.ProcessExecutor Maven / Gradle / Ivy

There is a newer version: 8.0.0-JDK17-M7
Show newest version
/*
 * Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.enterprise.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.ArrayList;


/**
 * @author Kedar
 * @version 1.0
 * @deprecated Use ProcessManager instead
 */
@Deprecated
public class ProcessExecutor {
    public static final long kDefaultTimeoutMillis = 600000;
    public static final long kSleepTime = 2000;
    private static final long DEFAULT_TIMEOUT_SEC = 600;
    private static final String NEWLINE = System.getProperty("line.separator");
    private long mTimeoutMilliseconds = 0;
    protected String[] mCmdStrings = null;
    protected File mOutFile = null;
    protected File mErrFile = null;
    private OutputStream mOutStream = null;
    private OutputStream mErrStream = null;
    private File mWorkingDir = null; //working directory
    private String[] mEnv = null; //environment
    private String[] mInputLines = null; // strings to set in process's InputStream (like from redirection)
    private int mExitValue = -1;
    private Process mSubProcess = null; // used to get handle to child process for ProcessManager funtionality
    private boolean mVerboseMode = false;
    private boolean retainExecutionLogs = false;
    private String lastExecutionOutputString = null;
    private String lastExecutionErrorString = null;
    private boolean bDebug = false;

    /**
     * Creates new ProcessExecutor
     */
    public ProcessExecutor(String[] cmd) {
        this(cmd, DEFAULT_TIMEOUT_SEC, null);
    }

    /**
     * Creates new ProcessExecutor
     */
    public ProcessExecutor(String[] cmd, String[] inputLines) {
        this(cmd, DEFAULT_TIMEOUT_SEC, inputLines);
    }

    /**
     * Creates new ProcessExecutor
     */
    public ProcessExecutor(String[] cmd, long timeoutSeconds) {
        this(cmd, timeoutSeconds, null);
    }

    public ProcessExecutor(String[] cmd, long timeoutSeconds, String[] inputLines) {
        this(cmd, timeoutSeconds, inputLines, null, null);
    }

    /**
     * Creates a new
     *  ProcessExecutor  that executes the given command.
     *
     * @param cmd String that has command name and its command line arguments
     * @param timeoutSeconds long integer timeout to be applied in seconds.
     * After this time if the process to execute does not end, it will be
     * destroyed.
     */
    public ProcessExecutor(String[] cmd, long timeoutSeconds, String[] inputLines,
            String[] env, File workingDir) {
        mCmdStrings = cmd;
        mInputLines = inputLines;
        mEnv = env;
        mWorkingDir = workingDir;
        char fwdSlashChar = '/';
        char backSlashChar = '\\';

        if (System.getProperty("Debug") != null) {
            // turn on debug, this option was added to help developers
            // debug the their code
            bDebug = true;
        }

        for (int i = 0; i < mCmdStrings.length; i++) {
            if (OS.isUnix()) {
                mCmdStrings[i] = mCmdStrings[i].replace(backSlashChar, fwdSlashChar);
            }
            else {
                mCmdStrings[i] = mCmdStrings[i].replace(fwdSlashChar, backSlashChar);
            }
        }
        mTimeoutMilliseconds = timeoutSeconds * 1000;
    }

    /**
     * This is the setting after the fact that an instance of ProcessExecutor is
     * created. This is to be used in case the output and error of the last
     * execute call has to be retained for latter analysis.
     *
     * @param s boolean representing whether to retain, true means the buffers
     * will be retained, false otherwise.
     */
    public void setExecutionRetentionFlag(final boolean s) {
        this.retainExecutionLogs = s;
    }

    public boolean getExecutionRetentionFlag() {
        return (this.retainExecutionLogs);
    }

    /**
     * Returns the last LAST_BYTES bytes in the error stream of last execution
     * as a String, if the ProcessExecutor was configured properly. It may
     * return null if the retentionFlag is set to false.
     */
    public String getLastExecutionError() {
        return (this.lastExecutionErrorString);
    }

    /**
     * Returns the last LAST_BYTES bytes in the output stream of last execution
     * as a String, if the ProcessExecutor was configured properly. It may
     * return null if the retentionFlag is set to false.
     */
    public String getLastExecutionOutput() {
        return (this.lastExecutionOutputString);
    }

    private void init() throws ExecException {
        try {
            mOutFile = File.createTempFile("stdout", null);
            mOutFile.deleteOnExit();
            mErrFile = File.createTempFile("stderr", null);
            mErrFile.deleteOnExit();
        }
        catch (IllegalArgumentException iae) {
            deleteTempFiles();
            throw new ExecException("Internal error (util.ProcessExecutor.init()): " + iae.getMessage());
        }
        catch (IOException ioe) {
            deleteTempFiles();
            throw new ExecException(cannotCreateTempFiles());
        }
    }

    private final static String cannotCreateTempFiles() {
        return "Could not create temporary files - check "
                + System.getProperty("java.io.tmpdir")
                + " to see if its writeable and not-full";
    }

    private void deleteTempFiles() {
        if (mOutStream != null) {
            try {
                mOutStream.flush();
                mOutStream.close();
            }
            catch (IOException ioe) {
                // Ignore
            }
        }

        if (mErrStream != null) {
            try {
                mErrStream.flush();
                mErrStream.close();
            }
            catch (IOException ioe) {
                // Ignore
            }
        }
        if (mOutFile != null)
            delete(mOutFile);
        if (mErrFile != null)
            delete(mErrFile);
    }

    public void execute() throws ExecException {
        execute(false);
    }

    /*
     * Executes the command. Redirects the standard output and error streams
     * safely to files. This makes the subprocess NOT block or wait on buffers
     * getting flushed. This is done in a threaded manner. Note that the
     * subprocess will be killed if it does not end in given timeout.
     *
     * @throws ExecException if anything goes wrong in subprocess, or subprocess
     * terminates abruptly.
     */
    public String[] execute(boolean bReturnOutputLines) throws ExecException {
        return execute(bReturnOutputLines, true);
    }

    /**
     * Allows a subclass to control the error message returned when a non-zero
     * exit code is returned from a failed execution
     *
     * @return
     */
    protected String getExceptionMessage() {
        /*
         * read the error message from error file
         */
        String errorMessage = getFileBuffer(mErrFile);
        if (errorMessage.length() == 0) {
            errorMessage = "The Process Output: " + getLatestOutput(mOutFile);
        }
        return "abnormal subprocess termination: Detailed Message:" + errorMessage;
    }

    /*
     * Executes the command. Redirects the standard output and error streams
     * safely to files. This makes the subprocess NOT block or wait on buffers
     * getting flushed. This is done in a threaded manner. Note that the
     * subprocess will be killed if it does not end in given timeout.
     *
     * @throws ExecException if anything goes wrong in subprocess, or subprocess
     * terminates abruptly.
     */
    public String[] execute(boolean bReturnOutputLines, boolean bStartUpTimeLimit) throws ExecException {
        init();
        InputStream inputStream = null;
        try {

            if (bDebug) {
                System.out.println("\n**** Executing command:");
                for (int ii = 0; ii < mCmdStrings.length; ii++) {
                    System.out.println(mCmdStrings[ii]);
                }
            }

            mSubProcess = Runtime.getRuntime().exec(mCmdStrings, mEnv, mWorkingDir);
            if (mInputLines != null)
                addInputLinesToProcessInput(mSubProcess);
            if (!bReturnOutputLines)
                mOutStream = redirectProcessOutput(mSubProcess);
            else
                inputStream = mSubProcess.getInputStream(); //attach to input stream for later reading
            mErrStream = redirectProcessError(mSubProcess);

            // see if process should startup in a limited ammount of time
            // processes used by ProcessManager don't return
            if (bStartUpTimeLimit) {
                long timeBefore = System.currentTimeMillis();
                boolean timeoutReached = false;
                boolean isSubProcessFinished = false;
                boolean shouldBeDone = false;
                while (!shouldBeDone) {
                    sleep(kSleepTime);
                    long timeAfter = System.currentTimeMillis();
                    timeoutReached = (timeAfter - timeBefore) >= mTimeoutMilliseconds;
                    try {
                        mExitValue = mSubProcess.exitValue();
                        isSubProcessFinished = true;
                    }
                    catch (IllegalThreadStateException itse) {
                        isSubProcessFinished = false;
                        //ignore exception
                    }
                    shouldBeDone = timeoutReached || isSubProcessFinished;
                }
                if (!isSubProcessFinished) {
                    mSubProcess.destroy();
                    mExitValue = -255;
                    throw new ExecException("Subprocess timed out after " + mTimeoutMilliseconds + "mS");
                }
                else {
                    mExitValue = mSubProcess.exitValue();
                    if (debug()) {
                        System.out.println("Subprocess command line = " + a2s(mCmdStrings));
                        System.out.println("Subprocess exit value = " + mExitValue);
                    }
                    if (mExitValue != 0) {
                        mExitValue = mSubProcess.exitValue();
                        if (mExitValue != 0) {
                            throw new ExecException(getExceptionMessage());
                        }
                    }
                }
            }
        }
        catch (SecurityException se) {
            throw new ExecException(se.getMessage());
        }
        catch (IOException ioe) {
            throw new ExecException(ioe.getMessage());
        }
        finally {

            // retain buffers before deleting them
            retainBuffers();

            // only delete files if the time is limited
            // for processes that don't return, the temp files will remain
            if (bStartUpTimeLimit) {
                deleteTempFiles();
            }
        }

        if (bReturnOutputLines) {
            return getInputStrings(inputStream);
        }
        else {
            return null;
        }

    }

    /**
     * Get the exit value of the process executed. If this method is called
     * before process execution is complete (i.e. before execute() method has
     * returned, it will return -1. If sub process is terminated at timeout, the
     * method will return -255
     */
    public int getProcessExitValue() {
        return mExitValue;
    }

    private void addInputLinesToProcessInput(Process subProcess) throws ExecException {
        if (mInputLines == null)
            return;

        PrintWriter out = null;
        try {
            out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(subProcess.getOutputStream())));

            for (int i = 0; i < mInputLines.length; i++) {
                if (bDebug) {
                    System.out.println("InputLine ->" + mInputLines[i] + "<-");
                }
                out.println(mInputLines[i]);
            }
            out.flush();
        }
        catch (Exception e) {
            throw new ExecException(e.getMessage());
        }
        finally {
            try {
                out.close();
            }
            catch (Throwable t) {
            }
        }
    }

    private String[] getInputStrings(InputStream inputStream) throws ExecException {
        if (inputStream == null)
            return null;
        BufferedReader in = null;
        ArrayList list = new ArrayList();
        String str;
        try {
            in = new BufferedReader(new InputStreamReader(inputStream));
            while ((str = in.readLine()) != null)
                list.add(str);
            if (list.size() < 1)
                return null;
            return (String[]) list.toArray(new String[list.size()]);

        }
        catch (Exception e) {
            throw new ExecException(e.getMessage());
        }
        finally {
            try {
                in.close();
            }
            catch (Throwable t) {
            }
        }
    }

    private OutputStream redirectProcessOutput(Process subProcess) throws ExecException {
        OutputStream out = null;
        try {
            InputStream in = subProcess.getInputStream();
            // Redirect stderr for verbose mode
            if (mVerboseMode) {
                // send output to stderr
                out = System.err;
            }
            else {
                // send to temp file
                out = new FileOutputStream(mOutFile);
            }

            new FlusherThread(in, out).start();
        }
        catch (Exception e) {
            throw new ExecException(e.getMessage());
        }
        return out;
    }

    private OutputStream redirectProcessError(Process subProcess) throws ExecException {
        OutputStream out = null;
        try {
            InputStream in = subProcess.getErrorStream();
            // Redirect stderr for verbose mode
            if (mVerboseMode) {
                // send output to stderr
                out = System.err;
            }
            else {
                // send to temp file
                out = new FileOutputStream(mErrFile);
            }
            new FlusherThread(in, out).start();
        }
        catch (Exception e) {
            throw new ExecException(e.getMessage());
        }
        return out;
    }

    public void setVerbose(boolean verbose) {
        mVerboseMode = verbose;
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        }
        catch (InterruptedException ie) {
            //ignore exception
        }
    }

    /**
     * Returns the contents of a file as a String. It never returns a null. If
     * the file is empty, an empty string is returned.
     *
     * @param file the file to read
     */
    protected String getFileBuffer(File file) {
        final StringBuffer sb = new StringBuffer();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(file));
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append(NEWLINE);
            }
        }
        catch (Exception e) {
            //squelch the exception
        }
        finally {
            try {
                reader.close();
            }
            catch (Exception e) {
            }
        }
        return (sb.toString());
    }

    protected String getLatestOutput(final File f) {
        return (new RAFileReader(f).readLastBytesAsString());
    }

    public void retainBuffers() {
        if (this.retainExecutionLogs) {
            this.lastExecutionErrorString = this.getLatestOutput(this.mErrFile);
            this.lastExecutionOutputString = this.getLatestOutput(this.mOutFile);
        }
    }

    private boolean debug() {
        final String td = System.getProperty("java.io.tmpdir");
        final String n = "as_debug_process_executor"; // a debug hook
        final File f = new File(td, n);
        return (f.exists());
    }

    private String a2s(String[] a) {
        final StringBuffer s = new StringBuffer();
        if (a != null) {
            for (int i = 0; i < a.length; i++) {
                s.append(a[i]);
                s.append(" ");
            }
        }
        return (s.toString());
    }

    /*
     * bnevins, April 2012
     * I added this method to solve a FindBugs low-level issue about ignoring the
     * return value.
     */
    private static void delete(File f) {
        if(f != null && !f.delete()) {
            f.deleteOnExit();
        }
    }

    private static class RAFileReader {
        final File file;
        final static int LAST_BYTES = 16384;
        final static String RMODE = "r"; //read

        RAFileReader(final File file) {
            this.file = file;
        }

        String readLastBytesAsString() {
            final int n = getNumberOfBytes(LAST_BYTES);
            final StringBuffer sb = new StringBuffer();
            final long ln = file.length(); //if SecurityManager is not present, this is safe.
            if (ln == 0)
                return (sb.toString()); //nothing to read, file may not exist, is protected, is a directory etc.
            assert (n <= ln) : ("Asked to read number of bytes more than size of file");
            final long s = ln - n;
            return (readWithoutCheck(s));
        }

        private String readWithoutCheck(final long seekPos) {
            final StringBuffer sb = new StringBuffer();
            RandomAccessFile rf = null;
            int lines = 0;
            try {
                rf = new RandomAccessFile(file, RMODE);
                rf.seek(seekPos);
                String tmp = rf.readLine();
                while (tmp != null) {
                    lines++;
                    sb.append(tmp);
                    //sb.append(Character.LINE_SEPARATOR);
                    sb.append('\n'); // adding a newline character is going to add one extra byte
                    tmp = rf.readLine();
                }
            }
            catch (Exception e) {
                //e.printStackTrace(); //ignore
            }
            finally {
                try {
                    if (rf != null)
                        rf.close();
                }
                catch (Exception e) {
                }//ignore;
            }
            //System.out.println("ln-seekPos = " + (ln - seekPos) );
            //System.out.println("bytes = " + sb.toString().getBytes().length);
            //System.out.println("lines = " + lines);
            //assert ((ln - seekPos) == (sb.toString().getBytes().length + lines)) : "Wrong number of bytes read";
            return (sb.toString());
        }

        private int getNumberOfBytes(final int max) {
            final long ln = file.length();
            return (max >= ln ? (int) ln : max);
        }
    }

    // used for ProcessManager to watchdog subProcess
    public Process getSubProcess() {
        return mSubProcess;
    }

    public static void main(String args[]) {
        testProcessError();
    }

    /*
     * This method tests the condition of process throwing an error. On Unixlike
     * systems this throws an error in error file. On non-unixlike Systems it
     * will throw IOException for CreateProcess, which is desired
     */
    private static void testProcessError() {
        ProcessExecutor executor = new ProcessExecutor(
                new String[]{"/usr/bin/ls", "-wrongPARAMS123"});
        try {
            executor.execute();
        }
        catch (ExecException ee) {
            System.out.println(ee.getMessage());
        }
    }
}
/**
 * inner class to flush runtime.exec process so it doesn't hang
 */
class FlusherThread extends Thread {
    InputStream mInStream = null;
    OutputStream mOutStream = null;
    public static final int kSize = 1024;

    FlusherThread(InputStream in, OutputStream out) {
        mInStream = in;
        mOutStream = out;
    }

    @Override
    public void run() {
        // check for null stream
        if (mInStream == null)
            return;

        // transfer bytes from input to output stream
        try {
            int byteCnt = 0;
            byte[] buffer = new byte[4096];
            while ((byteCnt = mInStream.read(buffer)) != -1) {
                if (mOutStream != null && byteCnt > 0) {
                    mOutStream.write(buffer, 0, byteCnt);
                    mOutStream.flush();
                }
                Thread.yield();
            }
        }
        catch (IOException e) {
            // ignore
        }
        finally {
            try {
                mOutStream.close();
            }
            catch (IOException ioe) {
                // ignore
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy