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

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