source: main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java@ 31643

Last change on this file since 31643 was 31643, checked in by ak19, 7 years ago

Taskkill does not seem to be a route, but adding in code that works, but will kill any running perl on Windows, not just specific perl processes launched by GS.

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