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

Last change on this file since 31591 was 31591, checked in by ak19, 7 years ago
  1. SafeProcess.CustomProcessHandler replaces SafeProcess.LineByLineHandler to allow GS developers more control over the handling of a Process' iostreams and more transparency to the developer about what happens. But SafeProcess provides default behaviours. 2. Some informative msgs in the GS2Construct method that marks the flat DB for when the oid entry does not occur in the DB.
  • Property svn:keywords set to Author Date Id Revision
File size: 29.6 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 // http://www.cgi101.com/class/ch3/text.html
327 String[] envvars = {
328 "QUERY_STRING=" + this.query_string,
329 "REQUEST_METHOD=GET"
330 };
331
332 String[] command_str = {};
333 command_str = command.toArray(command_str);
334
335 // http://www.cgi101.com/class/ch3/text.html
336 // setenv QUERY_STRING and REQUEST_METHOD = GET.
337 // Run the perl command as a simple process: no logging to the collection's build log
338 if (runPerlCommand(command_str, envvars, new File(cgi_directory)))
339 //new File(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi")))
340 {
341 // success!! - need to send the final completed message
342 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
343 }// else an error message has already been sent, do nothing
344
345 }
346
347 /** extracts all the args from the xml and returns them in a Vector */
348 protected Vector<String> extractParameters(Element param_list)
349 {
350
351 Vector<String> args = new Vector<String>();
352 if (param_list == null)
353 {
354 return args; // return an empty vector
355 }
356 NodeList params = param_list.getElementsByTagName(GSXML.PARAM_ELEM);
357
358 for (int i = 0; i < params.getLength(); i++)
359 {
360 Element p = (Element) params.item(i);
361 String name = p.getAttribute(GSXML.NAME_ATT);
362 String value = p.getAttribute(GSXML.VALUE_ATT);
363 if (!name.equals(""))
364 {
365 args.add("-" + name);
366 if (!value.equals(""))
367 {
368 args.add(value);
369 }
370 }
371 }
372
373 return args;
374 }
375
376 protected SafeProcess createPerlProcess(String[] command, String[] envvars, File dir) {
377 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
378 String srcHome = this.gsdl3home.substring(0, sepIndex);
379
380 ArrayList<String> args = new ArrayList<String>();
381 args.add("GSDLHOME=" + this.gsdl2home);
382 args.add("GSDL3HOME=" + this.gsdl3home);
383 args.add("GSDL3SRCHOME=" + srcHome);
384 args.add("GSDLOS=" + this.gsdlos);
385 args.add("GSDL-RUN-SETUP=true");
386 args.add("PERL_PERTURB_KEYS=0");
387
388 if(envvars != null) {
389 for(int i = 0; i < envvars.length; i++) {
390 args.add(envvars[i]);
391 }
392 }
393
394 for (String a : System.getenv().keySet()) {
395 args.add(a + "=" + System.getenv(a));
396 }
397
398 SafeProcess perlProcess
399 = new SafeProcess(command, args.toArray(new String[args.size()]), dir); // dir can be null
400
401 return perlProcess;
402 }
403
404 // ModifyMetadata operations call runSimplePerlCommand which produces no output in build log
405 protected boolean runSimplePerlCommand(String[] command) {
406 return runSimplePerlCommand(command, null, null);
407 }
408
409 protected boolean runSimplePerlCommand(String[] command, String[] envvars, File dir) {
410 boolean success = false;
411
412 String command_str = "";
413 for (int i = 0; i < command.length; i++) {
414 command_str = command_str + command[i] + " ";
415 }
416
417 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
418
419 logger.info("### Running simple command = " + command_str);
420
421 // This is where we create and run our perl process safely
422 SafeProcess perlProcess = createPerlProcess(command, envvars, dir); // dir can be null
423
424 perlProcess.setExceptionHandler(this);
425
426 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
427
428 int exitVal = perlProcess.runProcess(); // uses default processing of the perl process' iostreams provided by SafeProcess
429
430 if (exitVal == 0) {
431 success = true;
432 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
433 } else {
434 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
435 success = false; // explicit
436 }
437
438 return success;
439 }
440
441
442 /** returns true if completed correctly, false otherwise
443 * Building operations call runPerlCommand which sends build output to collect/log/build_log.#*.txt
444 */
445 protected boolean runPerlCommand(String[] command) {
446 return runPerlCommand(command, null, null);
447 }
448
449
450 protected boolean runPerlCommand(String[] command, String[] envvars, File dir)
451 {
452 boolean success = true;
453
454 String command_str = "";
455 for (int i = 0; i < command.length; i++) {
456 command_str = command_str + command[i] + " ";
457 }
458
459 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
460
461 logger.info("### Running logged command = " + command_str);
462
463 // This is where we create and run our perl process safely
464 SafeProcess perlProcess = createPerlProcess(command, envvars, dir); // dir can be null
465
466 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
467
468 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
469 if (!logDir.exists()) {
470 logDir.mkdir();
471 }
472
473 // Only from Java 7+: Try-with-Resources block will safely close the BufferedWriter
474 // https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
475 BufferedWriter bw = null;
476 try {
477
478 bw = new BufferedWriter(new FileWriter(new File(logDir, "build_log." + (System.currentTimeMillis()) + ".txt")));
479
480 bw.write("Document Editor Build \n");
481 bw.write("Command = " + command_str + "\n");
482
483 // handle each incoming line from stdout and stderr streams, and any exceptions that occur then
484 SafeProcess.CustomProcessHandler processOutHandler
485 = new SynchronizedProcessHandler(bw, SynchronizedProcessHandler.STDOUT);
486 SafeProcess.CustomProcessHandler processErrHandler
487 = new SynchronizedProcessHandler(bw, SynchronizedProcessHandler.STDERR);
488
489 // GS2PerlConstructor will do further handling of exceptions that may occur during the perl
490 // process (including if writing something to the process' inputstream, not that we're doing that for this perlProcess)
491 perlProcess.setExceptionHandler(this);
492
493 // finally, execute the process
494
495 // Captures the std err of a program and pipes it into
496 // std in of java, as before.
497
498 perlProcess.runProcess(null, processOutHandler, processErrHandler); // use default procIn handling
499
500 // The original runPerlCommand() code had an ineffective check for whether the cmd had been cancelled
501 // midway through executing the perl, as condition of a while loop reading from stderr and stdout.
502 // We don't include the cancel check here, as superclass CollectionConstructor.stopAction(), which set
503 // this.cancel to true, never got called anywhere.
504 // But I think a proper cancel of our perl process launched by this GS2PerlConstructor Thread object
505 // and of the worker threads it launches, could be implemented with interrupts. See:
506 // http://stackoverflow.com/questions/6859681/better-way-to-signal-other-thread-to-stop
507 // https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html
508 // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupted()
509 // https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
510 // The code that calls GS2PerlConstructor.stopAction() should also call GSPerlConstructor.interrupt()
511 // Then in SafeProcess.runProcess(), I think the waitFor() will throw an InterruptedException()
512 // This can be caught and interrupt() called on SafeProcess' workerthreads,
513 // Any workerthreads' run() methods that block (IO, loops) can test this.isInterrupted()
514 // and can break out of any loops and release resources in finally.
515 // Back in SafeProcess.runProcess, the InterruptedException catch block will be followed by finally
516 // that will clear up any further resources and destroy the process forcibly if it hadn't been ended.
517
518 } catch(IOException e) {
519 e.printStackTrace();
520 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
521 } finally {
522 SafeProcess.closeResource(bw);
523 }
524
525 if (!this.cancel) {
526 // Now display final message based on exit value
527
528 if (perlProcess.getExitValue() == 0) {
529 //status = OK;
530 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
531
532 success = true;
533 } else {
534 //status = ERROR;
535 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
536
537 //return false;
538 success = false;
539
540 }
541 } else { // cancelled
542
543 // 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.
544 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
545 //prcs.getOutputStream().close();
546 //prcs.destroy();
547 ////status = ERROR;
548
549 //return false;
550 success = false;
551 }
552 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
553 //return true;
554 return success;
555 }
556
557
558 // this method is blocking again, on Windows when adding user comments
559 protected boolean old_runPerlCommand(String[] command, String[] envvars, File dir)
560 {
561 boolean success = true;
562
563 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
564 String srcHome = this.gsdl3home.substring(0, sepIndex);
565
566 ArrayList<String> args = new ArrayList<String>();
567 args.add("GSDLHOME=" + this.gsdl2home);
568 args.add("GSDL3HOME=" + this.gsdl3home);
569 args.add("GSDL3SRCHOME=" + srcHome);
570 args.add("GSDLOS=" + this.gsdlos);
571 args.add("GSDL-RUN-SETUP=true");
572 args.add("PERL_PERTURB_KEYS=0");
573
574 if(envvars != null) {
575 for(int i = 0; i < envvars.length; i++) {
576 args.add(envvars[i]);
577 }
578 }
579
580 for (String a : System.getenv().keySet())
581 {
582 args.add(a + "=" + System.getenv(a));
583 }
584
585 String command_str = "";
586 for (int i = 0; i < command.length; i++)
587 {
588 command_str = command_str + command[i] + " ";
589 }
590
591 // logger.info("### old runPerlCmd, command = " + command_str);
592
593 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
594 Process prcs = null;
595 BufferedReader ebr = null;
596 BufferedReader stdinbr = null;
597 try
598 {
599 Runtime rt = Runtime.getRuntime();
600 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
601 prcs = (dir == null)
602 ? rt.exec(command, args.toArray(new String[args.size()]))
603 : rt.exec(command, args.toArray(new String[args.size()]), dir);
604
605 InputStreamReader eisr = new InputStreamReader(prcs.getErrorStream());
606 InputStreamReader stdinisr = new InputStreamReader(prcs.getInputStream());
607 ebr = new BufferedReader(eisr);
608 stdinbr = new BufferedReader(stdinisr);
609 // Captures the std err of a program and pipes it into
610 // std in of java
611
612 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
613 if (!logDir.exists())
614 {
615 logDir.mkdir();
616 }
617
618 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"));
619 bw.write("Document Editor Build \n");
620
621 bw.write("Command = " + command_str + "\n");
622
623 String eline = null;
624 String stdinline = null;
625 while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !this.cancel)
626 {
627 if (eline != null)
628 {
629 //System.err.println("ERROR: " + eline);
630 bw.write(eline + "\n");
631 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, eline));
632 }
633 if (stdinline != null)
634 {
635 //System.err.println("OUT: " + stdinline);
636 bw.write(stdinline + "\n");
637 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, stdinline));
638 }
639 }
640 SafeProcess.closeResource(bw);
641
642 if (!this.cancel)
643 {
644 // Now display final message based on exit value
645 prcs.waitFor();
646
647 if (prcs.exitValue() == 0)
648 {
649 //status = OK;
650 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
651
652 success = true;
653 }
654 else
655 {
656 //status = ERROR;
657 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
658
659 //return false;
660 success = false;
661
662 }
663 }
664 else
665 {
666 // 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.
667 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
668 //prcs.getOutputStream().close();
669 //prcs.destroy();
670 ////status = ERROR;
671
672 //return false;
673 success = false;
674 }
675 }
676 catch (Exception e)
677 {
678 e.printStackTrace();
679 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
680 } finally {
681 // http://steveliles.github.io/invoking_processes_from_java.html
682 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
683 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
684
685 if( prcs != null ) {
686 SafeProcess.closeResource(prcs.getErrorStream());
687 SafeProcess.closeResource(prcs.getOutputStream());
688 SafeProcess.closeResource(prcs.getInputStream());
689 prcs.destroy();
690 }
691
692 SafeProcess.closeResource(ebr);
693 SafeProcess.closeResource(stdinbr);
694 }
695
696 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
697 //return true;
698 return success;
699 }
700
701
702 // From interface SafeProcess.ExceptionHandler
703 // Called when an exception happens during the running of our perl process. However,
704 // exceptions when reading from our perl process' stderr and stdout streams are handled by
705 // SynchronizedProcessHandler.gotException() below, since they happen in separate threads
706 // from this one (the ine from which the perl process is run).
707 public synchronized void gotException(Exception e) {
708
709 // do what original runPerlCommand() code always did when an exception occurred
710 // when running the perl process:
711 e.printStackTrace();
712 sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR,
713 "Exception occurred " + e.toString()));
714 }
715
716 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
717 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
718 // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
719 // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
720 // the bufferedwriter object, needed to be made threadsafe.
721 // This class also handles exceptions during the running of the perl process.
722 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
723 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
724 // use of the EventListeners threadsafe.
725 protected class SynchronizedProcessHandler implements SafeProcess.CustomProcessHandler
726 {
727 public static final int STDERR = 0;
728 public static final int STDOUT = 1;
729
730 private final int source;
731 private final BufferedWriter bwHandle; // needs to be final to synchronize on the object
732
733
734 public SynchronizedProcessHandler(BufferedWriter bw, int src) {
735 this.bwHandle = bw; // caller will close bw, since many more than one
736 // SynchronizedProcessHandlers are using it
737 this.source = src; // STDERR or STDOUT
738 }
739
740 public void run(Closeable inputStream) {
741 InputStream is = (InputStream) inputStream;
742
743 BufferedReader br = null;
744 try {
745 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
746 String line=null;
747 while ( (line = br.readLine()) != null ) {
748
749 if(Thread.currentThread().isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted?
750 System.err.println("Got interrupted when reading lines from process err/out stream.");
751 break; // will go to finally block
752 }
753
754/// System.out.println("@@@ GOT LINE: " + line);
755/// GS2PerlConstructor.logger.info("@@@ GOT LINE: " + line + " STDOUT=1: " + source);
756
757 //if(this.source == STDERR) {
758 ///System.err.println("ERROR: " + line);
759 //} else {
760 ///System.err.println("OUT: " + line);
761 //}
762
763
764 this.gotLine(line); // synchronized
765
766 /*
767 try {
768 synchronized(bwHandle) { // get a lock on the writer handle, then write
769
770 bwHandle.write(line + "\n");
771 }
772 } catch(IOException ioe) {
773 String msg = (source == STDERR) ? "stderr" : "stdout";
774 msg = "Exception when writing out a line read from perl process' " + msg + " stream.";
775 GS2PerlConstructor.logger.error(msg, ioe);
776 }
777
778 // this next method is thread safe since only synchronized methods are invoked.
779 // and only immutable (final) vars are used.
780 // NO, What about the listeners???
781 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
782 */
783 }
784 } catch (IOException ioe) { // problem with reading in from process with BufferedReader br
785
786 String msg = (source == STDERR) ? "stderr" : "stdout";
787 msg = "Got exception when processing the perl process' " + msg + " stream.";
788 GS2PerlConstructor.logger.error(msg, ioe);
789 // now do what the original runPerlCommand() code always did:
790 ioe.printStackTrace();
791 logException(ioe); // synchronized
792
793 } catch (Exception e) { // problem with BufferedWriter bwHandle on processing each line
794 e.printStackTrace();
795 logException(e); // synchronized
796 } finally {
797 SafeProcess.closeResource(br);
798 }
799 }
800
801 // trying to keep synchronized methods as short as possible
802 private synchronized void logException(Exception e) {
803 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
804 }
805
806 // trying to keep synchronized methods as short as possible
807 private synchronized void gotLine(String line) throws Exception {
808
809 // BufferedWriter writes may not be atomic
810 // http://stackoverflow.com/questions/9512433/is-writer-an-atomic-method
811 // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception
812 // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi
813 try {
814 bwHandle.write(line + "\n");
815
816/// System.out.println("@@@ WROTE LINE: " + line);
817/// GS2PerlConstructor.logger.info("@@@ WROTE LINE: " + line);
818
819 // this next method is thread safe since only synchronized methods are invoked.
820 // and only immutable (final) vars are used.
821 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
822
823 } catch(IOException ioe) { // can't throw Exceptions, but are forced to handle Exceptions here
824 // since our method definition doesn't specify a throws list.
825 // "All methods on Logger are multi-thread safe", see
826 // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization
827
828 String msg = (source == STDERR) ? "stderr" : "stdout";
829 msg = "IOException when writing out a line read from perl process' " + msg + " stream.";
830 msg += "\nGot line: " + line + "\n";
831 throw new Exception(msg, ioe);
832 }
833 }
834
835 } // end inner class SynchronizedProcessHandler
836
837}
Note: See TracBrowser for help on using the repository browser.