1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.10 $, $Date: 2006/10/21 16:04:27 $, $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
199 fullCmdArray =
200 new String[commandArray.length + furtherArguments.length];
201 CollUtils.combineArrays(commandArray, furtherArguments,
202 fullCmdArray);
203 } else {
204
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
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
240 TaskRunner.invokeDefault(new Runnable() {
241 public final void run() {
242 InputStreamReader errorsFromProcess = null;
243 try {
244 errorsFromProcess = new InputStreamReader(
245 process.getErrorStream());
246
247 final String errorsMessages =
248 IOUtils.readToString(errorsFromProcess);
249
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
269 readFromProcess =
270 new InputStreamReader(process.getInputStream());
271 result = IOUtils.readToString(readFromProcess);
272
273
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 }