1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2001-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: Executor.java,v 1.1 2004/11/26 01:51:10 tanderson Exp $
44   */
45  package org.exolab.jms.net.jvm;
46  
47  import java.io.BufferedReader;
48  import java.io.InputStream;
49  import java.io.InputStreamReader;
50  import java.io.IOException;
51  import java.io.OutputStream;
52  import java.io.PrintStream;
53  
54  import org.apache.commons.logging.Log;
55  import org.apache.commons.logging.LogFactory;
56  
57  
58  /***
59   * This class enables commands to be executed, with the output being
60   * captured, or echoed to System.out and System.err
61   *
62   * @version     $Revision: 1.1 $ $Date: 2004/11/26 01:51:10 $
63   * @author      <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
64   */
65  public class Executor {
66  
67      /***
68       * The command to execute
69       */
70      private final String _command;
71  
72      /***
73       * The stream to direct standard out to
74       */
75      private final OutputStream _out;
76  
77      /***
78       * The stream to direct standard err to
79       */
80      private final OutputStream _err;
81  
82      /***
83       * The thread for _out
84       */
85      private Thread _outThread;
86  
87      /***
88       * The thread for _err
89       */
90      private Thread _errThread;
91  
92      /***
93       * The executing process
94       */
95      private volatile Process _process;
96  
97      /***
98       * The logger
99       */
100     private static final Log _log = LogFactory.getLog(Executor.class);
101 
102 
103     /***
104      * Constructor to execute a command, with output going to System.out and 
105      * System.err
106      *
107      * @param command the command to execute
108      */
109     public Executor(String command) {
110         this(command, System.out, System.err);
111     }
112 
113     /***
114      * Constructor to execute a command, with all output going to a stream
115      *
116      * @param command the command to execute
117      * @param log the stream to log output to
118      */
119     public Executor(String command, OutputStream log) {
120         this(command, log, log);
121     }
122 
123     /***
124      * Constructor to execute a command with standard output and error output
125      * going to two separate streams
126      *
127      * @param command the command to execute
128      * @param out the stream to direct standard output to
129      * @param err the stream to direct standard error to
130      */
131     public Executor(String command, OutputStream out, OutputStream err) {
132         if (command == null) {
133             throw new IllegalArgumentException("Argument 'command' is null");
134         }
135         if (out == null) {
136             throw new IllegalArgumentException("Argument 'out' is null");
137         }
138         if (err == null) {
139             throw new IllegalArgumentException("Argument 'err' is null");
140         }
141         _command = command;
142         _out = out;
143         _err = err;
144     }
145 
146     /***
147      * Start the command
148      *
149      * @throws IOException if the command cannot be started
150      */
151     public void start() throws IOException {
152         _log.debug("Starting " + _command);
153         _process = Runtime.getRuntime().exec(_command);
154         Reader out = new Reader(_process.getInputStream(), _out);
155         Reader err = new Reader(_process.getErrorStream(), _err);
156         _outThread = new Thread(out, "StdOut<" + _command + ">");
157         _errThread = new Thread(err, "StdErr<" + _command + ">");
158         _outThread.start();
159         _errThread.start();
160         
161     }
162 
163     /***
164      * Wait for the command to finish
165      *
166      * @return the command exit status
167      */
168     public int waitFor() {
169         boolean done = false;
170         int status = 1;
171 
172         while (!done) {
173             try {
174                 status = _process.waitFor();
175                 done = true;
176             } catch (InterruptedException ignore) {
177                 // do nothing
178             }
179         }
180 
181         while (true) {
182             try {
183                 _outThread.join();
184                 break;
185             } catch (InterruptedException ignore) {
186                 // do nothing
187             }
188         }
189 
190         while (true) {
191             try {
192                 _errThread.join();
193                 break;
194             } catch (InterruptedException ignore) {
195                 // do nothing
196             }
197         }
198 
199         _outThread = null;
200         _errThread = null;
201         _process = null;
202 
203         return status;        
204     }
205 
206     /***
207      * Execute the command and wait for it to complete
208      *
209      * @return the command exit status
210      * @throws IOException if the command cannot be executed
211      */
212     public int run() throws IOException {
213         start();
214         return waitFor();        
215     }
216 
217     /***
218      * Stop the process
219      */
220     public void stop() {
221         Process process = _process;
222         if (process != null) {
223             _log.debug("Stopping " + _command);
224             process.destroy();
225         }
226     }
227 
228     /***
229      * Helper class that reads from one stream and writes to another, in a
230      * separate thread.
231      */
232     class Reader implements Runnable {
233 
234         /***
235          * The thread executing the reader instance
236          */
237         private volatile Thread _thread = null;
238 
239         /***
240          * The input stream
241          */
242         private BufferedReader _input = null;
243 
244         /***
245          * The output stream
246          */
247         private PrintStream _output = null;
248 
249         /***
250          * The exception, if an exception was generated while performing I/O
251          */
252         private Exception _exception = null;
253 
254         /***
255          * Construct a reader, reading from the input stream and writing to 
256          * the output stream
257          *
258          * @param input the input stream
259          * @param output the output stream
260          */
261         public Reader(InputStream input, OutputStream output) {
262             _input = new BufferedReader(new InputStreamReader(input));
263             _output = new PrintStream(output);
264         }
265 
266         /***
267          * Run the reader
268          */
269         public void run() {
270             _thread = Thread.currentThread();
271             String line;
272             try {
273                 while (_thread != null && (line = _input.readLine()) != null) {
274                     _output.println(line);
275                 }
276             } catch (IOException exception) {
277                 // terminate this on an I/O error.
278                 _exception = exception;
279                 _thread = null;
280             }
281             _input = null;
282             _output = null;
283         }    
284 
285         /***
286          * Stop the reader
287          */
288         public void stop() {
289             if (_thread != null) {
290                 Thread interrupter = _thread;
291                 _thread = null;
292                 interrupter.interrupt();
293             }
294         }
295 
296         /***
297          * Returns the exception if one was generated. If {@link #stop} was
298          * invoked then an exception is likely to have been generated.
299          */
300         public Exception getException() {
301             return _exception;
302         }
303     }
304 
305 }