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

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

Bugfix for remote GS server: there's no building/build.cfg only index/build.cfg, so need the later to be zipped up instead

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