View Javadoc

1   /*
2    * Copyright (c) 1998-2004 The Jgroup Team.
3    *
4    * This program is free software; you can redistribute it and/or modify
5    * it under the terms of the GNU Lesser General Public License version 2 as
6    * published by the Free Software Foundation.
7    *
8    * This program is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   * GNU Lesser General Public License for more details.
12   *
13   * You should have received a copy of the GNU Lesser General Public License
14   * along with this program; if not, write to the Free Software
15   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16   *
17   */
18  
19  package jgroup.experiment;
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.util.Timer;
26  import java.util.TimerTask;
27  
28  /**
29   * General class for executing shell commands.
30   * @author Bjarte Svaeren
31   * @author Hein Meling
32   */
33  public final class ShellCommand
34  {
35  
36    ////////////////////////////////////////////////////////////////////////////////////////////
37    // Fields
38    ////////////////////////////////////////////////////////////////////////////////////////////
39  
40    private static final int WAIT_FOR_TIMEOUT = 10000;
41  
42    /**
43     * If the process generated output to stdout, this will be set to true.
44     */
45    private volatile boolean stdoutChanged = false;
46  
47    /**
48     * If the process generated output to stderr, this will be set to true.
49     */
50    private volatile boolean stderrChanged = false;
51  
52    /**
53     * The process object associated with this shell command
54     */
55    private volatile Process process;
56  
57    /**
58     * If a process failed during creation, stops blocking on this process
59     * if this boolean is set to true.  This is used in waitForProcess(),
60     * and if false, it will block until either the process is created or until
61     * a five second timeout expires, setting this to true.
62     */
63    private volatile boolean timeout = false;
64  
65    /**
66     * True if the command output should be suppressed; false otherwise.
67     * This does not suppress stderr outputs.
68     */
69    private volatile boolean suppressCmdOutput = false;
70  
71    /**
72     * True if the waitFor method did not yet complete, and a timeout was triggered.
73     * False otherwise.
74     */
75    private static boolean waitForTimeout = false;
76  
77  
78    ////////////////////////////////////////////////////////////////////////////////////////////
79    // Constructor
80    ////////////////////////////////////////////////////////////////////////////////////////////
81  
82    /**
83     * Constructs a shell command object for the given <code>cmd</code>
84     * string.  If a thread group is provided, the command will be forked
85     * in a separate thread.
86     *
87     * @param command
88     *   The command to be executed.
89     * @param threadGroup
90     *   The <code>ThreadGroup</code> the shell command will run in. 
91     *   If <code>null</code>, the command will run in the current thread. 
92     */
93    public ShellCommand(final String cmd, ThreadGroup threadGroup)
94    {
95      this(cmd, threadGroup, false);
96    }
97  
98  
99    /**
100    * Constructs a shell command object for the given <code>cmd</code>
101    * string.  If a thread group is provided, the command will be forked
102    * in a separate thread.
103    *
104    * @param command
105    *   The command to be executed.
106    * @param threadGroup
107    *   The <code>ThreadGroup</code> the shell command will run in. 
108    *   If <code>null</code>, the command will run in the current thread. 
109    * @param suppressCmdOutput
110    *   True will suppress command output.
111    */
112   public ShellCommand(final String cmd, final ThreadGroup threadGroup, boolean suppressCmdOutput)
113   {
114     this.suppressCmdOutput = suppressCmdOutput;
115     if (threadGroup != null) {
116       Thread newThread = new Thread(threadGroup, cmd) {
117         public void run() {
118           try {
119             createProcess(cmd, threadGroup);
120           } catch (IOException e) {
121             e.printStackTrace();
122             System.out.println("FAILED: " + cmd);
123           }
124         }
125       };
126       newThread.start();
127     } else {
128       try {
129         createProcess(cmd, null);
130       } catch (IOException e) {
131         e.printStackTrace();
132         System.out.println("FAILED: " + cmd);
133       }
134     }
135   }
136 
137   ////////////////////////////////////////////////////////////////////////////////////////////
138   // Public methods
139   ////////////////////////////////////////////////////////////////////////////////////////////
140 
141   public boolean waitForProcess()
142   {
143     TimerTask tt = new TimerTask() {
144       public void run() {
145         timeout = true;
146       }
147     };
148     Timer timer = new Timer("WaitForProcess", true);
149     timer.schedule(tt, 5000);
150     //FIXME this should not use busy waiting.
151     while (process == null && !timeout) { }
152     if (process == null)
153       return false;
154     try {
155       process.waitFor();
156     } catch (InterruptedException e) {
157       process.destroy();
158     }
159     timer.cancel();
160     return true;
161   }
162 
163   public boolean stdoutModified()
164   {
165     return stdoutChanged;
166   }
167 
168   public boolean stderrModified()
169   {
170     return stderrChanged;
171   }
172 
173 
174   ////////////////////////////////////////////////////////////////////////////////////////////
175   // Static access methods
176   ////////////////////////////////////////////////////////////////////////////////////////////
177 
178   /**
179    * Takes a <code>String</code> and a <code>ThreadGroup</code>
180    * as parameters, and executes the string as a shell command.
181    * A new <code>Thread</code> in the <code>ThreadGroup</code> 
182    * given as a parameter, unless the <code>ThreadGroup</code>
183    * parameter is <code>null</code>.
184    * If the parameter is <code>null</code>, the command is run in
185    * the current thread.
186    *  
187    * @param command The command to be executed.
188    * @param threadGroup The ThreadGroup the shell command will run in. 
189    * If <code>null</code>, the command will run in the current thread. 
190    * @throws Exception
191    */
192   public static void exec(final String cmd, ThreadGroup threadGroup)
193     throws IOException
194   {
195     new ShellCommand(cmd, threadGroup);
196   }
197 
198 
199   /**
200    * Same as exec(commandString, null).
201    * @param command
202    * @throws Exception
203    */
204   public static void exec(final String cmd)
205     throws IOException
206   {
207     exec(cmd, null);
208   }
209   
210 
211   /**
212    * This methods takes a <code>ThreadGroup</code> as a parameter,
213    * and simply loops until all the threads in the <code>ThreadGroup</code>
214    * have completed their tasks.
215    *  
216    * @param threadGroup The <code>ThreadGroup</code> that contain the threads
217    * to wait for.
218    */  
219   public static void waitFor(ThreadGroup threadGroup, boolean debug)
220   {
221     int count = threadGroup.activeCount();
222     final Thread curThread = Thread.currentThread();
223     Timer timer = new Timer("WaitForThreadGroup", true);
224     timer.schedule(new TimerTask() {
225       public void run() {
226         waitForTimeout = true;
227         curThread.interrupt();
228       }
229     }, WAIT_FOR_TIMEOUT*count);
230 
231     // Loop until all threads have finished
232     while (count > 0 && !waitForTimeout) {
233       try {
234         Thread.sleep(2000);
235       } catch (InterruptedException e) {
236         /*
237          * This means that the timer above was triggered and we should
238          * abort the waiting; that is the waitForTimeout=true.  This will
239          * read the active thread count again, and exit the while loop.
240          */
241       }
242       count = threadGroup.activeCount();
243     }
244     if (count > 0) {
245       // Interrupt the remaining threads in an attempt to continue
246       threadGroup.interrupt();
247       if (debug) {
248         System.out.println("Remaining threads: " + count);
249         threadGroup.list();
250       }
251     }
252     timer.cancel();
253     waitForTimeout = false;
254   }
255 
256   public static void waitFor(ThreadGroup threadGroup)
257   {
258     waitFor(threadGroup, false);
259   }
260 
261 
262   ////////////////////////////////////////////////////////////////////////////////////////////
263   // Private methods
264   ////////////////////////////////////////////////////////////////////////////////////////////
265 
266   private void createProcess(final String cmd, ThreadGroup threadGroup)
267     throws IOException
268   {
269     stdoutChanged = false;
270     stderrChanged = false;
271     process = Runtime.getRuntime().exec(cmd);
272     if (!suppressCmdOutput)
273       System.out.println(cmd);
274     final String scmd = cmd.split(" ")[0];
275 
276     Thread outThread = new Thread(scmd + "-out") {
277       public void run() {
278         InputStream processOut = process.getInputStream();
279         BufferedReader out = new BufferedReader(new InputStreamReader(processOut));
280         String stdout;
281 
282         try {
283           while ((stdout = out.readLine()) != null) {
284             synchronized(this) {
285               stdoutChanged = true;
286               System.out.println(scmd + "-out: " + stdout);
287             }
288           }
289           out.close();
290         } catch (java.io.IOException e) {
291           e.printStackTrace();
292           System.out.println(cmd + " failed");
293         }
294       }
295     };
296 
297     Thread errorThread = new Thread(scmd + "-err") {
298       public void run() {
299         InputStream processError = process.getErrorStream();
300         BufferedReader err = new BufferedReader(new InputStreamReader(processError));
301         String stderr;
302 
303         try {
304           while ((stderr = err.readLine()) != null) {
305             synchronized(this) {
306               stderrChanged = true;
307               System.out.println(cmd);
308               System.out.println(scmd + "-err: " + stderr);
309             }
310           }
311           err.close();
312         } catch (java.io.IOException e) {
313           System.out.println(cmd + " failed");
314         }
315       }
316     };
317   
318     outThread.start();
319     errorThread.start();
320   }  
321 }