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

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