/* JTestServer is a client/server framework for testing any JVM implementation. Copyright (C) 2008 Fabien DUMINY (fduminy@jnode.org) JTestServer 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 2 of the License, or (at your option) any later version. JTestServer 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.jtestserver.client.utils; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.jtestserver.client.utils.PipeInputStream.Listener; /** * Utility class used to provide a higher level API for running commands * on the command line by wrapping the {@link Runtime#exec(String)} method * and its variants.The main added features are the optional ability to wait * for process termination and to redirect output and error streams. * * @author Fabien DUMINY (fduminy@jnode.org) * */ public class ProcessRunner { /** * Logger used for our internal usage. */ private static final Logger LOGGER = Logger.getLogger(ProcessRunner.class.getName()); /** * Logger used by our {@link PipeInputStream}s. */ static final Logger SERVER_LOGGER = Logger.getLogger("Server"); /** * Default work directory for the processes we are launching. */ private static final File DEFAULT_WORK_DIR = new File(System.getProperty("user.home")); /** * {@link PipeInputStream} used to redirect the process output stream. */ private PipeInputStream outputPipe; /** * {@link PipeInputStream} used to redirect the process error stream. */ private PipeInputStream errorPipe; /** * The process we have launched. */ private Process process; /** * The actual work directory */ private File workDir = DEFAULT_WORK_DIR; /** * Executes a command and waits for its termination. * * @param command to execute. * @return true on successful termination of the command * @throws IOException */ public boolean executeAndWait(String... command) throws IOException { return executeAndWait(null, command); } /** * Executes a command and waits for its termination. * Also listen for lines received from the process streams. * * @param listener optional listener for doing additional processing on the lines received * from the process input & error streams. * @param command to execute. * @return true on successful termination of the command * @throws IOException */ public boolean executeAndWait(Listener listener, String... command) throws IOException { return executeAndWait(listener, new int[]{0}, command); } /** * Executes a command and waits for its termination. * Also listen for lines received from the process streams and give the return codes that * means successful completion of the command. * * @param listener optional listener for doing additional processing on the lines received * from the process input & error streams. * @param successExitValue list of codes by the command in case of success. * @param command to execute. * @return true on successful termination of the command * @throws IOException */ public boolean executeAndWait(Listener listener, int[] successExitValue, String... command) throws IOException { boolean success = false; execute(listener, listener, command); try { int exitValue = process.waitFor(); for (int ec : successExitValue) { if (exitValue == ec) { success = true; break; } } LOGGER.info("exit value : " + exitValue); } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, "error while waiting for process", e); } outputPipe.waitFor(); errorPipe.waitFor(); return success; } /** * Executes a command without waiting for its termination. * @param command to execute * @throws IOException */ public void execute(String... command) throws IOException { execute(null, null, command); } /** * Executes a command without waiting for its termination. * @param outputListener listener for additional processing on the process output stream * @param errorListener listener for additional processing on the process error stream * @param command to execute * @throws IOException */ public void execute(Listener outputListener, Listener errorListener, String... command) throws IOException { LOGGER.finer("command: " + command); Map<String, String> env = System.getenv(); String[] envArray = new String[env.size()]; int i = 0; for (String key : env.keySet()) { envArray[i++] = key + "=" + env.get(key); } process = Runtime.getRuntime().exec(command, envArray, workDir); outputPipe = new PipeInputStream(process.getInputStream(), SERVER_LOGGER, Level.FINEST, outputListener); outputPipe.start(); errorPipe = new PipeInputStream(process.getErrorStream(), SERVER_LOGGER, Level.SEVERE, errorListener); errorPipe.start(); } /** * Get the launched process * @return */ public Process getProcess() { return process; } /** * Defines the work directory. It must be called before any of the execute/executeAndWait methods. * @param workDir the work directory */ public void setWorkDir(File workDir) { this.workDir = ((workDir == null) || !workDir.isDirectory()) ? DEFAULT_WORK_DIR : workDir; } /** * Execute the given command line. * * @param cmdLine command line to execute * @return true if the execution succeed * @throws IOException */ public boolean execute(CommandLineBuilder cmdLine) throws IOException { final List<Boolean> errors = new Vector<Boolean>(); execute(null, new PipeInputStream.Listener() { @Override public void lineReceived(String line) { errors.add(Boolean.TRUE); } }, cmdLine.toArray()); LOGGER.log(Level.INFO, "command line: " + cmdLine.toString()); // wait a bit to see if an error happen // but don't wait the end of the process try { Thread.sleep(100); } catch (InterruptedException e) { // ignore } return errors.isEmpty(); } }