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

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

Adding a basic run process method, runBasicProcess(), one to be used when you don't work with the process' streams at all.

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