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

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

In place of the Input- and OutputStreamGobbler classes, am now shifting GLI code to use SafeProcess too, copied across from GS3 src code. Class SafeProcess includes the two streamgobblers as static inner classes and some more functionality with safely running an external process from Java.

File size: 15.5 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;
12
13import java.util.Arrays;
14
15import 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
21public class SafeProcess {
22
23 //static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
24
25 private String[] command_args = null;
26 private String[] envp = null;
27 private File dir = null;
28 private String inputStr = null;
29
30 private String outputStr = "";
31 private String errorStr = "";
32
33 private int exitValue = -1;
34
35 // user can write custom LineByLineHandler to deal with stdout lines as they come out one line at a time
36 // and stderr lines as they come out one at a time
37 private LineByLineHandler errLineByLineHandler = null;
38 private LineByLineHandler outLineByLineHandler = null;
39 private ExceptionHandler exceptionHandler = null;
40
41 // whether std/err output should be split at new lines
42 private boolean splitStdOutputNewLines = false;
43 private boolean splitStdErrorNewLines = false;
44
45 // call one of these constructors
46
47 // cmd args version
48 public SafeProcess(String[] cmd_args)
49 {
50 command_args = cmd_args;
51 }
52
53 // cmd args with env version, launchDir can be null.
54 public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
55 {
56 command_args = cmd_args;
57 envp = envparams;
58 dir = launchDir;
59 }
60
61 // The important methods:
62 // to get the output from std err and std out streams after the process has been run
63 public String getStdOutput() { return outputStr; }
64 public String getStdError() { return errorStr; }
65 public int getExitValue() { return exitValue; }
66
67
68 // set any string to send as input to the process spawned by SafeProcess
69 public void setInputString(String sendStr) {
70 inputStr = sendStr;
71 }
72
73 // register a handler whose gotLine() method will get called as each line is read from the process' stdout
74 public void setStdOutLineByLineHandler(LineByLineHandler out_lbl_handler) {
75 outLineByLineHandler = out_lbl_handler;
76 }
77
78 // register a handler whose gotLine() method will get called as each line is read from the process' stderr
79 public void setStdErrLineByLineHandler(LineByLineHandler err_lbl_handler) {
80 errLineByLineHandler = err_lbl_handler;
81 }
82
83 // register a SafeProcess ExceptionHandler whose gotException() method will
84 // get called for each exception encountered
85 public void setExceptionHandler(ExceptionHandler exception_handler) {
86 exceptionHandler = exception_handler;
87 }
88
89 // set if you want the std output or err output to have \n at each newline read from the stream
90 public void setSplitStdOutputNewLines(boolean split) {
91 splitStdOutputNewLines = split;
92 }
93 public void setSplitStdErrorNewLines(boolean split) {
94 splitStdErrorNewLines = split;
95 }
96
97//***************** Copied from gli's gui/FormatConversionDialog.java *************//
98 public void runProcess() {
99
100 Process prcs = null;
101 SafeProcess.OutputStreamGobbler inputGobbler = null;
102 SafeProcess.InputStreamGobbler errorGobbler = null;
103 SafeProcess.InputStreamGobbler outputGobbler = null;
104
105 try {
106 Runtime rt = Runtime.getRuntime();
107
108 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
109 //System.err.println("Running process: " + 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 //System.err.println("\twith: " + Arrays.toString(this.envp));
117 prcs = rt.exec(this.command_args, this.envp);
118 } else {
119 //System.err.println("\tfrom directory: " + this.dir);
120 //System.err.println("\twith: " + Arrays.toString(this.envp));
121 prcs = rt.exec(this.command_args, this.envp, this.dir);
122 }
123 }
124
125 // send inputStr to process. The following constructor can handle inputStr being null
126 inputGobbler = // WriterToProcessInputStream
127 new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr);
128
129 // monitor for any error messages
130 errorGobbler // ReaderFromProcessOutputStream
131 = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);
132
133 // monitor for the expected std output line(s)
134 outputGobbler
135 = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines);
136
137 // register line by line handlers, if any were set, for the process stderr and stdout streams
138 if(this.outLineByLineHandler != null) {
139 outputGobbler.setLineByLineHandler(this.outLineByLineHandler);
140 }
141 if(this.errLineByLineHandler != null) {
142 errorGobbler.setLineByLineHandler(this.errLineByLineHandler);
143 }
144 if(this.exceptionHandler != null) {
145 inputGobbler.setExceptionHandler(this.exceptionHandler);
146 }
147
148 // kick off the stream gobblers
149 inputGobbler.start();
150 errorGobbler.start();
151 outputGobbler.start();
152
153 // any error???
154 this.exitValue = prcs.waitFor(); // can throw an InterruptedException if process did not terminate
155 // From the comments of
156 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
157 // To avoid running into nondeterministic failures to get the process output
158 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
159 // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
160 outputGobbler.join();
161 errorGobbler.join();
162 inputGobbler.join();
163
164 // set the variables the code that created a SafeProcess object may want to inspect
165 this.outputStr = outputGobbler.getOutput();
166 this.errorStr = errorGobbler.getOutput();
167
168 // Since we didn't have an exception, process should have terminated now (waitFor blocks until then)
169 // Set process to null so we don't forcibly terminate it below with process.destroy()
170 prcs = null;
171
172 } catch(IOException ioe) {
173 //logger.error("IOexception: " + ioe.getMessage(), ioe);
174 System.err.println("IOexception " + ioe.getMessage());
175 ioe.printStackTrace();
176 if(exceptionHandler != null) exceptionHandler.gotException(ioe);
177 } catch(InterruptedException ie) {
178 //logger.error("Process InterruptedException: " + ie.getMessage(), ie);
179 System.err.println("Process InterruptedException " + ie.getMessage());
180 ie.printStackTrace();
181
182 if(exceptionHandler != null) exceptionHandler.gotException(ie);
183
184 // propagate interrupts to worker threads here?
185 // unless the interrupt emanated from any of them in any join()...
186 // Only if the thread SafeProcess runs in was interrupted
187 // do we propagate the interrupt to the worker threads.
188 // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
189 // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
190 // But SafeProcess owns the worker threads, so it have every right to interrupt them
191 // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
192 if(Thread.currentThread().isInterrupted()) {
193 inputGobbler.interrupt();
194 errorGobbler.interrupt();
195 outputGobbler.interrupt();
196 }
197
198 // On catchingInterruptedException, re-interrupt the thread.
199 // This is just how InterruptedExceptions tend to be handled
200 // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
201 // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
202
203 // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
204 // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
205 // "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."
206 // Does that mean that since this code implements this thread's interruption policy, it's ok
207 // to swallow the interrupt this time and not let it propagate by commenting out the next line?
208 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
209 } finally {
210
211 // Moved into here from GS2PerlConstructor which said
212 // "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."
213 // http://steveliles.github.io/invoking_processes_from_java.html
214 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
215 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
216 if( prcs != null ) {
217 prcs.destroy();
218 }
219 }
220
221 }
222
223
224//**************** Inner class definitions (stream gobblers copied from GLI) **********//
225// Static inner classes can be instantiated without having to instantiate an object of the outer class first
226
227// Can have public static interfaces too,
228// see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
229// Implementors need to take care that the implementations are thread safe
230// http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
231public static interface ExceptionHandler {
232
233 // SHOULD I DECLARE THIS SYNCHRONIZED?
234 // It ends up being thread safe for the particular instance I'm using it for, but that doesn't
235 // make it future proof...
236 public void gotException(Exception e);
237}
238
239// When reading from a process' stdout or stderr stream, you can create a LineByLineHandler
240// to do something on a line by line basis, such as sending the line to a log
241public static interface LineByLineHandler {
242 public void gotLine(String line);
243 public void gotException(Exception e); // for when an exception occurs instead of getting a line
244}
245
246
247// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
248// This class is used in FormatConversionDialog to properly read from the stdout and stderr
249// streams of a Process, Process.getInputStream() and Process.getErrorSream()
250public static class InputStreamGobbler extends Thread
251{
252 InputStream is = null;
253 StringBuffer outputstr = new StringBuffer();
254 boolean split_newlines = false;
255 LineByLineHandler lineByLineHandler = null;
256
257 public InputStreamGobbler(InputStream is)
258 {
259 this.is = is;
260 split_newlines = false;
261 }
262
263 public InputStreamGobbler(InputStream is, boolean split_newlines)
264 {
265 this.is = is;
266 this.split_newlines = split_newlines;
267 }
268
269 public void setLineByLineHandler(LineByLineHandler lblHandler) {
270 lineByLineHandler = lblHandler;
271 }
272
273
274 public void run()
275 {
276 BufferedReader br = null;
277 try {
278 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
279 String line=null;
280 while ( (line = br.readLine()) != null ) {
281
282 if(this.isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted?
283 System.err.println("Got interrupted when reading lines from process err/out stream.");
284 break; // will go to finally block
285 }
286
287 //System.out.println("@@@ GOT LINE: " + line);
288 outputstr.append(line);
289
290 if(split_newlines) {
291 outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
292 }
293
294 if(lineByLineHandler != null) { // let handler deal with newlines
295 lineByLineHandler.gotLine(line);
296 }
297 }
298 } catch (IOException ioe) {
299 //logger.error("Exception when reading from a process' stdout/stderr stream: ", ioe);
300 //ioe.printStackTrace();
301
302 if(lineByLineHandler != null) lineByLineHandler.gotException(ioe);
303
304 } finally {
305 SafeProcess.closeResource(br);
306 }
307 }
308
309 public String getOutput() {
310 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
311 }
312} // end static inner class InnerStreamGobbler
313
314
315// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
316// This class is used in FormatConversionDialog to properly write to the inputstream of a Process
317// Process.getOutputStream()
318public static class OutputStreamGobbler extends Thread
319{
320 OutputStream os = null;
321 String inputstr = "";
322 ExceptionHandler exceptionHandler = null;
323
324 public OutputStreamGobbler(OutputStream os, String inputstr)
325 {
326 this.os = os;
327 this.inputstr = inputstr;
328 }
329
330 public void setExceptionHandler(ExceptionHandler eHandler) {
331 exceptionHandler = eHandler;
332 }
333
334 public void run()
335 {
336 if (inputstr == null) {
337 return;
338 }
339
340 BufferedWriter osw = null;
341 try {
342 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
343 //System.out.println("@@@ SENDING LINE: " + inputstr);
344 osw.write(inputstr, 0, inputstr.length());
345 osw.newLine();//osw.write("\n");
346 osw.flush();
347
348 // Don't explicitly send EOF when using StreamGobblers as below,
349 // as the EOF char is echoed to output.
350 // Flushing the write handle and/or closing the resource seems
351 // to already send EOF silently.
352
353 /*if(Utility.isWindows()) {
354 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
355 } else { // EOF on Linux/Mac is Ctrl-D
356 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
357 }
358 osw.flush();
359 */
360 } catch (IOException ioe) {
361 //logger.error("Exception writing to SafeProcess' inputstream: ", ioe);
362 ioe.printStackTrace();
363
364 if (this.exceptionHandler != null) this.exceptionHandler.gotException(ioe);
365
366 } finally {
367 SafeProcess.closeResource(osw);
368 }
369 }
370} // end static inner class OutputStreamGobbler
371
372
373//**************** Copied from GLI's Utility.java ******************
374 // For safely closing streams/handles/resources.
375 // For examples of use look in the Input- and OutputStreamGobbler classes.
376 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
377 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
378 public static boolean closeResource(Closeable resourceHandle) {
379 boolean success = false;
380 try {
381 if(resourceHandle != null) {
382 resourceHandle.close();
383 resourceHandle = null;
384 success = true;
385 }
386 } catch(Exception e) {
387 //logger.error("Exception closing resource: ", e);
388 System.err.println("Exception closing resource: " + e.getMessage());
389 e.printStackTrace();
390 resourceHandle = null;
391 success = false;
392 } finally {
393 return success;
394 }
395 }
396
397 public static boolean closeProcess(Process prcs) {
398 boolean success = true;
399 if( prcs != null ) {
400 success = success && closeResource(prcs.getErrorStream());
401 success = success && closeResource(prcs.getOutputStream());
402 success = success && closeResource(prcs.getInputStream());
403 prcs.destroy();
404 }
405 return success;
406 }
407
408} // end class SafeProcess
Note: See TracBrowser for help on using the repository browser.