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

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

Bugfix to nullpointer exception caused when converting a GS2 collection to GS3. OpenCollectionDialog.convertToGS3Collection(OpenCollectionDialog.java:400) was passing in null as the caller parameter to the GShell constructor. This is added to the GShell's listener's list. Need to avoid adding null callers to listener list, to avoid firing off events to null variables.

  • Property svn:keywords set to Author Date Id Revision
File size: 28.6 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.concurrent.CopyOnWriteArrayList;
44import java.util.regex.*;
45import javax.swing.*;
46import javax.swing.event.*;
47import javax.swing.tree.*;
48import org.greenstone.gatherer.Configuration;
49import org.greenstone.gatherer.DebugStream;
50import org.greenstone.gatherer.Dictionary;
51import org.greenstone.gatherer.Gatherer;
52import org.greenstone.gatherer.cdm.CollectionConfiguration;
53import org.greenstone.gatherer.cdm.CollectionDesignManager;
54import org.greenstone.gatherer.cdm.CollectionMetaManager;
55import org.greenstone.gatherer.cdm.CollectionMeta;
56import org.greenstone.gatherer.collection.CollectionManager;
57import org.greenstone.gatherer.metadata.DocXMLFileManager;
58import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
59import org.greenstone.gatherer.util.SafeProcess;
60import org.greenstone.gatherer.util.StaticStrings;
61import org.greenstone.gatherer.util.Utility;
62
63
64/** 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.
65 * When modifying this class, bear in mind concurrency issues that could arise with SafeProcess'
66 * worker threads and where synchronization may be needed to prevent such issues.
67 */
68public class GShell
69 extends Thread implements SafeProcess.ExceptionHandler {
70 /** A flag used to determine if this process has been asked to cancel. */
71 private boolean cancel = false;
72 private BufferedOutputStream buffered_output_stream = null;
73 /** The list of listeners associated with this class.
74 * Make it a CopyOnWriteArrayList to make it threadsafe at a cost of decreased efficiency.
75 * see http://stackoverflow.com/questions/8259479/should-i-synchronize-listener-notifications-or-not
76 */
77 private final CopyOnWriteArrayList<GShellListener> listeners;
78 /** The current status of this shell process. */
79 private int status = -1;
80 /** The type of message being sent. */
81 private int msg_type = -1;
82 /** The type of shell process. */
83 private int type = -1;
84 /** The caller of this process, and thus the class most interested in messages. */
85 private GShellListener caller = null;
86 /** The progress monitor associated with this process. */
87 private GShellProgressMonitor progress = null;
88 /** Arguments to be given to the process (including the executable you are calling. */
89 private String args[] = null;
90 /** The command_output returned from executing a process */
91 private String commandOutput = null;
92
93 /** The process safely run in this GShell Thread. Make sure to set to prcs to null when done with. */
94 private SafeProcess prcs = null;
95
96 /** Elements in process type enumeration. */
97 static final public int BUILD = 0;
98 static final public int IMPORT = 1;
99 static final public int NEW = 2;
100 static final public int EXPORTAS = 3;
101 static final public int CDIMAGE = 4;
102 static final public int CONVERT = 5;
103 static final public int EXPLODE = 6;
104 static final public int SRCREPLACE = 7; // for replacing source docs with their html
105 static final public int SCHEDULE = 8;
106 static final public int DELETE = 9;
107
108 /** Elements in status type enumeration. */
109 static final public int ERROR = 0;
110 static final public int OK = 1;
111 static final public int CANCELLED = 2;
112
113 /** Elements in process type name enumeration. */
114 static public String GSHELL_BUILD = "gshell_build";
115 static public String GSHELL_IMPORT = "gshell_import";
116 static public String GSHELL_NEW = "gshell_new";
117 static public String GSHELL_EXPORTAS = "gshell_exportas";
118 static public String GSHELL_CDIMAGE = "gshell_cdimage";
119 static public String GSHELL_CONVERT = "gshell_convert";
120 static public String GSHELL_EXPLODE = "gshell_explode";
121 static public String GSHELL_SRCREPLACE = "gshell_srcreplace"; // for replacing source docs with their html versions
122 static public String GSHELL_SCHEDULE = "gshell_schedule";
123 static public String GSHELL_FEDORA_COLDELETE = "gshell_fedora_col_delete";
124
125
126 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
127 * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
128 * @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).
129 * @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>.
130 * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
131 * @param progress The <i>GShellProgressMonitor</i> associated with this process.
132 * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
133 */
134 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
135 super(name);
136 this.args = args;
137 this.msg_type = msg_type;
138 this.type = type;
139 this.caller = caller;
140 this.progress = progress;
141 this.status = 0;
142 // Lower this jobs priority
143 this.setPriority(Thread.MIN_PRIORITY);
144 listeners = new CopyOnWriteArrayList<GShellListener>();
145 if(caller != null) {
146 listeners.add(caller);
147 }
148 }
149 /** This method adds another shell listener to this process.
150 * @param listener The new <i>GShellListener</i>.
151 */
152 public void addGShellListener(GShellListener listener) {
153 listeners.add(listener);
154 }
155 /** This method removes a certain shell listener from this process.
156 * @param listener The <i>GShellListener</i> to be removed.
157 */
158 /* private void removeGShellListener(GShellListener listener) {
159 listeners.remove(listener);
160 } */
161
162 // This method is only used by old_runLocal(). If that is removed, then remove this too
163 protected StringBuffer get_stream_char(InputStreamReader isr, StringBuffer line_buffer,
164 BufferedOutputStream bos) throws IOException
165 {
166 int c = isr.read();
167 ///atherer.println("isr: '" + (char) c + "'");
168 if(c == '\n' || c == '\r') {
169 if(line_buffer.length() > 0) {
170 String line = line_buffer.toString();
171 // DebugStream.println("* " + line + " *");
172 fireMessage(type, typeAsString(type) + "> " + line, status, bos);
173 line_buffer = new StringBuffer();
174 }
175 }
176 else {
177 line_buffer.append((char)c);
178 }
179
180 return line_buffer;
181 }
182
183 public String getCommandOutput() { return commandOutput; }
184 public void resetCommandOutput() { commandOutput = null; }
185
186 private void runRemote(String[] args, BufferedOutputStream bos)
187 {
188 // Make sure the process hasn't been cancelled
189 if (hasSignalledStop()) {
190 return;
191 }
192
193 try {
194 int directory_name_end = args[0].lastIndexOf(File.separator);
195 String script_name = ((directory_name_end != -1) ? args[0].substring(directory_name_end + 1) : args[0]);
196 System.err.println("Script name: " + script_name);
197 String collection_name = args[args.length - 1];
198 System.err.println("Collection name: " + collection_name);
199
200
201 String script_args = "";
202 for (int i = 1; i < (args.length - 1); i++) {
203 // Skip arguments that won't make sense on the server
204 // and -site is added in RemoteGreenstonServer.java
205 if (args[i].equals("-collectdir") || args[i].equals("-importdir") || args[i].equals("-builddir") || args[i].equals("-site")) {
206 i++;
207 continue;
208 }
209
210 // Script arguments get changed to CGI arguments
211 if (args[i].startsWith("-")) {
212 script_args += "&" + args[i].substring(1) + "=";
213 if ((i + 1) < (args.length - 1) && !args[i + 1].startsWith("-")) {
214 script_args += URLEncoder.encode(args[i + 1], "UTF-8");
215 i++;
216 }
217 }
218 }
219
220 System.err.println("Script args: " + script_args);
221 buffered_output_stream = bos;
222 String command_output = Gatherer.remoteGreenstoneServer.runScript(collection_name, script_name, script_args, this);
223 this.commandOutput = command_output;
224 status = (command_output.equals("") ? CANCELLED : OK);
225 }
226 catch (Exception exception) {
227 DebugStream.printStackTrace(exception);
228 status = ERROR;
229 }
230 }
231
232
233 // old_runLocal uses a potentially unsafe way of running a process and an inefficient way of reading
234 // from the process stdout and stderr streams. Replaced by new runLocal() which uses SafeProcess and
235 // the new LineByLineHandler inner class instantiated for each of the 2 process output streams.
236 // If this method is ever removed from the file, can remove its helper get_stream_char() too.
237 private void old_runLocal(String[] args, BufferedOutputStream bos)
238 {
239 try {
240 String command = "";
241 for(int i = 0; i < args.length; i++) {
242 command = command + args[i] + " ";
243 }
244
245 ///ystem.err.println("Command: " + command);
246 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
247
248 Runtime rt = Runtime.getRuntime();
249 Process prcs = null;
250
251 prcs = rt.exec(args);
252 // If we used single argument exec, spaces in filenames or paths cause problems
253 // I.e. better to avoid: prcs = rt.exec(command);
254
255 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream(), "UTF-8" );
256 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream(), "UTF-8" );
257
258 StringBuffer eline_buffer = new StringBuffer();
259 StringBuffer stdline_buffer = new StringBuffer();
260
261 while(/*type != GShell.NEW &&*/ SafeProcess.processRunning(prcs) && !hasSignalledStop()) {
262 // Hopefully this doesn't block if the process is trying to write to STDOUT.
263 if((eisr!=null) && eisr.ready()) {
264 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
265 }
266 // Hopefully this won't block if the process is trying to write to STDERR
267 else if(stdisr.ready()) {
268 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
269 }
270 else {
271 try {
272 sleep(100);
273 }
274 catch(Exception exception) {
275 }
276 }
277 }
278
279 if(!hasSignalledStop()) {
280 // Of course, just because the process is finished doesn't
281 // mean the incoming streams are empty. Unfortunately I've
282 // got no chance of preserving order, so I'll process the
283 // error stream first, then the out stream
284 while(eisr.ready()) {
285 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
286 }
287
288 while(stdisr.ready()) {
289 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
290 }
291
292 // Ensure that any messages still remaining in the string buffers are fired off.
293 if(eline_buffer.length() > 0) {
294 String eline = eline_buffer.toString();
295 //DebugStream.println("Last bit of eline: " + eline);
296 fireMessage(type, typeAsString(type) + "> " + eline, status, bos);
297 eline = null;
298 }
299
300 if(stdline_buffer.length() > 0) {
301 String stdline = stdline_buffer.toString();
302 //DebugStream.println("Last bit of stdline: " + stdline);
303 fireMessage(type, typeAsString(type) + "> " + stdline, status, null);
304 stdline = null;
305 }
306 }
307 else {
308 DebugStream.println("We've been asked to stop.");
309 }
310
311
312 if(!hasSignalledStop()) {
313 // Now display final message based on exit value
314
315 prcs.waitFor();
316
317 if(prcs.exitValue() == 0) {
318 status = OK;
319 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
320 }
321 else {
322 status = ERROR;
323 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
324 }
325
326 eisr.close();
327 stdisr.close();
328 }
329 else {
330 // I need to somehow kill the child process. Unfortunately
331 // Thread.stop() and Process.destroy() both fail to do
332 // this. But now, thankx to the magic of Michaels 'close the
333 // stream suggestion', it works fine (no it doesn't!)
334 prcs.getInputStream().close();
335 prcs.getErrorStream().close();
336 prcs.getOutputStream().close();
337 prcs.destroy();
338 status = CANCELLED;
339 }
340 }
341 // Exception
342 catch (Exception exception) {
343 DebugStream.println("Exception in GShell.runLocal() - unexpected");
344 DebugStream.printStackTrace(exception);
345 status = ERROR;
346 }
347 }
348
349 private void runLocal(String[] args, BufferedOutputStream bos)
350 {
351 // in case we stop between import and build, let's not launch further processes
352 // that only pressing cancel, which results in interrupts, can subsequently stop.
353 if(hasSignalledStop()) {
354 return;
355 }
356
357 String command = "";
358 for(int i = 0; i < args.length; i++) {
359 command = command + args[i] + " ";
360 }
361
362 ///ystem.err.println("Command: " + command);
363 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
364
365 prcs = new SafeProcess(args);
366 SafeProcess.LineByLineHandler processOutLineHandler
367 = new SynchronizedLineByLineHandler(SafeProcess.STDOUT);
368 SafeProcess.LineByLineHandler processErrLineHandler
369 = new SynchronizedLineByLineHandler(SafeProcess.STDERR);
370
371 prcs.setExceptionHandler(this);
372 int exitValue = prcs.runProcess(processOutLineHandler, processErrLineHandler); // use default procIn handling
373
374 if(exitValue == 0) {
375 //System.err.println("*** Exitted normally");
376 status = OK;
377 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
378 }
379 else {
380 //System.err.println("*** Exitted abnormally with exit value " + exitValue);
381 status = ERROR;
382 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
383 }
384
385 prcs = null;
386 }
387
388 /** When cancel is called on this GShell thread from a separate thread,
389 * This GShell will safely terminate any process it's currently running (by interrupting it)
390 * and will set this.prcs to null at the end. */
391 public void cancel() {
392 if(prcs != null) { //GShell is running a process, so interrupt the GShell/SafeProcess thread
393
394 SafeProcess.log("********** HAS SIGNALLED STOP. INTERRUPTING THE GSHELL/SAFEPROCESS THREAD");
395
396 // The following is implemented to interrupt the SafeProcess thread (which is *this* GShell thread),
397 // but only if any process is being run by SafeProcess.
398 // This will propagate the CANCEL status to any worker threads launched by the SafeProcess.
399 // Everything will clean up nicely after itself (even terminating subprocesses that prcs launched on Windows!)
400 prcs.cancelRunningProcess();
401
402 prcs = null;
403 }
404 setStatus(CANCELLED); //or: hasSignalledStop(); // synchronized. Either will set status to CANCELLED in thread-safe manner.
405 }
406
407
408 /** Any threaded class must include this method to allow the thread body to be run. */
409 public void run() {
410 String col_name = args[args.length-1];
411
412 // Determine if the user has asked for an outfile.
413 String out_name = null;
414 BufferedOutputStream bos = null;
415 if(type == IMPORT || type == BUILD || type == SCHEDULE) {
416 if(type == IMPORT) {
417 out_name = (String) Gatherer.c_man.getCollection().import_options.getValue("out");
418 }
419 else if(type == BUILD) {
420 out_name = (String) Gatherer.c_man.getCollection().build_options.getValue("out");
421 }
422 else { // SCHEDULE
423 out_name = (String) Gatherer.c_man.getCollection().schedule_options.getValue("out");
424 }
425 if(out_name != null && out_name.length() > 0) {
426 try {
427 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
428 }
429 catch (Exception error) {
430 DebugStream.printStackTrace(error);
431 }
432 }
433 }
434
435 // Issue a processBegun event
436 //ystem.err.println("\nFiring process begun for " + type + "...");
437 fireProcessBegun(type, status);
438 //ystem.err.println("Done process begun.");
439 if (Gatherer.isGsdlRemote) {
440 runRemote(args,bos);
441 }
442 else {
443 runLocal(args,bos);
444 }
445 //ystem.err.println("Done runLocal().");
446
447 if(status == OK) {
448 if (type == NEW) {
449 if (Gatherer.isGsdlRemote) {
450 Gatherer.remoteGreenstoneServer.downloadCollection(col_name);
451 }
452 }
453 else if(type == IMPORT) {
454
455 // download the archives directory (if gsdl server is remote)
456 if (Gatherer.isGsdlRemote) {
457
458 if (progress!=null) {
459 progress.messageOnProgressBar("Downloading archive data from server");
460 }
461
462 Gatherer.remoteGreenstoneServer.downloadCollectionArchives(col_name);
463
464 if (progress!=null) {
465 progress.messageOnProgressBar("");
466 }
467 }
468
469 // Refresh the DocXMLFileManager
470 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status, null);
471 DocXMLFileManager.clearDocXMLFiles();
472 if (Configuration.fedora_info.isActive()) { // FLI case
473 File collection_export_directory = new File(CollectionManager.getLoadedCollectionExportDirectoryPath());
474 DocXMLFileManager.loadDocXMLFiles(collection_export_directory,"docmets.xml");
475 }
476 else {
477 File collection_archives_directory = new File(CollectionManager.getLoadedCollectionArchivesDirectoryPath());
478 DocXMLFileManager.loadDocXMLFiles(collection_archives_directory,"doc.xml");
479 }
480
481
482 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status, null);
483 }
484
485 else if(type == BUILD) {
486 // download the building directory (if gsdl server is remote)
487 if ((Gatherer.isGsdlRemote) && (!Configuration.fedora_info.isActive())) {
488 if (progress!=null) {
489 progress.messageOnProgressBar("Downloading index data from server");
490 }
491
492 if (!Gatherer.GS3){
493 // Only need to download build.cfg file
494 File build_cfg_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "build.cfg");
495 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
496 }else{
497 // Only need to download buildConfig.xml file
498 File buildConfig_xml_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "buildConfig.xml");
499 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, buildConfig_xml_file);
500 }
501
502 if (progress!=null) {
503 progress.messageOnProgressBar("");
504 }
505 }
506 }
507 else if(type == CDIMAGE) {
508 // download exported files from tmp folder (if gsdl server is remote)
509 if (Gatherer.isGsdlRemote) {
510 if (progress!=null) {
511 progress.messageOnProgressBar("Downloading CD-ROM data from server");
512 }
513
514 // !! TO DO
515
516 if (progress!=null) {
517 progress.messageOnProgressBar("");
518 }
519 }
520 }
521 }
522
523 // We're done.
524 //ystem.err.println("Firing process complete for " + type + "...");
525 fireProcessComplete(type, status);
526 // Close bos
527 if(bos != null) {
528 try {
529 bos.close();
530 bos = null;
531 }
532 catch(Exception error) {
533 DebugStream.printStackTrace(error);
534 }
535 }
536 }
537
538 // Now synchronized, since separate threads handling process out and err streams can call fireMessage()
539 // and need to reserve member objects and member variables used in this method while doing so.
540 public synchronized void fireMessage(String message)
541 {
542 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
543 }
544
545
546 /** Method for firing a message to all interested listeners.
547 * @param type An <strong>int</strong> indicating the process type.
548 * @param message The message as a <strong>String</strong>.
549 * @param status An <strong>int</strong> specifying the current status of the process.
550 */
551 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
552 GShellEvent event = new GShellEvent(this, 0, type, message, status);
553 // 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.
554 ArrayList message_queue = new ArrayList();
555 message_queue.add(event);
556 if(progress != null) {
557 progress.process(message_queue);
558 }
559 for(int j = 0; j < message_queue.size(); j++) {
560 GShellEvent current_event = (GShellEvent) message_queue.get(j);
561 // If the event hasn't been vetoed, pass it on to other listeners
562 if(!current_event.isVetoed()) {
563 // See http://stackoverflow.com/questions/8259479/should-i-synchronize-listener-notifications-or-not
564 for (GShellListener l: this.listeners) {
565 l.message(current_event);
566 }
567 }
568 }
569 // And if we have a buffered output stream from error messages, send the message there
570 if(bos != null) {
571 try {
572 bos.write(message.getBytes(), 0, message.length());
573 }
574 catch(Exception exception) {
575 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
576 DebugStream.printStackTrace(exception);
577 }
578 }
579 message_queue = null;
580 event = null;
581 }
582
583 /** Method for firing a process begun event which is called, strangely enough, when the process begins.
584 * @param type An <strong>int</strong> indicating the process type.
585 * @param status An <strong>int</strong> specifying the current status of the process.
586 */
587 protected void fireProcessBegun(int type, int status) {
588 // Start the progres monitor if available
589 if(progress != null) {
590 //ystem.err.println("About to call progress.start().");
591 progress.start();
592 //ystem.err.println("Called progress.start().");
593 }
594 // Fire an event
595 GShellEvent event = new GShellEvent(this, 0, type, "", status);
596 for (GShellListener l: this.listeners) {
597 l.processBegun(event);
598 }
599 }
600
601
602 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
603 * @param type An <strong>int</strong> indicating the process type.
604 * @param status An <strong>int</strong> specifying the current status of the process.
605 */
606 protected void fireProcessComplete(int type, int status) {
607 // 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).
608 if(progress != null && status != CANCELLED) {
609 progress.stop();
610 }
611
612 // If we were cancelled, and we are lower details modes, fire off one last message.
613 if(status == CANCELLED && Configuration.getMode() <= Configuration.LIBRARIAN_MODE) {
614 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
615 for (GShellListener l: this.listeners) {
616 l.message(current_event);
617 }
618 }
619
620 String msg = "";
621 // If we are creating collection and have trouble with permissions, we need more messages
622 if(status == ERROR && type == GShell.NEW){
623 msg = args[args.length-1];
624 }
625 // And firing off an event
626 GShellEvent event = new GShellEvent(this, 0, type, msg, status);
627 for (GShellListener l: this.listeners) {
628 l.processComplete(event);
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 public SynchronizedLineByLineHandler(int src) {
722 super(src); // will set this.source to STDERR or STDOUT
723 }
724
725 // trying to keep synchronized methods as short as possible
726 public void gotException(Exception e) {
727 String msg = "Got exception when processing the perl process' " + SafeProcess.streamToString(this.source) + " stream.";
728
729 DebugStream.println(msg); // method is already synchronized
730 DebugStream.printStackTrace(e); // method is already synchronized
731 SafeProcess.log("*** " + msg, e);
732
733 GShell.this.setStatus(GShell.ERROR); // synchronized
734 }
735
736 private void log(String msg) {
737 DebugStream.println(msg); // already synchro
738 System.err.println("@@@@@ " + msg);
739 }
740
741 // every time we read a line from the SafeProcess' stderr or stdout stream we come here.
742 // Don't synchronize the gotLine() method, as it will synchronize on *this* (this object, this
743 // instance of LineByLineHandler). But we want to synchronize on the (outer class) GShell's
744 // variables that are used in fireMessage(). So it's the fireMessage(String) method that
745 // needs to be synchronized. See
746 // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
747 public void gotLine(String line) {
748 fireMessage(line); // synchronized, so even though process STDERR and STDOUT threads
749 // will be firing messages, they won't be able to do so simultaneously
750 }
751
752 }
753
754}
Note: See TracBrowser for help on using the repository browser.