hoot.services.command.CommandRunner.java Source code

Java tutorial

Introduction

Here is the source code for hoot.services.command.CommandRunner.java

Source

/*
 * This file is part of Hootenanny.
 *
 * Hootenanny is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * --------------------------------------------------------------------
 *
 * The following copyright notices are generated automatically. If you
 * have a new notice to add, please use the format:
 * " * @copyright Copyright ..."
 * This will properly maintain the copyright information. DigitalGlobe
 * copyrights will be updated automatically.
 *
 * @copyright Copyright (C) 2014, 2015 DigitalGlobe (http://www.digitalglobe.com/)
 */
package hoot.services.command;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;

import static org.apache.commons.io.IOUtils.closeQuietly;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.log4j.Logger;

/**
 * Utility class for running a subprocess synchronously from Java and retrieving
 * the contents of System.out and/or system.err.
 */
public class CommandRunner implements ICommandRunner {

    private static final Logger _log = Logger.getLogger(CommandRunner.class);
    private Process process;
    private MutableBoolean sig_interrupt;
    private List<CharPump> _outputList = new ArrayList<CharPump>();

    private int _processState = -1;

    private StringWriter _out = null;
    private StringWriter _err = null;

    public CommandRunner() {
        sig_interrupt = new MutableBoolean(false);
    }

    public void terminate() {
        if (process != null) {
            process.destroy();
        }
    }

    public int getProcessState() {
        return _processState;
    }

    public void terminateClean() throws Exception {
        sig_interrupt.setValue(true);

        if (_outputList.size() > 1) {
            CharPump p1 = _outputList.get(0);
            if (p1 != null) {
                synchronized (p1) {
                    p1.wait(1000);
                }
            }

            CharPump p2 = _outputList.get(1);

            if (p2 != null) {
                synchronized (p2) {
                    p2.wait(1000);
                }
            }
        }
        cleanUpProcess();
    }

    public String getStdOut() {
        return _out.toString();
    }

    public CommandResult exec(String pCmd) throws IOException, InterruptedException {
        StringWriter out = new StringWriter();
        StringWriter err = new StringWriter();

        CommandResult result = exec(pCmd, out, err);
        return result;
    }

    public CommandResult exec(String pCmd, Map<String, String> env, boolean includeSysEnv)
            throws IOException, InterruptedException {

        CommandResult result = exec(pCmd.split(" "), env, includeSysEnv);
        return result;
    }

    public CommandResult exec(String[] pCmd, String[] pEnv) throws IOException, InterruptedException {
        return exec(pCmd, pEnv, null);
    }

    public CommandResult exec(String[] pCmd, String[] pEnv, File dir) throws IOException, InterruptedException {
        Map<String, String> env = new HashMap<String, String>();
        for (int i = 0; i < pEnv.length; i++) {
            StringTokenizer st = new StringTokenizer(pEnv[i], "=");
            env.put(st.nextToken(), st.nextToken());
        }
        return exec(pCmd, env, dir);
    }

    public CommandResult exec(String[] pCmd) throws IOException, InterruptedException {
        _out = new StringWriter();
        _err = new StringWriter();

        CommandResult result = exec(pCmd, _out, _err);
        return result;
    }

    public CommandResult exec(String[] pCmd, Map<String, String> pEnv) throws IOException, InterruptedException {
        return exec(pCmd, pEnv, true);
    }

    public CommandResult exec(String[] pCmd, Map<String, String> pEnv, boolean useSysEnv)
            throws IOException, InterruptedException {
        StringWriter out = new StringWriter();
        StringWriter err = new StringWriter();

        CommandResult result = exec(pCmd, pEnv, useSysEnv, out, err);
        return result;
    }

