1 | package org.greenstone.gatherer.util;
|
---|
2 |
|
---|
3 | import java.io.BufferedReader;
|
---|
4 | import java.io.BufferedWriter;
|
---|
5 | import java.io.Closeable;
|
---|
6 | import java.io.File;
|
---|
7 | import java.io.InputStream;
|
---|
8 | import java.io.InputStreamReader;
|
---|
9 | import java.io.IOException;
|
---|
10 | import java.io.OutputStream;
|
---|
11 | import java.io.OutputStreamWriter;
|
---|
12 |
|
---|
13 | import java.util.Arrays;
|
---|
14 |
|
---|
15 | import org.apache.log4j.*;
|
---|
16 |
|
---|
17 | // Use this class to run a Java Process. It follows the good and safe practices at
|
---|
18 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
19 | // to avoid blocking problems that can arise from a Process' input and output streams.
|
---|
20 |
|
---|
21 | public class SafeProcess {
|
---|
22 |
|
---|
23 | public static final int STDERR = 0;
|
---|
24 | public static final int STDOUT = 1;
|
---|
25 | public static final int STDIN = 2;
|
---|
26 |
|
---|
27 | ///static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
|
---|
28 |
|
---|
29 | // input to SafeProcess and initialising it
|
---|
30 | private String command = null;
|
---|
31 | private String[] command_args = null;
|
---|
32 | private String[] envp = null;
|
---|
33 | private File dir = null;
|
---|
34 | private String inputStr = null;
|
---|
35 |
|
---|
36 | // output from running SafeProcess.runProcess()
|
---|
37 | private String outputStr = "";
|
---|
38 | private String errorStr = "";
|
---|
39 | private int exitValue = -1;
|
---|
40 |
|
---|
41 | // allow callers to process exceptions of the main process thread if they want
|
---|
42 | private ExceptionHandler exceptionHandler = null;
|
---|
43 |
|
---|
44 | // whether std/err output should be split at new lines
|
---|
45 | private boolean splitStdOutputNewLines = false;
|
---|
46 | private boolean splitStdErrorNewLines = false;
|
---|
47 |
|
---|
48 | // call one of these constructors
|
---|
49 |
|
---|
50 | // cmd args version
|
---|
51 | public SafeProcess(String[] cmd_args)
|
---|
52 | {
|
---|
53 | command_args = cmd_args;
|
---|
54 | }
|
---|
55 |
|
---|
56 | // cmd string version
|
---|
57 | public SafeProcess(String cmdStr)
|
---|
58 | {
|
---|
59 | command = cmdStr;
|
---|
60 | }
|
---|
61 |
|
---|
62 | // cmd args with env version, launchDir can be null.
|
---|
63 | public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
|
---|
64 | {
|
---|
65 | command_args = cmd_args;
|
---|
66 | envp = envparams;
|
---|
67 | dir = launchDir;
|
---|
68 | }
|
---|
69 |
|
---|
70 | // The important methods:
|
---|
71 | // to get the output from std err and std out streams after the process has been run
|
---|
72 | public String getStdOutput() { return outputStr; }
|
---|
73 | public String getStdError() { return errorStr; }
|
---|
74 | public int getExitValue() { return exitValue; }
|
---|
75 |
|
---|
76 |
|
---|
77 | // set any string to send as input to the process spawned by SafeProcess
|
---|
78 | public void setInputString(String sendStr) {
|
---|
79 | inputStr = sendStr;
|
---|
80 | }
|
---|
81 |
|
---|
82 | // register a SafeProcess ExceptionHandler whose gotException() method will
|
---|
83 | // get called for each exception encountered
|
---|
84 | public void setExceptionHandler(ExceptionHandler exception_handler) {
|
---|
85 | exceptionHandler = exception_handler;
|
---|
86 | }
|
---|
87 |
|
---|
88 | // set if you want the std output or err output to have \n at each newline read from the stream
|
---|
89 | public void setSplitStdOutputNewLines(boolean split) {
|
---|
90 | splitStdOutputNewLines = split;
|
---|
91 | }
|
---|
92 | public void setSplitStdErrorNewLines(boolean split) {
|
---|
93 | splitStdErrorNewLines = split;
|
---|
94 | }
|
---|
95 |
|
---|
96 | private Process doRuntimeExec() throws IOException {
|
---|
97 | Process prcs = null;
|
---|
98 | Runtime rt = Runtime.getRuntime();
|
---|
99 |
|
---|
100 | if(this.command != null) {
|
---|
101 | //logger.info("SafeProcess running: " + command);
|
---|
102 | System.err.println("SafeProcess running: " + command_args);
|
---|
103 | prcs = rt.exec(this.command);
|
---|
104 | }
|
---|
105 | else { // at least command_args must be set now
|
---|
106 |
|
---|
107 | // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
|
---|
108 | //logger.info("SafeProcess running: " + Arrays.toString(command_args));
|
---|
109 | System.err.println("SafeProcess running: " + Arrays.toString(command_args));
|
---|
110 |
|
---|
111 | if(this.envp == null) {
|
---|
112 | prcs = rt.exec(this.command_args);
|
---|
113 | } else { // launch process using cmd str with env params
|
---|
114 |
|
---|
115 | if(this.dir == null) {
|
---|
116 | ///logger.info("\twith: " + Arrays.toString(this.envp));
|
---|
117 | ///System.err.println("\twith: " + Arrays.toString(this.envp));
|
---|
118 | prcs = rt.exec(this.command_args, this.envp);
|
---|
119 | } else {
|
---|
120 | ///logger.info("\tfrom directory: " + this.dir);
|
---|
121 | ///logger.info("\twith: " + Arrays.toString(this.envp));
|
---|
122 | ///System.err.println("\tfrom directory: " + this.dir);
|
---|
123 | ///System.err.println("\twith: " + Arrays.toString(this.envp));
|
---|
124 | prcs = rt.exec(this.command_args, this.envp, this.dir);
|
---|
125 | }
|
---|
126 | }
|
---|
127 | }
|
---|
128 |
|
---|
129 | return prcs;
|
---|
130 | }
|
---|
131 |
|
---|
132 | // Copied from gli's gui/FormatConversionDialog.java
|
---|
133 | private int waitForWithStreams(Process prcs,
|
---|
134 | SafeProcess.OutputStreamGobbler inputGobbler,
|
---|
135 | SafeProcess.InputStreamGobbler outputGobbler,
|
---|
136 | SafeProcess.InputStreamGobbler errorGobbler)
|
---|
137 | throws IOException, InterruptedException
|
---|
138 | {
|
---|
139 |
|
---|
140 | // kick off the stream gobblers
|
---|
141 | inputGobbler.start();
|
---|
142 | errorGobbler.start();
|
---|
143 | outputGobbler.start();
|
---|
144 |
|
---|
145 | // any error???
|
---|
146 | this.exitValue = prcs.waitFor(); // can throw an InterruptedException if process did not terminate
|
---|
147 |
|
---|
148 | ///logger.info("Process exitValue: " + exitValue);
|
---|
149 | ///System.err.println("Process exitValue: " + exitValue);
|
---|
150 |
|
---|
151 | // From the comments of
|
---|
152 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
153 | // To avoid running into nondeterministic failures to get the process output
|
---|
154 | // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
|
---|
155 | // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
|
---|
156 | outputGobbler.join();
|
---|
157 | errorGobbler.join();
|
---|
158 | inputGobbler.join();
|
---|
159 |
|
---|
160 |
|
---|
161 | // set the variables the code that created a SafeProcess object may want to inspect
|
---|
162 | this.outputStr = outputGobbler.getOutput();
|
---|
163 | this.errorStr = errorGobbler.getOutput();
|
---|
164 |
|
---|
165 | // Since we didn't have an exception, process should have terminated now (waitFor blocks until then)
|
---|
166 | // Set process to null so we don't forcibly terminate it below with process.destroy()
|
---|
167 | prcs = null;
|
---|
168 |
|
---|
169 | return this.exitValue;
|
---|
170 | }
|
---|
171 |
|
---|
172 |
|
---|
173 | // Run a very basic process: with no reading from or writing to the Process' iostreams,
|
---|
174 | // this just execs the process and waits for it to return
|
---|
175 | public int runBasicProcess() {
|
---|
176 | Process prcs = null;
|
---|
177 | try {
|
---|
178 | // 1. create the process
|
---|
179 | prcs = doRuntimeExec();
|
---|
180 | // 2. basic waitFor the process to finish
|
---|
181 | this.exitValue = prcs.waitFor();
|
---|
182 |
|
---|
183 |
|
---|
184 | } catch(IOException ioe) {
|
---|
185 | if(exceptionHandler != null) {
|
---|
186 | exceptionHandler.gotException(ioe);
|
---|
187 | } else {
|
---|
188 | //logger.error("IOException: " + ioe.getMessage(), ioe);
|
---|
189 | System.err.println("IOException " + ioe.getMessage());
|
---|
190 | ioe.printStackTrace();
|
---|
191 | }
|
---|
192 | } catch(InterruptedException ie) {
|
---|
193 |
|
---|
194 | if(exceptionHandler != null) {
|
---|
195 | exceptionHandler.gotException(ie);
|
---|
196 | } else {
|
---|
197 | //logger.error("Process InterruptedException: " + ie.getMessage(), ie);
|
---|
198 | System.err.println("Process InterruptedException " + ie.getMessage());
|
---|
199 | ///ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action
|
---|
200 | }
|
---|
201 |
|
---|
202 | Thread.currentThread().interrupt();
|
---|
203 | } finally {
|
---|
204 |
|
---|
205 | if( prcs != null ) {
|
---|
206 | prcs.destroy(); // see runProcess() below
|
---|
207 | }
|
---|
208 | }
|
---|
209 | return this.exitValue;
|
---|
210 | }
|
---|
211 |
|
---|
212 | // Runs a process with default stream processing. Returns the exitValue
|
---|
213 | public int runProcess() {
|
---|
214 | return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
|
---|
215 | }
|
---|
216 |
|
---|
217 | // Run a process with custom stream processing (any custom handlers passed in that are null
|
---|
218 | // will use the default stream processing).
|
---|
219 | // Returns the exitValue from running the Process
|
---|
220 | public int runProcess(CustomProcessHandler procInHandler,
|
---|
221 | CustomProcessHandler procOutHandler,
|
---|
222 | CustomProcessHandler procErrHandler)
|
---|
223 | {
|
---|
224 | Process prcs = null;
|
---|
225 | SafeProcess.OutputStreamGobbler inputGobbler = null;
|
---|
226 | SafeProcess.InputStreamGobbler errorGobbler = null;
|
---|
227 | SafeProcess.InputStreamGobbler outputGobbler = null;
|
---|
228 |
|
---|
229 | try {
|
---|
230 | // 1. get the Process object
|
---|
231 | prcs = doRuntimeExec();
|
---|
232 |
|
---|
233 |
|
---|
234 | // 2. create the streamgobblers and set any specified handlers on them
|
---|
235 |
|
---|
236 | // PROC INPUT STREAM
|
---|
237 | if(procInHandler == null) {
|
---|
238 | // send inputStr to process. The following constructor can handle inputStr being null
|
---|
239 | inputGobbler = // WriterToProcessInputStream
|
---|
240 | new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr);
|
---|
241 | } else { // user will do custom handling of process' InputStream
|
---|
242 | inputGobbler = new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), procInHandler);
|
---|
243 | }
|
---|
244 |
|
---|
245 | // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
|
---|
246 | if(procErrHandler == null) {
|
---|
247 | errorGobbler // ReaderFromProcessOutputStream
|
---|
248 | = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);
|
---|
249 | } else {
|
---|
250 | errorGobbler
|
---|
251 | = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), procErrHandler);
|
---|
252 | }
|
---|
253 |
|
---|
254 | // PROC OUT STREAM to monitor for the expected std output line(s)
|
---|
255 | if(procOutHandler == null) {
|
---|
256 | outputGobbler
|
---|
257 | = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines);
|
---|
258 | } else {
|
---|
259 | outputGobbler
|
---|
260 | = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), procOutHandler);
|
---|
261 | }
|
---|
262 |
|
---|
263 |
|
---|
264 | // 3. kick off the stream gobblers
|
---|
265 | this.exitValue = waitForWithStreams(prcs, inputGobbler, outputGobbler, errorGobbler);
|
---|
266 |
|
---|
267 | } catch(IOException ioe) {
|
---|
268 | if(exceptionHandler != null) {
|
---|
269 | exceptionHandler.gotException(ioe);
|
---|
270 | } else {
|
---|
271 | //logger.error("IOexception: " + ioe.getMessage(), ioe);
|
---|
272 | System.err.println("IOexception " + ioe.getMessage());
|
---|
273 | ioe.printStackTrace();
|
---|
274 | }
|
---|
275 | } catch(InterruptedException ie) {
|
---|
276 |
|
---|
277 | if(exceptionHandler != null) {
|
---|
278 | exceptionHandler.gotException(ie);
|
---|
279 | } else {
|
---|
280 | //logger.error("Process InterruptedException: " + ie.getMessage(), ie);
|
---|
281 | System.err.println("Process InterruptedException " + ie.getMessage());
|
---|
282 | ///ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action
|
---|
283 | }
|
---|
284 |
|
---|
285 | // propagate interrupts to worker threads here?
|
---|
286 | // unless the interrupt emanated from any of them in any join()...
|
---|
287 | // Only if the thread SafeProcess runs in was interrupted
|
---|
288 | // do we propagate the interrupt to the worker threads.
|
---|
289 | // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
|
---|
290 | // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
|
---|
291 | // But SafeProcess owns the worker threads, so it have every right to interrupt them
|
---|
292 | // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
|
---|
293 | if(Thread.currentThread().isInterrupted()) {
|
---|
294 | inputGobbler.interrupt();
|
---|
295 | errorGobbler.interrupt();
|
---|
296 | outputGobbler.interrupt();
|
---|
297 | }
|
---|
298 |
|
---|
299 | // On catchingInterruptedException, re-interrupt the thread.
|
---|
300 | // This is just how InterruptedExceptions tend to be handled
|
---|
301 | // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
|
---|
302 | // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
|
---|
303 |
|
---|
304 | // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
|
---|
305 | // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
|
---|
306 | // "Only code that implements a thread's interruption policy may swallow an interruption request. General-purpose task and library code should never swallow interruption requests."
|
---|
307 | // Does that mean that since this code implements this thread's interruption policy, it's ok
|
---|
308 | // to swallow the interrupt this time and not let it propagate by commenting out the next line?
|
---|
309 | Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
|
---|
310 | } finally {
|
---|
311 |
|
---|
312 | // Moved into here from GS2PerlConstructor which said
|
---|
313 | // "I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine."
|
---|
314 | // http://steveliles.github.io/invoking_processes_from_java.html
|
---|
315 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
316 | // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
|
---|
317 | if( prcs != null ) {
|
---|
318 | prcs.destroy();
|
---|
319 | }
|
---|
320 | }
|
---|
321 |
|
---|
322 | return this.exitValue;
|
---|
323 | }
|
---|
324 |
|
---|
325 | public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
|
---|
326 | {
|
---|
327 | Process prcs = null;
|
---|
328 | SafeProcess.OutputStreamGobbler inputGobbler = null;
|
---|
329 | SafeProcess.InputStreamGobbler errorGobbler = null;
|
---|
330 | SafeProcess.InputStreamGobbler outputGobbler = null;
|
---|
331 |
|
---|
332 | try {
|
---|
333 | // 1. get the Process object
|
---|
334 | prcs = doRuntimeExec();
|
---|
335 |
|
---|
336 |
|
---|
337 | // 2. create the streamgobblers and set any specified handlers on them
|
---|
338 |
|
---|
339 | // PROC INPUT STREAM
|
---|
340 | // send inputStr to process. The following constructor can handle inputStr being null
|
---|
341 | inputGobbler = // WriterToProcessInputStream
|
---|
342 | new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr);
|
---|
343 |
|
---|
344 | // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
|
---|
345 | errorGobbler // ReaderFromProcessOutputStream
|
---|
346 | = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);
|
---|
347 | // PROC OUT STREAM to monitor for the expected std output line(s)
|
---|
348 | outputGobbler
|
---|
349 | = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines);
|
---|
350 |
|
---|
351 |
|
---|
352 | // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
|
---|
353 | if(outLineByLineHandler != null) {
|
---|
354 | outputGobbler.setLineByLineHandler(outLineByLineHandler);
|
---|
355 | }
|
---|
356 | if(errLineByLineHandler != null) {
|
---|
357 | errorGobbler.setLineByLineHandler(errLineByLineHandler);
|
---|
358 | }
|
---|
359 |
|
---|
360 |
|
---|
361 | // 4. kick off the stream gobblers
|
---|
362 | this.exitValue = waitForWithStreams(prcs, inputGobbler, outputGobbler, errorGobbler);
|
---|
363 |
|
---|
364 | } catch(IOException ioe) {
|
---|
365 | if(exceptionHandler != null) {
|
---|
366 | exceptionHandler.gotException(ioe);
|
---|
367 | } else {
|
---|
368 | //logger.error("IOexception: " + ioe.getMessage(), ioe);
|
---|
369 | System.err.println("IOexception " + ioe.getMessage());
|
---|
370 | ioe.printStackTrace();
|
---|
371 | }
|
---|
372 | } catch(InterruptedException ie) {
|
---|
373 |
|
---|
374 | if(exceptionHandler != null) {
|
---|
375 | exceptionHandler.gotException(ie);
|
---|
376 | } else {
|
---|
377 | //logger.error("Process InterruptedException: " + ie.getMessage(), ie);
|
---|
378 | System.err.println("Process InterruptedException " + ie.getMessage());
|
---|
379 | ///ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action
|
---|
380 | }
|
---|
381 |
|
---|
382 | // propagate interrupts to worker threads here?
|
---|
383 | // unless the interrupt emanated from any of them in any join()...
|
---|
384 | // Only if the thread SafeProcess runs in was interrupted
|
---|
385 | // do we propagate the interrupt to the worker threads.
|
---|
386 | // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
|
---|
387 | // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
|
---|
388 | // But SafeProcess owns the worker threads, so it have every right to interrupt them
|
---|
389 | // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
|
---|
390 | if(Thread.currentThread().isInterrupted()) {
|
---|
391 | inputGobbler.interrupt();
|
---|
392 | errorGobbler.interrupt();
|
---|
393 | outputGobbler.interrupt();
|
---|
394 | }
|
---|
395 |
|
---|
396 | // On catchingInterruptedException, re-interrupt the thread.
|
---|
397 | // This is just how InterruptedExceptions tend to be handled
|
---|
398 | // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
|
---|
399 | // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
|
---|
400 |
|
---|
401 | // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
|
---|
402 | // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
|
---|
403 | // "Only code that implements a thread's interruption policy may swallow an interruption request. General-purpose task and library code should never swallow interruption requests."
|
---|
404 | // Does that mean that since this code implements this thread's interruption policy, it's ok
|
---|
405 | // to swallow the interrupt this time and not let it propagate by commenting out the next line?
|
---|
406 | Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
|
---|
407 | } finally {
|
---|
408 |
|
---|
409 | // Moved into here from GS2PerlConstructor which said
|
---|
410 | // "I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine."
|
---|
411 | // http://steveliles.github.io/invoking_processes_from_java.html
|
---|
412 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
413 | // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
|
---|
414 | if( prcs != null ) {
|
---|
415 | prcs.destroy();
|
---|
416 | }
|
---|
417 | }
|
---|
418 |
|
---|
419 | return this.exitValue;
|
---|
420 | }
|
---|
421 |
|
---|
422 |
|
---|
423 | //******************** Inner class and interface definitions ********************//
|
---|
424 | // Static inner classes can be instantiated without having to instantiate an object of the outer class first
|
---|
425 |
|
---|
426 | // Can have public static interfaces too,
|
---|
427 | // see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
|
---|
428 | // Implementors need to take care that the implementations are thread safe
|
---|
429 | // http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
|
---|
430 | public static interface ExceptionHandler {
|
---|
431 |
|
---|
432 | // when implementing ExceptionHandler.gotException(), if it manipulates anything that's
|
---|
433 | // not threadsafe, declare gotException() as a synchronized method to ensure thread safety
|
---|
434 | public void gotException(Exception e); // can't declare as synchronized in interface method declaration
|
---|
435 | }
|
---|
436 |
|
---|
437 | // Write your own run() body for any StreamGobbler. You need to create an instance of a class
|
---|
438 | // extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
|
---|
439 | // Do not create a single CustomProcessHandler instance and reuse it for all three streams,
|
---|
440 | // i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
|
---|
441 | // Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
|
---|
442 | // example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
|
---|
443 | // CustomProcessHandler is made an abstract class instead of an interface to force classes that want
|
---|
444 | // to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
|
---|
445 | // that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
|
---|
446 | // CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
|
---|
447 | // executed, rather than from the individual threads that deal with each iostream of the Process.
|
---|
448 | public static abstract class CustomProcessHandler {
|
---|
449 |
|
---|
450 | protected final int source;
|
---|
451 |
|
---|
452 | protected CustomProcessHandler(int src) {
|
---|
453 | this.source = src; // STDERR or STDOUT or STDIN
|
---|
454 | }
|
---|
455 |
|
---|
456 | public abstract void run(Closeable stream); //InputStream or OutputStream
|
---|
457 | }
|
---|
458 |
|
---|
459 | // When using the default stream processing to read from a process' stdout or stderr stream, you can
|
---|
460 | // create a class extending LineByLineHandler for the process' err stream and one for its output stream
|
---|
461 | // to do something on a line by line basis, such as sending the line to a log
|
---|
462 | public static abstract class LineByLineHandler {
|
---|
463 | protected final int source;
|
---|
464 |
|
---|
465 | protected LineByLineHandler(int src) {
|
---|
466 | this.source = src; // STDERR or STDOUT
|
---|
467 | }
|
---|
468 |
|
---|
469 | public abstract void gotLine(String line); // first non-null line
|
---|
470 | public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
|
---|
471 | }
|
---|
472 |
|
---|
473 |
|
---|
474 | //**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
|
---|
475 |
|
---|
476 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
477 | // This class is used in FormatConversionDialog to properly read from the stdout and stderr
|
---|
478 | // streams of a Process, Process.getInputStream() and Process.getErrorSream()
|
---|
479 | public static class InputStreamGobbler extends Thread
|
---|
480 | {
|
---|
481 | private InputStream is = null;
|
---|
482 | private StringBuffer outputstr = new StringBuffer();
|
---|
483 | private boolean split_newlines = false;
|
---|
484 | private CustomProcessHandler customHandler = null;
|
---|
485 | private LineByLineHandler lineByLineHandler = null;
|
---|
486 |
|
---|
487 | public InputStreamGobbler(InputStream is)
|
---|
488 | {
|
---|
489 | this.is = is;
|
---|
490 | this.split_newlines = false;
|
---|
491 | }
|
---|
492 |
|
---|
493 | public InputStreamGobbler(InputStream is, boolean split_newlines)
|
---|
494 | {
|
---|
495 | this.is = is;
|
---|
496 | this.split_newlines = split_newlines;
|
---|
497 | }
|
---|
498 |
|
---|
499 | public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
|
---|
500 | {
|
---|
501 | this.is = is;
|
---|
502 | this.customHandler = customHandler;
|
---|
503 | }
|
---|
504 |
|
---|
505 | public void setLineByLineHandler(LineByLineHandler lblHandler) {
|
---|
506 | this.lineByLineHandler = lblHandler;
|
---|
507 | }
|
---|
508 |
|
---|
509 | // default run() behaviour
|
---|
510 | public void runDefault()
|
---|
511 | {
|
---|
512 | BufferedReader br = null;
|
---|
513 | try {
|
---|
514 | br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
---|
515 | String line=null;
|
---|
516 | while ( (line = br.readLine()) != null ) {
|
---|
517 |
|
---|
518 | if(this.isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted?
|
---|
519 | //logger.info("Got interrupted when reading lines from process err/out stream.");
|
---|
520 | //System.err.println("InputStreamGobbler.runDefault() Got interrupted when reading lines from process err/out stream.");
|
---|
521 | break; // will go to finally block
|
---|
522 | }
|
---|
523 |
|
---|
524 | //System.out.println("@@@ GOT LINE: " + line);
|
---|
525 | outputstr.append(line);
|
---|
526 | if(split_newlines) {
|
---|
527 | outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
|
---|
528 | }
|
---|
529 |
|
---|
530 | if(lineByLineHandler != null) { // let handler deal with newlines
|
---|
531 | lineByLineHandler.gotLine(line);
|
---|
532 | }
|
---|
533 | }
|
---|
534 | } catch (IOException ioe) {
|
---|
535 | if(lineByLineHandler != null) {
|
---|
536 | lineByLineHandler.gotException(ioe);
|
---|
537 | } else {
|
---|
538 | //logger.error("Exception when reading from a process' stdout/stderr stream: ", ioe);
|
---|
539 | System.err.println("Exception when reading from a process' stdout/stderr stream: ");
|
---|
540 | ioe.printStackTrace();
|
---|
541 | }
|
---|
542 | } finally {
|
---|
543 | SafeProcess.closeResource(br);
|
---|
544 | }
|
---|
545 | }
|
---|
546 |
|
---|
547 | public void runCustom() {
|
---|
548 | this.customHandler.run(is);
|
---|
549 | }
|
---|
550 |
|
---|
551 | public void run() {
|
---|
552 | if(this.customHandler == null) {
|
---|
553 | runDefault();
|
---|
554 | } else {
|
---|
555 | runCustom();
|
---|
556 | }
|
---|
557 | }
|
---|
558 |
|
---|
559 | public String getOutput() {
|
---|
560 | return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
|
---|
561 | }
|
---|
562 | } // end static inner class InnerStreamGobbler
|
---|
563 |
|
---|
564 |
|
---|
565 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
566 | // This class is used in FormatConversionDialog to properly write to the inputstream of a Process
|
---|
567 | // Process.getOutputStream()
|
---|
568 | public static class OutputStreamGobbler extends Thread
|
---|
569 | {
|
---|
570 | private OutputStream os = null;
|
---|
571 | private String inputstr = "";
|
---|
572 | private CustomProcessHandler customHandler = null;
|
---|
573 |
|
---|
574 | public OutputStreamGobbler(OutputStream os) {
|
---|
575 | this.os = os;
|
---|
576 | }
|
---|
577 |
|
---|
578 | public OutputStreamGobbler(OutputStream os, String inputstr)
|
---|
579 | {
|
---|
580 | this.os = os;
|
---|
581 | this.inputstr = inputstr;
|
---|
582 | }
|
---|
583 |
|
---|
584 | public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
|
---|
585 | this.os = os;
|
---|
586 | this.customHandler = customHandler;
|
---|
587 | }
|
---|
588 |
|
---|
589 | // default run() behaviour
|
---|
590 | public void runDefault() {
|
---|
591 |
|
---|
592 | if (inputstr == null) {
|
---|
593 | return;
|
---|
594 | }
|
---|
595 |
|
---|
596 | BufferedWriter osw = null;
|
---|
597 | try {
|
---|
598 | osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
|
---|
599 | //System.out.println("@@@ SENDING LINE: " + inputstr);
|
---|
600 | osw.write(inputstr, 0, inputstr.length());
|
---|
601 | osw.newLine();//osw.write("\n");
|
---|
602 | osw.flush();
|
---|
603 |
|
---|
604 | // Don't explicitly send EOF when using StreamGobblers as below,
|
---|
605 | // as the EOF char is echoed to output.
|
---|
606 | // Flushing the write handle and/or closing the resource seems
|
---|
607 | // to already send EOF silently.
|
---|
608 |
|
---|
609 | /*if(Utility.isWindows()) {
|
---|
610 | osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
|
---|
611 | } else { // EOF on Linux/Mac is Ctrl-D
|
---|
612 | osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
|
---|
613 | }
|
---|
614 | osw.flush();
|
---|
615 | */
|
---|
616 | } catch (IOException ioe) {
|
---|
617 | //logger.error("Exception writing to SafeProcess' inputstream: ", ioe);
|
---|
618 | System.err.println("Exception writing to SafeProcess' inputstream: ");
|
---|
619 | ioe.printStackTrace();
|
---|
620 | } finally {
|
---|
621 | SafeProcess.closeResource(osw);
|
---|
622 | }
|
---|
623 | }
|
---|
624 |
|
---|
625 | // call the user's custom handler for the run() method
|
---|
626 | public void runCustom() {
|
---|
627 | this.customHandler.run(os);
|
---|
628 | }
|
---|
629 |
|
---|
630 | public void run()
|
---|
631 | {
|
---|
632 | if(this.customHandler == null) {
|
---|
633 | runDefault();
|
---|
634 | } else {
|
---|
635 | runCustom();
|
---|
636 | }
|
---|
637 |
|
---|
638 | }
|
---|
639 | } // end static inner class OutputStreamGobbler
|
---|
640 |
|
---|
641 |
|
---|
642 | //**************** Useful static methods. Copied from GLI's Utility.java ******************
|
---|
643 | // For safely closing streams/handles/resources.
|
---|
644 | // For examples of use look in the Input- and OutputStreamGobbler classes.
|
---|
645 | // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
|
---|
646 | // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
|
---|
647 | public static boolean closeResource(Closeable resourceHandle) {
|
---|
648 | boolean success = false;
|
---|
649 | try {
|
---|
650 | if(resourceHandle != null) {
|
---|
651 | resourceHandle.close();
|
---|
652 | resourceHandle = null;
|
---|
653 | success = true;
|
---|
654 | }
|
---|
655 | } catch(Exception e) {
|
---|
656 | //logger.error("Exception closing resource: ", e);
|
---|
657 | System.err.println("Exception closing resource: " + e.getMessage());
|
---|
658 | e.printStackTrace();
|
---|
659 | resourceHandle = null;
|
---|
660 | success = false;
|
---|
661 | } finally {
|
---|
662 | return success;
|
---|
663 | }
|
---|
664 | }
|
---|
665 |
|
---|
666 | public static boolean closeProcess(Process prcs) {
|
---|
667 | boolean success = true;
|
---|
668 | if( prcs != null ) {
|
---|
669 | success = success && closeResource(prcs.getErrorStream());
|
---|
670 | success = success && closeResource(prcs.getOutputStream());
|
---|
671 | success = success && closeResource(prcs.getInputStream());
|
---|
672 | prcs.destroy();
|
---|
673 | }
|
---|
674 | return success;
|
---|
675 | }
|
---|
676 |
|
---|
677 | } // end class SafeProcess
|
---|