Command.java

  1. package gov.usgs.earthquake.distribution;

  2. import gov.usgs.util.StreamUtils;

  3. import java.io.ByteArrayOutputStream;
  4. import java.io.File;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.OutputStream;
  8. import java.util.LinkedList;
  9. import java.util.List;
  10. import java.util.StringTokenizer;
  11. import java.util.Timer;
  12. import java.util.TimerTask;

  13. public class Command {

  14.     private String[] commandArray = null;
  15.     private String[] envp = null;
  16.     private File dir = null;

  17.     private InputStream stdin = null;
  18.     private ByteArrayOutputStream stdout = new ByteArrayOutputStream();
  19.     private ByteArrayOutputStream stderr = new ByteArrayOutputStream();

  20.     private long timeout = 0;
  21.     private int exitCode = -1;

  22.     /** Empty command constructor */
  23.     public Command() {
  24.     }

  25.     /**
  26.      * @throws CommandTimeout CommandTimeout
  27.      * @throws IOException IOException
  28.      * @throws InterruptedException InterruptedException
  29.      */
  30.     public void execute() throws CommandTimeout, IOException,
  31.             InterruptedException {
  32.         StreamTransferThread outputTransfer = null;
  33.         StreamTransferThread errorTransfer = null;

  34.         try {
  35.             final Process process = Runtime.getRuntime().exec(commandArray,
  36.                     envp, dir);

  37.             final Timer timer;
  38.             if (timeout > 0) {
  39.                 timer = new Timer();
  40.                 timer.schedule(new TimerTask() {
  41.                     public void run() {
  42.                         process.destroy();
  43.                     }
  44.                 }, timeout);
  45.             } else {
  46.                 timer = null;
  47.             }

  48.             try {
  49.                 OutputStream processStdin = process.getOutputStream();
  50.                 if (stdin != null) {
  51.                     StreamUtils.transferStream(stdin, processStdin);
  52.                 }
  53.                 StreamUtils.closeStream(processStdin);

  54.                 outputTransfer = new StreamTransferThread(process.getInputStream(),
  55.                         stdout);
  56.                 outputTransfer.start();
  57.                 errorTransfer = new StreamTransferThread(process.getErrorStream(),
  58.                         stderr);
  59.                 errorTransfer.start();

  60.                 // now wait for process to complete
  61.                 exitCode = process.waitFor();
  62.                 if (exitCode == 143) {
  63.                     throw new CommandTimeout();
  64.                 }
  65.             } finally {
  66.                 // cancel destruction of process, if it hasn't already run
  67.                 if (timer != null) {
  68.                     timer.cancel();
  69.                 }
  70.             }
  71.         } finally {
  72.             try {
  73.                 outputTransfer.interrupt();
  74.                 outputTransfer.join();
  75.             } catch (Exception e) {
  76.             }
  77.             try {
  78.                 errorTransfer.interrupt();
  79.                 errorTransfer.join();
  80.             } catch (Exception e) {
  81.             }
  82.         }
  83.     }

  84.     /** @return string[] */
  85.     public String[] getCommand() {
  86.         return commandArray;
  87.     }

  88.     /** @param command single command */
  89.     public void setCommand(final String command) {
  90.         setCommand(splitCommand(command));
  91.     }

  92.     /** @param commandArray string[] */
  93.     public void setCommand(final String[] commandArray) {
  94.         this.commandArray = commandArray;
  95.     }

  96.     /** @return envp */
  97.     public String[] getEnvp() {
  98.         return envp;
  99.     }

  100.     /** @param envp String[] */
  101.     public void setEnvp(final String[] envp) {
  102.         this.envp = envp;
  103.     }

  104.     /** @return dir */
  105.     public File getDir() {
  106.         return dir;
  107.     }

  108.     /** @param dir File */
  109.     public void setDir(final File dir) {
  110.         this.dir = dir;
  111.     }

  112.     /** @return timeout */
  113.     public long getTimeout() {
  114.         return timeout;
  115.     }

  116.     /** @param timeout long */
  117.     public void setTimeout(final long timeout) {
  118.         this.timeout = timeout;
  119.     }

  120.     /** @return exitCode */
  121.     public int getExitCode() {
  122.         return exitCode;
  123.     }

  124.     /** @param stdin InputStream */
  125.     public void setStdin(final InputStream stdin) {
  126.         this.stdin = stdin;
  127.     }

  128.     /** @return stdout byte[] */
  129.     public byte[] getStdout() {
  130.         return stdout.toByteArray();
  131.     }

  132.     /** @return stderr byte[] */
  133.     public byte[] getStderr() {
  134.         return stderr.toByteArray();
  135.     }

  136.     /**
  137.      * Split a command string into a command array.
  138.      *
  139.      * This version uses a StringTokenizer to split arguments. Quoted arguments
  140.      * are supported (single or double), with quotes removed before passing to
  141.      * runtime. Double quoting arguments will preserve quotes when passing to
  142.      * runtime.
  143.      *
  144.      * @param command
  145.      *            command to run.
  146.      * @return Array of arguments suitable for passing to
  147.      *         Runtime.exec(String[]).
  148.      */
  149.     protected static String[] splitCommand(final String command) {
  150.         List<String> arguments = new LinkedList<String>();
  151.         String currentArgument = null;

  152.         // use a tokenizer because that's how Runtime.exec does it currently...
  153.         StringTokenizer tokens = new StringTokenizer(command);
  154.         while (tokens.hasMoreTokens()) {
  155.             String token = tokens.nextToken();

  156.             if (currentArgument == null) {
  157.                 currentArgument = token;
  158.             } else {
  159.                 // continuing previous argument, that was split on whitespace
  160.                 currentArgument = currentArgument + " " + token;
  161.             }

  162.             if (currentArgument.startsWith("\"")) {
  163.                 // double quoted argument
  164.                 if (currentArgument.endsWith("\"")) {
  165.                     // that has balanced quotes
  166.                     // remove quotes and add argument
  167.                     currentArgument = currentArgument.substring(1,
  168.                             currentArgument.length() - 1);
  169.                 } else {
  170.                     // unbalanced quotes, keep going
  171.                     continue;
  172.                 }
  173.             } else if (currentArgument.startsWith("'")) {
  174.                 // single quoted argument
  175.                 if (currentArgument.endsWith("'")) {
  176.                     // that has balanced quotes
  177.                     // remove quotes and add argument
  178.                     currentArgument = currentArgument.substring(1,
  179.                             currentArgument.length() - 1);
  180.                 } else {
  181.                     // unbalanced quotes, keep going
  182.                     continue;
  183.                 }
  184.             }

  185.             arguments.add(currentArgument);
  186.             currentArgument = null;
  187.         }

  188.         if (currentArgument != null) {
  189.             // weird, but add argument anyways
  190.             arguments.add(currentArgument);
  191.         }

  192.         return arguments.toArray(new String[] {});
  193.     }

  194.     public static class CommandTimeout extends Exception {

  195.         private static final long serialVersionUID = 1L;

  196.     }

  197.     private static class StreamTransferThread extends Thread {
  198.         private InputStream in;
  199.         private OutputStream out;

  200.         public StreamTransferThread(final InputStream in, final OutputStream out) {
  201.             this.in = in;
  202.             this.out = out;
  203.         }

  204.         @Override
  205.         public void run() {
  206.             try {
  207.                 StreamUtils.transferStream(in, out);
  208.             } catch (Exception e) {
  209.             }
  210.         }
  211.     }

  212. }