package edu.mayo.bior.cli.cmd; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.log4j.Logger; import edu.mayo.bior.util.StreamConnector; import edu.mayo.cli.CommandPlugin; /** * Command that executes a script. Since the script will run in a process * independent of "this" JVM process, this class does the logic for handling * the STDIN, STDOUT, and STDERR streams between the 2 independent processes. * * @author duffp * */ public abstract class GenericScriptCommand implements CommandPlugin { private static final Logger sLogger = Logger.getLogger(GenericScriptCommand.class); // TODO: does this need to be configurable? private static final int BUFFER_SIZE = 1024; public void init(Properties props) throws Exception { } /** * Name of the script to execute. This should be the absolute path to the script if the script is not on the JVM process' PATH. * * @param line * @return */ public abstract String getScriptName(CommandLine line); /** * Zero or more arguments passed to the script. * * @param line * @return */ public abstract String[] getScriptArgs(CommandLine line); /** * Shell environment variables setup for the script execution. * * @param line * @return */ public abstract Map<String, String> getEnvVars(CommandLine line); public void execute(CommandLine line, Options opts) throws Exception { String script = getScriptName(line); String[] scriptArgs = getScriptArgs(line); Map<String, String> env = new HashMap<String, String>(); // inject current environment of this JVM process env.putAll(System.getenv()); // inject any additional environment vars env.putAll(getEnvVars(line)); execute(script, scriptArgs, env); } /** * Executes the script. * * @param scriptName name of the script * @param scriptArgs array of zero or more script arguments * @param env Map of name/value pairs that represent environment variables * @throws IOException * @throws InterruptedException * @throws Exception */ private void execute(String scriptName, String[] scriptArgs, Map<String, String> env) throws IOException, InterruptedException, Exception { // translate command and command args into a string array List<String> cmdList = new ArrayList<String>(); cmdList.add(scriptName); for (String arg : scriptArgs) { cmdList.add(arg); } String[] cmdArray = cmdList.toArray(new String[0]); // translate env vars into a string array List<String> envList = new ArrayList<String>(); for (String name: env.keySet()) { String value = env.get(name); envList.add(name + "=" + value); } String[] envArray = envList.toArray(new String[0]); // setup script process Process p = Runtime.getRuntime().exec(cmdArray, envArray); // connect STDERR from script process and store in local memory // STDERR [script process] ---> local byte array ByteArrayOutputStream stderrOutputStream = new ByteArrayOutputStream(); StreamConnector stderrConnector = new StreamConnector(p.getErrorStream(), stderrOutputStream, BUFFER_SIZE); new Thread(stderrConnector).start(); // connect STDIN from this JVM process to STDIN for script process // STDIN [this jvm] ---> STDIN [script process] StreamConnector stdinConnector = new StreamConnector(System.in, p.getOutputStream(), BUFFER_SIZE); new Thread(stdinConnector).start(); // connect STDOUT for script process to STDOUT for this JVM process // STDOUT [script process] ---> STDOUT [this jvm] StreamConnector stdoutConnector = new StreamConnector(p.getInputStream(), System.out, BUFFER_SIZE); new Thread(stdoutConnector).start(); // block until process ends int exitCode = p.waitFor(); // check if process exited abnormally String stderr = stderrOutputStream.toString("UTF-8"); if (stderr.length() > 0) { System.err.println(stderr); sLogger.error(stderr); } if (exitCode != 0) { throw new Exception(stderr); } } }