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

Last change on this file since 32855 was 32855, checked in by ak19, 5 years ago

Debugging from perl (mod)metadataaction cgiaction now gets printed to tomcat console. Was commented out before. Hope this won't break anything in the release. But it's useful output for debugging if something has gone wrong during setting/getting meta from archives or the other supported locations.

  • 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("-library_name");
217 command.add(this.library_name);
218// command.add("-removeold"); // saves some seconds processing time when this flag's added in explicitly
219 command.addAll(extractParameters(this.process_params));
220 command.add(this.collection_name);
221
222 String[] command_str = {};
223 command_str = command.toArray(command_str);
224
225 if (runPerlCommand(command_str))
226 {
227 // success!! - need to send the final completed message
228 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
229 }// else an error message has already been sent, do nothing
230 }
231
232 protected void activateCollection()
233 {
234 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: activate collection."));
235
236 // first check that we have a building directory
237 // (don't want to bother running activate.pl otherwise)
238 File build_dir = new File(GSFile.collectionIndexDir(this.site_home, this.collection_name));
239 if (!build_dir.exists())
240 {
241 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "build dir doesn't exist!"));
242 return;
243 }
244
245 /*
246
247 // move building to index
248 File index_dir = new File(GSFile.collectionIndexDir(this.site_home, this.collection_name));
249 if (index_dir.exists())
250 {
251 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "deleting index directory"));
252 GSFile.deleteFile(index_dir);
253 if (index_dir.exists())
254 {
255 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "index directory still exists!"));
256 return;
257 }
258 }
259
260 GSFile.moveDirectory(build_dir, index_dir);
261 if (!index_dir.exists())
262 {
263 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "index dir wasn't created!"));
264 }
265
266 // success!! - need to send the final completed message
267 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
268 */
269
270 // Running activate.pl instead of making java move building to index as above
271 // circumvents the issue of the jdbm .lg log file (managed by TransactionManager)
272 // in index dir not getting deleted at times. The perl code is able to delete this
273 // sucessfully consistently during testing, whereas java at times is unable to delete it.
274 Vector<String> command = new Vector<String>();
275
276 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
277 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
278 {
279 perlPath = perlPath + File.separator;
280 }
281
282 command.add(perlPath + "perl");
283 command.add("-S");
284 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "activate.pl");
285 command.add("-incremental");
286 command.add("-builddir");
287 command.add(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator +"index");
288 command.add("-site");
289 command.add(this.site_name);
290 command.add("-collectdir");
291 command.add(GSFile.collectDir(this.site_home));
292// command.add("-removeold"); // saves some seconds processing time when this flag's added in explicitly. Shouldn't be added for incremental building
293 command.add("-skipactivation"); // gsdl3/service/GS2Construct does the activation and reactivation
294 command.addAll(extractParameters(this.process_params));
295 command.add(this.collection_name);
296
297 String[] command_str = {};
298 command_str = command.toArray(command_str);
299
300 if (runPerlCommand(command_str))
301 {
302 // success!! - need to send the final completed message
303 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
304 }// else an error message has already been sent, do nothing
305
306 }
307
308
309 protected void modifyMetadataForCollection()
310 {
311 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection metadata: modifyMetadata (set or remove meta) for collection."));
312
313 Vector<String> command = new Vector<String>();
314
315 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
316 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
317 {
318 perlPath = perlPath + File.separator;
319 }
320
321 String cgi_directory = GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi";
322 command.add(perlPath + "perl");
323 command.add("-S");
324 //command.add(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi" + File.separator + "metadata-server.pl");
325 command.add(cgi_directory + File.separator + "metadata-server.pl");
326
327 // Need to set QUERY_STRING and REQUEST_METHOD=GET in environment
328 // Also set GS3_AUTHENTICATED, to allow running metadata-server.pl with mod (set and remove) commands
329 // http://www.cgi101.com/class/ch3/text.html
330 String[] envvars = {
331 "QUERY_STRING=" + this.query_string,
332 "REQUEST_METHOD=GET",
333 "GS3_AUTHENTICATED=true" // how do we set the env var without having to assign it a value?
334 };
335
336 String[] command_str = {};
337 command_str = command.toArray(command_str);
338
339 // http://www.cgi101.com/class/ch3/text.html
340 // setenv QUERY_STRING and REQUEST_METHOD = GET.
341 if (runPerlCommand(command_str, envvars, new File(cgi_directory)))
342 //new File(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi")))
343 {
344 // success!! - need to send the final completed message
345 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
346 }// else an error message has already been sent, do nothing
347
348 }
349
350 /** extracts all the args from the xml and returns them in a Vector */
351 protected Vector<String> extractParameters(Element param_list)
352 {
353
354 Vector<String> args = new Vector<String>();
355 if (param_list == null)
356 {
357 return args; // return an empty vector
358 }
359 NodeList params = param_list.getElementsByTagName(GSXML.PARAM_ELEM);
360
361 for (int i = 0; i < params.getLength(); i++)
362 {
363 Element p = (Element) params.item(i);
364 String name = p.getAttribute(GSXML.NAME_ATT);
365 String value = p.getAttribute(GSXML.VALUE_ATT);
366 if (!name.equals(""))
367 {
368 args.add("-" + name);
369 if (!value.equals(""))
370 {
371 args.add(value);
372 }
373 }
374 }
375
376 return args;
377 }
378
379 protected SafeProcess createPerlProcess(String[] command, String[] envvars, File dir) {
380 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
381 String srcHome = this.gsdl3home.substring(0, sepIndex);
382
383 ArrayList<String> args = new ArrayList<String>();
384 args.add("GSDLHOME=" + this.gsdl2home);
385 args.add("GSDL3HOME=" + this.gsdl3home);
386 args.add("GSDL3SRCHOME=" + srcHome);
387 args.add("GSDLOS=" + this.gsdlos);
388 args.add("GSDL-RUN-SETUP=true");
389 args.add("PERL_PERTURB_KEYS=0");
390
391 if(envvars != null) {
392 for(int i = 0; i < envvars.length; i++) {
393 args.add(envvars[i]);
394 }
395 }
396
397 for (String a : System.getenv().keySet()) {
398 args.add(a + "=" + System.getenv(a));
399 }
400
401 SafeProcess perlProcess
402 = new SafeProcess(command, args.toArray(new String[args.size()]), dir); // dir can be null
403
404 return perlProcess;
405 }
406
407 // If you want to run a Perl command without doing GS2PerlConstructor's custom logging in the build log
408 // The use the runSimplePerlCommand() versions, which use the default behaviour of running a SafeProcess
409 protected boolean runSimplePerlCommand(String[] command) {
410 return runSimplePerlCommand(command, null, null);
411 }
412
413 protected boolean runSimplePerlCommand(String[] command, String[] envvars, File dir) {
414 boolean success = false;
415
416 String command_str = "";
417 for (int i = 0; i < command.length; i++) {
418 command_str = command_str + command[i] + " ";
419 }
420
421 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
422
423 ///logger.info("### Running simple command = " + command_str);
424
425 // This is where we create and run our perl process safely
426 SafeProcess perlProcess = createPerlProcess(command, envvars, dir); // dir can be null
427
428 perlProcess.setExceptionHandler(this);
429
430 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
431
432 int exitVal = perlProcess.runProcess(); // uses default processing of the perl process' iostreams provided by SafeProcess
433
434 if (exitVal == 0) {
435 success = true;
436 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
437 } else {
438 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
439 success = false; // explicit
440 }
441
442 return success;
443 }
444
445
446 /** returns true if completed correctly, false otherwise
447 * Building operations call runPerlCommand which sends build output to collect/log/build_log.#*.txt
448 */
449 protected boolean runPerlCommand(String[] command) {
450 return runPerlCommand(command, null, null);
451 }
452
453
454 protected boolean runPerlCommand(String[] command, String[] envvars, File dir)
455 {
456 boolean success = true;
457
458 String command_str = "";
459 for (int i = 0; i < command.length; i++) {
460 command_str = command_str + command[i] + " ";
461 }
462
463 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
464
465 ///logger.info("### Running logged command = " + command_str);
466
467 // This is where we create and run our perl process safely
468 SafeProcess perlProcess = createPerlProcess(command, envvars, dir); // dir can be null
469
470 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
471
472 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
473 if (!logDir.exists()) {
474 logDir.mkdir();
475 }
476
477 // Only from Java 7+: Try-with-Resources block will safely close the BufferedWriter
478 // https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
479 BufferedWriter bw = null;
480 try {
481
482 bw = new BufferedWriter(new FileWriter(new File(logDir, "build_log." + (System.currentTimeMillis()) + ".txt")));
483
484 bw.write("Document Editor Build \n");
485 bw.write("Command = " + command_str + "\n");
486
487 // handle each incoming line from stdout and stderr streams, and any exceptions that occur then
488 SafeProcess.CustomProcessHandler processOutHandler
489 = new SynchronizedProcessHandler(bw, SafeProcess.STDOUT);
490 SafeProcess.CustomProcessHandler processErrHandler
491 = new SynchronizedProcessHandler(bw, SafeProcess.STDERR);
492
493 // GS2PerlConstructor will do further handling of exceptions that may occur during the perl
494 // process (including if writing something to the process' inputstream, not that we're doing that for this perlProcess)
495 perlProcess.setExceptionHandler(this);
496
497 // finally, execute the process
498
499 // Captures the std err of a program and pipes it into
500 // std in of java, as before.
501
502 perlProcess.runProcess(null, processOutHandler, processErrHandler); // use default procIn handling
503
504 // The original runPerlCommand() code had an ineffective check for whether the cmd had been cancelled
505 // midway through executing the perl, as condition of a while loop reading from stderr and stdout.
506 // We don't include the cancel check here, as superclass CollectionConstructor.stopAction(), which set
507 // this.cancel to true, never got called anywhere.
508 // But a proper cancel of our perl process launched by this GS2PerlConstructor Thread object
509 // and of the worker threads it launches is now implemented in SafeProcess.cancelRunningProcess()
510 // with interrupts. See:
511 // http://stackoverflow.com/questions/6859681/better-way-to-signal-other-thread-to-stop
512 // https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html
513 // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupted()
514 // https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
515 // If cancel is to be implemented for GS2PerlConstructor, then the code that calls
516 // GS2PerlConstructor.stopAction() should also call or result in a call to the SafeProcess instance's
517 // cancelRunningProcess() method.
518 // For a simple example of the use of SafeProcess' cancel feature, see GLI's GShell. For a more
519 // complicated example, see GLI's DownloadJob.java, which implements SafeProcess.MainHandler to do
520 // additional work when a process is cancelled while still running (and therefore has to be prematurely
521 // terminated) a.o.t. a process that's cancelled when it's already terminated naturally.
522
523 } catch(IOException e) {
524 e.printStackTrace();
525 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
526 } finally {
527 SafeProcess.closeResource(bw);
528 }
529
530 if (!this.cancel) {
531 // Now display final message based on exit value
532
533 if (perlProcess.getExitValue() == 0) { //status = OK;
534
535 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
536 success = true;
537
538 } else { //status = ERROR;
539 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
540 success = false;
541
542 }
543 } else { // cancelled. The code would never come here, including in the old version of runPerlCommand
544 // but leaving this here for an exact port of the old runPerlCommand to the new one which
545 // uses SafeProcess. Also to allow the cancel functionality in future
546
547 // 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.
548 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
549 success = false;
550 }
551 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
552 //return true;
553 return success;
554 }
555
556
557 // this method is blocking again, on Windows when adding user comments
558 protected boolean old_runPerlCommand(String[] command, String[] envvars, File dir)
559 {
560 boolean success = true;
561
562 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
563 String srcHome = this.gsdl3home.substring(0, sepIndex);
564
565 ArrayList<String> args = new ArrayList<String>();
566 args.add("GSDLHOME=" + this.gsdl2home);
567 args.add("GSDL3HOME=" + this.gsdl3home);
568 args.add("GSDL3SRCHOME=" + srcHome);
569 args.add("GSDLOS=" + this.gsdlos);
570 args.add("GSDL-RUN-SETUP=true");
571 args.add("PERL_PERTURB_KEYS=0");
572
573 if(envvars != null) {
574 for(int i = 0; i < envvars.length; i++) {
575 args.add(envvars[i]);
576 }
577 }
578
579 for (String a : System.getenv().keySet())
580 {
581 args.add(a + "=" + System.getenv(a));
582 }
583
584 String command_str = "";
585 for (int i = 0; i < command.length; i++)
586 {
587 command_str = command_str + command[i] + " ";
588 }
589
590 // logger.info("### old runPerlCmd, command = " + command_str);
591
592 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
593 Process prcs = null;
594 BufferedReader ebr = null;
595 BufferedReader stdinbr = null;
596 try
597 {
598 Runtime rt = Runtime.getRuntime();
599 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
600 prcs = (dir == null)
601 ? rt.exec(command, args.toArray(new String[args.size()]))
602 : rt.exec(command, args.toArray(new String[args.size()]), dir);
603
604 InputStreamReader eisr = new InputStreamReader(prcs.getErrorStream());
605 InputStreamReader stdinisr = new InputStreamReader(prcs.getInputStream());
606 ebr = new BufferedReader(eisr);
607 stdinbr = new BufferedReader(stdinisr);
608 // Captures the std err of a program and pipes it into
609 // std in of java
610
611 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
612 if (!logDir.exists())
613 {
614 logDir.mkdir();
615 }
616
617 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"));
618 bw.write("Document Editor Build \n");
619
620 bw.write("Command = " + command_str + "\n");
621
622 String eline = null;
623 String stdinline = null;
624 while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !this.cancel)
625 {
626 if (eline != null)
627 {
628 //System.err.println("ERROR: " + eline);
629 bw.write(eline + "\n");
630 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, eline));
631 }
632 if (stdinline != null)
633 {
634 //System.err.println("OUT: " + stdinline);
635 bw.write(stdinline + "\n");
636 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, stdinline));
637 }
638 }
639 SafeProcess.closeResource(bw);
640
641 if (!this.cancel)
642 {
643 // Now display final message based on exit value
644 prcs.waitFor();
645
646 if (prcs.exitValue() == 0)
647 {
648 //status = OK;
649 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
650
651 success = true;
652 }
653 else
654 {
655 //status = ERROR;
656 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
657
658 //return false;
659 success = false;
660
661 }
662 }
663 else
664 {
665 // 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.
666 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
667 //prcs.getOutputStream().close();
668 //prcs.destroy();
669 ////status = ERROR;
670
671 //return false;
672 success = false;
673 }
674 }
675 catch (Exception e)
676 {
677 e.printStackTrace();
678 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
679 } finally {
680 // http://steveliles.github.io/invoking_processes_from_java.html
681 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
682 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
683
684 if( prcs != null ) {
685 SafeProcess.closeResource(prcs.getErrorStream());
686 SafeProcess.closeResource(prcs.getOutputStream());
687 SafeProcess.closeResource(prcs.getInputStream());
688 prcs.destroy();
689 }
690
691 SafeProcess.closeResource(ebr);
692 SafeProcess.closeResource(stdinbr);
693 }
694
695 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
696 //return true;
697 return success;
698 }
699
700
701 // From interface SafeProcess.ExceptionHandler
702 // Called when an exception happens during the running of our perl process. However,
703 // exceptions when reading from our perl process' stderr and stdout streams are handled by
704 // SynchronizedProcessHandler.gotException() below, since they happen in separate threads
705 // from this one (the one from which the perl process is run). So this method's operations
706 // have been made thread-safe, rather than synchronizing on the method itself which locks
707 // *this* object and which isn't actually useful for what I want to do.
708 public void gotException(Exception e) {
709
710 // do what original runPerlCommand() code always did when an exception occurred
711 // when running the perl process:
712 e.printStackTrace();
713 sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR,
714 "Exception occurred " + e.toString()));
715 }
716
717 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
718 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
719 // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
720 // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
721 // the bufferedwriter object, needed to be made threadsafe.
722 // This class also handles exceptions during the running of the perl process.
723 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
724 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
725 // use of the EventListeners threadsafe.
726 protected class SynchronizedProcessHandler extends SafeProcess.CustomProcessHandler
727 {
728 private final BufferedWriter bwHandle; // needs to be final to synchronize on the object
729
730 public SynchronizedProcessHandler(BufferedWriter bw, int src) {
731 super(src); // will set this.source to STDERR or STDOUT
732 this.bwHandle = bw; // caller will close bw, since many more than one
733 // SynchronizedProcessHandlers are using it
734 }
735
736 public void run(Closeable inputStream) {
737 InputStream is = (InputStream) inputStream;
738
739 BufferedReader br = null;
740 try {
741 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
742 String line=null;
743 while ( (line = br.readLine()) != null ) {
744
745 // check if we got cancelled
746 if(Thread.currentThread().isInterrupted()) { // Did the SafeProcess thread interrupt us? Then break out of loop pre-maturely and finish up
747 System.err.println("Got interrupted when reading lines from process err/out stream.");
748 break; // will go to finally block
749 }
750
751 ///System.out.println("@@@ GOT LINE: " + line);
752
753
754 if(this.source == SafeProcess.STDERR) {
755 System.err.println("STDERR: " + line);
756 } else {
757 System.err.println("STDOUT: " + line);
758 }
759
760 this.gotLine(line); // behaves threadsafe
761 }
762 } catch (IOException ioe) { // problem with reading in from process with BufferedReader br
763
764 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
765 msg = "Got exception when processing the perl process' " + msg + " stream.";
766 GS2PerlConstructor.logger.error(msg, ioe);
767 // now do what the original runPerlCommand() code always did:
768 ioe.printStackTrace();
769 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + ioe.toString())); // now synchronized
770
771 } catch (Exception e) { // problem with BufferedWriter bwHandle on processing each line
772 e.printStackTrace();
773 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); // now synchronized
774 } finally {
775 SafeProcess.closeResource(br);
776 }
777 }
778
779 // helper method, deals with things in a thread-safe manner
780 // When to use synchronized blocks vs methods
781 // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
782 private void gotLine(String line) throws Exception {
783
784 // BufferedWriter writes may not be atomic
785 // http://stackoverflow.com/questions/9512433/is-writer-an-atomic-method
786 // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception
787 // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi
788 try {
789
790 // try to keep synchronized methods and synchronized blocks as short as possible
791 synchronized(bwHandle) { // get a lock on the writer handle, then write
792 bwHandle.write(line + "\n"); // can throw IOException
793 }
794
795/// GS2PerlConstructor.logger.info("@@@ WROTE LINE: " + line);
796
797 // this next method is thread safe since only synchronized methods are invoked.
798 // and only immutable (final) vars are used. And the listeners of this class have
799 // now been made threadsafe by using CopyOnWriteArrayList in place of EventListenerList.
800 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
801
802 } catch(IOException ioe) {
803 // "All methods on Logger are multi-thread safe", see
804 // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization
805
806 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
807 msg = "IOException when writing out a line read from perl process' " + msg + " stream.";
808 msg += "\nGot line: " + line + "\n";
809 throw new Exception(msg, ioe);
810 }
811 }
812
813 } // end inner class SynchronizedProcessHandler
814
815}
Note: See TracBrowser for help on using the repository browser.