    // TRAC 1801
    private void cleanUpProcess() throws IOException {
        if (process == null) {
            return;
        }
        OutputStream os = process.getOutputStream();
        InputStream is = process.getInputStream();
        InputStream es = process.getErrorStream();

        // TRAC 1801
        if (os != null) {
            // System.err.println("Closing os");
            os.flush();
        }
        closeQuietly(os);
        closeQuietly(is);
        closeQuietly(es);

        if (process != null) {
            process.destroy();
            process = null;
        }
        // System.gc();
    }

    public CommandResult exec(String[] pCmd, String[] pEnv, Writer pOut, Writer pErr)
            throws IOException, InterruptedException {

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        if (_log.isInfoEnabled())
            _log.info("Executing '" + pCmdString + "' with Environment '" + ArrayUtils.toString(pEnv) + "'");
        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = Runtime.getRuntime().exec(pCmd, pEnv);
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmdString + "' completed in " + clock.getTime() + " ms");
        }
        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    public CommandResult exec(String[] pCmd, String[] pEnv, File dir, Writer pOut, Writer pErr)
            throws IOException, InterruptedException {

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        if (_log.isInfoEnabled())
            _log.info("Executing '" + pCmdString + "' with Environment '" + ArrayUtils.toString(pEnv) + "'");
        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = Runtime.getRuntime().exec(pCmd, pEnv, dir);
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmdString + "' completed in " + clock.getTime() + " ms");
        }

        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    public CommandResult exec(String[] pCmd, Map<String, String> pEnv, File dir)
            throws IOException, InterruptedException {
        return exec(pCmd, pEnv, true, dir);
    }

    public CommandResult exec(String[] pCmd, Map<String, String> pEnv, boolean useSysEnv, File dir)
            throws IOException, InterruptedException {
        StringWriter out = new StringWriter();
        StringWriter err = new StringWriter();

        CommandResult result = exec(pCmd, pEnv, useSysEnv, dir, out, err);
        return result;
    }

    public CommandResult exec(String pCmd, File dir) throws IOException, InterruptedException {
        StringWriter out = new StringWriter();
        StringWriter err = new StringWriter();

        CommandResult result = exec(pCmd.split(" "), dir, out, err);
        return result;
    }

    public CommandResult exec(String[] pCmd, File dir) throws IOException, InterruptedException {
        return exec(pCmd, dir, null);
    }

    public CommandResult exec(String[] pCmd, Writer pOut, Writer pErr) throws IOException, InterruptedException {
        ProcessBuilder builder = new ProcessBuilder();
        Map<String, String> env = builder.environment();

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        logExec(pCmdString, env);

        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = Runtime.getRuntime().exec(pCmd);
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } catch (Exception e) {
            //System.out.println(e.fillInStackTrace().toString());
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmdString + "' completed in " + clock.getTime() + " ms");
        }

        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    public CommandResult exec(String[] pCmd, Map<String, String> pEnv, boolean useSysEnv, Writer pOut, Writer pErr)
            throws IOException, InterruptedException {

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        ProcessBuilder builder = new ProcessBuilder();
        builder.command(pCmd);

        Map<String, String> env = builder.environment();
        if (!useSysEnv)
            env.clear();
        for (String name : pEnv.keySet()) {
            env.put(name, pEnv.get(name));
        }

        logExec(pCmdString, env);

        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = builder.start();
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmdString + "' completed in " + clock.getTime() + " ms");
        }

        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    public CommandResult exec(String[] pCmd, Map<String, String> pEnv, boolean useSysEnv, File dir, Writer pOut,
            Writer pErr) throws IOException, InterruptedException {

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        ProcessBuilder builder = new ProcessBuilder();
        builder.command(pCmd);
        Map<String, String> env = builder.environment();
        if (!useSysEnv)
            env.clear();
        for (String name : pEnv.keySet()) {
            env.put(name, pEnv.get(name));
        }
        builder.directory(dir);

        logExec(pCmdString, env);

        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = builder.start();
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmdString + "' completed in " + clock.getTime() + " ms");
        }
        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    public CommandResult exec(String pCmdString, Writer pOut, Writer pErr)
            throws IOException, InterruptedException {
        return exec(pCmdString.split(" "), pOut, pErr);
    }

    public CommandResult exec(String pCmdString, File dir, Writer pOut, Writer pErr)
            throws IOException, InterruptedException {
        return exec(pCmdString.split(" "), dir, pOut, pErr);
    }

    public CommandResult exec(String[] pCmd, File dir, Writer pOut, Writer pErr)
            throws IOException, InterruptedException {
        ProcessBuilder builder = new ProcessBuilder();
        Map<String, String> env = builder.environment();

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        logExec(pCmdString, env);

        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = Runtime.getRuntime().exec(pCmd, null, dir);
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmd + "' completed in " + clock.getTime() + " ms");
        }
        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    // private methods

    private int handleProcess(Process pProcess, String pOrigCmd, Writer pOut, Writer pErr,
            List<CharPump> stdOutErrList, MutableBoolean interrupt) throws IOException, InterruptedException {

        _processState = 0;
        int res = handleProcessStatic(pProcess, pOrigCmd, pOut, pErr, stdOutErrList, interrupt);
        _processState = 1;
        return res;
    }

    private static int handleProcessStatic(Process pProcess, String pOrigCmd, Writer pOut, Writer pErr,
            List<CharPump> stdOutErrList, MutableBoolean interrupt) throws IOException, InterruptedException {

        CharPump outpump = new CharPump(new BufferedReader(new InputStreamReader(pProcess.getInputStream())), pOut,
                pOrigCmd, interrupt);
        stdOutErrList.add(outpump);
        CharPump errpump = new CharPump(new BufferedReader(new InputStreamReader(pProcess.getErrorStream())), pErr,
                pOrigCmd, interrupt);
        stdOutErrList.add(errpump);
        outpump.start();
        errpump.start();
        outpump.join();
        errpump.join();

        if (_log.isInfoEnabled())
            _log.info("Waiting for '" + pOrigCmd + "' to complete.");
        int status = pProcess.waitFor();

        return status;
    }

    /** Inner class, copies from Reader to Writer. */
    public static class CharPump extends Thread {

        private static Logger pumpLog = Logger.getLogger(CharPump.class);

        private Reader iIn;

        private Writer iOut;

        private String iCmd;

        private int bufSize;

        private MutableBoolean interrupt_sig;

        public CharPump(Reader pIn, Writer pOut, String pCmd, MutableBoolean pInterrupt) {

            interrupt_sig = pInterrupt;
            iIn = pIn;
            iOut = pOut;
            iCmd = pCmd;
            String bufSizeString = System.getProperty("ew.util.os.charPumpBuffer", "1024");
            try {
                bufSize = Integer.parseInt(bufSizeString);
            } catch (NumberFormatException e) {
                e.printStackTrace();
                bufSize = 1024;
            }
            pumpLog.debug("CharPump will use buffer size = " + bufSize);
        }

        public void run() {
            boolean ok = true;
            char[] buf = new char[bufSize];
            synchronized (this) {
                try {
                    while (ok) {
                        ok = !interrupt_sig.getValue();
                        long t0 = 0, t1 = 0, t2 = 0;
                        if (pumpLog.isDebugEnabled()) {
                            /*
                             * t0 = System.currentTimeMillis(); while (
                             * !iIn.ready()) { Thread.sleep(100); }
                             */
                            t1 = System.currentTimeMillis();
                            /*
                             * pumpLog.debug( "CharPump waited "+(t1-t0)+" ms for
                             * input.");
                             */
                        }

                        int n = iIn.read(buf, 0, buf.length);
                        if (0 > n) {
                            pumpLog.debug("CharPump has encountered EOF");
                            break;
                        }
                        if (pumpLog.isDebugEnabled()) {
                            t2 = System.currentTimeMillis();
                            pumpLog.debug("CharPump read " + n + " bytes in " + (t2 - t1) + " ms.");
                        }
                        if (pumpLog.isDebugEnabled()) {
                            t1 = System.currentTimeMillis();
                        }
                        iOut.write(buf, 0, n);
                        if (pumpLog.isDebugEnabled()) {
                            t2 = System.currentTimeMillis();
                            pumpLog.debug("CharPump wrote " + n + " bytes in " + (t2 - t1) + " ms.");
                        }
                        iOut.flush();
                    }
                    iOut.flush();

                } catch (Exception e) {
                    _log.error(e);
                    e.printStackTrace();
                } finally {
                    try {
                        if (iIn != null) {
                            iIn.close();
                        }
                        if (iOut != null) {
                            iOut.close();
                        }
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                    notifyAll();
                }
            }
        }
    }

    /**
     * Main routine, for testing. The name of the executable to run and its
     * arguments are the command line arguments.
     */
    public static void main(String[] pArgs) {
        try {
            ICommandRunner runner = new CommandRunner();
            CommandResult result = null;
            if (1 == pArgs.length) {
                StringWriter out = new StringWriter();
                StringWriter err = new StringWriter();
                result = runner.exec(pArgs[0], out, err);
            } else {
                result = runner.exec(pArgs);
            }
            //System.out.println ( result.getStdout () );
            //System.out.println ( result.getStderr () );
        } catch (Exception e) {
            System.err.println(e.toString());
        }
    }

    /**
     * @param externalInstallerName
     * @param file
     * @param env
     * @return
     */
    public CommandResult exec(String[] pCmd, File dir, String[] env) throws IOException, InterruptedException {
        StringWriter out = new StringWriter();
        StringWriter err = new StringWriter();

        CommandResult result = exec(pCmd, dir, out, err, env);
        return result;
    }

    /**
     * @param cmd
     * @param dir
     * @param out
     * @param err
     * @param env
     * @return
     */
    private CommandResult exec(String[] pCmd, File dir, StringWriter pOut, StringWriter pErr, String[] env)
            throws IOException, InterruptedException {

        int out = 0;
        String pCmdString = ArrayUtils.toString(pCmd);
        if (_log.isInfoEnabled())
            _log.info("Executing '" + pCmdString + "' with Environment '" + ArrayUtils.toString(env) + "'");
        StopWatch clock = new StopWatch();
        clock.start();
        try {
            process = Runtime.getRuntime().exec(pCmd, env, dir);
            out = handleProcess(process, pCmdString, pOut, pErr, _outputList, sig_interrupt);
        } finally {
            this.cleanUpProcess();
            clock.stop();
            if (_log.isInfoEnabled())
                _log.info("'" + pCmd + "' completed in " + clock.getTime() + " ms");
        }
        if (sig_interrupt.getValue() == true) {
            out = -9999;
        }
        CommandResult result = new CommandResult(pCmdString, out, pOut.toString(), pErr.toString());
        return result;
    }

    private void logExec(String pCmdString, Map<String, String> unsortedEnv) {

        if (_log.isInfoEnabled()) {

            TreeMap<String, String> env = new TreeMap<String, String>();
            env.putAll(unsortedEnv);

            _log.info("Executing '" + pCmdString + "'");
            _log.info("Enviroment:");
            FileWriter writer = null;
            try {
                if (_log.isDebugEnabled()) {
                    File envvarFile = File.createTempFile("envvars", ".txt");
                    writer = new FileWriter(envvarFile);
                    _log.debug("ENVVARS will be written to " + envvarFile.getAbsolutePath());
                }
                for (String key : env.keySet()) {
                    _log.info(String.format("  %s", new Object[] { key + "=" + env.get(key) }));
                    if (_log.isDebugEnabled())
                        writer.write(String.format("  %s%n", new Object[] { key + "=" + env.get(key) }));
                }
                if (_log.isDebugEnabled())
                    writer.close();
            } catch (Exception e) {
                _log.error("Unable to log exec call: " + ExceptionUtils.getStackTrace(e));
            }
        }
    }
}