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

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

Correcting another difference with gli version of SafeProcess

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