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

Last change on this file since 31639 was 31639, checked in by ak19, 7 years ago
  1. Correcting switch statement in SafeProcess and tidying up some debugging. 2. GShell needs to have its own LineByLineHandlers not CustomProcessHandlers, since most of the err/out stream processing code should be the default provided by SafeProcess. 3. GShell.gotException wasn't being called during an expected InterruptedException on Cancel, so corrected that to make the code write a message to the DebugStream as the original code did when a build operation had been cancelled.
  • Property svn:keywords set to Author Date Id Revision
File size: 28.1 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 // This method is only used by old_runLocal(). If that is removed, then remove this too
155 protected StringBuffer get_stream_char(InputStreamReader isr, StringBuffer line_buffer,
156 BufferedOutputStream bos) throws IOException
157 {
158 int c = isr.read();
159 ///atherer.println("isr: '" + (char) c + "'");
160 if(c == '\n' || c == '\r') {
161 if(line_buffer.length() > 0) {
162 String line = line_buffer.toString();
163 // DebugStream.println("* " + line + " *");
164 fireMessage(type, typeAsString(type) + "> " + line, status, bos);
165 line_buffer = new StringBuffer();
166 }
167 }
168 else {
169 line_buffer.append((char)c);
170 }
171
172 return line_buffer;
173 }
174
175 public String getCommandOutput() { return commandOutput; }
176 public void resetCommandOutput() { commandOutput = null; }
177
178 private void runRemote(String[] args, BufferedOutputStream bos)
179 {
180 // Make sure the process hasn't been cancelled
181 if (hasSignalledStop()) {
182 return;
183 }
184
185 try {
186 int directory_name_end = args[0].lastIndexOf(File.separator);
187 String script_name = ((directory_name_end != -1) ? args[0].substring(directory_name_end + 1) : args[0]);
188 System.err.println("Script name: " + script_name);
189 String collection_name = args[args.length - 1];
190 System.err.println("Collection name: " + collection_name);
191
192
193 String script_args = "";
194 for (int i = 1; i < (args.length - 1); i++) {
195 // Skip arguments that won't make sense on the server
196 // and -site is added in RemoteGreenstonServer.java
197 if (args[i].equals("-collectdir") || args[i].equals("-importdir") || args[i].equals("-builddir") || args[i].equals("-site")) {
198 i++;
199 continue;
200 }
201
202 // Script arguments get changed to CGI arguments
203 if (args[i].startsWith("-")) {
204 script_args += "&" + args[i].substring(1) + "=";
205 if ((i + 1) < (args.length - 1) && !args[i + 1].startsWith("-")) {
206 script_args += URLEncoder.encode(args[i + 1], "UTF-8");
207 i++;
208 }
209 }
210 }
211
212 System.err.println("Script args: " + script_args);
213 buffered_output_stream = bos;
214 String command_output = Gatherer.remoteGreenstoneServer.runScript(collection_name, script_name, script_args, this);
215 this.commandOutput = command_output;
216 status = (command_output.equals("") ? CANCELLED : OK);
217 }
218 catch (Exception exception) {
219 DebugStream.printStackTrace(exception);
220 status = ERROR;
221 }
222 }
223
224
225 // old_runLocal uses a potentially unsafe way of running a process and an inefficient way of reading
226 // from the process stdout and stderr streams. Replaced by new runLocal() which uses SafeProcess and
227 // the new LineByLineHandler inner class instantiated for each of the 2 process output streams.
228 // If this method is ever removed from the file, can remove its helper get_stream_char() too.
229 private void old_runLocal(String[] args, BufferedOutputStream bos)
230 {
231 try {
232 String command = "";
233 for(int i = 0; i < args.length; i++) {
234 command = command + args[i] + " ";
235 }
236
237 ///ystem.err.println("Command: " + command);
238 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
239
240 Runtime rt = Runtime.getRuntime();
241 Process prcs = null;
242
243 prcs = rt.exec(args);
244 // If we used single argument exec, spaces in filenames or paths cause problems
245 // I.e. better to avoid: prcs = rt.exec(command);
246
247 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream(), "UTF-8" );
248 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream(), "UTF-8" );
249
250 StringBuffer eline_buffer = new StringBuffer();
251 StringBuffer stdline_buffer = new StringBuffer();
252
253 while(/*type != GShell.NEW &&*/ SafeProcess.processRunning(prcs) && !hasSignalledStop()) {
254 // Hopefully this doesn't block if the process is trying to write to STDOUT.
255 if((eisr!=null) && eisr.ready()) {
256 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
257 }
258 // Hopefully this won't block if the process is trying to write to STDERR
259 else if(stdisr.ready()) {
260 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
261 }
262 else {
263 try {
264 sleep(100);
265 }
266 catch(Exception exception) {
267 }
268 }
269 }
270
271 if(!hasSignalledStop()) {
272 // Of course, just because the process is finished doesn't
273 // mean the incoming streams are empty. Unfortunately I've
274 // got no chance of preserving order, so I'll process the
275 // error stream first, then the out stream
276 while(eisr.ready()) {
277 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
278 }
279
280 while(stdisr.ready()) {
281 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
282 }
283
284 // Ensure that any messages still remaining in the string buffers are fired off.
285 if(eline_buffer.length() > 0) {
286 String eline = eline_buffer.toString();
287 //DebugStream.println("Last bit of eline: " + eline);
288 fireMessage(type, typeAsString(type) + "> " + eline, status, bos);
289 eline = null;
290 }
291
292 if(stdline_buffer.length() > 0) {
293 String stdline = stdline_buffer.toString();
294 //DebugStream.println("Last bit of stdline: " + stdline);
295 fireMessage(type, typeAsString(type) + "> " + stdline, status, null);
296 stdline = null;
297 }
298 }
299 else {
300 DebugStream.println("We've been asked to stop.");
301 }
302
303
304 if(!hasSignalledStop()) {
305 // Now display final message based on exit value
306
307 prcs.waitFor();
308
309 if(prcs.exitValue() == 0) {
310 status = OK;
311 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
312 }
313 else {
314 status = ERROR;
315 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
316 }
317
318 eisr.close();
319 stdisr.close();
320 }
321 else {
322 // I need to somehow kill the child process. Unfortunately
323 // Thread.stop() and Process.destroy() both fail to do
324 // this. But now, thankx to the magic of Michaels 'close the
325 // stream suggestion', it works fine (no it doesn't!)
326 prcs.getInputStream().close();
327 prcs.getErrorStream().close();
328 prcs.getOutputStream().close();
329 prcs.destroy();
330 status = CANCELLED;
331 }
332 }
333 // Exception
334 catch (Exception exception) {
335 DebugStream.println("Exception in GShell.runLocal() - unexpected");
336 DebugStream.printStackTrace(exception);
337 status = ERROR;
338 }
339 }
340
341 private void runLocal(String[] args, BufferedOutputStream bos)
342 {
343 // in case we stop between import and build, let's not launch further processes
344 // that only pressing cancel, which results in interrupts, can subsequently stop.
345 if(hasSignalledStop()) {
346 return;
347 }
348
349 String command = "";
350 for(int i = 0; i < args.length; i++) {
351 command = command + args[i] + " ";
352 }
353
354 ///ystem.err.println("Command: " + command);
355 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
356
357 prcs = new SafeProcess(args);
358 SafeProcess.LineByLineHandler processOutLineHandler
359 = new SynchronizedLineByLineHandler(bos, SafeProcess.STDOUT);
360 SafeProcess.LineByLineHandler processErrLineHandler
361 = new SynchronizedLineByLineHandler(bos, SafeProcess.STDERR);
362
363 prcs.setExceptionHandler(this);
364 int exitValue = prcs.runProcess(processOutLineHandler, processErrLineHandler); // use default procIn handling
365
366 if(exitValue == 0) {
367 //System.err.println("*** Exitted normally");
368 status = OK;
369 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
370 }
371 else {
372 //System.err.println("*** Exitted abnormally with exit value " + exitValue);
373 status = ERROR;
374 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
375 }
376
377 prcs = null;
378 }
379
380 /** When cancel is called on this GShell thread from a separate thread,
381 * This GShell will safely terminate any process it's currently running (by interrupting it)
382 * and will set this.prcs to null at the end. */
383 public void cancel() {
384 if(prcs != null) { //GShell is running a process, so interrupt the GShell/SafeProcess thread
385
386 SafeProcess.log("********** HAS SIGNALLED STOP. INTERRUPTING THE GSHELL/SAFEPROCESS THREAD");
387
388 this.interrupt(); // interrupt this thread which is running the SafeProcess prcs
389 // this will propagate the CANCEL status to any worker threads launched by the SafeProcess
390 prcs = null;
391 }
392 setStatus(CANCELLED); //or: hasSignalledStop(); // synchronized. Either will set status to CANCELLED in thread-safe manner.
393 }
394
395
396 /** Any threaded class must include this method to allow the thread body to be run. */
397 public void run() {
398 String col_name = args[args.length-1];
399
400 // Determine if the user has asked for an outfile.
401 String out_name = null;
402 BufferedOutputStream bos = null;
403 if(type == IMPORT || type == BUILD || type == SCHEDULE) {
404 if(type == IMPORT) {
405 out_name = (String) Gatherer.c_man.getCollection().import_options.getValue("out");
406 }
407 else if(type == BUILD) {
408 out_name = (String) Gatherer.c_man.getCollection().build_options.getValue("out");
409 }
410 else { // SCHEDULE
411 out_name = (String) Gatherer.c_man.getCollection().schedule_options.getValue("out");
412 }
413 if(out_name != null && out_name.length() > 0) {
414 try {
415 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
416 }
417 catch (Exception error) {
418 DebugStream.printStackTrace(error);
419 }
420 }
421 }
422
423 // Issue a processBegun event
424 //ystem.err.println("\nFiring process begun for " + type + "...");
425 fireProcessBegun(type, status);
426 //ystem.err.println("Done process begun.");
427 if (Gatherer.isGsdlRemote) {
428 runRemote(args,bos);
429 }
430 else {
431 runLocal(args,bos);
432 }
433 //ystem.err.println("Done runLocal().");
434
435 if(status == OK) {
436 if (type == NEW) {
437 if (Gatherer.isGsdlRemote) {
438 Gatherer.remoteGreenstoneServer.downloadCollection(col_name);
439 }
440 }
441 else if(type == IMPORT) {
442
443 // download the archives directory (if gsdl server is remote)
444 if (Gatherer.isGsdlRemote) {
445
446 if (progress!=null) {
447 progress.messageOnProgressBar("Downloading archive data from server");
448 }
449
450 Gatherer.remoteGreenstoneServer.downloadCollectionArchives(col_name);
451
452 if (progress!=null) {
453 progress.messageOnProgressBar("");
454 }
455 }
456
457 // Refresh the DocXMLFileManager
458 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status, null);
459 DocXMLFileManager.clearDocXMLFiles();
460 if (Configuration.fedora_info.isActive()) { // FLI case
461 File collection_export_directory = new File(CollectionManager.getLoadedCollectionExportDirectoryPath());
462 DocXMLFileManager.loadDocXMLFiles(collection_export_directory,"docmets.xml");
463 }
464 else {
465 File collection_archives_directory = new File(CollectionManager.getLoadedCollectionArchivesDirectoryPath());
466 DocXMLFileManager.loadDocXMLFiles(collection_archives_directory,"doc.xml");
467 }
468
469
470 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status, null);
471 }
472
473 else if(type == BUILD) {
474 // download the building directory (if gsdl server is remote)
475 if ((Gatherer.isGsdlRemote) && (!Configuration.fedora_info.isActive())) {
476 if (progress!=null) {
477 progress.messageOnProgressBar("Downloading index data from server");
478 }
479
480 if (!Gatherer.GS3){
481 // Only need to download build.cfg file
482 File build_cfg_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "build.cfg");
483 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
484 }else{
485 // Only need to download buildConfig.xml file
486 File buildConfig_xml_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "buildConfig.xml");
487 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, buildConfig_xml_file);
488 }
489
490 if (progress!=null) {
491 progress.messageOnProgressBar("");
492 }
493 }
494 }
495 else if(type == CDIMAGE) {
496 // download exported files from tmp folder (if gsdl server is remote)
497 if (Gatherer.isGsdlRemote) {
498 if (progress!=null) {
499 progress.messageOnProgressBar("Downloading CD-ROM data from server");
500 }
501
502 // !! TO DO
503
504 if (progress!=null) {
505 progress.messageOnProgressBar("");
506 }
507 }
508 }
509 }
510
511 // We're done.
512 //ystem.err.println("Firing process complete for " + type + "...");
513 fireProcessComplete(type, status);
514 // Close bos
515 if(bos != null) {
516 try {
517 bos.close();
518 bos = null;
519 }
520 catch(Exception error) {
521 DebugStream.printStackTrace(error);
522 }
523 }
524 }
525
526
527 public void fireMessage(String message)
528 {
529 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
530 }
531
532
533 /** Method for firing a message to all interested listeners.
534 * @param type An <strong>int</strong> indicating the process type.
535 * @param message The message as a <strong>String</strong>.
536 * @param status An <strong>int</strong> specifying the current status of the process.
537 */
538 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
539 GShellEvent event = new GShellEvent(this, 0, type, message, status);
540 // 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.
541 ArrayList message_queue = new ArrayList();
542 message_queue.add(event);
543 if(progress != null) {
544 progress.process(message_queue);
545 }
546 for(int j = 0; j < message_queue.size(); j++) {
547 GShellEvent current_event = (GShellEvent) message_queue.get(j);
548 // If the event hasn't been vetoed, pass it on to other listeners
549 if(!current_event.isVetoed()) {
550 Object[] concerned = listeners.getListenerList();
551 for(int i = 0; i < concerned.length ; i++) {
552 if(concerned[i] == GShellListener.class) {
553 ((GShellListener)concerned[i+1]).message(current_event);
554 }
555 }
556 concerned = null;
557 }
558 }
559 // And if we have a buffered output stream from error messages, send the message there
560 if(bos != null) {
561 try {
562 bos.write(message.getBytes(), 0, message.length());
563 }
564 catch(Exception exception) {
565 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
566 DebugStream.printStackTrace(exception);
567 }
568 }
569 message_queue = null;
570 event = null;
571 }
572
573 /** Method for firing a process begun event which is called, strangely enough, when the process begins.
574 * @param type An <strong>int</strong> indicating the process type.
575 * @param status An <strong>int</strong> specifying the current status of the process.
576 */
577 protected void fireProcessBegun(int type, int status) {
578 // Start the progres monitor if available
579 if(progress != null) {
580 //ystem.err.println("About to call progress.start().");
581 progress.start();
582 //ystem.err.println("Called progress.start().");
583 }
584 // Fire an event
585 GShellEvent event = new GShellEvent(this, 0, type, "", status);
586 Object[] concerned = listeners.getListenerList();
587 for(int i = 0; i < concerned.length ; i++) {
588 if(concerned[i] == GShellListener.class) {
589 ((GShellListener)concerned[i+1]).processBegun(event);
590 }
591 }
592 }
593
594
595 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
596 * @param type An <strong>int</strong> indicating the process type.
597 * @param status An <strong>int</strong> specifying the current status of the process.
598 */
599 protected void fireProcessComplete(int type, int status) {
600 // 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).
601 if(progress != null && status != CANCELLED) {
602 progress.stop();
603 }
604
605 // If we were cancelled, and we are lower details modes, fire off one last message.
606 if(status == CANCELLED && Configuration.getMode() <= Configuration.LIBRARIAN_MODE) {
607 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
608 Object[] concerned = listeners.getListenerList();
609 for(int i = 0; i < concerned.length ; i++) {
610 if(concerned[i] == GShellListener.class) {
611 ((GShellListener)concerned[i+1]).message(current_event);
612 }
613 }
614 concerned = null;
615 }
616
617 String msg = "";
618 // If we are creating collection and have trouble with permissions, we need more messages
619 if(status == ERROR && type == GShell.NEW){
620 msg = args[args.length-1];
621 }
622 // And firing off an event
623 GShellEvent event = new GShellEvent(this, 0, type, msg, status);
624 Object[] concerned = listeners.getListenerList();
625 for(int i = 0; i < concerned.length ; i++) {
626 if(concerned[i] == GShellListener.class) {
627 ((GShellListener)concerned[i+1]).processComplete(event);
628 }
629 }
630 }
631
632 /** Method to determine if the user, via the progress monitor, has signalled stop.
633 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
634 */
635 public synchronized boolean hasSignalledStop() {
636 boolean has_signalled_stop = false;
637 if(progress != null) {
638 has_signalled_stop = progress.hasSignalledStop();
639 }
640 if(has_signalled_stop) {
641 status = CANCELLED;
642 }
643 return has_signalled_stop;
644 }
645
646 /** Converts a type into a text representation.
647 * @param type An <strong>int</strong> which maps to a shell process type.
648 * @return A <strong>String</strong> which is the thread process's text name.
649 */
650 public String typeAsString(int type) {
651 String name = null;
652 switch(type) {
653 case BUILD:
654 name = "buildcol.pl";
655 break;
656 case IMPORT:
657 name = "import.pl";
658 break;
659 case NEW:
660 name = "mkcol.pl";
661 break;
662 case EXPORTAS:
663 name = "export.pl";
664 break;
665 case CDIMAGE:
666 name = "exportcol.pl";
667 break;
668 case CONVERT:
669 name = "convert_coll_from_gs2.pl";
670 break;
671 case EXPLODE:
672 name = "explode_metadata_database.pl";
673 break;
674 case SRCREPLACE: // To work with replace_srcdoc_with_html.pl
675 name = "replace_srcdoc_with_html.pl";
676 break;
677 case SCHEDULE:
678 name = "schedule.pl";
679 break;
680 default:
681 name = "";
682 }
683 return name;
684 }
685
686 // From interface SafeProcess.ExceptionHandler
687 // Called when an exception happens during the running of our perl process, as we want to set
688 // the GShell status to ERROR.
689 // However, exceptions when reading from our perl process' stderr and stdout streams are handled
690 // by SynchronizedProcessHandler.gotException() below, since they happen in separate threads
691 // from this one (the one from which the perl process is run).
692 public void gotException(Exception e) {
693
694 if(e instanceof InterruptedException) {
695 DebugStream.println("We've been asked to stop.");
696 SafeProcess.log("@@@ Interruption to SafeProcess run by GShell.runLocal()");
697 setStatus(CANCELLED); // expected exception
698 } else {
699 DebugStream.println("Exception in GShell.runLocal() - unexpected");
700 DebugStream.printStackTrace(e);
701 setStatus(ERROR); // status particularly needs to be synchronized on
702 }
703 }
704
705 public synchronized void setStatus(int newStatus) {
706 status = newStatus;
707 }
708
709 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
710 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
711 // instance of this class for each stream. However, since multiple instances of this LineByLineHandler
712 // are firing off events to the same outputstream object (bos) in their own threads, several objects,
713 // not just bost, needed to be made threadsafe. So gotLine needs to be synchronized, and created a
714 // synchronized setStatus() method above too.
715 // This class also handles exceptions that may occur when reading from the perl process' stderr or stout.
716 // The old GShell.runLocal() code would set the GShell.status to ERROR on each exception, so we ensure
717 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
718 // use of the EventListeners threadsafe.
719 protected class SynchronizedLineByLineHandler extends SafeProcess.LineByLineHandler
720 {
721 private final BufferedOutputStream bos; // needs to be final to be able to synchronize on the shared object
722
723 public SynchronizedLineByLineHandler(BufferedOutputStream bos, int src) {
724 super(src); // will set this.source to STDERR or STDOUT
725 this.bos = bos; // caller will close bw, since many more than one
726 // SynchronizedLineByLineHandlers are using it
727 }
728
729 // trying to keep synchronized methods as short as possible
730 public void gotException(Exception e) {
731 String msg = "Got exception when processing the perl process' " + SafeProcess.streamToString(this.source) + " stream.";
732
733 DebugStream.println(msg); // method is already synchronized
734 DebugStream.printStackTrace(e); // method is already synchronized
735 SafeProcess.log("*** " + msg, e);
736
737 GShell.this.setStatus(GShell.ERROR); // synchronized
738 }
739
740 private void log(String msg) {
741 DebugStream.println(msg); // already synchro
742 System.err.println("@@@@@ " + msg);
743 }
744
745 // every time we read a line from the SafeProcess' stderr or stdout stream we come here.
746 // a different isntance of SynchronizedL
747 public synchronized void gotLine(String line) {
748 fireMessage(GShell.this.type, // not final, needs synchro
749 GShell.this.typeAsString(type) + "> " + line,
750 GShell.this.status, // not final, needs synchro
751 this.bos); // needs synchro
752 }
753
754 }
755
756}
Note: See TracBrowser for help on using the repository browser.