289N/A/*
289N/A * CDDL HEADER START
289N/A *
289N/A * The contents of this file are subject to the terms of the
289N/A * Common Development and Distribution License (the "License").
289N/A * You may not use this file except in compliance with the License.
289N/A *
289N/A * See LICENSE.txt included in this distribution for the specific
289N/A * language governing permissions and limitations under the License.
289N/A *
289N/A * When distributing Covered Code, include this CDDL HEADER in each
289N/A * file and include the License file at LICENSE.txt.
289N/A * If applicable, add the following below this CDDL HEADER, with the
289N/A * fields enclosed by brackets "[]" replaced with your own identifying
289N/A * information: Portions Copyright [yyyy] [name of copyright owner]
289N/A *
289N/A * CDDL HEADER END
289N/A */
577N/A
965N/A/*
1228N/A * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
965N/A */
965N/A
289N/Apackage org.opensolaris.opengrok.util;
289N/A
472N/Aimport java.io.ByteArrayInputStream;
472N/Aimport java.io.ByteArrayOutputStream;
289N/Aimport java.io.File;
289N/Aimport java.io.IOException;
289N/Aimport java.io.InputStream;
472N/Aimport java.io.InputStreamReader;
472N/Aimport java.io.Reader;
1264N/Aimport java.lang.Thread.UncaughtExceptionHandler;
1053N/Aimport java.util.Arrays;
289N/Aimport java.util.List;
427N/Aimport java.util.logging.Level;
1264N/Aimport java.util.logging.Logger;
1264N/A
289N/A/**
289N/A * Wrapper to Java Process API
289N/A *
289N/A * @author Emilio Monti - emilmont@gmail.com
289N/A */
289N/Apublic class Executor {
289N/A
1264N/A private static final Logger log = Logger.getLogger(Executor.class.getName());
289N/A private List<String> cmdList;
289N/A private File workingDirectory;
472N/A private byte[] stdout;
472N/A private byte[] stderr;
289N/A
514N/A /**
577N/A * Create a new instance of the Executor.
514N/A * @param cmd An array containing the command to execute
514N/A */
514N/A public Executor(String[] cmd) {
1053N/A this(Arrays.asList(cmd));
514N/A }
514N/A
577N/A /**
577N/A * Create a new instance of the Executor.
577N/A * @param cmdList A list containing the command to execute
577N/A */
289N/A public Executor(List<String> cmdList) {
289N/A this(cmdList, null);
289N/A }
289N/A
577N/A /**
577N/A * Create a new instance of the Executor
577N/A * @param cmdList A list containing the command to execute
577N/A * @param workingDirectory The directory the process should have as the
577N/A * working directory
577N/A */
289N/A public Executor(List<String> cmdList, File workingDirectory) {
289N/A this.cmdList = cmdList;
289N/A this.workingDirectory = workingDirectory;
289N/A }
289N/A
514N/A /**
514N/A * Execute the command and collect the output. All exceptions will be
514N/A * logged.
514N/A *
514N/A * @return The exit code of the process
514N/A */
513N/A public int exec() {
514N/A return exec(true);
514N/A }
573N/A
514N/A /**
514N/A * Execute the command and collect the output
1190N/A *
514N/A * @param reportExceptions Should exceptions be added to the log or not
514N/A * @return The exit code of the process
514N/A */
514N/A public int exec(boolean reportExceptions) {
573N/A SpoolHandler spoolOut = new SpoolHandler();
573N/A int ret = exec(reportExceptions, spoolOut);
573N/A stdout = spoolOut.getBytes();
573N/A return ret;
573N/A }
573N/A
573N/A /**
573N/A * Execute the command and collect the output
1190N/A *
573N/A * @param reportExceptions Should exceptions be added to the log or not
573N/A * @param handler The handler to handle data from standard output
573N/A * @return The exit code of the process
573N/A */
573N/A public int exec(final boolean reportExceptions, StreamHandler handler) {
513N/A int ret = -1;
513N/A
289N/A ProcessBuilder processBuilder = new ProcessBuilder(cmdList);
289N/A if (workingDirectory != null) {
289N/A processBuilder.directory(workingDirectory);
663N/A if (processBuilder.environment().containsKey("PWD")) {
663N/A processBuilder.environment().put("PWD", workingDirectory.getAbsolutePath());
663N/A }
289N/A }
289N/A
1327N/A log.log(Level.FINE, "Executing command [{0}] in directory ''{1}''",
965N/A new Object[] {
965N/A processBuilder.command(),
965N/A processBuilder.directory(),
965N/A });
965N/A
289N/A Process process = null;
289N/A try {
289N/A process = processBuilder.start();
573N/A
573N/A final InputStream errorStream = process.getErrorStream();
573N/A final SpoolHandler err = new SpoolHandler();
573N/A Thread thread = new Thread(new Runnable() {
573N/A
815N/A @Override
573N/A public void run() {
573N/A try {
573N/A err.processStream(errorStream);
573N/A } catch (IOException ex) {
573N/A if (reportExceptions) {
1327N/A log.warning("Error during process pipe listening: "
1327N/A + ex.getMessage());
1327N/A log.log(Level.FINE, "run", ex);
573N/A }
573N/A }
573N/A }
573N/A });
573N/A thread.start();
573N/A
573N/A handler.processStream(process.getInputStream());
573N/A
513N/A ret = process.waitFor();
513N/A process = null;
573N/A thread.join();
573N/A stderr = err.getBytes();
289N/A } catch (IOException e) {
514N/A if (reportExceptions) {
1327N/A log.warning("Failed to read from process " + cmdList.get(0)
1327N/A + ": " + e.getMessage());
1327N/A log.log(Level.FINE, "exec", e);
514N/A }
289N/A } catch (InterruptedException e) {
514N/A if (reportExceptions) {
1327N/A log.warning("Waiting for process interrupted: " + cmdList.get(0)
1327N/A + ": " + e.getMessage());
1327N/A log.log(Level.FINE, "exec", e);
514N/A }
289N/A } finally {
289N/A try {
289N/A if (process != null) {
513N/A ret = process.exitValue();
289N/A }
289N/A } catch (IllegalThreadStateException e) {
289N/A process.destroy();
289N/A }
289N/A }
513N/A
1228N/A if (ret != 0 && reportExceptions) {
1228N/A int MAX_MSG_SZ = 512; /* limit to avoid floodding the logs */
1228N/A StringBuilder msg = new StringBuilder("Non-zero exit status ")
1327N/A .append(ret).append(" from command [")
1228N/A .append(processBuilder.command().toString())
1327N/A .append("] in directory '");
1228N/A File cwd = processBuilder.directory();
1229N/A if (cwd == null) {
1229N/A msg.append(System.getProperty("user.dir"));
1228N/A } else {
1229N/A msg.append(cwd.toString());
1228N/A }
1228N/A if (stderr != null && stderr.length > 0) {
1327N/A msg.append("': ");
1228N/A if (stderr.length > MAX_MSG_SZ) {
1228N/A msg.append(new String(stderr, 0, MAX_MSG_SZ)).append("...");
1228N/A } else {
1228N/A msg.append(new String(stderr));
1228N/A }
1327N/A } else {
1327N/A msg.append("'");
1228N/A }
1327N/A log.warning(msg.toString());
965N/A }
965N/A
513N/A return ret;
289N/A }
289N/A
577N/A /**
577N/A * Get the output from the process as a string.
1190N/A *
577N/A * @return The output from the process
577N/A */
472N/A public String getOutputString() {
692N/A String ret = null;
692N/A if (stdout != null) {
692N/A ret = new String(stdout);
692N/A }
692N/A
692N/A return ret;
289N/A }
289N/A
577N/A /**
577N/A * Get a reader to read the output from the process
1190N/A *
577N/A * @return A reader reading the process output
577N/A */
472N/A public Reader getOutputReader() {
472N/A return new InputStreamReader(getOutputStream());
472N/A }
472N/A
577N/A /**
577N/A * Get an input stream read the output from the process
1190N/A *
577N/A * @return A reader reading the process output
577N/A */
472N/A public InputStream getOutputStream() {
472N/A return new ByteArrayInputStream(stdout);
472N/A }
472N/A
577N/A /**
577N/A * Get the output from the process written to the error stream as a string.
1190N/A *
577N/A * @return The error output from the process
577N/A */
472N/A public String getErrorString() {
692N/A String ret = null;
692N/A if (stderr != null) {
692N/A ret = new String(stderr);
692N/A }
692N/A
692N/A return ret;
289N/A }
289N/A
577N/A /**
577N/A * Get a reader to read the output the process wrote to the error stream.
1190N/A *
577N/A * @return A reader reading the process error stream
577N/A */
472N/A public Reader getErrorReader() {
472N/A return new InputStreamReader(getErrorStream());
472N/A }
472N/A
577N/A /**
577N/A * Get an inputstreamto read the output the process wrote to the error stream.
1190N/A *
577N/A * @return An inputstream for reading the process error stream
577N/A */
472N/A public InputStream getErrorStream() {
472N/A return new ByteArrayInputStream(stderr);
289N/A }
289N/A
573N/A /**
573N/A * You should use the StreamHandler interface if you would like to process
573N/A * the output from a process while it is running
573N/A */
573N/A public static interface StreamHandler {
289N/A
573N/A /**
573N/A * Process the data in the stream. The processStream function is
573N/A * called _once_ during the lifetime of the process, and you should
573N/A * process all of the input you want before returning from the function.
1190N/A *
573N/A * @param in The InputStream containing the data
573N/A * @throws java.io.IOException
573N/A */
573N/A public void processStream(InputStream in) throws IOException;
573N/A }
573N/A
573N/A private static class SpoolHandler implements StreamHandler {
573N/A
504N/A private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
472N/A
472N/A public byte[] getBytes() {
472N/A return bytes.toByteArray();
472N/A }
289N/A
815N/A @Override
573N/A public void processStream(InputStream in) throws IOException {
573N/A byte[] buffer = new byte[8092];
573N/A int len;
289N/A
573N/A while ((len = in.read(buffer)) != -1) {
573N/A if (len > 0) {
573N/A bytes.write(buffer, 0, len);
289N/A }
289N/A }
289N/A }
289N/A }
1264N/A
1264N/A public static void registerErrorHandler() {
1327N/A Thread.currentThread();
1264N/A UncaughtExceptionHandler dueh =
1327N/A Thread.getDefaultUncaughtExceptionHandler();
1264N/A if (dueh == null) {
1264N/A log.fine("Installing default uncaught exception handler");
1264N/A Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
1264N/A @Override
1264N/A public void uncaughtException(Thread t, Throwable e) {
1327N/A // this should be the only one case, where an exception
1327N/A // stack trace gets logged with a Level > FINE !!!
1264N/A log.log(Level.SEVERE, "Uncaught exception in thread "
1264N/A + t.getName() + " with ID " + t.getId() + ": "
1264N/A + e.getMessage(), e);
1264N/A }
1264N/A });
1264N/A }
1264N/A }
289N/A}