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

org.whitesource.utils.cli.CommandLineProcess Maven / Gradle / Ivy

package org.whitesource.utils.cli;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.Kernel32;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.whitesource.utils.Constants;
import org.whitesource.utils.Prints.PrintUtils;
import org.whitesource.utils.OsDetector;
import org.whitesource.utils.files.UniqueNamesGenerator;
import org.whitesource.utils.logger.LoggerFactory;

import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author raz.nitzan
 */
public class CommandLineProcess {

    /* --- Members --- */
    private String rootDirectory;
    private String[] args;
    private long timeoutReadLineSeconds;
    private long timeoutProcessMinutes;
    private boolean errorInProcess = false;
    private Process processStart = null;
    private File errorLog = new File(UniqueNamesGenerator.createUniqueName(Constants.ERROR, ".log"));

    /* --- Statics Members --- */
    private static boolean includeErrorLines;
    private static final long DEFAULT_TIMEOUT_READLINE_SECONDS = 300;
    private static final long DEFAULT_TIMEOUT_PROCESS_MINUTES = 15;
    private final Logger logger = LoggerFactory.getLogger(CommandLineProcess.class);

    protected CommandLineProcess(String rootDirectory, String[] args) {
        this.rootDirectory = rootDirectory;
        this.args = args;
        this.timeoutReadLineSeconds = DEFAULT_TIMEOUT_READLINE_SECONDS;
        this.timeoutProcessMinutes = DEFAULT_TIMEOUT_PROCESS_MINUTES;
    }

    protected List executeProcess() throws IOException {
        return executeProcess(true, includeErrorLines);
    }

    protected void executeProcessWithoutOutput() throws IOException {
        executeProcess(false, includeErrorLines);
    }

    protected List executeProcessWithErrorOutput() throws IOException {
        return executeProcess(false, true);
    }

