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

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

Part 1 of 2 commits to do with getting errorCallBack working on documentEditing for determining when changes have been saved or not to decided whether editableInitStates can finally be overwritten with current (saved) values.

  • Property svn:keywords set to Author Date Id Revision
File size: 29.8 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 // Then 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 //logger.info("@@@@@@@@ runPerlCommand success - exitvalue = " + perlProcess.getExitValue());
535 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
536 success = true;
537
538 } else { //status = ERROR;
539 logger.debug("@@@@@@@@ runPerlCommand failed, exitvalue = " + perlProcess.getExitValue());
540 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
541 success = false;
542
543 }
544 } else { // cancelled. The code would never come here, including in the old version of runPerlCommand
545 // but leaving this here for an exact port of the old runPerlCommand to the new one which
546 // uses SafeProcess. Also to allow the cancel functionality in future
547
548 // 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.
549 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
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 one from which the perl process is run). So this method's operations
707 // have been made thread-safe, rather than synchronizing on the method itself which locks
708 // *this* object and which isn't actually useful for what I want to do.
709 public void gotException(Exception e) {
710
711 // do what original runPerlCommand() code always did when an exception occurred
712 // when running the perl process:
713 e.printStackTrace();
714 sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR,
715 "Exception occurred " + e.toString()));
716 }
717
718 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
719 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
720 // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
721 // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
722 // the bufferedwriter object, needed to be made threadsafe.
723 // This class also handles exceptions during the running of the perl process.
724 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
725 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
726 // use of the EventListeners threadsafe.
727 protected class SynchronizedProcessHandler extends SafeProcess.CustomProcessHandler
728 {
729 private final BufferedWriter bwHandle; // needs to be final to synchronize on the object
730
731 public SynchronizedProcessHandler(BufferedWriter bw, int src) {
732 super(src); // will set this.source to STDERR or STDOUT
733 this.bwHandle = bw; // caller will close bw, since many more than one
734 // SynchronizedProcessHandlers are using it
735 }
736
737 public void run(Closeable inputStream) {
738 InputStream is = (InputStream) inputStream;
739
740 BufferedReader br = null;
741 try {
742 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
743 String line=null;
744 while ( (line = br.readLine()) != null ) {
745
746 // check if we got cancelled
747 if(Thread.currentThread().isInterrupted()) { // Did the SafeProcess thread interrupt us? Then break out of loop pre-maturely and finish up
748 System.err.println("Got interrupted when reading lines from process err/out stream.");
749 break; // will go to finally block
750 }
751
752 ///System.out.println("@@@ GOT LINE: " + line);
753
754
755 if(this.source == SafeProcess.STDERR) {
756 System.err.println("STDERR: " + line);
757 } else {
758 System.err.println("STDOUT: " + line);
759 }
760
761 this.gotLine(line); // behaves threadsafe
762 }
763 } catch (IOException ioe) { // problem with reading in from process with BufferedReader br
764
765 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
766 msg = "Got exception when processing the perl process' " + msg + " stream.";
767 GS2PerlConstructor.logger.error(msg, ioe);
768 // now do what the original runPerlCommand() code always did:
769 ioe.printStackTrace();
770 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + ioe.toString())); // now synchronized
771
772 } catch (Exception e) { // problem with BufferedWriter bwHandle on processing each line
773 e.printStackTrace();
774 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); // now synchronized
775 } finally {
776 SafeProcess.closeResource(br);
777 }
778 }
779
780 // helper method, deals with things in a thread-safe manner
781 // When to use synchronized blocks vs methods
782 // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
783 private void gotLine(String line) throws Exception {
784
785 // BufferedWriter writes may not be atomic
786 // http://stackoverflow.com/questions/9512433/is-writer-an-atomic-method
787 // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception
788 // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi
789 try {
790
791 // try to keep synchronized methods and synchronized blocks as short as possible
792 synchronized(bwHandle) { // get a lock on the writer handle, then write
793 bwHandle.write(line + "\n"); // can throw IOException
794 }
795
796/// GS2PerlConstructor.logger.info("@@@ WROTE LINE: " + line);
797
798 // this next method is thread safe since only synchronized methods are invoked.
799 // and only immutable (final) vars are used. And the listeners of this class have
800 // now been made threadsafe by using CopyOnWriteArrayList in place of EventListenerList.
801 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));
802
803 } catch(IOException ioe) {
804 // "All methods on Logger are multi-thread safe", see
805 // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization
806
807 String msg = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
808 msg = "IOException when writing out a line read from perl process' " + msg + " stream.";
809 msg += "\nGot line: " + line + "\n";
810 throw new Exception(msg, ioe);
811 }
812 }
813
814 } // end inner class SynchronizedProcessHandler
815
816}
Note: See TracBrowser for help on using the repository browser.