source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/build/GS2PerlConstructor.java@ 31631

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

Further changes to SafeProcess to turn some interfaces to abstract classes. Corresponding changes to GS2PerlConstructor which makes use of this.

  • Property svn:keywords set to Author Date Id Revision
File size: 29.7 KB
Line 
1package org.greenstone.gsdl3.build;
2
3// greenstome classes
4import org.greenstone.gsdl3.util.*;
5import org.greenstone.util.Misc;
6import org.greenstone.util.GlobalProperties;
7import org.greenstone.util.SafeProcess;
8
9// xml classes
10import org.w3c.dom.Element;
11import org.w3c.dom.NodeList;
12
13//general java classes
14import java.io.BufferedReader;
15import java.io.BufferedWriter;
16import java.io.Closeable;
17import java.io.FileWriter;
18import java.io.InputStreamReader;
19import java.io.File;
20import java.io.InputStream;
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Vector;
24
25import org.apache.log4j.*;
26
27/**
28 * CollectionConstructor class for greenstone 2 compatible building it uses the
29 * perl scripts to do the building stuff
30 */
31public class GS2PerlConstructor extends CollectionConstructor implements SafeProcess.ExceptionHandler
32{
33 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.build.GS2PerlConstructor.class.getName());
34
35 public static final int NEW = 0;
36 public static final int IMPORT = 1;
37 public static final int BUILD = 2;
38 public static final int ACTIVATE = 3;
39 public static final int MODIFY_METADATA_SERVER = 4;
40
41 /**
42 * gsdlhome for greenstone 2 - we use the perl modules and building scripts
43 * from there
44 */
45 protected String gsdl2home = null;
46 /** gsdlhome for gsdl3 - shouldn't need this eventually ?? */
47 protected String gsdl3home = null;
48 /** gsdlos for greenstone 2 */
49 protected String gsdlos = null;
50 /** the path environment variable */
51 protected String path = null;
52
53
54 public GS2PerlConstructor(String name)
55 {
56 super(name);
57 }
58
59 /** retrieves the necessary environment variables */
60 public boolean configure()
61 {
62 // try to get the environment variables
63 this.gsdl3home = GlobalProperties.getGSDL3Home();
64 this.gsdl2home = this.gsdl3home + File.separator + ".." + File.separator + "gs2build";
65 this.gsdlos = Misc.getGsdlOS();
66
67 this.path = System.getenv("PATH");
68
69 if (this.gsdl2home == null)
70 {
71 System.err.println("You must have gs2build installed, and GSDLHOME set for GS2Perl building to work!!");
72 return false;
73 }
74 if (this.gsdl3home == null || this.gsdlos == null)
75 {
76 System.err.println("You must have GSDL3HOME and GSDLOS set for GS2Perl building to work!!");
77 return false;
78 }
79 if (this.path == null)
80 {
81 System.err.println("You must have the PATH set for GS2Perl building to work!!");
82 return false;
83 }
84 return true;
85 }
86
87 public void run()
88 {
89 String msg;
90 ConstructionEvent evt;
91 if (this.process_type == -1)
92 {
93 msg = "Error: you must set the action type";
94 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
95 sendMessage(evt);
96 return;
97 }
98 if (this.site_home == null)
99 {
100 msg = "Error: you must set site_home";
101 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
102 sendMessage(evt);
103 return;
104 }
105 if (this.process_type != NEW && this.collection_name == null)
106 {
107 msg = "Error: you must set collection_name";
108 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
109 sendMessage(evt);
110 return;
111 }
112
113 switch (this.process_type)
114 {
115 case NEW:
116 newCollection();
117 break;
118 case IMPORT:
119 importCollection();
120 break;
121 case BUILD:
122 buildCollection();
123 break;
124 case ACTIVATE:
125 activateCollection();
126 break;
127 case MODIFY_METADATA_SERVER:
128 modifyMetadataForCollection();
129 break;
130 default:
131 msg = "wrong type of action specified!";
132 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
133 sendMessage(evt);
134 break;
135 }
136 }
137
138 protected void newCollection()
139 {
140 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: new collection."));
141 Vector<String> command = new Vector<String>();
142 command.add("gs2_mkcol.pl");
143 command.add("-site");
144 command.add(this.site_home);
145 command.add("-collectdir");
146 command.add(GSFile.collectDir(this.site_home));
147 command.addAll(extractParameters(this.process_params));
148 command.add(this.collection_name);
149 String[] command_str = {};
150 command_str = command.toArray(command_str);
151 if (runPerlCommand(command_str))
152 {
153 // success!! - need to send the final completed message
154 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
155 } // else an error message has already been sent, do nothing
156
157 }
158
159 protected void importCollection()
160 {
161 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: import collection."));
162 Vector<String> command = new Vector<String>();
163
164 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
165 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
166 {
167 perlPath = perlPath + File.separator;
168 }
169
170 command.add(perlPath + "perl");
171 command.add("-S");
172 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "import.pl");
173 if (this.manifest_file != null)
174 {
175 command.add("-keepold");
176 command.add("-manifest");
177 command.add(this.manifest_file);
178 }
179 command.add("-site");
180 command.add(this.site_name);
181 command.add("-collectdir");
182 command.add(GSFile.collectDir(this.site_home));
183 command.addAll(extractParameters(this.process_params));
184 command.add(this.collection_name);
185 String[] command_str = {};
186 command_str = command.toArray(command_str);
187
188 if (runPerlCommand(command_str))
189 {
190 // success!! - need to send the final completed message
191 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
192 } // else an error message has already been sent, do nothing
193 }
194
195 protected void buildCollection()
196 {
197 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: build collection."));
198 Vector<String> command = new Vector<String>();
199
200 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
201 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
202 {
203 perlPath = perlPath + File.separator;
204 }
205
206 command.add(perlPath + "perl");
207 command.add("-S");
208 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "incremental-buildcol.pl");
209 command.add("-incremental");
210 command.add("-builddir");
211 command.add(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator +"index");
212 command.add("-site");
213 command.add(this.site_name);
214 command.add("-collectdir");
215 command.add(GSFile.collectDir(this.site_home));
216// command.add("-removeold"); // saves some seconds processing time when this flag's added in explicitly
217 command.addAll(extractParameters(this.process_params));
218 command.add(this.collection_name);
219
220 String[] command_str = {};
221 command_str = command.toArray(command_str);
222
223 if (runPerlCommand(command_str))
224 {
225 // success!! - need to send the final completed message
226 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
227 }// else an error message has already been sent, do nothing
228 }
229
230 protected void activateCollection()
231 {
232 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: activate collection."));
233
234 // first check that we have a building directory
235 // (don't want to bother running activate.pl otherwise)
236 File build_dir = new File(GSFile.collectionIndexDir(this.site_home, this.collection_name));
237 if (!build_dir.exists())
238 {
239 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "build dir doesn't exist!"));
240 return;
241 }
242
243 /*
244
245 // move building to index
246 File index_dir = new File(GSFile.collectionIndexDir(this.site_home, this.collection_name));
247 if (index_dir.exists())
248 {
249 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "deleting index directory"));
250 GSFile.deleteFile(index_dir);
251 if (index_dir.exists())
252 {
253 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "index directory still exists!"));
254 return;
255 }
256 }
257
258 GSFile.moveDirectory(build_dir, index_dir);
259 if (!index_dir.exists())
260 {
261 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "index dir wasn't created!"));
262 }
263
264 // success!! - need to send the final completed message
265 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
266 */
267
268 // Running activate.pl instead of making java move building to index as above
269 // circumvents the issue of the jdbm .lg log file (managed by TransactionManager)
270 // in index dir not getting deleted at times. The perl code is able to delete this
271 // sucessfully consistently during testing, whereas java at times is unable to delete it.
272 Vector<String> command = new Vector<String>();
273
274 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
275 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
276 {
277 perlPath = perlPath + File.separator;
278 }
279
280 command.add(perlPath + "perl");
281 command.add("-S");
282 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "activate.pl");
283 command.add("-incremental");
284 command.add("-builddir");
285 command.add(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator +"index");
286 command.add("-site");
287 command.add(this.site_name);
288 command.add("-collectdir");
289 command.add(GSFile.collectDir(this.site_home));
290// command.add("-removeold"); // saves some seconds processing time when this flag's added in explicitly. Shouldn't be added for incremental building
291 command.add("-skipactivation"); // gsdl3/util/GS2Construct does the activation and reactivation
292 command.addAll(extractParameters(this.process_params));
293 command.add(this.collection_name);
294
295 String[] command_str = {};
296 command_str = command.toArray(command_str);
297
298 if (runPerlCommand(command_str))
299 {
300 // success!! - need to send the final completed message
301 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
302 }// else an error message has already been sent, do nothing
303
304 }
305
306
307 protected void modifyMetadataForCollection()
308 {
309 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection metadata: modifyMetadata (set or remove meta) for collection."));
310
311 Vector<String> command = new Vector<String>();
312
313 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
314 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
315 {
316 perlPath = perlPath + File.separator;
317 }
318
319 String cgi_directory = GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi";
320 command.add(perlPath + "perl");
321 command.add("-S");
322 //command.add(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi" + File.separator + "metadata-server.pl");
323 command.add(cgi_directory + File.separator + "metadata-server.pl");
324
325 // Need to set QUERY_STRING and REQUEST_METHOD=GET in environment
326 // Also set GS3_AUTHENTICATED, to allow running metadata-server.pl with mod (set and remove) commands
327 // http://www.cgi101.com/class/ch3/text.html
328 String[] envvars = {
329 "QUERY_STRING=" + this.query_string,
330 "REQUEST_METHOD=GET",
331 "GS3_AUTHENTICATED=true" // how do we set the env var without having to assign it a value?
332 };
333
334 String[] command_str = {};
335 command_str = command.toArray(command_str);
336
337 // http://www.cgi101.com/class/ch3/text.html
338 // setenv QUERY_STRING and REQUEST_METHOD = GET.
339 if (runPerlCommand(command_str, envvars, new File(cgi_directory)))
340 //new File(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi")))
341 {
342 // success!! - need to send the final completed message
343 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
344 }// else an error message has already been sent, do nothing
345
346 }
347
348 /** extracts all the args from the xml and returns them in a Vector */
349 protected Vector<String> extractParameters(Element param_list)
350 {
351
352 Vector<String> args = new Vector<String>();
353 if (param_list == null)
354 {
355 return args; // return an empty vector
356 }
357 NodeList params = param_list.getElementsByTagName(GSXML.PARAM_ELEM);
358
359 for (int i = 0; i < params.getLength(); i++)
360 {
361 Element p = (Element) params.item(i);
362 String name = p.getAttribute(GSXML.NAME_ATT);
363 String value = p.getAttribute(GSXML.VALUE_ATT);
364 if (!name.equals(""))
365 {
366 args.add("-" + name);
367 if (!value.equals(""))
368 {
369 args.add(value);
370 }
371 }
372 }
373
374 return args;
375 }
376
377 protected SafeProcess createPerlProcess(String[] command, String[] envvars, File dir) {
378 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
379 String srcHome = this.gsdl3home.substring(0, sepIndex);
380
381 ArrayList<String> args = new ArrayList<String>();
382 args.add("GSDLHOME=" + this.gsdl2home);
383 args.add("GSDL3HOME=" + this.gsdl3home);
384 args.add("GSDL3SRCHOME=" + srcHome);
385 args.add("GSDLOS=" + this.gsdlos);
386 args.add("GSDL-RUN-SETUP=true");
387 args.add("PERL_PERTURB_KEYS=0");
388
389 if(envvars != null) {
390 for(int i = 0; i < envvars.length; i++) {
391 args.add(envvars[i]);
392 }
393 }
394
395 for (String a : System.getenv().keySet()) {
396 args.add(a + "=" + System.getenv(a));
397 }
398
399 SafeProcess perlProcess
400 = new SafeProcess(command, args.toArray(new String[args.size()]), dir); // dir can be null
401
402 return perlProcess;
403 }
404
405 // If you want to run a Perl command without doing GS2PerlConstructor's custom logging in the build log
406 // The use the runSimplePerlCommand() versions, which use the default behaviour of running a SafeProcess
407 protected boolean runSimplePerlCommand(String[] command) {
408 return runSimplePerlCommand(command, null, null);
409 }
410
411 protected boolean runSimplePerlCommand(String[] command, String[] envvars, File dir) {
412 boolean success = false;
413
414 String command_str = "";
415 for (int i = 0; i < command.length; i++) {
416 command_str = command_str + command[i] + " ";
417 }
418
419 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
420
421 ///logger.info("### Running simple command = " + command_str);
422
423 // This is where we create and run our perl process safely
424 SafeProcess perlProcess = createPerlProcess(command, envvars, dir); // dir can be null
425
426 perlProcess.setExceptionHandler(this);
427
428 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
429
430 int exitVal = perlProcess.runProcess(); // uses default processing of the perl process' iostreams provided by SafeProcess
431
432 if (exitVal == 0) {
433 success = true;
434 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
435 } else {
436 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
437 success = false; // explicit
438 }
439
440 return success;
441 }
442
443
444 /** returns true if completed correctly, false otherwise
445 * Building operations call runPerlCommand which sends build output to collect/log/build_log.#*.txt
446 */
447 protected boolean runPerlCommand(String[] command) {
448 return runPerlCommand(command, null, null);
449 }
450
451
452 protected boolean runPerlCommand(String[] command, String[] envvars, File dir)
453 {
454 boolean success = true;
455
456 String command_str = "";
457 for (int i = 0; i < command.length; i++) {
458 command_str = command_str + command[i] + " ";
459 }
460
461 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
462
463 ///logger.info("### Running logged command = " + command_str);
464
465 // This is where we create and run our perl process safely
466 SafeProcess perlProcess = createPerlProcess(command, envvars, dir); // dir can be null
467
468 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
469
470 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
471 if (!logDir.exists()) {
472 logDir.mkdir();
473 }
474
475 // Only from Java 7+: Try-with-Resources block will safely close the BufferedWriter
476 // https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
477 BufferedWriter bw = null;
478 try {
479
480 bw = new BufferedWriter(new FileWriter(new File(logDir, "build_log." + (System.currentTimeMillis()) + ".txt")));
481
482 bw.write("Document Editor Build \n");
483 bw.write("Command = " + command_str + "\n");
484
485 // handle each incoming line from stdout and stderr streams, and any exceptions that occur then
486 SafeProcess.CustomProcessHandler processOutHandler
487 = new SynchronizedProcessHandler(bw, SafeProcess.STDOUT);
488 SafeProcess.CustomProcessHandler processErrHandler
489 = new SynchronizedProcessHandler(bw, SafeProcess.STDERR);
490
491 // GS2PerlConstructor will do further handling of exceptions that may occur during the perl
492 // process (including if writing something to the process' inputstream, not that we're doing that for this perlProcess)
493 perlProcess.setExceptionHandler(this);
494
495 // finally, execute the process
496
497 // Captures the std err of a program and pipes it into
498 // std in of java, as before.
499
500 perlProcess.runProcess(null, processOutHandler, processErrHandler); // use default procIn handling
501
502 // The original runPerlCommand() code had an ineffective check for whether the cmd had been cancelled
503 // midway through executing the perl, as condition of a while loop reading from stderr and stdout.
504 // We don't include the cancel check here, as superclass CollectionConstructor.stopAction(), which set
505 // this.cancel to true, never got called anywhere.
506 // But I think a proper cancel of our perl process launched by this GS2PerlConstructor Thread object
507 // and of the worker threads it launches, could be implemented with interrupts. See:
508 // http://stackoverflow.com/questions/6859681/better-way-to-signal-other-thread-to-stop
509 // https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html
510 // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupted()
511 // https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
512 // The code that calls GS2PerlConstructor.stopAction() should also call GSPerlConstructor.interrupt()
513 // Then in SafeProcess.runProcess(), I think the waitFor() will throw an InterruptedException()
514 // This can be caught and interrupt() called on SafeProcess' workerthreads,
515 // Any workerthreads' run() methods that block (IO, loops) can test this.isInterrupted()
516 // and can break out of any loops and release resources in finally.
517 // Back in SafeProcess.runProcess, the InterruptedException catch block will be followed by finally
518 // that will clear up any further resources and destroy the process forcibly if it hadn't been ended.
519
520 } catch(IOException e) {
521 e.printStackTrace();
522 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
523 } finally {
524 SafeProcess.closeResource(bw);
525 }
526
527 if (!this.cancel) {
528 // Now display final message based on exit value
529
530 if (perlProcess.getExitValue() == 0) { //status = OK;
531
532 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
533 success = true;
534
535 } else { //status = ERROR;
536 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
537 success = false;
538
539 }
540 } else { // cancelled. The code would never come here, including in the old version of runPerlCommand
541 // but leaving this here for an exact port of the old runPerlCommand to the new one which
542 // uses SafeProcess. Also to allow the cancel functionality in future
543
544 // 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.
545 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
546 success = false;
547 }
548 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
549 //return true;
550 return success;
551 }
552
553
554 // this method is blocking again, on Windows when adding user comments
555 protected boolean old_runPerlCommand(String[] command, String[] envvars, File dir)
556 {
557 boolean success = true;
558
559 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
560 String srcHome = this.gsdl3home.substring(0, sepIndex);
561
562 ArrayList<String> args = new ArrayList<String>();
563 args.add("GSDLHOME=" + this.gsdl2home);
564 args.add("GSDL3HOME=" + this.gsdl3home);
565 args.add("GSDL3SRCHOME=" + srcHome);
566 args.add("GSDLOS=" + this.gsdlos);
567 args.add("GSDL-RUN-SETUP=true");
568 args.add("PERL_PERTURB_KEYS=0");
569
570 if(envvars != null) {
571 for(int i = 0; i < envvars.length; i++) {
572 args.add(envvars[i]);
573 }
574 }
575
576 for (String a : System.getenv().keySet())
577 {
578 args.add(a + "=" + System.getenv(a));
579 }
580
581 String command_str = "";
582 for (int i = 0; i < command.length; i++)
583 {
584 command_str = command_str + command[i] + " ";
585 }
586
587 // logger.info("### old runPerlCmd, command = " + command_str);
588
589 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
590 Process prcs = null;
591 BufferedReader ebr = null;
592 BufferedReader stdinbr = null;
593 try
594 {
595 Runtime rt = Runtime.getRuntime();
596 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
597 prcs = (dir == null)
598 ? rt.exec(command, args.toArray(new String[args.size()]))
599 : rt.exec(command, args.toArray(new String[args.size()]), dir);
600
601 InputStreamReader eisr = new InputStreamReader(prcs.getErrorStream());
602 InputStreamReader stdinisr = new InputStreamReader(prcs.getInputStream());
603 ebr = new BufferedReader(eisr);
604 stdinbr = new BufferedReader(stdinisr);
605 // Captures the std err of a program and pipes it into
606 // std in of java
607
608 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
609 if (!logDir.exists())
610 {
611 logDir.mkdir();
612 }
613
614 BufferedWriter bw = new BufferedWriter(new FileWriter(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log" + File.separator + "build_log." + (System.currentTimeMillis()) + ".txt"));
615 bw.write("Document Editor Build \n");
616
617 bw.write("Command = " + command_str + "\n");
618
619 String eline = null;
620 String stdinline = null;
621 while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !this.cancel)
622 {
623 if (eline != null)
624 {
625 //System.err.println("ERROR: " + eline);
626 bw.write(eline + "\n");
627 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, eline));
628 }
629 if (stdinline != null)
630 {
631 //System.err.println("OUT: " + stdinline);
632 bw.write(stdinline + "\n");
633 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, stdinline));
634 }
635 }
636 SafeProcess.closeResource(bw);
637
638 if (!this.cancel)
639 {
640 // Now display final message based on exit value
641 prcs.waitFor();
642
643 if (prcs.exitValue() == 0)
644 {
645 //status = OK;
646 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
647
648 success = true;
649 }
650 else
651 {
652 //status = ERROR;
653 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
654
655 //return false;
656 success = false;
657
658 }
659 }
660 else
661 {
662 // 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.
663 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
664 //prcs.getOutputStream().close();
665 //prcs.destroy();
666 ////status = ERROR;
667
668 //return false;
669 success = false;
670 }
671 }
672 catch (Exception e)
673 {
674 e.printStackTrace();
675 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
676 } finally {
677 // http://steveliles.github.io/invoking_processes_from_java.html
678 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
679 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
680
681 if( prcs != null ) {
682 SafeProcess.closeResource(prcs.getErrorStream());
683 SafeProcess.closeResource(prcs.getOutputStream());
684 SafeProcess.closeResource(prcs.getInputStream());
685 prcs.destroy();
686 }
687
688 SafeProcess.closeResource(ebr);
689 SafeProcess.closeResource(stdinbr);
690 }
691
692 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
693 //return true;
694 return success;
695 }
696
697
698 // From interface SafeProcess.ExceptionHandler
699 // Called when an exception happens during the running of our perl process. However,
700 // exceptions when reading from our perl process' stderr and stdout streams are handled by
701 // SynchronizedProcessHandler.gotException() below, since they happen in separate threads
702 // from this one (the one from which the perl process is run).
703 public synchronized void gotException(Exception e) {
704
705 // do what original runPerlCommand() code always did when an exception occurred
706 // when running the perl process:
707 e.printStackTrace();
708 sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR,
709 "Exception occurred " + e.toString()));
710 }
711
712 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
713 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
714 // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
715 // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
716 // the bufferedwriter object, needed to be made threadsafe.
717 // This class also handles exceptions during the running of the perl process.
718 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
719 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
720 // use of the EventListeners threadsafe.
721 protected class SynchronizedProcessHandler extends SafeProcess.CustomProcessHandler
722 {
723 private final BufferedWriter bwHandle; // needs to be final to synchronize on the object
724
725 public SynchronizedProcessHandler(BufferedWriter bw, int src) {
726 super(src); // will set this.source to STDERR or STDOUT
727 this.bwHandle = bw; // caller will close bw, since many more than one
728 // SynchronizedProcessHandlers are using it
729 }
730
731 public void run(Closeable inputStream) {
732 InputStream is = (InputStream) inputStream;
733
734 BufferedReader br = null;
735 try {
736 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
737 String line=null;
738 while ( (line = br.readLine()) != null ) {
739
740 if(Thread.currentThread().isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted?
741 System.err.println("Got interrupted when reading lines from process err/out stream.");
742 break; // will go to finally block
743 }
744
745 ///System.out.println("@@@ GOT LINE: " + line);
746
747
748 //if(this.source == SafeProcess.STDERR) {
749 ///System.err.println("ERROR: " + line);
750 //} else {
751 ///System.err.println("OUT: " + line);
752 //}
753
754
755 this.gotLine(line); // synchronized
756
757 /*
758 try {
759 synchronized(bwHandle) { // get a lock on the writer handle, then write
760
761 bwHandle.write(line + "\n");
762 }
763 } catch(IOException ioe) {
764 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
765 msg = "Exception when writing out a line read from perl process' " + msg + " stream.";
766 GS2PerlConstructor.logger.error(msg, ioe);
767 }
768
769 // this next method is thread safe since only synchronized methods are invoked.
770 // and only immutable (final) vars are used.
771 // NO, What about the listeners???
772 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
773 */
774 }
775 } catch (IOException ioe) { // problem with reading in from process with BufferedReader br
776
777 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
778 msg = "Got exception when processing the perl process' " + msg + " stream.";
779 GS2PerlConstructor.logger.error(msg, ioe);
780 // now do what the original runPerlCommand() code always did:
781 ioe.printStackTrace();
782 logException(ioe); // synchronized
783
784 } catch (Exception e) { // problem with BufferedWriter bwHandle on processing each line
785 e.printStackTrace();
786 logException(e); // synchronized
787 } finally {
788 SafeProcess.closeResource(br);
789 }
790 }
791
792 // trying to keep synchronized methods as short as possible
793 private synchronized void logException(Exception e) {
794 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
795 }
796
797 // trying to keep synchronized methods as short as possible
798 private synchronized void gotLine(String line) throws Exception {
799
800 // BufferedWriter writes may not be atomic
801 // http://stackoverflow.com/questions/9512433/is-writer-an-atomic-method
802 // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception
803 // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi
804 try {
805 bwHandle.write(line + "\n");
806
807/// GS2PerlConstructor.logger.info("@@@ WROTE LINE: " + line);
808
809 // this next method is thread safe since only synchronized methods are invoked.
810 // and only immutable (final) vars are used.
811 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
812
813 } catch(IOException ioe) { // can't throw Exceptions, but are forced to handle Exceptions here
814 // since our method definition doesn't specify a throws list.
815 // "All methods on Logger are multi-thread safe", see
816 // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization
817
818 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
819 msg = "IOException when writing out a line read from perl process' " + msg + " stream.";
820 msg += "\nGot line: " + line + "\n";
821 throw new Exception(msg, ioe);
822 }
823 }
824
825 } // end inner class SynchronizedProcessHandler
826
827}
Note: See TracBrowser for help on using the repository browser.