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

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

Tweak to Friday's commit to URL encode param values: can't use nio.StandardCharsets because our release kit JDK is too old to recognise this import, so hardcoding UTF-8 charset name which is what people used to do in the past. Also updated comments. And removed some unnecessary commented out code from GS2PerlConstructor.java

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