    // when includeErrorLines = true, includeOutput should false
    private List executeProcess(boolean includeOutput, boolean includeErrorLines) throws IOException {
        List linesOutput = new LinkedList<>();
        ProcessBuilder pb = new ProcessBuilder(args);
        String osName = System.getProperty(Constants.OS_NAME);
        if (osName.startsWith(Constants.WINDOWS)) {
            rootDirectory = getShortPath(rootDirectory);
        }
        pb.directory(new File(rootDirectory));
        // redirect the error output to avoid output of npm ls by operating system
        String redirectErrorOutput = OsDetector.isWindows() ? "nul" : "/dev/null";
        if (includeErrorLines) {
            pb.redirectError(errorLog);
        } else {
            pb.redirectError(new File(redirectErrorOutput));
        }
        if (!includeOutput || includeErrorLines) {
            pb.redirectOutput(new File(redirectErrorOutput));
        }
        if (!includeErrorLines) {
            logger.debug("start execute command '{}' in '{}'", PrintUtils.commandArgsToString(args), rootDirectory);
        }
        this.processStart = pb.start();
        if (includeOutput) {
            InputStreamReader inputStreamReader;
            BufferedReader reader;
            ExecutorService executorService = Executors.newFixedThreadPool(1);
            if (!includeErrorLines) {
                inputStreamReader = new InputStreamReader(this.processStart.getInputStream());
            } else {
                inputStreamReader = new InputStreamReader(this.processStart.getErrorStream());
            }
            reader = new BufferedReader(inputStreamReader);
            this.errorInProcess = readBlock(inputStreamReader, reader, executorService, linesOutput, includeErrorLines);
        }
        try {
            this.processStart.waitFor(this.timeoutProcessMinutes, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            this.errorInProcess = true;
            logger.error("'{}' was interrupted {}", args, e);
        }
        if (this.processStart.isAlive() && errorInProcess) {
            logger.debug("error executing command destroying process");
            this.processStart.destroy();
            return linesOutput;
        }
        if (this.getExitStatus() != 0) {
            logger.debug("error in execute command {}", this.getExitStatus());
            this.errorInProcess = true;
        }
        printErrors();
        return linesOutput;
    }
    // using this technique to print to the log the Process's errors as it the easiest way i found to do so -
    // ues a file to redirect the errors to, read from it and then delete it.
    // if you find a better way - go ahead and replace it
    private void printErrors() {
        if (errorLog.isFile()) {
            try (FileReader fileReader = new FileReader(errorLog);
                 BufferedReader bufferedReader = new BufferedReader(fileReader)){
                String currLine;
                while ((currLine = bufferedReader.readLine()) != null) {
                    logger.warn(currLine);
                }
            } catch (Exception e) {
                logger.warn("Error printing cmd command errors {} ", e.getMessage());
                logger.debug("Error: {}", e.getStackTrace());
            } finally {
                try {
                    FileUtils.forceDelete(errorLog);
                } catch (IOException e) {
                    logger.warn("Error closing cmd command errors file {} ", e.getMessage());
                    logger.debug("Error: {}", e.getStackTrace());
                }
            }
        }
    }
    //get windows short path
    private String getShortPath(String rootPath) {
        File file = new File(rootPath);
        String lastPathAfterSeparator = null;
        String shortPath = getWindowsShortPath(file.getAbsolutePath());
        if (StringUtils.isNotEmpty(shortPath)) {
            return getWindowsShortPath(file.getAbsolutePath());
        } else {
            while (StringUtils.isEmpty(getWindowsShortPath(file.getAbsolutePath()))) {
                String filePath = file.getAbsolutePath();
                if (StringUtils.isNotEmpty(lastPathAfterSeparator)) {
                    lastPathAfterSeparator = file.getAbsolutePath().substring(filePath.lastIndexOf(Constants.BACK_SLASH), filePath.length()) + lastPathAfterSeparator;
                } else {
                    lastPathAfterSeparator = file.getAbsolutePath().substring(filePath.lastIndexOf(Constants.BACK_SLASH), filePath.length());
                }
                file = file.getParentFile();
            }
            return getWindowsShortPath(file.getAbsolutePath()) + lastPathAfterSeparator;
        }
    }

    private String getWindowsShortPath(String path) {
        if (path.length() >= 256) {
            char[] result = new char[256];

            //Call CKernel32 interface to execute GetShortPathNameA method
            Kernel32.INSTANCE.GetShortPathName(path, result, result.length);
            return Native.toString(result);
        }
        return path;
    }

    private boolean readBlock(InputStreamReader inputStreamReader, BufferedReader reader, ExecutorService executorService, List lines, boolean includeErrorLines) {
        boolean wasError = false;
        boolean continueReadingLines = true;
        try {
            if (!includeErrorLines) {
                logger.debug("trying to read lines using '{}'", PrintUtils.commandArgsToString(args));
            }
            int lineIndex = 1;
            String line = Constants.EMPTY_STRING;
            while (continueReadingLines && line != null) {
                Future future = executorService.submit(new CommandLineProcess.ReadLineTask(reader));
                try {
                    line = future.get(this.timeoutReadLineSeconds, TimeUnit.SECONDS);
                    if (!includeErrorLines) {
                        if (StringUtils.isNotBlank(line)) {
                            logger.debug("Read line #{}: {}", lineIndex, line);
                            lines.add(line);
                        } else {
                            logger.debug("Finished reading {} lines", lineIndex - 1);
                        }
                    } else {
                        if (StringUtils.isNotBlank(line)) {
                            lines.add(line);
                        }
                    }
                } catch (TimeoutException e) {
                    logger.debug("Received timeout when reading line #" + lineIndex, e.getStackTrace());
                    continueReadingLines = false;
                    wasError = true;
                } catch (Exception e) {
                    logger.debug("Error reading line #" + lineIndex, e.getStackTrace());
                    continueReadingLines = false;
                    wasError = true;
                }
                lineIndex++;
            }
        } catch (Exception e) {
            logger.error("error parsing output : {}", e.getStackTrace());
        } finally {
            executorService.shutdown();
            IOUtils.closeQuietly(inputStreamReader);
            IOUtils.closeQuietly(reader);
        }
        return wasError;
    }

    public void setTimeoutReadLineSeconds(long timeoutReadLineSeconds) {
        this.timeoutReadLineSeconds = timeoutReadLineSeconds;
    }

    public void setTimeoutProcessMinutes(long timeoutProcessMinutes) {
        this.timeoutProcessMinutes = timeoutProcessMinutes;
    }

    public boolean isErrorInProcess() {
        return this.errorInProcess;
    }

    public int getExitStatus() {
        if (processStart != null) {
            return processStart.exitValue();
        }
        return 0;
    }

    public String getRootDirectory() {
        return rootDirectory;
    }

    public void setRootDirectory(String rootDirectory) {
        this.rootDirectory = rootDirectory;
    }

    public String[] getArgs() {
        return args;
    }

    public void setArgs(String[] args) {
        this.args = args;
    }

    public static boolean isIncludeErrorLines() {
        return includeErrorLines;
    }

    public static void setIncludeErrorLines(boolean value) {
        includeErrorLines = value;
    }

    /* --- Nested classes --- */

    class ReadLineTask implements Callable {

        /* --- Members --- */

        private final BufferedReader reader;

        /* --- Constructors --- */

        ReadLineTask(BufferedReader reader) {
            this.reader = reader;
        }

        /* --- Overridden methods --- */

        @Override
        public String call() throws Exception {
            return reader.readLine();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy