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