    1   /* ProcessUtils.java
    2    *
    3    * $Id: ProcessUtils.java 4223 2006-05-02 02:54:20Z stack-sf $
    4    *
    5    * Created Jul 19, 2005
    6    *
    7    * Copyright (C) 2005 Internet Archive.
    8    *
    9    * This file is part of the Heritrix web crawler (crawler.archive.org).
    10   *
    11   * Heritrix is free software; you can redistribute it and/or modify
    12   * it under the terms of the GNU Lesser Public License as published by
    13   * the Free Software Foundation; either version 2.1 of the License, or
    14   * any later version.
    15   *
    16   * Heritrix is distributed in the hope that it will be useful,
    17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    19   * GNU Lesser Public License for more details.
    20   *
    21   * You should have received a copy of the GNU Lesser Public License
    22   * along with Heritrix; if not, write to the Free Software
    23   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    24   */
    25  package org.archive.util;
    26
    27  import java.io.BufferedReader;
    28  import java.io.IOException;
    29  import java.io.InputStream;
    30  import java.io.InputStreamReader;
    31  import java.util.logging.Level;
    32  import java.util.logging.Logger;
    33
    34  /***
    35   * Class to run an external process.
    36   * @author stack
    37   * @version $Date: 2006-05-02 02:54:20 +0000 (Tue, 02 May 2006) $ $Revision: 4223 $
    38   */
    39  public class ProcessUtils {
    40      private static final Logger LOGGER =
    41          Logger.getLogger(ProcessUtils.class.getName());
    42
    43      protected ProcessUtils() {
    44          super();
    45      }
    46
    47      /***
    48       * Thread to gobble up an output stream.
    49       * See http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
    50       */
    51      protected class StreamGobbler extends Thread {
    52          private final InputStream is;
    53          private final StringBuffer sink = new StringBuffer();
    54
    55          StreamGobbler(InputStream is, String name) {
    56              this.is = is;
    57              setName(name);
    58          }
    59
    60          public void run() {
    61              try {
    62                  BufferedReader br =
    63                      new BufferedReader(new InputStreamReader(this.is));
    64                  for (String line = null; (line = br.readLine()) != null;) {
    65                      this.sink.append(line);
    66                  }
    67              } catch (IOException ioe) {
    68                  ioe.printStackTrace();
    69              }
    70          }
    71
    72          public String getSink() {
    73              return this.sink.toString();
    74          }
    75      }
    76
    77      /***
    78       * Data structure to hold result of a process exec.
    79       * @author stack
    80       * @version $Date: 2006-05-02 02:54:20 +0000 (Tue, 02 May 2006) $ $Revision: 4223 $
    81       */
    82      public class ProcessResult {
    83          private final String [] args;
    84          private final int result;
    85          private final String stdout;
    86          private final String stderr;
    87
    88          protected ProcessResult(String [] args, int result, String stdout,
    89                      String stderr) {
    90              this.args = args;
    91              this.result = result;
    92              this.stderr = stderr;
    93              this.stdout = stdout;
    94          }
    95
    96          public int getResult() {
    97              return this.result;
    98          }
    99
    100         public String getStdout() {
    101             return this.stdout;
    102         }
    103
    104         public String getStderr() {
    105             return this.stderr;
    106         }
    107
    108         public String toString() {
    109             StringBuffer sb = new StringBuffer();
    110             for (int i = 0; i < this.args.length; i++) {
    111                 sb.append(this.args[i]);
    112                 sb.append(", ");
    113             }
    114             return sb.toString() + " exit code: " + this.result +
    115                 ((this.stderr != null && this.stderr.length() > 0)?
    116                     "\nSTDERR: " + this.stderr: "") +
    117                 ((this.stdout != null && this.stdout.length() > 0)?
    118                     "\nSTDOUT: " + this.stdout: "");
    119         }
    120     }
    121
    122     /***
    123      * Runs process.
    124      * @param args List of process args.
    125      * @return A ProcessResult data structure.
    126      * @throws IOException If interrupted, we throw an IOException. If non-zero
    127      * exit code, we throw an IOException (This may need to change).
    128      */
    129     public static ProcessUtils.ProcessResult exec(String [] args)
    130     throws IOException {
    131         Process p = Runtime.getRuntime().exec(args);
    132         ProcessUtils pu = new ProcessUtils();
    133         // Gobble up any output.
    134         StreamGobbler err = pu.new StreamGobbler(p.getErrorStream(), "stderr");
    135         err.setDaemon(true);
    136         err.start();
    137         StreamGobbler out = pu.new StreamGobbler(p.getInputStream(), "stdout");
    138         out.setDaemon(true);
    139         out.start();
    140         int exitVal;
    141         try {
    142             exitVal = p.waitFor();
    143         } catch (InterruptedException e) {
    144             throw new IOException("Wait on process " + args + " interrupted: "
    145                 + e.getMessage());
    146         }
    147         ProcessUtils.ProcessResult result =
    148             pu.new ProcessResult(args, exitVal, out.getSink(), err.getSink());
    149         if (exitVal != 0) {
    150             throw new IOException(result.toString());
    151         } else if (LOGGER.isLoggable(Level.INFO)) {
    152             LOGGER.info(result.toString());
    153         }
    154         return result;
    155     }
    156 }