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

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

Uses synchronized methods vs synchronizing on objects in the right way and at the appropriate times.

  • 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 // 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(SafeProcess.STDOUT);
360 SafeProcess.LineByLineHandler processErrLineHandler
361 = new SynchronizedLineByLineHandler(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 // Now synchronized, since separate threads handling process out and err streams can call fireMessage()
527 // and need to reserve member objects and member variables used in this method while doing so.
528 public synchronized void fireMessage(String message)
529 {
530 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
531 }
532
533
534 /** Method for firing a message to all interested listeners.
535 * @param type An <strong>int</strong> indicating the process type.
536 * @param message The message as a <strong>String</strong>.
537 * @param status An <strong>int</strong> specifying the current status of the process.
538 */
539 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
540 GShellEvent event = new GShellEvent(this, 0, type, message, status);
541 // 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.
542 ArrayList message_queue = new ArrayList();
543 message_queue.add(event);
544 if(progress != null) {
545 progress.process(message_queue);
546 }
547 for(int j = 0; j < message_queue.size(); j++) {
548 GShellEvent current_event = (GShellEvent) message_queue.get(j);
549 // If the event hasn't been vetoed, pass it on to other listeners
550 if(!current_event.isVetoed()) {
551 Object[] concerned = listeners.getListenerList();
552 for(int i = 0; i < concerned.length ; i++) {
553 if(concerned[i] == GShellListener.class) {
554 ((GShellListener)concerned[i+1]).message(current_event);
555 }
556 }
557 concerned = null;
558 }
559 }
560 // And if we have a buffered output stream from error messages, send the message there
561 if(bos != null) {
562 try {
563 bos.write(message.getBytes(), 0, message.length());
564 }
565 catch(Exception exception) {
566 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
567 DebugStream.printStackTrace(exception);
568 }
569 }
570 message_queue = null;
571 event = null;
572 }
573
574 /** Method for firing a process begun event which is called, strangely enough, when the process begins.
575 * @param type An <strong>int</strong> indicating the process type.
576 * @param status An <strong>int</strong> specifying the current status of the process.
577 */
578 protected void fireProcessBegun(int type, int status) {
579 // Start the progres monitor if available
580 if(progress != null) {
581 //ystem.err.println("About to call progress.start().");
582 progress.start();
583 //ystem.err.println("Called progress.start().");
584 }
585 // Fire an event
586 GShellEvent event = new GShellEvent(this, 0, type, "", status);
587 Object[] concerned = listeners.getListenerList();
588 for(int i = 0; i < concerned.length ; i++) {
589 if(concerned[i] == GShellListener.class) {
590 ((GShellListener)concerned[i+1]).processBegun(event);
591 }
592 }
593 }
594
595
596 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
597 * @param type An <strong>int</strong> indicating the process type.
598 * @param status An <strong>int</strong> specifying the current status of the process.
599 */
600 protected void fireProcessComplete(int type, int status) {
601 // 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).
602 if(progress != null && status != CANCELLED) {
603 progress.stop();
604 }
605
606 // If we were cancelled, and we are lower details modes, fire off one last message.
607 if(status == CANCELLED && Configuration.getMode() <= Configuration.LIBRARIAN_MODE) {
608 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
609 Object[] concerned = listeners.getListenerList();
610 for(int i = 0; i < concerned.length ; i++) {
611 if(concerned[i] == GShellListener.class) {
612 ((GShellListener)concerned[i+1]).message(current_event);
613 }
614 }
615 concerned = null;
616 }
617
618 String msg = "";
619 // If we are creating collection and have trouble with permissions, we need more messages
620 if(status == ERROR && type == GShell.NEW){
621 msg = args[args.length-1];
622 }
623 // And firing off an event
624 GShellEvent event = new GShellEvent(this, 0, type, msg, status);
625 Object[] concerned = listeners.getListenerList();
626 for(int i = 0; i < concerned.length ; i++) {
627 if(concerned[i] == GShellListener.class) {
628 ((GShellListener)concerned[i+1]).processComplete(event);
629 }
630 }
631 }
632
633 /** Method to determine if the user, via the progress monitor, has signalled stop.
634 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
635 */
636 public synchronized boolean hasSignalledStop() {
637 boolean has_signalled_stop = false;
638 if(progress != null) {
639 has_signalled_stop = progress.hasSignalledStop();
640 }
641 if(has_signalled_stop) {
642 status = CANCELLED;
643 }
644 return has_signalled_stop;
645 }
646
647 /** Converts a type into a text representation.
648 * @param type An <strong>int</strong> which maps to a shell process type.
649 * @return A <strong>String</strong> which is the thread process's text name.
650 */
651 public String typeAsString(int type) {
652 String name = null;
653 switch(type) {
654 case BUILD:
655 name = "buildcol.pl";
656 break;
657 case IMPORT:
658 name = "import.pl";
659 break;
660 case NEW:
661 name = "mkcol.pl";
662 break;
663 case EXPORTAS:
664 name = "export.pl";
665 break;
666 case CDIMAGE:
667 name = "exportcol.pl";
668 break;
669 case CONVERT:
670 name = "convert_coll_from_gs2.pl";
671 break;
672 case EXPLODE:
673 name = "explode_metadata_database.pl";
674 break;
675 case SRCREPLACE: // To work with replace_srcdoc_with_html.pl
676 name = "replace_srcdoc_with_html.pl";
677 break;
678 case SCHEDULE:
679 name = "schedule.pl";
680 break;
681 default:
682 name = "";
683 }
684 return name;
685 }
686
687 // From interface SafeProcess.ExceptionHandler
688 // Called when an exception happens during the running of our perl process, as we want to set
689 // the GShell status to ERROR.
690 // However, exceptions when reading from our perl process' stderr and stdout streams are handled
691 // by SynchronizedProcessHandler.gotException() below, since they happen in separate threads
692 // from this one (the one from which the perl process is run).
693 public void gotException(Exception e) {
694
695 if(e instanceof InterruptedException) {
696 DebugStream.println("We've been asked to stop.");
697 SafeProcess.log("@@@ Interruption to SafeProcess run by GShell.runLocal()");
698 setStatus(CANCELLED); // expected exception
699 } else {
700 DebugStream.println("Exception in GShell.runLocal() - unexpected");
701 DebugStream.printStackTrace(e);
702 setStatus(ERROR); // status particularly needs to be synchronized on
703 }
704 }
705
706 public synchronized void setStatus(int newStatus) {
707 status = newStatus;
708 }
709
710 // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
711 // This class deals with each incoming line from the perl process' stderr or stdout streams. One
712 // instance of this class for each stream. However, since multiple instances of this LineByLineHandler
713 // are firing off events to the same outputstream object (bos) in their own threads, several objects,
714 // not just bost, needed to be made threadsafe. So gotLine needs to be synchronized, and created a
715 // synchronized setStatus() method above too.
716 // This class also handles exceptions that may occur when reading from the perl process' stderr or stout.
717 // The old GShell.runLocal() code would set the GShell.status to ERROR on each exception, so we ensure
718 // we do that here too, to continue original behaviour. These calls are also synchronized to make their
719 // use of the EventListeners threadsafe.
720 protected class SynchronizedLineByLineHandler extends SafeProcess.LineByLineHandler
721 {
722 public SynchronizedLineByLineHandler(int src) {
723 super(src); // will set this.source to STDERR or STDOUT
724 }
725
726 // trying to keep synchronized methods as short as possible
727 public void gotException(Exception e) {
728 String msg = "Got exception when processing the perl process' " + SafeProcess.streamToString(this.source) + " 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); // synchronized
735 }
736
737 private void log(String msg) {
738 DebugStream.println(msg); // already synchro
739 System.err.println("@@@@@ " + msg);
740 }
741
742 // every time we read a line from the SafeProcess' stderr or stdout stream we come here.
743 // Don't synchronize the gotLine() method, as it will synchronize on *this* (this object, this
744 // instance of LineByLineHandler). But we want to synchronize on the (outer class) GShell's
745 // variables that are used in fireMessage(). So it's the fireMessage(String) method that
746 // needs to be synchronized. See
747 // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
748 public void gotLine(String line) {
749 fireMessage(line); // synchronized, so even though process STDERR and STDOUT threads
750 // will be firing messages, they won't be able to do so simultaneously
751 }
752
753 }
754
755}
Note: See TracBrowser for help on using the repository browser.