source: main/trunk/gli/src/org/greenstone/gatherer/shell/GShell.java@ 31638

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

Rewriting GShell.runLocal() to use SafeProcess instead of Process. This required overhaul of SafeProcess to correctly deal with interrupts as the build/import process launched in GShell can be cancelled from the CreatePane. SafeProcess now also keeps track of its internal process object and has static logging methods to simplify the changes required when swapping between the GS3 version and GS2 version of SafeProcess. May eventually call DebugStream methods from the log methods for GLI. Will add a DEBUG flag to allow verbose logging.

  • Property svn:keywords set to Author Date Id Revision
File size: 28.4 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37package org.greenstone.gatherer.shell;
38
39import java.io.*;
40import java.net.*;
41import java.util.ArrayList;
42import java.util.Enumeration;
43import java.util.regex.*;
44import javax.swing.*;
45import javax.swing.event.*;
46import javax.swing.tree.*;
47import org.greenstone.gatherer.Configuration;
48import org.greenstone.gatherer.DebugStream;
49import org.greenstone.gatherer.Dictionary;
50import org.greenstone.gatherer.Gatherer;
51import org.greenstone.gatherer.cdm.CollectionConfiguration;
52import org.greenstone.gatherer.cdm.CollectionDesignManager;
53import org.greenstone.gatherer.cdm.CollectionMetaManager;
54import org.greenstone.gatherer.cdm.CollectionMeta;
55import org.greenstone.gatherer.collection.CollectionManager;
56import org.greenstone.gatherer.metadata.DocXMLFileManager;
57import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
58import org.greenstone.gatherer.util.SafeProcess;
59import org.greenstone.gatherer.util.StaticStrings;
60import org.greenstone.gatherer.util.Utility;
61
62
63/** The <strong>GShell</strong> is reponsible for running a separately threaded process in the command shell. This is necessary for executing the Perl Scripts and also for other system related funcitonality.
64 */
65public class GShell
66 extends Thread implements SafeProcess.ExceptionHandler {
67 /** A flag used to determine if this process has been asked to cancel. */
68 private boolean cancel = false;
69 private BufferedOutputStream buffered_output_stream = null;
70 /** The list of listeners associated with this class. */
71 private EventListenerList listeners = null;
72 /** The current status of this shell process. */
73 private int status = -1;
74 /** The type of message being sent. */
75 private int msg_type = -1;
76 /** The type of shell process. */
77 private int type = -1;
78 /** The caller of this process, and thus the class most interested in messages. */
79 private GShellListener caller = null;
80 /** The progress monitor associated with this process. */
81 private GShellProgressMonitor progress = null;
82 /** Arguments to be given to the process (including the executable you are calling. */
83 private String args[] = null;
84 /** The command_output returned from executing a process */
85 private String commandOutput = null;
86
87 /** The process safely run in this GShell. Make sure to set to null when done with. */
88 SafeProcess prcs = null;
89
90 /** Elements in process type enumeration. */
91 static final public int BUILD = 0;
92 static final public int IMPORT = 1;
93 static final public int NEW = 2;
94 static final public int EXPORTAS = 3;
95 static final public int CDIMAGE = 4;
96 static final public int CONVERT = 5;
97 static final public int EXPLODE = 6;
98 static final public int SRCREPLACE = 7; // for replacing source docs with their html
99 static final public int SCHEDULE = 8;
100 static final public int DELETE = 9;
101
102 /** Elements in status type enumeration. */
103 static final public int ERROR = 0;
104 static final public int OK = 1;
105 static final public int CANCELLED = 2;
106
107 /** Elements in process type name enumeration. */
108 static public String GSHELL_BUILD = "gshell_build";
109 static public String GSHELL_IMPORT = "gshell_import";
110 static public String GSHELL_NEW = "gshell_new";
111 static public String GSHELL_EXPORTAS = "gshell_exportas";
112 static public String GSHELL_CDIMAGE = "gshell_cdimage";
113 static public String GSHELL_CONVERT = "gshell_convert";
114 static public String GSHELL_EXPLODE = "gshell_explode";
115 static public String GSHELL_SRCREPLACE = "gshell_srcreplace"; // for replacing source docs with their html versions
116 static public String GSHELL_SCHEDULE = "gshell_schedule";
117 static public String GSHELL_FEDORA_COLDELETE = "gshell_fedora_col_delete";
118
119
120 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
121 * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
122 * @param type An <strong>int</strong> that indicates what group of processes this process belongs to, as some are treated slightly differently (i.e an IMPORT type process is always followed by a BUILD one).
123 * @param msg_type As process threads may be background (like a makecol.pl call) or important processes in their own right (such as an IMPORT-BUILD) we must be able to set what level messages posted by this class will have by usings this <strong>int</strong>.
124 * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
125 * @param progress The <i>GShellProgressMonitor</i> associated with this process.
126 * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
127 */
128 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
129 super(name);
130 this.args = args;
131 this.msg_type = msg_type;
132 this.type = type;
133 this.caller = caller;
134 this.progress = progress;
135 this.status = 0;
136 // Lower this jobs priority
137 this.setPriority(Thread.MIN_PRIORITY);
138 listeners = new EventListenerList();
139 listeners.add(GShellListener.class, caller);
140 }
141 /** This method adds another shell listener to this process.
142 * @param listener The new <i>GShellListener</i>.
143 */
144 public void addGShellListener(GShellListener listener) {
145 listeners.add(GShellListener.class, listener);
146 }
147 /** This method removes a certain shell listener from this process.
148 * @param listener The <i>GShellListener</i> to be removed.
149 */
150 /* private void removeGShellListener(GShellListener listener) {
151 listeners.remove(GShellListener.class, listener);
152 } */
153
154 protected StringBuffer get_stream_char(InputStreamReader isr, StringBuffer line_buffer,
155 BufferedOutputStream bos) throws IOException
156 {
157 int c = isr.read();
158 ///atherer.println("isr: '" + (char) c + "'");
159 if(c == '\n' || c == '\r') {
160 if(line_buffer.length() > 0) {
161 String line = line_buffer.toString();
162 // DebugStream.println("* " + line + " *");
163 fireMessage(type, typeAsString(type) + "> " + line, status, bos);
164 line_buffer = new StringBuffer();
165 }
166 }
167 else {
168 line_buffer.append((char)c);
169 }
170
171 return line_buffer;
172 }
173
174 public String getCommandOutput() { return commandOutput; }
175 public void resetCommandOutput() { commandOutput = null; }
176
177 private void runRemote(String[] args, BufferedOutputStream bos)
178 {
179 // Make sure the process hasn't been cancelled
180 if (hasSignalledStop()) {
181 return;
182 }
183
184 try {
185 int directory_name_end = args[0].lastIndexOf(File.separator);
186 String script_name = ((directory_name_end != -1) ? args[0].substring(directory_name_end + 1) : args[0]);
187 System.err.println("Script name: " + script_name);
188 String collection_name = args[args.length - 1];
189 System.err.println("Collection name: " + collection_name);
190
191
192 String script_args = "";
193 for (int i = 1; i < (args.length - 1); i++) {
194 // Skip arguments that won't make sense on the server
195 // and -site is added in RemoteGreenstonServer.java
196 if (args[i].equals("-collectdir") || args[i].equals("-importdir") || args[i].equals("-builddir") || args[i].equals("-site")) {
197 i++;
198 continue;
199 }
200
201 // Script arguments get changed to CGI arguments
202 if (args[i].startsWith("-")) {
203 script_args += "&" + args[i].substring(1) + "=";
204 if ((i + 1) < (args.length - 1) && !args[i + 1].startsWith("-")) {
205 script_args += URLEncoder.encode(args[i + 1], "UTF-8");
206 i++;
207 }
208 }
209 }
210
211 System.err.println("Script args: " + script_args);
212 buffered_output_stream = bos;
213 String command_output = Gatherer.remoteGreenstoneServer.runScript(collection_name, script_name, script_args, this);
214 this.commandOutput = command_output;
215 status = (command_output.equals("") ? CANCELLED : OK);
216 }
217 catch (Exception exception) {
218 DebugStream.printStackTrace(exception);
219 status = ERROR;
220 }
221 }
222
223
224 // old_runLocal uses a potentially unsafe way of running a process and an inefficient way of reading
225 // from the process stdout and stderr streams. Replaced by new runLocal() which uses SafeProcess and
226 // the new CustomProcessHandler inner class instantiated for each of the 2 process output streams.
227 private void old_runLocal(String[] args, BufferedOutputStream bos)
228 {
229 try {
230 String command = "";
231 for(int i = 0; i < args.length; i++) {
232 command = command + args[i] + " ";
233 }
234
235 ///ystem.err.println("Command: " + command);
236 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
237
238 Runtime rt = Runtime.getRuntime();
239 Process prcs = null;
240
241 prcs = rt.exec(args);
242 // If we used single argument exec, spaces in filenames or paths cause problems
243 // I.e. better to avoid: prcs = rt.exec(command);
244
245 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream(), "UTF-8" );
246 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream(), "UTF-8" );
247
248 StringBuffer eline_buffer = new StringBuffer();
249 StringBuffer stdline_buffer = new StringBuffer();
250
251 while(/*type != GShell.NEW &&*/ SafeProcess.processRunning(prcs) && !hasSignalledStop()) {
252 // Hopefully this doesn't block if the process is trying to write to STDOUT.
253 if((eisr!=null) && eisr.ready()) {
254 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
255 }
256 // Hopefully this won't block if the process is trying to write to STDERR
257 else if(stdisr.ready()) {
258 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
259 }
260 else {
261 try {
262 sleep(100);
263 }
264 catch(Exception exception) {
265 }
266 }
267 }
268
269 if(!hasSignalledStop()) {
270 // Of course, just because the process is finished doesn't
271 // mean the incoming streams are empty. Unfortunately I've
272 // got no chance of preserving order, so I'll process the
273 // error stream first, then the out stream
274 while(eisr.ready()) {
275 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
276 }
277
278 while(stdisr.ready()) {
279 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
280 }
281
282 // Ensure that any messages still remaining in the string buffers are fired off.
283 if(eline_buffer.length() > 0) {
284 String eline = eline_buffer.toString();
285 //DebugStream.println("Last bit of eline: " + eline);
286 fireMessage(type, typeAsString(type) + "> " + eline, status, bos);
287 eline = null;
288 }
289
290 if(stdline_buffer.length() > 0) {
291 String stdline = stdline_buffer.toString();
292 //DebugStream.println("Last bit of stdline: " + stdline);
293 fireMessage(type, typeAsString(type) + "> " + stdline, status, null);
294 stdline = null;
295 }
296 }
297 else {
298 DebugStream.println("We've been asked to stop.");
299 }
300
301
302 if(!hasSignalledStop()) {
303 // Now display final message based on exit value
304
305 prcs.waitFor();
306
307 if(prcs.exitValue() == 0) {
308 status = OK;
309 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
310 }
311 else {
312 status = ERROR;
313 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
314 }
315
316 eisr.close();
317 stdisr.close();
318 }
319 else {
320 // I need to somehow kill the child process. Unfortunately
321 // Thread.stop() and Process.destroy() both fail to do
322 // this. But now, thankx to the magic of Michaels 'close the
323 // stream suggestion', it works fine (no it doesn't!)
324 prcs.getInputStream().close();
325 prcs.getErrorStream().close();
326 prcs.getOutputStream().close();
327 prcs.destroy();
328 status = CANCELLED;
329 }
330 }
331 // Exception
332 catch (Exception exception) {
333 DebugStream.println("Exception in GShell.runLocal() - unexpected");
334 DebugStream.printStackTrace(exception);
335 status = ERROR;
336 }
337 }
338
339 private void runLocal(String[] args, BufferedOutputStream bos)
340 {
341 // in case we stop between import and build, let's not launch further processes
342 // that only pressing cancel, which results in interrupts, can subsequently stop.
343 if(hasSignalledStop()) {
344 return;
345 }
346
347 String command = "";
348 for(int i = 0; i < args.length; i++) {
349 command = command + args[i] + " ";
350 }
351
352 ///ystem.err.println("Command: " + command);
353 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
354
355 prcs = new SafeProcess(args);
356 SafeProcess.CustomProcessHandler processOutHandler
357 = new SynchronizedProcessHandler(bos, SafeProcess.STDOUT);
358 SafeProcess.CustomProcessHandler processErrHandler
359 = new SynchronizedProcessHandler(bos, SafeProcess.STDERR);
360
361 prcs.setExceptionHandler(this);
362 int exitValue = prcs.runProcess(null, processOutHandler, processErrHandler); // use default procIn handling
363
364 if(exitValue == 0) {
365 System.err.println("*** Exited normally");
366 status = OK;
367 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
368 }
369 else {
370 System.err.println("*** Exited abnormally with exit value " + exitValue);
371 status = ERROR;
372 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
373 }
374
375 prcs = null;
376 }
377
378 /** When cancel is called on this GShell thread from a separate thread,
379 * This GShell will safely terminate any process it's currently running (by interrupting it)
380 * and will set this.prcs to null at the end. */
381 public void cancel() {
382 if(prcs != null) { //GShell is running a process, so interrupt the GShell/SafeProcess thread
383
384 SafeProcess.log("********** HAS SIGNALLED STOP. INTERRUPTING THE GSHELL/SAFEPROCESS THREAD");
385
386 this.interrupt(); // interrupt this thread which is running the SafeProcess prcs
387 // this will propagate the CANCEL status
388 prcs = null;
389 }
390 hasSignalledStop(); // synchronized. Will set status to CANCELLED in thread-safe manner.
391 }
392
393
394 /** Any threaded class must include this method to allow the thread body to be run. */
395 public void run() {
396 String col_name = args[args.length-1];
397
398 // Determine if the user has asked for an outfile.
399 String out_name = null;
400 BufferedOutputStream bos = null;
401 if(type == IMPORT || type == BUILD || type == SCHEDULE) {
402 if(type == IMPORT) {
403 out_name = (String) Gatherer.c_man.getCollection().import_options.getValue("out");
404 }
405 else if(type == BUILD) {
406 out_name = (String) Gatherer.c_man.getCollection().build_options.getValue("out");
407 }
408 else { // SCHEDULE
409 out_name = (String) Gatherer.c_man.getCollection().schedule_options.getValue("out");
410 }
411 if(out_name != null && out_name.length() > 0) {
412 try {
413 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
414 }
415 catch (Exception error) {
416 DebugStream.printStackTrace(error);
417 }
418 }
419 }
420
421 // Issue a processBegun event
422 //ystem.err.println("\nFiring process begun for " + type + "...");
423 fireProcessBegun(type, status);
424 //ystem.err.println("Done process begun.");
425 if (Gatherer.isGsdlRemote) {
426 runRemote(args,bos);
427 }
428 else {
429 runLocal(args,bos);
430 }
431 //ystem.err.println("Done runLocal().");
432
433 if(status == OK) {
434 if (type == NEW) {
435 if (Gatherer.isGsdlRemote) {
436 Gatherer.remoteGreenstoneServer.downloadCollection(col_name);
437 }
438 }
439 else if(type == IMPORT) {
440
441 // download the archives directory (if gsdl server is remote)
442 if (Gatherer.isGsdlRemote) {
443
444 if (progress!=null) {
445 progress.messageOnProgressBar("Downloading archive data from server");
446 }
447
448 Gatherer.remoteGreenstoneServer.downloadCollectionArchives(col_name);
449
450 if (progress!=null) {
451 progress.messageOnProgressBar("");
452 }
453 }
454
455 // Refresh the DocXMLFileManager
456 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status, null);
457 DocXMLFileManager.clearDocXMLFiles();
458 if (Configuration.fedora_info.isActive()) { // FLI case
459 File collection_export_directory = new File(CollectionManager.getLoadedCollectionExportDirectoryPath());
460 DocXMLFileManager.loadDocXMLFiles(collection_export_directory,"docmets.xml");
461 }
462 else {
463 File collection_archives_directory = new File(CollectionManager.getLoadedCollectionArchivesDirectoryPath());
464 DocXMLFileManager.loadDocXMLFiles(collection_archives_directory,"doc.xml");
465 }
466
467
468 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status, null);
469 }
470
471 else if(type == BUILD) {
472 // download the building directory (if gsdl server is remote)
473 if ((Gatherer.isGsdlRemote) && (!Configuration.fedora_info.isActive())) {
474 if (progress!=null) {
475 progress.messageOnProgressBar("Downloading index data from server");
476 }
477
478 if (!Gatherer.GS3){
479 // Only need to download build.cfg file
480 File build_cfg_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "build.cfg");
481 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
482 }else{
483 // Only need to download buildConfig.xml file
484 File buildConfig_xml_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "buildConfig.xml");
485 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, buildConfig_xml_file);
486 }
487
488 if (progress!=null) {
489 progress.messageOnProgressBar("");
490 }
491 }
492 }
493 else if(type == CDIMAGE) {
494 // download exported files from tmp folder (if gsdl server is remote)
495 if (Gatherer.isGsdlRemote) {
496 if (progress!=null) {
497 progress.messageOnProgressBar("Downloading CD-ROM data from server");
498 }
499
500 // !! TO DO
501
502 if (progress!=null) {
503 progress.messageOnProgressBar("");
504 }
505 }
506 }
507 }
508
509 // We're done.
510 //ystem.err.println("Firing process complete for " + type + "...");
511 fireProcessComplete(type, status);
512 // Close bos
513 if(bos != null) {
514 try {
515 bos.close();
516 bos = null;
517 }
518 catch(Exception error) {
519 DebugStream.printStackTrace(error);
520 }
521 }
522 }
523
524
525 public void fireMessage(String message)
526 {
527 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
528 }
529
530
531 /** Method for firing a message to all interested listeners.
532 * @param type An <strong>int</strong> indicating the process type.
533 * @param message The message as a <strong>String</strong>.
534 * @param status An <strong>int</strong> specifying the current status of the process.
535 */
536 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
537 GShellEvent event = new GShellEvent(this, 0, type, message, status);
538 // If there is a progress monitor attached, pass the event to it first. Note that we pass a queue of messages as the processing may cause one message to be split into several.
539 ArrayList message_queue = new ArrayList();
540 message_queue.add(event);
541 if(progress != null) {
542 progress.process(message_queue);
543 }
544 for(int j = 0; j < message_queue.size(); j++) {
545 GShellEvent current_event = (GShellEvent) message_queue.get(j);
546 // If the event hasn't been vetoed, pass it on to other listeners
547 if(!current_event.isVetoed()) {
548 Object[] concerned = listeners.getListenerList();
549 for(int i = 0; i < concerned.length ; i++) {
550 if(concerned[i] == GShellListener.class) {
551 ((GShellListener)concerned[i+1]).message(current_event);
552 }
553 }
554 concerned = null;
555 }
556 }
557 // And if we have a buffered output stream from error messages, send the message there
558 if(bos != null) {
559 try {
560 bos.write(message.getBytes(), 0, message.length());
561 }
562 catch(Exception exception) {
563 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
564 DebugStream.printStackTrace(exception);
565 }
566 }
567 message_queue = null;
568 event = null;
569 }
570
571 /** Method for firing a process begun event which is called, strangely enough, when the process begins.
572 * @param type An <strong>int</strong> indicating the process type.
573 * @param status An <strong>int</strong> specifying the current status of the process.
574 */
575 protected void fireProcessBegun(int type, int status) {
576 // Start the progres monitor if available
577 if(progress != null) {
578 //ystem.err.println("About to call progress.start().");
579 progress.start();
580 //ystem.err.println("Called progress.start().");
581 }
582 // Fire an event
583 GShellEvent event = new GShellEvent(this, 0, type, "", status);
584 Object[] concerned = listeners.getListenerList();
585 for(int i = 0; i < concerned.length ; i++) {
586 if(concerned[i] == GShellListener.class) {
587 ((GShellListener)concerned[i+1]).processBegun(event);
588 }
589 }
590 }
591
592
593 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
594 * @param type An <strong>int</strong> indicating the process type.
595 * @param status An <strong>int</strong> specifying the current status of the process.
596 */
597 protected void fireProcessComplete(int type, int status) {
598 // Tidy up by stopping the progress bar. If it was cancelled then the cancel command has arrived via the progress bars and they don't need to be told again (it actually causes problems).
599 if(progress != null && status != CANCELLED) {
600 progress.stop();
601 }
602
603 // If we were cancelled, and we are lower details modes, fire off one last message.
604 if(status == CANCELLED && Configuration.getMode() <= Configuration.LIBRARIAN_MODE) {
605 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
606 Object[] concerned = listeners.getListenerList();
607 for(int i = 0; i < concerned.length ; i++) {
608 if(concerned[i] == GShellListener.class) {
609 ((GShellListener)concerned[i+1]).message(current_event);
610 }
611 }
612 concerned = null;
613 }
614
615 String msg = "";
616 // If we are creating collection and have trouble with permissions, we need more messages
617 if(status == ERROR && type == GShell.NEW){
618 msg = args[args.length-1];
619 }
620 // And firing off an event
621 GShellEvent event = new GShellEvent(this, 0, type, msg, status);
622 Object[] concerned = listeners.getListenerList();
623 for(int i = 0; i < concerned.length ; i++) {
624 if(concerned[i] == GShellListener.class) {
625 ((GShellListener)concerned[i+1]).processComplete(event);
626 }
627 }
628 }
629
630 /** Method to determine if the user, via the progress monitor, has signalled stop.
631 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
632 */
633 public synchronized boolean hasSignalledStop() {
634 boolean has_signalled_stop = false;
635 if(progress != null) {
636 has_signalled_stop = progress.hasSignalledStop();
637 }
638 if(has_signalled_stop) {
639 status = CANCELLED;
640 }
641 return has_signalled_stop;
642 }
643
644 /** Converts a type into a text representation.
645 * @param type An <strong>int</strong> which maps to a shell process type.
646 * @return A <strong>String</strong> which is the thread process's text name.
647 */
648 public String typeAsString(int type) {
649 String name = null;
650 switch(type) {
651 case BUILD:
652 name = "buildcol.pl";
653 break;
654 case IMPORT:
655 name = "import.pl";
656 break;
657 case NEW:
658 name = "mkcol.pl";
659 break;
660 case EXPORTAS:
661 name = "export.pl";
662 break;
663 case CDIMAGE:
664 name = "exportcol.pl";
665 break;
666 case CONVERT:
667 name = "convert_coll_from_gs2.pl";
668 break;
669 case EXPLODE:
670 name = "explode_metadata_database.pl";
671 break;
672 case SRCREPLACE: // To work with replace_srcdoc_with_html.pl
673 name = "replace_srcdoc_with_html.pl";
674 break;
675 case SCHEDULE:
676 name = "schedule.pl";
677 break;
678 default:
679 name = "";
680 }
681 return name;
682 }
683
684 // From interface SafeProcess.ExceptionHandler
685 // Called when an exception happens during the running of our perl process, as we want to set
686 // the GShell status to ERROR.
687 // However, exceptions when reading from our perl process' stderr and stdout streams are handled
688 // by SynchronizedProcessHandler.gotException() below, since they happen in separate threads
689 // from this one (the one from which the perl process is run).
690 public void gotException(Exception e) {
691
692 if(e instanceof InterruptedException) {
693 SafeProcess.log("*** InterruptedException in GShell.runLocal()");
694 setStatus(CANCELLED); // expected exception
695 } else {
696 DebugStream.println("Exception in GShell.runLocal() - unexpected");
697 DebugStream.printStackTrace(e);
698 setStatus(ERROR); // status particularly needs to be synchronized on
699 }
700 }
701
702 public synchronized void setStatus(int newStatus) {
703 status = newStatus;
704 }
705
706 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
707 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
708 // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
709 // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
710 // the bufferedwriter object, needed to be made threadsafe.
711 // This class also handles exceptions during the running of the perl process.
712 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
713 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
714 // use of the EventListeners threadsafe.
715 protected class SynchronizedProcessHandler extends SafeProcess.CustomProcessHandler
716 {
717 private final BufferedOutputStream bos; // needs to be final to be able to synchronize on the shared object
718
719 public SynchronizedProcessHandler(BufferedOutputStream bos, int src) {
720 super(src); // will set this.source to STDERR or STDOUT
721 this.bos = bos; // caller will close bw, since many more than one
722 // SynchronizedProcessHandlers are using it
723 }
724
725 // trying to keep synchronized methods as short as possible
726 private void logException(Exception e) {
727 String stream = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
728 String msg = "Got exception when processing the perl process' " + stream + " stream.";
729
730 DebugStream.println(msg); // method is already synchronized
731 DebugStream.printStackTrace(e); // method is already synchronized
732 SafeProcess.log("*** " + msg, e);
733
734 GShell.this.setStatus(GShell.ERROR);
735 }
736
737 private void log(String msg) {
738 DebugStream.println(msg); // already synchro
739 System.err.println("@@@@@ " + msg);
740 }
741
742 public void run(Closeable inputStream) {
743 InputStream is = (InputStream) inputStream;
744
745 BufferedReader br = null;
746 try {
747
748 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
749 String line=null;
750
751 ///while ( !GShell.this.prcs.processRunning() || hasSignalledStop()) { // need to sync on prcs
752 while ( !Thread.currentThread().isInterrupted() && (line = br.readLine()) != null ) {
753 this.gotLine(line); // synchronized
754 }
755
756 } catch (Exception e) {
757 logException(e);
758 } finally {
759 System.err.println("*********");
760 if(Thread.currentThread().isInterrupted()) {
761 log("We've been asked to stop.");
762
763 }
764 SafeProcess.closeResource(br);
765 String stream = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
766 System.err.println("*** In finally of " + stream + " stream");
767 System.err.println("*********");
768 }
769 }
770
771 protected synchronized void gotLine(String line) {
772 fireMessage(GShell.this.type, // not final, needs synchro
773 GShell.this.typeAsString(type) + "> " + line,
774 GShell.this.status, // not final, needs synchro
775 this.bos); // needs synchro
776 }
777
778 }
779
780}
Note: See TracBrowser for help on using the repository browser.