1 | package org.greenstone.gatherer.util;
|
---|
2 |
|
---|
3 | import java.io.BufferedReader;
|
---|
4 | import java.io.BufferedWriter;
|
---|
5 | import java.io.Closeable;
|
---|
6 | import java.io.File;
|
---|
7 | import java.io.InputStream;
|
---|
8 | import java.io.InputStreamReader;
|
---|
9 | import java.io.IOException;
|
---|
10 | import java.io.OutputStream;
|
---|
11 | import java.io.OutputStreamWriter;
|
---|
12 | import java.util.Arrays;
|
---|
13 | import java.util.Scanner;
|
---|
14 |
|
---|
15 | import com.sun.jna.*;
|
---|
16 | import com.sun.jna.platform.win32.Kernel32;
|
---|
17 | import com.sun.jna.platform.win32.WinNT;
|
---|
18 |
|
---|
19 | import java.lang.reflect.Field;
|
---|
20 | //import java.lang.reflect.Method;
|
---|
21 |
|
---|
22 | import org.apache.log4j.*;
|
---|
23 |
|
---|
24 | import org.greenstone.gatherer.DebugStream;
|
---|
25 |
|
---|
26 | // Use this class to run a Java Process. It follows the good and safe practices at
|
---|
27 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
28 | // to avoid blocking problems that can arise from a Process' input and output streams.
|
---|
29 |
|
---|
30 | public class SafeProcess {
|
---|
31 | //public static int DEBUG = 0;
|
---|
32 |
|
---|
33 | public static final int STDERR = 0;
|
---|
34 | public static final int STDOUT = 1;
|
---|
35 | public static final int STDIN = 2;
|
---|
36 |
|
---|
37 | // charset for reading process stderr and stdout streams
|
---|
38 | //public static final String UTF8 = "UTF-8";
|
---|
39 |
|
---|
40 | ///static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
|
---|
41 |
|
---|
42 | // input to SafeProcess and initialising it
|
---|
43 | private String command = null;
|
---|
44 | private String[] command_args = null;
|
---|
45 | private String[] envp = null;
|
---|
46 | private File dir = null;
|
---|
47 | private String inputStr = null;
|
---|
48 | private Process process = null;
|
---|
49 | private boolean forciblyTerminateProcess = false;
|
---|
50 |
|
---|
51 | // output from running SafeProcess.runProcess()
|
---|
52 | private String outputStr = "";
|
---|
53 | private String errorStr = "";
|
---|
54 | private int exitValue = -1;
|
---|
55 | //private String charset = null;
|
---|
56 |
|
---|
57 | // allow callers to process exceptions of the main process thread if they want
|
---|
58 | private ExceptionHandler exceptionHandler = null;
|
---|
59 |
|
---|
60 | // whether std/err output should be split at new lines
|
---|
61 | private boolean splitStdOutputNewLines = false;
|
---|
62 | private boolean splitStdErrorNewLines = false;
|
---|
63 |
|
---|
64 | // call one of these constructors
|
---|
65 |
|
---|
66 | // cmd args version
|
---|
67 | public SafeProcess(String[] cmd_args)
|
---|
68 | {
|
---|
69 | command_args = cmd_args;
|
---|
70 | }
|
---|
71 |
|
---|
72 | // cmd string version
|
---|
73 | public SafeProcess(String cmdStr)
|
---|
74 | {
|
---|
75 | command = cmdStr;
|
---|
76 | }
|
---|
77 |
|
---|
78 | // cmd args with env version, launchDir can be null.
|
---|
79 | public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
|
---|
80 | {
|
---|
81 | command_args = cmd_args;
|
---|
82 | envp = envparams;
|
---|
83 | dir = launchDir;
|
---|
84 | }
|
---|
85 |
|
---|
86 | // The important methods:
|
---|
87 | // to get the output from std err and std out streams after the process has been run
|
---|
88 | public String getStdOutput() { return outputStr; }
|
---|
89 | public String getStdError() { return errorStr; }
|
---|
90 | public int getExitValue() { return exitValue; }
|
---|
91 |
|
---|
92 | //public void setStreamCharSet(String charset) { this.charset = charset; }
|
---|
93 |
|
---|
94 | // set any string to send as input to the process spawned by SafeProcess
|
---|
95 | public void setInputString(String sendStr) {
|
---|
96 | inputStr = sendStr;
|
---|
97 | }
|
---|
98 |
|
---|
99 | // register a SafeProcess ExceptionHandler whose gotException() method will
|
---|
100 | // get called for each exception encountered
|
---|
101 | public void setExceptionHandler(ExceptionHandler exception_handler) {
|
---|
102 | exceptionHandler = exception_handler;
|
---|
103 | }
|
---|
104 |
|
---|
105 | // set if you want the std output or err output to have \n at each newline read from the stream
|
---|
106 | public void setSplitStdOutputNewLines(boolean split) {
|
---|
107 | splitStdOutputNewLines = split;
|
---|
108 | }
|
---|
109 | public void setSplitStdErrorNewLines(boolean split) {
|
---|
110 | splitStdErrorNewLines = split;
|
---|
111 | }
|
---|
112 |
|
---|
113 | private Process doRuntimeExec() throws IOException {
|
---|
114 | Process prcs = null;
|
---|
115 | Runtime rt = Runtime.getRuntime();
|
---|
116 |
|
---|
117 | if(this.command != null) {
|
---|
118 | log("SafeProcess running: " + command);
|
---|
119 | prcs = rt.exec(this.command);
|
---|
120 | }
|
---|
121 | else { // at least command_args must be set now
|
---|
122 |
|
---|
123 | // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
|
---|
124 | log("SafeProcess running: " + Arrays.toString(command_args));
|
---|
125 |
|
---|
126 | if(this.envp == null) {
|
---|
127 | prcs = rt.exec(this.command_args);
|
---|
128 | } else { // launch process using cmd str with env params
|
---|
129 |
|
---|
130 | if(this.dir == null) {
|
---|
131 | //log("\twith: " + Arrays.toString(this.envp));
|
---|
132 | prcs = rt.exec(this.command_args, this.envp);
|
---|
133 | } else {
|
---|
134 | //log("\tfrom directory: " + this.dir);
|
---|
135 | //log("\twith: " + Arrays.toString(this.envp));
|
---|
136 | prcs = rt.exec(this.command_args, this.envp, this.dir);
|
---|
137 | }
|
---|
138 | }
|
---|
139 | }
|
---|
140 |
|
---|
141 | return prcs;
|
---|
142 | }
|
---|
143 |
|
---|
144 | // Copied from gli's gui/FormatConversionDialog.java
|
---|
145 | private int waitForWithStreams(SafeProcess.OutputStreamGobbler inputGobbler,
|
---|
146 | SafeProcess.InputStreamGobbler outputGobbler,
|
---|
147 | SafeProcess.InputStreamGobbler errorGobbler)
|
---|
148 | throws IOException, InterruptedException
|
---|
149 | {
|
---|
150 | // kick off the stream gobblers
|
---|
151 | inputGobbler.start();
|
---|
152 | errorGobbler.start();
|
---|
153 | outputGobbler.start();
|
---|
154 |
|
---|
155 | // any error???
|
---|
156 | try {
|
---|
157 | this.exitValue = process.waitFor(); // can throw an InterruptedException if process did not terminate
|
---|
158 | } catch(InterruptedException ie) {
|
---|
159 | log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation.");
|
---|
160 | // don't print stacktrace: an interrupt here is not an error, it's expected to be a cancel action
|
---|
161 | if(exceptionHandler != null) {
|
---|
162 | exceptionHandler.gotException(ie);
|
---|
163 | }
|
---|
164 |
|
---|
165 | // propagate interrupts to worker threads here
|
---|
166 | // unless the interrupt emanated from any of them in any join(),
|
---|
167 | // which will be caught by caller's catch on InterruptedException.
|
---|
168 | // Only if the thread that SafeProcess runs in was interrupted
|
---|
169 | // should we propagate the interrupt to the worker threads.
|
---|
170 | // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
|
---|
171 | // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
|
---|
172 | // But SafeProcess owns the worker threads, so it has every right to interrupt them
|
---|
173 | // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
|
---|
174 |
|
---|
175 | // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
|
---|
176 | // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
|
---|
177 | // "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."
|
---|
178 | // Does that mean that since this code implements this thread's interruption policy, it's ok
|
---|
179 | // to swallow the interrupt this time and not let it propagate by commenting out the next line?
|
---|
180 | //Thread.currentThread().interrupt(); // re-interrupt the thread
|
---|
181 |
|
---|
182 | inputGobbler.interrupt();
|
---|
183 | errorGobbler.interrupt();
|
---|
184 | outputGobbler.interrupt();
|
---|
185 |
|
---|
186 | // Since we have been cancelled (InterruptedException), or on any Exception, we need
|
---|
187 | // to forcibly terminate process eventually after the finally code first waits for each worker thread
|
---|
188 | // to die off. Don't set process=null until after we've forcibly terminated it if needs be.
|
---|
189 | this.forciblyTerminateProcess = true;
|
---|
190 |
|
---|
191 | // even after the interrupts, we want to proceed to calling join() on all the worker threads
|
---|
192 | // in order to wait for each of them to die before attempting to destroy the process if it
|
---|
193 | // still hasn't terminated after all that.
|
---|
194 | } finally {
|
---|
195 |
|
---|
196 | //log("Process exitValue: " + exitValue);
|
---|
197 |
|
---|
198 | // From the comments of
|
---|
199 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
200 | // To avoid running into nondeterministic failures to get the process output
|
---|
201 | // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
|
---|
202 | // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
|
---|
203 |
|
---|
204 | // Wait for each of the threads to die, before attempting to destroy the process
|
---|
205 | // Any of these can throw InterruptedExceptions too
|
---|
206 | // and will be processed by the calling function's catch on InterruptedException.
|
---|
207 | // However, no one besides us will interrupting these threads I think...
|
---|
208 | // and we won't be throwing the InterruptedException from within the threads...
|
---|
209 | // So if any streamgobbler.join() call throws an InterruptedException, that would be unexpected
|
---|
210 |
|
---|
211 | outputGobbler.join();
|
---|
212 | errorGobbler.join();
|
---|
213 | inputGobbler.join();
|
---|
214 |
|
---|
215 |
|
---|
216 | // set the variables that the code which created a SafeProcess object may want to inspect
|
---|
217 | this.outputStr = outputGobbler.getOutput();
|
---|
218 | this.errorStr = errorGobbler.getOutput();
|
---|
219 | }
|
---|
220 |
|
---|
221 | // Don't return from finally, it's considered an abrupt completion and exceptions are lost, see
|
---|
222 | // http://stackoverflow.com/questions/18205493/can-we-use-return-in-finally-block
|
---|
223 | return this.exitValue;
|
---|
224 | }
|
---|
225 |
|
---|
226 |
|
---|
227 | public synchronized boolean processRunning() {
|
---|
228 | if(process == null) return false;
|
---|
229 | return SafeProcess.processRunning(this.process);
|
---|
230 | }
|
---|
231 |
|
---|
232 | // Run a very basic process: with no reading from or writing to the Process' iostreams,
|
---|
233 | // this just execs the process and waits for it to return
|
---|
234 | public int runBasicProcess() {
|
---|
235 | try {
|
---|
236 | this.forciblyTerminateProcess = true;
|
---|
237 |
|
---|
238 | // 1. create the process
|
---|
239 | process = doRuntimeExec();
|
---|
240 | // 2. basic waitFor the process to finish
|
---|
241 | this.exitValue = process.waitFor();
|
---|
242 |
|
---|
243 | this.forciblyTerminateProcess = false;
|
---|
244 | } catch(IOException ioe) {
|
---|
245 |
|
---|
246 | if(exceptionHandler != null) {
|
---|
247 | exceptionHandler.gotException(ioe);
|
---|
248 | } else {
|
---|
249 | log("IOException: " + ioe.getMessage(), ioe);
|
---|
250 | }
|
---|
251 | } catch(InterruptedException ie) {
|
---|
252 |
|
---|
253 | if(exceptionHandler != null) {
|
---|
254 | exceptionHandler.gotException(ie);
|
---|
255 | } else { // Unexpected InterruptedException, so printstacktrace
|
---|
256 | log("Process InterruptedException: " + ie.getMessage(), ie);
|
---|
257 | }
|
---|
258 |
|
---|
259 | Thread.currentThread().interrupt();
|
---|
260 | } finally {
|
---|
261 |
|
---|
262 | if( this.forciblyTerminateProcess ) {
|
---|
263 | destroyProcess(process); // see runProcess() below
|
---|
264 | }
|
---|
265 | process = null;
|
---|
266 | this.forciblyTerminateProcess = false; // reset
|
---|
267 | }
|
---|
268 | return this.exitValue;
|
---|
269 | }
|
---|
270 |
|
---|
271 | // Runs a process with default stream processing. Returns the exitValue
|
---|
272 | public int runProcess() {
|
---|
273 | return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
|
---|
274 | }
|
---|
275 |
|
---|
276 | // Run a process with custom stream processing (any custom handlers passed in that are null
|
---|
277 | // will use the default stream processing).
|
---|
278 | // Returns the exitValue from running the Process
|
---|
279 | public int runProcess(CustomProcessHandler procInHandler,
|
---|
280 | CustomProcessHandler procOutHandler,
|
---|
281 | CustomProcessHandler procErrHandler)
|
---|
282 | {
|
---|
283 | SafeProcess.OutputStreamGobbler inputGobbler = null;
|
---|
284 | SafeProcess.InputStreamGobbler errorGobbler = null;
|
---|
285 | SafeProcess.InputStreamGobbler outputGobbler = null;
|
---|
286 |
|
---|
287 | try {
|
---|
288 | this.forciblyTerminateProcess = false;
|
---|
289 |
|
---|
290 | // 1. get the Process object
|
---|
291 | process = doRuntimeExec();
|
---|
292 |
|
---|
293 |
|
---|
294 | // 2. create the streamgobblers and set any specified handlers on them
|
---|
295 |
|
---|
296 | // PROC INPUT STREAM
|
---|
297 | if(procInHandler == null) {
|
---|
298 | // send inputStr to process. The following constructor can handle inputStr being null
|
---|
299 | inputGobbler = // WriterToProcessInputStream
|
---|
300 | new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
|
---|
301 | } else { // user will do custom handling of process' InputStream
|
---|
302 | inputGobbler = new SafeProcess.OutputStreamGobbler(process.getOutputStream(), procInHandler);
|
---|
303 | }
|
---|
304 |
|
---|
305 | // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
|
---|
306 | if(procErrHandler == null) {
|
---|
307 | errorGobbler // ReaderFromProcessOutputStream
|
---|
308 | = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
|
---|
309 | } else {
|
---|
310 | errorGobbler
|
---|
311 | = new SafeProcess.InputStreamGobbler(process.getErrorStream(), procErrHandler);
|
---|
312 | }
|
---|
313 |
|
---|
314 | // PROC OUT STREAM to monitor for the expected std output line(s)
|
---|
315 | if(procOutHandler == null) {
|
---|
316 | outputGobbler
|
---|
317 | = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
|
---|
318 | } else {
|
---|
319 | outputGobbler
|
---|
320 | = new SafeProcess.InputStreamGobbler(process.getInputStream(), procOutHandler);
|
---|
321 | }
|
---|
322 |
|
---|
323 |
|
---|
324 | // 3. kick off the stream gobblers
|
---|
325 | this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
|
---|
326 |
|
---|
327 | } catch(IOException ioe) {
|
---|
328 | this.forciblyTerminateProcess = true;
|
---|
329 |
|
---|
330 | if(exceptionHandler != null) {
|
---|
331 | exceptionHandler.gotException(ioe);
|
---|
332 | } else {
|
---|
333 | log("IOexception: " + ioe.getMessage(), ioe);
|
---|
334 | }
|
---|
335 | } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace
|
---|
336 | this.forciblyTerminateProcess = true;
|
---|
337 |
|
---|
338 | if(exceptionHandler != null) {
|
---|
339 | exceptionHandler.gotException(ie);
|
---|
340 | log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
|
---|
341 | } else {
|
---|
342 | log("*** Unexpected InterruptException when waiting for process stream gobblers to die:" + ie.getMessage(), ie);
|
---|
343 | }
|
---|
344 |
|
---|
345 | // see comments in other runProcess()
|
---|
346 | Thread.currentThread().interrupt();
|
---|
347 |
|
---|
348 | } finally {
|
---|
349 | //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
|
---|
350 | //log("*** In finally of SafeProcess.runProcess(3 params): " + cmd);
|
---|
351 |
|
---|
352 | if( this.forciblyTerminateProcess ) {
|
---|
353 | log("*** Going to call process.destroy 2");
|
---|
354 | destroyProcess(process);
|
---|
355 | log("*** Have called process.destroy 2");
|
---|
356 | }
|
---|
357 | process = null;
|
---|
358 | this.forciblyTerminateProcess = false; // reset
|
---|
359 | }
|
---|
360 |
|
---|
361 | return this.exitValue;
|
---|
362 | }
|
---|
363 |
|
---|
364 | public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
|
---|
365 | {
|
---|
366 | SafeProcess.OutputStreamGobbler inputGobbler = null;
|
---|
367 | SafeProcess.InputStreamGobbler errorGobbler = null;
|
---|
368 | SafeProcess.InputStreamGobbler outputGobbler = null;
|
---|
369 |
|
---|
370 | try {
|
---|
371 | this.forciblyTerminateProcess = false;
|
---|
372 |
|
---|
373 | // 1. get the Process object
|
---|
374 | process = doRuntimeExec();
|
---|
375 |
|
---|
376 |
|
---|
377 | // 2. create the streamgobblers and set any specified handlers on them
|
---|
378 |
|
---|
379 | // PROC INPUT STREAM
|
---|
380 | // send inputStr to process. The following constructor can handle inputStr being null
|
---|
381 | inputGobbler = // WriterToProcessInputStream
|
---|
382 | new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
|
---|
383 |
|
---|
384 | // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
|
---|
385 | errorGobbler // ReaderFromProcessOutputStream
|
---|
386 | = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
|
---|
387 | // PROC OUT STREAM to monitor for the expected std output line(s)
|
---|
388 | outputGobbler
|
---|
389 | = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
|
---|
390 |
|
---|
391 |
|
---|
392 | // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
|
---|
393 | if(outLineByLineHandler != null) {
|
---|
394 | outputGobbler.setLineByLineHandler(outLineByLineHandler);
|
---|
395 | }
|
---|
396 | if(errLineByLineHandler != null) {
|
---|
397 | errorGobbler.setLineByLineHandler(errLineByLineHandler);
|
---|
398 | }
|
---|
399 |
|
---|
400 |
|
---|
401 | // 4. kick off the stream gobblers
|
---|
402 | this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
|
---|
403 |
|
---|
404 | } catch(IOException ioe) {
|
---|
405 | this.forciblyTerminateProcess = true;
|
---|
406 |
|
---|
407 | if(exceptionHandler != null) {
|
---|
408 | exceptionHandler.gotException(ioe);
|
---|
409 | } else {
|
---|
410 | log("IOexception: " + ioe.getMessage(), ioe);
|
---|
411 | }
|
---|
412 | } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it
|
---|
413 | this.forciblyTerminateProcess = true;
|
---|
414 |
|
---|
415 | if(exceptionHandler != null) {
|
---|
416 | exceptionHandler.gotException(ie);
|
---|
417 | log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
|
---|
418 | } else {
|
---|
419 | log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
|
---|
420 | }
|
---|
421 | // We're not causing any interruptions that may occur when trying to stop the worker threads
|
---|
422 | // So resort to default behaviour in this catch?
|
---|
423 | // "On catching InterruptedException, re-interrupt the thread."
|
---|
424 | // This is just how InterruptedExceptions tend to be handled
|
---|
425 | // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
|
---|
426 | // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
|
---|
427 | Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
|
---|
428 |
|
---|
429 | } finally {
|
---|
430 |
|
---|
431 | // Moved into here from GS2PerlConstructor and GShell.runLocal() which said
|
---|
432 | // "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 (no it doesn't!)"
|
---|
433 | // http://steveliles.github.io/invoking_processes_from_java.html
|
---|
434 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
435 | // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
|
---|
436 |
|
---|
437 | //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
|
---|
438 | //log("*** In finally of SafeProcess.runProcess(2 params): " + cmd);
|
---|
439 |
|
---|
440 | if( this.forciblyTerminateProcess ) {
|
---|
441 | log("*** Going to call process.destroy 1");
|
---|
442 | destroyProcess(process);
|
---|
443 | log("*** Have called process.destroy 1");
|
---|
444 | }
|
---|
445 | process = null;
|
---|
446 | this.forciblyTerminateProcess = false; //reset
|
---|
447 | }
|
---|
448 |
|
---|
449 | return this.exitValue;
|
---|
450 | }
|
---|
451 |
|
---|
452 | // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
|
---|
453 | // (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
|
---|
454 | // stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
|
---|
455 | // Searching for: "forcibly terminate external process launched by Java on Windows"
|
---|
456 | static void killAnyPerl() {
|
---|
457 | if(!Utility.isWindows()) return;
|
---|
458 |
|
---|
459 | // only on Windows do we need to still kill perl despite process.destroy()
|
---|
460 | try{
|
---|
461 | //Runtime.getRuntime().exec("taskkill /f /im perl.exe");
|
---|
462 | SafeProcess proc = new SafeProcess("taskkill /f /im perl.exe");
|
---|
463 | // /f Specifies to forcefully terminate the process(es).
|
---|
464 | // /t Terminates the specified process and any child processes which were started by it.
|
---|
465 | // /im is the image name (the name of the program), see Image Name column in Win Task Manager.
|
---|
466 | int exitValue = proc.runBasicProcess(); // don't care about IOstreams
|
---|
467 | } catch(Exception e) {
|
---|
468 | log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
|
---|
469 | }
|
---|
470 | }
|
---|
471 |
|
---|
472 | // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
|
---|
473 | // http://www.golesny.de/p/code/javagetpid
|
---|
474 | // Need to use JNA (Java Native Access), http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
|
---|
475 | // Got 4.1.0 from https://mvnrepository.com/artifact/net.java.dev.jna/jna/4.1.0 and https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform/4.1.0
|
---|
476 | // (see also https://mvnrepository.com/artifact/net.java.dev.jna/jna), since github's GettingStarted doc talks about downloading jna.jar but never provides it.
|
---|
477 | // See https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
|
---|
478 | public static long getProcessID(Process p)
|
---|
479 | {
|
---|
480 | long pid = -1;
|
---|
481 | try
|
---|
482 | {
|
---|
483 | //for windows
|
---|
484 | if (p.getClass().getName().equals("java.lang.Win32Process") ||
|
---|
485 | p.getClass().getName().equals("java.lang.ProcessImpl"))
|
---|
486 | {
|
---|
487 | Field f = p.getClass().getDeclaredField("handle");
|
---|
488 | f.setAccessible(true);
|
---|
489 | long handl = f.getLong(p);
|
---|
490 | Kernel32 kernel = Kernel32.INSTANCE;
|
---|
491 | WinNT.HANDLE hand = new WinNT.HANDLE();
|
---|
492 | hand.setPointer(Pointer.createConstant(handl));
|
---|
493 | pid = kernel.GetProcessId(hand);
|
---|
494 | f.setAccessible(false);
|
---|
495 | }
|
---|
496 | //for unix based operating systems
|
---|
497 | else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
|
---|
498 | {
|
---|
499 | Field f = p.getClass().getDeclaredField("pid");
|
---|
500 | f.setAccessible(true);
|
---|
501 | pid = f.getLong(p);
|
---|
502 | f.setAccessible(false);
|
---|
503 | }
|
---|
504 | }
|
---|
505 | catch(Exception ex)
|
---|
506 | {
|
---|
507 | log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex);
|
---|
508 | pid = -1;
|
---|
509 | }
|
---|
510 | return pid;
|
---|
511 | }
|
---|
512 |
|
---|
513 | // UNUSED - only kills toplevel process, same as p.destroy().
|
---|
514 | // Call destroyProcess(p) instead to kill any childprocesses launched
|
---|
515 | static void killProcessIfOnWindows(Process p) {
|
---|
516 | if(!Utility.isWindows()) return;
|
---|
517 |
|
---|
518 | // only on Windows do we need to still kill perl despite process.destroy()
|
---|
519 | // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
|
---|
520 | // http://www.golesny.de/p/code/javagetpid
|
---|
521 | long processID = getProcessID(p);
|
---|
522 |
|
---|
523 | SafeProcess.killWinProcessWithID(processID);
|
---|
524 | }
|
---|
525 |
|
---|
526 |
|
---|
527 | // Eventually move to using https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java ?
|
---|
528 | // Works with Apache license, http://www.apache.org/licenses/LICENSE-2.0
|
---|
529 |
|
---|
530 |
|
---|
531 | // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
|
---|
532 | // (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
|
---|
533 | // stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
|
---|
534 | // Searching for: "forcibly terminate external process launched by Java on Windows"
|
---|
535 | static void killWinProcessWithID(long processID) {
|
---|
536 |
|
---|
537 | // check if we have taskkill or tskill
|
---|
538 | String cmd = null;
|
---|
539 | if (SafeProcess.isAvailable("wmic")) {
|
---|
540 | // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
|
---|
541 | cmd = "wmic process " + processID + " delete"; // like "kill -9" on Windows
|
---|
542 | } else if(SafeProcess.isAvailable("taskkill")) {
|
---|
543 | cmd = "taskkill /f /t /PID " + processID;
|
---|
544 | } else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
|
---|
545 | cmd = "tskill " + processID; // https://ss64.com/nt/tskill.html
|
---|
546 | }
|
---|
547 | cmd = "tskill " + processID;
|
---|
548 |
|
---|
549 | try{
|
---|
550 | // Despite the /T flag, the following still doesn't kill subprocesses. Taskkill is also not available
|
---|
551 | // universally. Try alternates tskill and pskill, though they too don't kill further processes launched by p.
|
---|
552 | log("Attempting to taskkil process with ID " + processID);
|
---|
553 | //Runtime.getRuntime().exec("taskkill /f /t /PID " + processID);
|
---|
554 | SafeProcess proc = new SafeProcess(cmd);
|
---|
555 | // /f Specifies to forcefully terminate the process(es).
|
---|
556 | // /t Terminates the specified process and any child processes which were started by it.
|
---|
557 | int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" there is output that needs flushing
|
---|
558 |
|
---|
559 | } catch(Exception e) {
|
---|
560 | log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
|
---|
561 | }
|
---|
562 | }
|
---|
563 |
|
---|
564 | // On linux and mac, p.destroy() suffices to kill processes launched by p as well.
|
---|
565 | // On Windows we need to do more work, since processes launched by p remain around executing until termination otherwise.
|
---|
566 | // e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until termination.
|
---|
567 | static void destroyProcess(Process p) {
|
---|
568 | //killProcessIfOnWindows(p); // doesn't work: only terminates first level process launched, same as p.destroy()
|
---|
569 | //p.destroy();
|
---|
570 | //killAnyPerl(); // kills any running perl, even those not launched by GS
|
---|
571 |
|
---|
572 | if(!Utility.isWindows()) {
|
---|
573 | p.destroy();
|
---|
574 | return;
|
---|
575 | }
|
---|
576 |
|
---|
577 |
|
---|
578 |
|
---|
579 | // We don't want to kill all perl running processes. Just the ones whose command we sort of know and can see in TaskManager
|
---|
580 | // under the "Command Line" column of the Processes tab.
|
---|
581 | // This is possible with wmic, available since xp. First check wmic is really present on this Windows machine
|
---|
582 | // https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line
|
---|
583 | if(!SafeProcess.isAvailable("wmic")) {
|
---|
584 | log("wmic used to kill subprocesses is not available. Unable to terminate subprocesses...");
|
---|
585 | log("Kill them manually from the TaskManager or they will proceed to run to termination");
|
---|
586 |
|
---|
587 | // At least we can get rid of the top level process we launched
|
---|
588 | p.destroy();
|
---|
589 | return;
|
---|
590 | }
|
---|
591 |
|
---|
592 |
|
---|
593 | // get the process id of the process we launched
|
---|
594 | long processID = SafeProcess.getProcessID(p);
|
---|
595 |
|
---|
596 |
|
---|
597 | // What we want to be able to do on Windows is to kill a process by its command line as seen in Task Manager for that process.
|
---|
598 | // full-import.pl launches import.pl, full-build will launch buildcol.pl and activate.pl.
|
---|
599 | // These tend to have the collection name still at the end, so we can terminate a process knowing the collection name, since
|
---|
600 | // we can use wildcards in our wmic termination command.
|
---|
601 | // However, lucene_passes.pl is launched by buildcol.pl and doesn't have the collection name in its launching cmd line.
|
---|
602 |
|
---|
603 | // Works beatufilly to kill any CommandLine (as seen in TaskManager) that matches
|
---|
604 | //C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate
|
---|
605 | // https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/
|
---|
606 | // "WMIC Wildcard Search using like and %"
|
---|
607 | // All subsequent perl processes launched by Greenstone are launched with bin\windows\perl\bin\perl. So this works well:
|
---|
608 | // wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate
|
---|
609 | // However do we want to terminate all perl processes launched by Greenstone, or only those that we can infer were spawned by
|
---|
610 | // the SafeProcess that we've been running?
|
---|
611 |
|
---|
612 | /*
|
---|
613 | // Works in terminal, but when run through GLI, GLI is terminated too...
|
---|
614 | try{
|
---|
615 |
|
---|
616 | log("Attempting to use Windows to terminate sub processes");
|
---|
617 | Runtime.getRuntime().exec("wmic PATH win32_process Where \"CommandLine like '%bin%windows%perl%bin%perl%'\" Call Terminate");
|
---|
618 | } catch(Exception e) {
|
---|
619 | log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
|
---|
620 | }
|
---|
621 | */
|
---|
622 |
|
---|
623 | // https://superuser.com/questions/851692/track-which-program-launches-a-certain-process
|
---|
624 | // Can get parent process id and process id, and then kill those with child process id
|
---|
625 | // http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows
|
---|
626 | // Can get the pids of all childprocesses launched by parent process with parent pid.
|
---|
627 | // And vice versa:
|
---|
628 | // e.g. if you know the parent pid and want to know all the pids of the child processes spawned:
|
---|
629 | // wmic process where (parentprocessid=596) get processid
|
---|
630 | // if you know a child process id and want to know the parent's id:
|
---|
631 | // wmic process where (processid=180) get parentprocessid
|
---|
632 |
|
---|
633 | String[] child_pids = null;
|
---|
634 |
|
---|
635 |
|
---|
636 | log("Attempting to use Windows to terminate sub processes");
|
---|
637 | SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+processID+") get processid");
|
---|
638 | proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
|
---|
639 | // Output looks like:
|
---|
640 | // ProcessId
|
---|
641 | // 6040
|
---|
642 | // 180
|
---|
643 | // 4948
|
---|
644 | // 1084
|
---|
645 | // 6384
|
---|
646 | // If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
|
---|
647 | // No Instance(s) Available.
|
---|
648 | int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.
|
---|
649 | log("@@@@ Return value from proc: " + exitValue);
|
---|
650 | String stdOutput = proc.getStdOutput();
|
---|
651 | // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
|
---|
652 |
|
---|
653 | log("@@@@ Got output:\n" + stdOutput);
|
---|
654 | if(stdOutput.indexOf("No Instance(s) Available.") == -1) {
|
---|
655 | //int start = stdOutput.indexOf("\n"); // skip first line, which is the column name "ProcessId"
|
---|
656 | //stdOutput = stdOutput.substring(start+1);
|
---|
657 | //child_pids = stdOutput.split("/(\\r?\\n/)+"); // get rid of newline, so we should only have a list of Long numbers
|
---|
658 |
|
---|
659 | // Now we can get rid of the top level process
|
---|
660 | p.destroy();
|
---|
661 | // find all childprocesses for that pid and terminate them too:
|
---|
662 | Scanner sc = new Scanner(stdOutput);
|
---|
663 | while (sc.hasNext()) {
|
---|
664 | if(!sc.hasNextLong()) {
|
---|
665 | sc.next(); // discard the current token since it's not a Long
|
---|
666 | } else {
|
---|
667 | long child_pid = sc.nextLong();
|
---|
668 | log("Attempting to terminate child pid: " + child_pid);
|
---|
669 | SafeProcess.killWinProcessWithID(child_pid);
|
---|
670 | }
|
---|
671 | }
|
---|
672 | sc.close();
|
---|
673 | } else {
|
---|
674 | // No child process. Only the top level process needs to be got rid of:
|
---|
675 | p.destroy();
|
---|
676 |
|
---|
677 | }
|
---|
678 |
|
---|
679 | /*
|
---|
680 |
|
---|
681 | // Now we can get rid of the top level process
|
---|
682 | p.destroy();
|
---|
683 |
|
---|
684 |
|
---|
685 | // Then kill each subprocess.
|
---|
686 |
|
---|
687 | if(child_pids != null) {
|
---|
688 | for(int i = 0; i < child_pids.length; i++) {
|
---|
689 | log("Attempting to terminate child pid: |" + child_pids[i] + "|");
|
---|
690 | SafeProcess.killWinProcessWithID(Long.parseLong(child_pids[i].trim()));
|
---|
691 | }
|
---|
692 | }
|
---|
693 | */
|
---|
694 |
|
---|
695 | }
|
---|
696 |
|
---|
697 |
|
---|
698 | // Run `which` on a program to find out if it is available. which.exe is included in winbin.
|
---|
699 | // On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
|
---|
700 | // where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
|
---|
701 | // There is no `where` on Linux/Mac, must use which for them.
|
---|
702 | // On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names.
|
---|
703 | public static boolean isAvailable(String program) {
|
---|
704 | try {
|
---|
705 | // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
|
---|
706 | // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
|
---|
707 | SafeProcess prcs = new SafeProcess("which " + program);
|
---|
708 | prcs.runProcess();
|
---|
709 | String output = prcs.getStdOutput();
|
---|
710 | if(output.equals("")) {
|
---|
711 | return false;
|
---|
712 | }
|
---|
713 | //System.err.println("*** 'which " + program + "' returned: " + output);
|
---|
714 | return true;
|
---|
715 | } catch (Exception exc) {
|
---|
716 | return false;
|
---|
717 | }
|
---|
718 | }
|
---|
719 |
|
---|
720 | // Google Java external process destroy kill subprocesses
|
---|
721 | // https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
|
---|
722 |
|
---|
723 | //******************** Inner class and interface definitions ********************//
|
---|
724 | // Static inner classes can be instantiated without having to instantiate an object of the outer class first
|
---|
725 |
|
---|
726 | // Can have public static interfaces too,
|
---|
727 | // see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
|
---|
728 | // Implementors need to take care that the implementations are thread safe
|
---|
729 | // http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
|
---|
730 | public static interface ExceptionHandler {
|
---|
731 |
|
---|
732 | // when implementing ExceptionHandler.gotException(), if it manipulates anything that's
|
---|
733 | // not threadsafe, declare gotException() as a synchronized method to ensure thread safety
|
---|
734 | public void gotException(Exception e); // can't declare as synchronized in interface method declaration
|
---|
735 | }
|
---|
736 |
|
---|
737 | // Write your own run() body for any StreamGobbler. You need to create an instance of a class
|
---|
738 | // extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
|
---|
739 | // Do not create a single CustomProcessHandler instance and reuse it for all three streams,
|
---|
740 | // i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
|
---|
741 | // Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
|
---|
742 | // example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
|
---|
743 | // CustomProcessHandler is made an abstract class instead of an interface to force classes that want
|
---|
744 | // to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
|
---|
745 | // that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
|
---|
746 | // CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
|
---|
747 | // executed, rather than from the individual threads that deal with each iostream of the Process.
|
---|
748 | public static abstract class CustomProcessHandler {
|
---|
749 |
|
---|
750 | protected final int source;
|
---|
751 |
|
---|
752 | protected CustomProcessHandler(int src) {
|
---|
753 | this.source = src; // STDERR or STDOUT or STDIN
|
---|
754 | }
|
---|
755 |
|
---|
756 | public String getThreadNamePrefix() {
|
---|
757 | return SafeProcess.streamToString(this.source);
|
---|
758 | }
|
---|
759 |
|
---|
760 | public abstract void run(Closeable stream); //InputStream or OutputStream
|
---|
761 | }
|
---|
762 |
|
---|
763 | // When using the default stream processing to read from a process' stdout or stderr stream, you can
|
---|
764 | // create a class extending LineByLineHandler for the process' err stream and one for its output stream
|
---|
765 | // to do something on a line by line basis, such as sending the line to a log
|
---|
766 | public static abstract class LineByLineHandler {
|
---|
767 | protected final int source;
|
---|
768 |
|
---|
769 | protected LineByLineHandler(int src) {
|
---|
770 | this.source = src; // STDERR or STDOUT
|
---|
771 | }
|
---|
772 |
|
---|
773 | public String getThreadNamePrefix() {
|
---|
774 | return SafeProcess.streamToString(this.source);
|
---|
775 | }
|
---|
776 |
|
---|
777 | public abstract void gotLine(String line); // first non-null line
|
---|
778 | public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
|
---|
779 | }
|
---|
780 |
|
---|
781 |
|
---|
782 | //**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
|
---|
783 |
|
---|
784 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
785 | // This class is used in FormatConversionDialog to properly read from the stdout and stderr
|
---|
786 | // streams of a Process, Process.getInputStream() and Process.getErrorSream()
|
---|
787 | public static class InputStreamGobbler extends Thread
|
---|
788 | {
|
---|
789 | private InputStream is = null;
|
---|
790 | private StringBuffer outputstr = new StringBuffer();
|
---|
791 | private boolean split_newlines = false;
|
---|
792 | private CustomProcessHandler customHandler = null;
|
---|
793 | private LineByLineHandler lineByLineHandler = null;
|
---|
794 |
|
---|
795 | protected InputStreamGobbler() {
|
---|
796 | super("InputStreamGobbler");
|
---|
797 | }
|
---|
798 |
|
---|
799 | public InputStreamGobbler(InputStream is)
|
---|
800 | {
|
---|
801 | this(); // sets thread name
|
---|
802 | this.is = is;
|
---|
803 | this.split_newlines = false;
|
---|
804 | }
|
---|
805 |
|
---|
806 | public InputStreamGobbler(InputStream is, boolean split_newlines)
|
---|
807 | {
|
---|
808 | this(); // sets thread name
|
---|
809 | this.is = is;
|
---|
810 | this.split_newlines = split_newlines;
|
---|
811 |
|
---|
812 | }
|
---|
813 |
|
---|
814 | public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
|
---|
815 | {
|
---|
816 | this(); // thread name
|
---|
817 | this.is = is;
|
---|
818 | this.customHandler = customHandler;
|
---|
819 | this.adjustThreadName(customHandler.getThreadNamePrefix());
|
---|
820 | }
|
---|
821 |
|
---|
822 |
|
---|
823 | private void adjustThreadName(String prefix) {
|
---|
824 | this.setName(prefix + this.getName());
|
---|
825 | }
|
---|
826 |
|
---|
827 | public void setLineByLineHandler(LineByLineHandler lblHandler) {
|
---|
828 | this.lineByLineHandler = lblHandler;
|
---|
829 | this.adjustThreadName(lblHandler.getThreadNamePrefix());
|
---|
830 | }
|
---|
831 |
|
---|
832 | // default run() behaviour
|
---|
833 | public void runDefault()
|
---|
834 | {
|
---|
835 | BufferedReader br = null;
|
---|
836 | try {
|
---|
837 | br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
---|
838 | String line=null;
|
---|
839 | while ( !this.isInterrupted() && (line = br.readLine()) != null ) {
|
---|
840 |
|
---|
841 | //log("@@@ GOT LINE: " + line);
|
---|
842 | outputstr.append(line);
|
---|
843 | if(split_newlines) {
|
---|
844 | outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
|
---|
845 | }
|
---|
846 |
|
---|
847 | if(lineByLineHandler != null) { // let handler deal with newlines
|
---|
848 | lineByLineHandler.gotLine(line);
|
---|
849 | }
|
---|
850 | }
|
---|
851 |
|
---|
852 | } catch (IOException ioe) {
|
---|
853 | if(lineByLineHandler != null) {
|
---|
854 | lineByLineHandler.gotException(ioe);
|
---|
855 | } else {
|
---|
856 | log("Exception when reading process stream with " + this.getName() + ": ", ioe);
|
---|
857 | }
|
---|
858 | } finally {
|
---|
859 | if(this.isInterrupted()) {
|
---|
860 | log("@@@ Successfully interrupted " + this.getName() + ".");
|
---|
861 | }
|
---|
862 | SafeProcess.closeResource(br);
|
---|
863 | }
|
---|
864 | }
|
---|
865 |
|
---|
866 | public void runCustom() {
|
---|
867 | this.customHandler.run(is);
|
---|
868 | }
|
---|
869 |
|
---|
870 | public void run() {
|
---|
871 | if(this.customHandler == null) {
|
---|
872 | runDefault();
|
---|
873 | } else {
|
---|
874 | runCustom();
|
---|
875 | }
|
---|
876 | }
|
---|
877 |
|
---|
878 | public String getOutput() {
|
---|
879 | return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
|
---|
880 | }
|
---|
881 | } // end static inner class InnerStreamGobbler
|
---|
882 |
|
---|
883 |
|
---|
884 | // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
|
---|
885 | // This class is used in FormatConversionDialog to properly write to the inputstream of a Process
|
---|
886 | // Process.getOutputStream()
|
---|
887 | public static class OutputStreamGobbler extends Thread
|
---|
888 | {
|
---|
889 | private OutputStream os = null;
|
---|
890 | private String inputstr = "";
|
---|
891 | private CustomProcessHandler customHandler = null;
|
---|
892 |
|
---|
893 | protected OutputStreamGobbler() {
|
---|
894 | super("stdinOutputStreamGobbler"); // thread name
|
---|
895 | }
|
---|
896 |
|
---|
897 | public OutputStreamGobbler(OutputStream os) {
|
---|
898 | this(); // set thread name
|
---|
899 | this.os = os;
|
---|
900 | }
|
---|
901 |
|
---|
902 | public OutputStreamGobbler(OutputStream os, String inputstr)
|
---|
903 | {
|
---|
904 | this(); // set thread name
|
---|
905 | this.os = os;
|
---|
906 | this.inputstr = inputstr;
|
---|
907 | }
|
---|
908 |
|
---|
909 | public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
|
---|
910 | this(); // set thread name
|
---|
911 | this.os = os;
|
---|
912 | this.customHandler = customHandler;
|
---|
913 | }
|
---|
914 |
|
---|
915 | // default run() behaviour
|
---|
916 | public void runDefault() {
|
---|
917 |
|
---|
918 | if (inputstr == null) {
|
---|
919 | return;
|
---|
920 | }
|
---|
921 |
|
---|
922 | // also quit if the process was interrupted before we could send anything to its stdin
|
---|
923 | if(this.isInterrupted()) {
|
---|
924 | log(this.getName() + " thread was interrupted.");
|
---|
925 | return;
|
---|
926 | }
|
---|
927 |
|
---|
928 | BufferedWriter osw = null;
|
---|
929 | try {
|
---|
930 | osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
|
---|
931 | //System.out.println("@@@ SENDING LINE: " + inputstr);
|
---|
932 | osw.write(inputstr, 0, inputstr.length());
|
---|
933 | osw.newLine();//osw.write("\n");
|
---|
934 | osw.flush();
|
---|
935 |
|
---|
936 | // Don't explicitly send EOF when using StreamGobblers as below,
|
---|
937 | // as the EOF char is echoed to output.
|
---|
938 | // Flushing the write handle and/or closing the resource seems
|
---|
939 | // to already send EOF silently.
|
---|
940 |
|
---|
941 | /*if(Utility.isWindows()) {
|
---|
942 | osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
|
---|
943 | } else { // EOF on Linux/Mac is Ctrl-D
|
---|
944 | osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
|
---|
945 | }
|
---|
946 | osw.flush();
|
---|
947 | */
|
---|
948 | } catch (IOException ioe) {
|
---|
949 | log("Exception writing to SafeProcess' inputstream: ", ioe);
|
---|
950 | } finally {
|
---|
951 | SafeProcess.closeResource(osw);
|
---|
952 | }
|
---|
953 | }
|
---|
954 |
|
---|
955 | // call the user's custom handler for the run() method
|
---|
956 | public void runCustom() {
|
---|
957 | this.customHandler.run(os);
|
---|
958 | }
|
---|
959 |
|
---|
960 | public void run()
|
---|
961 | {
|
---|
962 | if(this.customHandler == null) {
|
---|
963 | runDefault();
|
---|
964 | } else {
|
---|
965 | runCustom();
|
---|
966 | }
|
---|
967 | }
|
---|
968 | } // end static inner class OutputStreamGobbler
|
---|
969 |
|
---|
970 | //**************** Static methods **************//
|
---|
971 |
|
---|
972 |
|
---|
973 | // logger and DebugStream print commands are synchronized, therefore thread safe.
|
---|
974 | public static void log(String msg) {
|
---|
975 | //logger.info(msg);
|
---|
976 |
|
---|
977 | System.err.println(msg);
|
---|
978 |
|
---|
979 | //DebugStream.println(msg);
|
---|
980 | }
|
---|
981 |
|
---|
982 | public static void log(String msg, Exception e) { // Print stack trace on the exception
|
---|
983 | //logger.error(msg, e);
|
---|
984 |
|
---|
985 | System.err.println(msg);
|
---|
986 | e.printStackTrace();
|
---|
987 |
|
---|
988 | //DebugStream.println(msg);
|
---|
989 | //DebugStream.printStackTrace(e);
|
---|
990 | }
|
---|
991 |
|
---|
992 | public static void log(Exception e) {
|
---|
993 | //logger.error(e);
|
---|
994 |
|
---|
995 | e.printStackTrace();
|
---|
996 |
|
---|
997 | //DebugStream.printStackTrace(e);
|
---|
998 | }
|
---|
999 |
|
---|
1000 | public static void log(String msg, Exception e, boolean printStackTrace) {
|
---|
1001 | if(printStackTrace) {
|
---|
1002 | log(msg, e);
|
---|
1003 | } else {
|
---|
1004 | log(msg);
|
---|
1005 | }
|
---|
1006 | }
|
---|
1007 |
|
---|
1008 | public static String streamToString(int src) {
|
---|
1009 | String stream;
|
---|
1010 | switch(src) {
|
---|
1011 | case STDERR:
|
---|
1012 | stream = "stderr";
|
---|
1013 | break;
|
---|
1014 | case STDOUT:
|
---|
1015 | stream = "stdout";
|
---|
1016 | break;
|
---|
1017 | default:
|
---|
1018 | stream = "stdin";
|
---|
1019 | }
|
---|
1020 | return stream;
|
---|
1021 | }
|
---|
1022 |
|
---|
1023 | //**************** Useful static methods. Copied from GLI's Utility.java ******************
|
---|
1024 | // For safely closing streams/handles/resources.
|
---|
1025 | // For examples of use look in the Input- and OutputStreamGobbler classes.
|
---|
1026 | // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
|
---|
1027 | // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
|
---|
1028 | public static boolean closeResource(Closeable resourceHandle) {
|
---|
1029 | boolean success = false;
|
---|
1030 | try {
|
---|
1031 | if(resourceHandle != null) {
|
---|
1032 | resourceHandle.close();
|
---|
1033 | resourceHandle = null;
|
---|
1034 | success = true;
|
---|
1035 | }
|
---|
1036 | } catch(Exception e) {
|
---|
1037 | log("Exception closing resource: " + e.getMessage(), e);
|
---|
1038 | resourceHandle = null;
|
---|
1039 | success = false;
|
---|
1040 | } finally {
|
---|
1041 | return success;
|
---|
1042 | }
|
---|
1043 | }
|
---|
1044 |
|
---|
1045 | public static boolean closeProcess(Process prcs) {
|
---|
1046 | boolean success = true;
|
---|
1047 | if( prcs != null ) {
|
---|
1048 | success = success && closeResource(prcs.getErrorStream());
|
---|
1049 | success = success && closeResource(prcs.getOutputStream());
|
---|
1050 | success = success && closeResource(prcs.getInputStream());
|
---|
1051 | prcs.destroy();
|
---|
1052 | }
|
---|
1053 | return success;
|
---|
1054 | }
|
---|
1055 |
|
---|
1056 | // Moved from GShell.java
|
---|
1057 | /** Determine if the given process is still executing. It does this by attempting to throw an exception - not the most efficient way, but the only one as far as I know
|
---|
1058 | * @param process the Process to test
|
---|
1059 | * @return true if it is still executing, false otherwise
|
---|
1060 | */
|
---|
1061 | static public boolean processRunning(Process process) {
|
---|
1062 | boolean process_running = false;
|
---|
1063 |
|
---|
1064 | try {
|
---|
1065 | process.exitValue(); // This will throw an exception if the process hasn't ended yet.
|
---|
1066 | }
|
---|
1067 | catch(IllegalThreadStateException itse) {
|
---|
1068 | process_running = true;
|
---|
1069 | }
|
---|
1070 | catch(Exception exception) {
|
---|
1071 | log(exception); // DebugStream.printStackTrace(exception);
|
---|
1072 | }
|
---|
1073 | return process_running;
|
---|
1074 | }
|
---|
1075 |
|
---|
1076 | } // end class SafeProcess
|
---|