source: main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java@ 31592

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

Removed unwanted debugging now that Windows testing of the new SafeProcess.CustomProcessHandler related changes worked out successfully too.

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