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

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

Minor correction to a comment

  • 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/service/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 a proper cancel of our perl process launched by this GS2PerlConstructor Thread object
507 // and of the worker threads it launches is now implemented in SafeProcess.cancelRunningProcess()
508 // with interrupts. See:
509 // http://stackoverflow.com/questions/6859681/better-way-to-signal-other-thread-to-stop
510 // https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html
511 // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupted()
512 // https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
513 // If cancel is to be implemented for GS2PerlConstructor, then the code that calls
514 // GS2PerlConstructor.stopAction() should also call or result in a call to the SafeProcess instance's
515 // cancelRunningProcess() method.
516 // For a simple example of the use of SafeProcess' cancel feature, see GLI's GShell. For a more
517 // complicated example, see GLI's DownloadJob.java, which implements SafeProcess.MainHandler to do
518 // additional work when a process is cancelled while still running (and therefore has to be prematurely
519 // terminated) a.o.t. a process that's cancelled when it's already terminated naturally.
520
521 } catch(IOException e) {
522 e.printStackTrace();
523 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
524 } finally {
525 SafeProcess.closeResource(bw);
526 }
527
528 if (!this.cancel) {
529 // Now display final message based on exit value
530
531 if (perlProcess.getExitValue() == 0) { //status = OK;
532
533 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
534 success = true;
535
536 } else { //status = ERROR;
537 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
538 success = false;
539
540 }
541 } else { // cancelled. The code would never come here, including in the old version of runPerlCommand
542 // but leaving this here for an exact port of the old runPerlCommand to the new one which
543 // uses SafeProcess. Also to allow the cancel functionality in future
544
545 // 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.
546 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
547 success = false;
548 }
549 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
550 //return true;
551 return success;
552 }
553
554
555 // this method is blocking again, on Windows when adding user comments
556 protected boolean old_runPerlCommand(String[] command, String[] envvars, File dir)
557 {
558 boolean success = true;
559
560 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
561 String srcHome = this.gsdl3home.substring(0, sepIndex);
562
563 ArrayList<String> args = new ArrayList<String>();
564 args.add("GSDLHOME=" + this.gsdl2home);
565 args.add("GSDL3HOME=" + this.gsdl3home);
566 args.add("GSDL3SRCHOME=" + srcHome);
567 args.add("GSDLOS=" + this.gsdlos);
568 args.add("GSDL-RUN-SETUP=true");
569 args.add("PERL_PERTURB_KEYS=0");
570
571 if(envvars != null) {
572 for(int i = 0; i < envvars.length; i++) {
573 args.add(envvars[i]);
574 }
575 }
576
577 for (String a : System.getenv().keySet())
578 {
579 args.add(a + "=" + System.getenv(a));
580 }
581
582 String command_str = "";
583 for (int i = 0; i < command.length; i++)
584 {
585 command_str = command_str + command[i] + " ";
586 }
587
588 // logger.info("### old runPerlCmd, command = " + command_str);
589
590 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
591 Process prcs = null;
592 BufferedReader ebr = null;
593 BufferedReader stdinbr = null;
594 try
595 {
596 Runtime rt = Runtime.getRuntime();
597 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
598 prcs = (dir == null)
599 ? rt.exec(command, args.toArray(new String[args.size()]))
600 : rt.exec(command, args.toArray(new String[args.size()]), dir);
601
602 InputStreamReader eisr = new InputStreamReader(prcs.getErrorStream());
603 InputStreamReader stdinisr = new InputStreamReader(prcs.getInputStream());
604 ebr = new BufferedReader(eisr);
605 stdinbr = new BufferedReader(stdinisr);
606 // Captures the std err of a program and pipes it into
607 // std in of java
608
609 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
610 if (!logDir.exists())
611 {
612 logDir.mkdir();
613 }
614
615 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"));
616 bw.write("Document Editor Build \n");
617
618 bw.write("Command = " + command_str + "\n");
619
620 String eline = null;
621 String stdinline = null;
622 while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !this.cancel)
623 {
624 if (eline != null)
625 {
626 //System.err.println("ERROR: " + eline);
627 bw.write(eline + "\n");
628 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, eline));
629 }
630 if (stdinline != null)
631 {
632 //System.err.println("OUT: " + stdinline);
633 bw.write(stdinline + "\n");
634 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, stdinline));
635 }
636 }
637 SafeProcess.closeResource(bw);
638
639 if (!this.cancel)
640 {
641 // Now display final message based on exit value
642 prcs.waitFor();
643
644 if (prcs.exitValue() == 0)
645 {
646 //status = OK;
647 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
648
649 success = true;
650 }
651 else
652 {
653 //status = ERROR;
654 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
655
656 //return false;
657 success = false;
658
659 }
660 }
661 else
662 {
663 // 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.
664 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
665 //prcs.getOutputStream().close();
666 //prcs.destroy();
667 ////status = ERROR;
668
669 //return false;
670 success = false;
671 }
672 }
673 catch (Exception e)
674 {
675 e.printStackTrace();
676 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
677 } finally {
678 // http://steveliles.github.io/invoking_processes_from_java.html
679 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
680 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
681
682 if( prcs != null ) {
683 SafeProcess.closeResource(prcs.getErrorStream());
684 SafeProcess.closeResource(prcs.getOutputStream());
685 SafeProcess.closeResource(prcs.getInputStream());
686 prcs.destroy();
687 }
688
689 SafeProcess.closeResource(ebr);
690 SafeProcess.closeResource(stdinbr);
691 }
692
693 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
694 //return true;
695 return success;
696 }
697
698
699 // From interface SafeProcess.ExceptionHandler
700 // Called when an exception happens during the running of our perl process. However,
701 // exceptions when reading from our perl process' stderr and stdout streams are handled by
702 // SynchronizedProcessHandler.gotException() below, since they happen in separate threads
703 // from this one (the one from which the perl process is run). So this method's operations
704 // have been made thread-safe, rather than synchronizing on the method itself which locks
705 // *this* object and which isn't actually useful for what I want to do.
706 public void gotException(Exception e) {
707
708 // do what original runPerlCommand() code always did when an exception occurred
709 // when running the perl process:
710 e.printStackTrace();
711 sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR,
712 "Exception occurred " + e.toString()));
713 }
714
715 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
716 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
717 // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
718 // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
719 // the bufferedwriter object, needed to be made threadsafe.
720 // This class also handles exceptions during the running of the perl process.
721 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
722 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
723 // use of the EventListeners threadsafe.
724 protected class SynchronizedProcessHandler extends SafeProcess.CustomProcessHandler
725 {
726 private final BufferedWriter bwHandle; // needs to be final to synchronize on the object
727
728 public SynchronizedProcessHandler(BufferedWriter bw, int src) {
729 super(src); // will set this.source to STDERR or STDOUT
730 this.bwHandle = bw; // caller will close bw, since many more than one
731 // SynchronizedProcessHandlers are using it
732 }
733
734 public void run(Closeable inputStream) {
735 InputStream is = (InputStream) inputStream;
736
737 BufferedReader br = null;
738 try {
739 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
740 String line=null;
741 while ( (line = br.readLine()) != null ) {
742
743 // check if we got cancelled
744 if(Thread.currentThread().isInterrupted()) { // Did the SafeProcess thread interrupt us? Then break out of loop pre-maturely and finish up
745 System.err.println("Got interrupted when reading lines from process err/out stream.");
746 break; // will go to finally block
747 }
748
749 ///System.out.println("@@@ GOT LINE: " + line);
750
751
752 //if(this.source == SafeProcess.STDERR) {
753 ///System.err.println("ERROR: " + line);
754 //} else {
755 ///System.err.println("OUT: " + line);
756 //}
757
758 this.gotLine(line); // behaves threadsafe
759 }
760 } catch (IOException ioe) { // problem with reading in from process with BufferedReader br
761
762 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
763 msg = "Got exception when processing the perl process' " + msg + " stream.";
764 GS2PerlConstructor.logger.error(msg, ioe);
765 // now do what the original runPerlCommand() code always did:
766 ioe.printStackTrace();
767 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + ioe.toString())); // now synchronized
768
769 } catch (Exception e) { // problem with BufferedWriter bwHandle on processing each line
770 e.printStackTrace();
771 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); // now synchronized
772 } finally {
773 SafeProcess.closeResource(br);
774 }
775 }
776
777 // helper method, deals with things in a thread-safe manner
778 // When to use synchronized blocks vs methods
779 // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
780 private void gotLine(String line) throws Exception {
781
782 // BufferedWriter writes may not be atomic
783 // http://stackoverflow.com/questions/9512433/is-writer-an-atomic-method
784 // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception
785 // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi
786 try {
787
788 // try to keep synchronized methods and synchronized blocks as short as possible
789 synchronized(bwHandle) { // get a lock on the writer handle, then write
790 bwHandle.write(line + "\n"); // can throw IOException
791 }
792
793/// GS2PerlConstructor.logger.info("@@@ WROTE LINE: " + line);
794
795 // this next method is thread safe since only synchronized methods are invoked.
796 // and only immutable (final) vars are used. And the listeners of this class have
797 // now been made threadsafe by using CopyOnWriteArrayList in place of EventListenerList.
798 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
799
800 } catch(IOException ioe) {
801 // "All methods on Logger are multi-thread safe", see
802 // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization
803
804 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
805 msg = "IOException when writing out a line read from perl process' " + msg + " stream.";
806 msg += "\nGot line: " + line + "\n";
807 throw new Exception(msg, ioe);
808 }
809 }
810
811 } // end inner class SynchronizedProcessHandler
812
813}
Note: See TracBrowser for help on using the repository browser.