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

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

Removed debugging and other minor cosmetic changes after successful Windows testing of the new SafeProcess class used by the modified GS2PerlConstructor.runPerlCommand() method.

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