View Javadoc

1   /*
2    * Copyright (C) 2003-2004 Christian Siefkes <christian@siefkes.net>.
3    * Development of this software is supported by the German Research Society,
4    * Berlin-Brandenburg Graduate School in Distributed Information Systems
5    * (DFG grant no. GRK 316).
6    *
7    * This library is free software; you can redistribute it and/or
8    * modify it under the terms of the GNU Lesser General Public
9    * License as published by the Free Software Foundation; either
10   * version 2.1 of the License, or (at your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this library; if not, visit
19   * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21   */
22  package de.fu_berlin.ties.util;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.OutputStreamWriter;
28  
29  import org.apache.commons.lang.builder.ToStringBuilder;
30  
31  import de.fu_berlin.ties.io.IOUtils;
32  
33  /***
34   * A simple wrapper for external scripts or programs. Calls an external program
35   * with arguments and environment settings as specified and returns the output
36   * of the program.
37   *
38   * <p>This class is thread-safe as each call to {@link #execute()} creates
39   * another instances of the program. It uses the default
40   * {@link de.fu_berlin.ties.util.TaskRunner}. <em>To allow efficient thread
41   * re-use, it is highly recommended to initially
42   * {@linkplain de.fu_berlin.ties.util.TaskRunner#registerInterest() register
43   * your interest} in the default task runner and to finally
44   * {@linkplain de.fu_berlin.ties.util.TaskRunner#deregisterInterest()
45   * deregister} when you no longer need it.</em>
46   *
47   * @author Christian Siefkes
48   * @version $Revision: 1.6 $, $Date: 2004/12/06 17:59:41 $, $Author: siefkes $
49   */
50  public class ExternalCommand {
51  
52      /***
53       * Array containing the command to call and its general arguments.
54       */
55      private final String[] commandArray;
56  
57      /***
58       * An array of strings, each element of which has
59       * environment variable settings in format <em>name=value</em>.
60       */
61      private final String[] envp;
62  
63      /***
64       * The working directory of the process.
65       */
66      private final File workingDir;
67  
68      /***
69       * Creates a new instance, without specifying environment parameters and
70       * working directory.
71       * Communication with the external process occurs in several parallel
72       * threads using the default {@link TaskRunner} for asynchronous tasks.
73       *
74       * @param cmdArray array containing the command to call and its general
75       * arguments, i.e. arguments that should be used used every time the
76       * program is called
77       * @throws IllegalArgumentException if the <code>cmdArray</code> is empty,
78       * i.e. the command name is missing
79       */
80      public ExternalCommand(final String[] cmdArray)
81                             throws IllegalArgumentException {
82          this(cmdArray, null);
83      }
84  
85      /***
86       * Creates a new instance, without specifying environment parameters.
87       * Communication with the external process occurs in several parallel
88       * threads using the default {@link TaskRunner} for asynchronous tasks.
89       *
90       * @param cmdArray array containing the command to call and its general
91       * arguments, i.e. arguments that should be used used every time the
92       * program is called
93       * @param workDir the working directory of the process; or <code>null</code>
94       * if the command should inherit the working directory of the current
95       * process
96       * @throws IllegalArgumentException if the <code>cmdArray</code> is empty,
97       * i.e. the command name is missing
98       */
99      public ExternalCommand(final String[] cmdArray, final File workDir)
100                            throws IllegalArgumentException {
101         this(cmdArray, null, workDir);
102     }
103 
104     /***
105      * Creates a new instance. Communication with the external process occurs
106      * in several parallel threads using the default {@link TaskRunner} for
107      * asynchronous tasks.
108      *
109      * @param cmdArray array containing the command to call and its general
110      * arguments, i.e. arguments that should be used used every time the
111      * program is called
112      * @param envparams an array of strings, each element of which has
113      * environment variable settings in format <em>name=value</em>; might be
114      * <code>null</code> or empty
115      * @param workDir the working directory of the process; or <code>null</code>
116      * if the command should inherit the working directory of the current
117      * process
118      * @throws IllegalArgumentException if the <code>cmdArray</code> is empty,
119      * i.e. the command name is missing
120      */
121     public ExternalCommand(final String[] cmdArray, final String[] envparams,
122                            final File workDir) throws IllegalArgumentException {
123         super();
124         if (cmdArray.length == 0) {
125             throw new IllegalArgumentException(
126                 "Command name missing (command array is empty)");
127         }
128         commandArray = cmdArray;
129         envp = envparams;
130         workingDir = workDir;
131     }
132 
133     /***
134      * Executed the external command any further arguments and without sending
135      * input and returns its output (standard out).
136      * Messages written to the standard error stream (stderr) are logged as
137      * info statements. The system-specific default character set is used for
138      * conversion between strings and streams.
139      *
140      * @return the output returned by the command's standard output stream.
141      * @throws IOException if an I/O error occurs
142      */
143     public String execute() throws IOException {
144         return execute(null, null);
145     }
146 
147     /***
148      * Executed the external command without any further arguments and returns
149      * its output (standard out).
150      * Messages written to the standard error stream (stderr) are logged as
151      * info statements. The system-specific default character set is used for
152      * conversion between strings and streams.
153      *
154      * @param input the input to send to the command's standard input stream;
155      * or <code>null</code> if no input should be sent
156      * @return the output returned by the command's standard output stream.
157      * @throws IOException if an I/O error occurs
158      */
159     public String execute(final CharSequence input) throws IOException {
160         return execute(null, input);
161     }
162 
163     /***
164      * Executed the external command without sending input and returns
165      * its output (standard out).
166      * Messages written to the standard error stream (stderr) are logged as
167      * info statements. The system-specific default character set is used for
168      * conversion between strings and streams.
169      *
170      * @param furtherArguments an array of further arguments to use for this
171      * single call, might be <code>null</code>; appended <em>after</em> the
172      * general arguments specified in the constructor
173      * @return the output returned by the command's standard output stream.
174      * @throws IOException if an I/O error occurs
175      */
176     public String execute(final String[] furtherArguments) throws IOException {
177         return execute(furtherArguments, null);
178     }
179 
180     /***
181      * Executed the external command and returns its output (standard out).
182      * Messages written to the standard error stream (stderr) are logged as
183      * info statements. The system-specific default character set is used for
184      * conversion between strings and streams.
185      *
186      * @param furtherArguments an array of further arguments to use for this
187      * single call, might be <code>null</code>; appended <em>after</em> the
188      * general arguments specified in the constructor
189      * @param input the input to send to the command's standard input stream;
190      * or <code>null</code> if no input should be sent
191      * @return the output returned by the command's standard output stream.
192      * @throws IOException if an I/O error occurs
193      */
194     public String execute(final String[] furtherArguments,
195             final CharSequence input) throws IOException {
196         final String[] fullCmdArray;
197         if ((furtherArguments != null) && (furtherArguments.length > 0)) {
198             // add further arguments after general arguments
199             fullCmdArray =
200                 new String[commandArray.length + furtherArguments.length];
201             CollectionUtils.combineArrays(commandArray, furtherArguments,
202                     fullCmdArray);
203         } else {
204             // just use general arguments
205             fullCmdArray = commandArray;
206         }
207 
208         final String commandName = commandArray[0];
209         Util.LOG.debug("Invoking " + commandName);
210         final Process process =
211             Runtime.getRuntime().exec(fullCmdArray, envp, workingDir);
212         InputStreamReader readFromProcess = null;
213         String result = null;
214 
215         TaskRunner.registerInterest();
216         try {
217             // asynchronously feed input to the process, if any
218             if (input != null) {
219                 TaskRunner.invokeDefault(new Runnable() {
220                         public final void run() {
221                             OutputStreamWriter feedToProcess = null;
222                             try {
223                                 feedToProcess = new OutputStreamWriter(
224                                     process.getOutputStream());
225                                 IOUtils.writeToWriter(input, feedToProcess);
226                             } catch (IOException ioe) {
227                                 Util.LOG.error("Error while feeding input to "
228                                     + commandName + " process",
229                                     ioe);
230                             } finally {
231                                 IOUtils.tryToClose(feedToProcess);
232                             }
233                         }
234                     },
235                     commandName + " stdin feeder"
236                 );
237             }
238 
239             // asynchronously read and log errors messages from stream (if any)
240             TaskRunner.invokeDefault(new Runnable() {
241                     public final void run() {
242                         InputStreamReader errorsFromProcess = null;
243                         try {
244                             errorsFromProcess = new InputStreamReader(
245                                 process.getErrorStream());
246                             // check the error/warn message of the process
247                             final String errorsMessages =
248                                 IOUtils.readToString(errorsFromProcess);
249                             // log as debug
250                             if ((errorsMessages != null)
251                                     && (!"".equals(errorsMessages))) {
252                                 Util.LOG.debug(commandName
253                                     + " wrote to stderr: " + errorsMessages);
254                             }
255                         } catch (IOException ioe) {
256                             Util.LOG.error("Error while feed input to "
257                                 + commandName + " process",
258                                 ioe);
259                         } finally {
260                             IOUtils.tryToClose(errorsFromProcess);
261                         }
262                     }
263                 },
264                 commandName + " stderr reader"
265             );
266 
267             try {
268                 // read the data returned by the process
269                 readFromProcess =
270                     new InputStreamReader(process.getInputStream());
271                 result = IOUtils.readToString(readFromProcess);
272 
273                 // wait for process to finish and get return code
274                 try {
275                     final int exitValue = process.waitFor();
276                     if (exitValue != 0) {
277                         Util.LOG.warn(commandName + " returned exit code "
278                                 + exitValue);
279                     }
280                 } catch (InterruptedException ie) {
281                     Util.LOG.warn("Interrupted while waiting for " + commandName
282                         + " to finish");
283                 }
284 
285             } finally {
286                 IOUtils.tryToClose(readFromProcess);
287             }
288         } finally {
289             TaskRunner.deregisterInterest();
290         }
291         return result;
292     }
293 
294     /***
295      * Returns a string representation of this object.
296      *
297      * @return a textual representation
298      */
299     public String toString() {
300         return new ToStringBuilder(this).
301             append("command array", commandArray).
302             append("environment", envp).
303             append("working dir", workingDir).
304             toString();
305     }
306 
307 }