1  package com.palantir.blog.processspawner;
2  
3  /*
4   * All source code and information in this file is made
5   * available under the following licensing terms:
6   *
7   * Copyright (c) 2009, Palantir Technologies, Inc.
8   * All rights reserved.
9   *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions are
12  * met:
13  *
14  *     * Redistributions of source code must retain the above copyright
15  *       notice, this list of conditions and the following disclaimer.
16  *
17  *     * Redistributions in binary form must reproduce the above
18  *       copyright notice, this list of conditions and the following
19  *       disclaimer in the documentation and/or other materials provided
20  *       with the distribution.
21  *
22  *     * Neither the name of Palantir Technologies, Inc. nor the names of its
23  *       contributors may be used to endorse or promote products derived
24  *       from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  *
38  *
39  */ 
40 
41 import java.io.BufferedReader;
42 import java.io.ByteArrayOutputStream;
43 import java.io.File;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.PrintStream;
48 import java.util.List;
49 import java.util.Map;
50 
51 import org.apache.log4j.LogManager;
52 import org.apache.log4j.Logger;
53 
54 public class ProcessSpawner {
55 
56    static final Logger log = LogManager.getLogger(ProcessSpawner.class);
57    
58    ProcessBuilder pb = null;
59    OutputPiper out = null;
60    OutputPiper err = null;
61    Process p = null;
62    String[] args;
63 
64    /**
65     * @param workingDirectory Directory this should run in.
66     * @param args Command line to be executed 
67     * @param environmentToMerge environment entries to be merged (overwriting) with the current environment
68     * to be used as the environment for spawned processes.
69     */
70    public ProcessSpawner(File workingDirectory, String[] cmdarray,Map<String,String> environmentToMerge) {
71       pb = new ProcessBuilder();
72       // set the working directory
73       if(workingDirectory != null) {
74          pb.directory(workingDirectory);  
75       }
76       pb.command(cmdarray);
77       if(log.isInfoEnabled()) {
78          StringBuilder cmdLine = new StringBuilder();
79          for(String token : cmdarray) {
80             cmdLine.append(token).append(" ");
81          }
82          log.info("Build process spawner for the following command line:");
83          log.info(cmdLine.toString());
84       }
85       // deal with the environment
86       Map<String,String> env = pb.environment();
87       if(environmentToMerge != null && environmentToMerge.size() > 0) {
88          env.putAll(environmentToMerge);
89       }
90    
91       if(log.isDebugEnabled()) {
92          log.debug("Environment for new processses: \n");
93          for(Map.Entry<String,String> envEntry : env.entrySet()) {
94             log.debug("\t" + envEntry.getKey() + "\t=\t" + envEntry.getValue() );
95          }
96       }
97    }
98 
99    @SuppressWarnings("unused")
100   private ProcessSpawner() {/**/}
101
102   public static class OutputPiper extends Thread  {
103      InputStream in;
104      PrintStream out;
105      String tag = null;
106
107      public OutputPiper(String tag, InputStream in,PrintStream out) {
108         this.in = in;
109         this.out = out;
110         this.tag = tag; 
111         // make sure that we don't keep the VM alive
112         this.setDaemon(true);
113         this.setName("OutputPiper-" + tag);
114         out.println("Starting output piper for tag: " + tag);
115         this.start();
116      }
117
118      @Override
119      public void run() {
120         try {
121            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
122            String line = null;
123            do {
124               line = reader.readLine();
125               if(line != null) {
126                  out.println(tag + ": " + line);
127               }
128            }while(line != null);
129         }
130         catch (Exception e) {
131            //
132         }
133         out.println("Output piper exiting for tag: " + tag);
134      }
135
136      public static OutputPiper createOutputPiper(String tag, InputStream in, PrintStream out) {
137         OutputPiper rc = new OutputPiper(tag, in,out);
138         return rc;
139      }
140   }     
141
142   public List<String> command() {
143      return pb.command();
144   }
145
146   public File directory() {
147      return pb.directory();
148   }
149
150   public ProcessBuilder directory(File directory) {
151      return pb.directory(directory);
152   }
153
154   public Map<String, String> environment() {
155      return pb.environment();
156   }
157
158   public boolean redirectErrorStream() {
159      return pb.redirectErrorStream();
160   }
161
162   public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
163      return pb.redirectErrorStream(redirectErrorStream);
164   }
165
166   public Process start() throws IOException {
167      p = pb.start();
168      return p;
169   }     
170
171   /**
172    * Like calling start, but uses {@link OutputPiper} classes
173    * to redirect ouptut back to this JVM's console.
174    * 
175    * @param tag to tag the Outpiper's output with
176    * @return
177    * @throws IOException
178    */
179   public Process startStdinStderrInstance(String tag) throws IOException {
180      start();
181      out = OutputPiper.createOutputPiper(tag+ "-stdout", p.getInputStream(), System.out);
182      err = OutputPiper.createOutputPiper(tag+ "-stderr", p.getErrorStream(), System.err);
183      return p;
184   }
185   
186   public void waitForProcess() throws Exception {
187      if(p != null) {
188         try {
189            if(out != null) {
190               out.join();
191            }
192            if(err != null) {
193               err.join();
194            }
195            p.waitFor();
196         } catch (InterruptedException e) {
197            throw new Exception("Interrupted while waiting for process to exit",e);
198         }
199      } else {
200         throw new Exception("Process not yet started!");
201      }
202   }
203
204   public static String exec(String cmdLine) throws Exception {
205      ByteArrayOutputStream baos = new ByteArrayOutputStream();
206      PrintStream out = new PrintStream(baos);
207      String[] cmdArray = cmdLine.split("\\s+");
208      ProcessSpawner ps = new ProcessSpawner(null,cmdArray,null);
209      Process p = ps.start();
210      OutputPiper pipe = OutputPiper.createOutputPiper(null, p.getInputStream(), out);
211      pipe.join();
212      p.waitFor();
213      out.close();
214      byte[] bytes = baos.toByteArray();
215      return new String(bytes);
216   }
217}
218
219