source: gli/branches/rtl-gli/src/org/greenstone/gatherer/shell/GShell.java@ 18360

Last change on this file since 18360 was 18360, checked in by kjdon, 15 years ago

updated the rtl-gli branch with files from trunk. Result of a merge 14807:18318

  • Property svn:keywords set to Author Date Id Revision
File size: 22.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.StaticStrings;
59import org.greenstone.gatherer.util.Utility;
60
61
62/** 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.
63 */
64public class GShell
65 extends Thread {
66 /** A flag used to determine if this process has been asked to cancel. */
67 private boolean cancel = false;
68 private BufferedOutputStream buffered_output_stream = null;
69 /** The list of listeners associated with this class. */
70 private EventListenerList listeners = null;
71 /** The current status of this shell process. */
72 private int status = -1;
73 /** The type of message being sent. */
74 private int msg_type = -1;
75 /** The type of shell process. */
76 private int type = -1;
77 /** The caller of this process, and thus the class most interested in messages. */
78 private GShellListener caller = null;
79 /** The progress monitor associated with this process. */
80 private GShellProgressMonitor progress = null;
81 /** Arguments to be given to the process (including the executable you are calling. */
82 private String args[] = null;
83 /** Elements in process type enumeration. */
84 static final public int BUILD = 0;
85 static final public int IMPORT = 1;
86 static final public int NEW = 2;
87 static final public int EXPORTAS = 3;
88 static final public int CDIMAGE = 4;
89 static final public int CONVERT = 5;
90 static final public int EXPLODE = 6;
91 static final public int SRCREPLACE = 7; // for replacing source docs with their html
92 static final public int SCHEDULE = 8;
93
94 /** Elements in status type enumeration. */
95 static final public int ERROR = 0;
96 static final public int OK = 1;
97 static final public int CANCELLED = 2;
98
99 /** Elements in process type name enumeration. */
100 static public String GSHELL_BUILD = "gshell_build";
101 static public String GSHELL_IMPORT = "gshell_import";
102 static public String GSHELL_NEW = "gshell_new";
103 static public String GSHELL_EXPORTAS = "gshell_exportas";
104 static public String GSHELL_CDIMAGE = "gshell_cdimage";
105 static public String GSHELL_CONVERT = "gshell_convert";
106 static public String GSHELL_EXPLODE = "gshell_explode";
107 static public String GSHELL_SRCREPLACE = "gshell_srcreplace"; // for replacing source docs with their html versions
108 static public String GSHELL_SCHEDULE = "gshell_schedule";
109
110 /** Determine if the given process is still executing. It does this by attempting to throw an exception - not the most efficient way, but the only one as far as I know
111 * @param process the Process to test
112 * @return true if it is still executing, false otherwise
113 */
114 static public boolean processRunning(Process process) {
115 boolean process_running = false;
116
117 try {
118 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
119 }
120 catch(IllegalThreadStateException itse) {
121 process_running = true;
122 }
123 catch(Exception exception) {
124 DebugStream.printStackTrace(exception);
125 }
126 return process_running;
127 }
128
129 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
130 * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
131 * @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).
132 * @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>.
133 * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
134 * @param progress The <i>GShellProgressMonitor</i> associated with this process.
135 * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
136 */
137 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
138 super(name);
139 this.args = args;
140 this.msg_type = msg_type;
141 this.type = type;
142 this.caller = caller;
143 this.progress = progress;
144 this.status = 0;
145 // Lower this jobs priority
146 this.setPriority(Thread.MIN_PRIORITY);
147 listeners = new EventListenerList();
148 listeners.add(GShellListener.class, caller);
149 }
150 /** This method adds another shell listener to this process.
151 * @param listener The new <i>GShellListener</i>.
152 */
153 public void addGShellListener(GShellListener listener) {
154 listeners.add(GShellListener.class, listener);
155 }
156 /** This method removes a certain shell listener from this process.
157 * @param listener The <i>GShellListener</i> to be removed.
158 */
159 /* private void removeGShellListener(GShellListener listener) {
160 listeners.remove(GShellListener.class, listener);
161 } */
162
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
184 private void runRemote(String[] args, BufferedOutputStream bos)
185 {
186 // Make sure the process hasn't been cancelled
187 if (hasSignalledStop()) {
188 return;
189 }
190
191 try {
192 int directory_name_end = args[0].lastIndexOf(File.separator);
193 String script_name = ((directory_name_end != -1) ? args[0].substring(directory_name_end + 1) : args[0]);
194 System.err.println("Script name: " + script_name);
195 String collection_name = args[args.length - 1];
196 System.err.println("Collection name: " + collection_name);
197
198
199 String script_args = "";
200 for (int i = 1; i < (args.length - 1); i++) {
201 // Skip arguments that won't make sense on the server
202 if (args[i].equals("-collectdir") || args[i].equals("-importdir") || args[i].equals("-builddir")) {
203 i++;
204 continue;
205 }
206
207 // Script arguments get changed to CGI arguments
208 if (args[i].startsWith("-")) {
209 script_args += "&" + args[i].substring(1) + "=";
210 if ((i + 1) < (args.length - 1) && !args[i + 1].startsWith("-")) {
211 script_args += URLEncoder.encode(args[i + 1], "UTF-8");
212 i++;
213 }
214 }
215 }
216
217 System.err.println("Script args: " + script_args);
218 buffered_output_stream = bos;
219 String command_output = Gatherer.remoteGreenstoneServer.runScript(collection_name, script_name, script_args, this);
220 status = (command_output.equals("") ? CANCELLED : OK);
221 }
222 catch (Exception exception) {
223 DebugStream.printStackTrace(exception);
224 status = ERROR;
225 }
226 }
227
228
229 private void 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 &&*/ 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
342
343 /** Any threaded class must include this method to allow the thread body to be run. */
344 public void run() {
345 String col_name = args[args.length-1];
346
347 // Determine if the user has asked for an outfile.
348 String out_name = null;
349 BufferedOutputStream bos = null;
350 if(type == IMPORT || type == BUILD || type == SCHEDULE) {
351 if(type == IMPORT) {
352 out_name = (String) Gatherer.c_man.getCollection().import_options.getValue("out");
353 }
354 else if(type == BUILD) {
355 out_name = (String) Gatherer.c_man.getCollection().build_options.getValue("out");
356 }
357 else { // SCHEDULE
358 out_name = (String) Gatherer.c_man.getCollection().schedule_options.getValue("out");
359 }
360 if(out_name != null && out_name.length() > 0) {
361 try {
362 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
363 }
364 catch (Exception error) {
365 DebugStream.printStackTrace(error);
366 }
367 }
368 }
369
370 // Issue a processBegun event
371 //ystem.err.println("\nFiring process begun for " + type + "...");
372 fireProcessBegun(type, status);
373 //ystem.err.println("Done process begun.");
374 if (Gatherer.isGsdlRemote) {
375 runRemote(args,bos);
376 }
377 else {
378 runLocal(args,bos);
379 }
380 //ystem.err.println("Done runLocal().");
381
382 if(status == OK) {
383 if (type == NEW) {
384 if (Gatherer.isGsdlRemote) {
385 Gatherer.remoteGreenstoneServer.downloadCollection(col_name);
386 }
387 }
388 else if(type == IMPORT) {
389
390 // download the archives directory (if gsdl server is remote)
391 if (Gatherer.isGsdlRemote) {
392
393 if (progress!=null) {
394 progress.messageOnProgressBar("Downloading archive data from server");
395 }
396
397 Gatherer.remoteGreenstoneServer.downloadCollectionArchives(col_name);
398
399 if (progress!=null) {
400 progress.messageOnProgressBar("");
401 }
402 }
403
404 // Refresh the DocXMLFileManager
405 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status, null);
406 DocXMLFileManager.clearDocXMLFiles();
407 if (Configuration.fedora_info.isActive()) { // FLI case
408 File collection_export_directory = new File(CollectionManager.getLoadedCollectionExportDirectoryPath());
409 DocXMLFileManager.loadDocXMLFiles(collection_export_directory,"docmets.xml");
410 }
411 else {
412 File collection_archives_directory = new File(CollectionManager.getLoadedCollectionArchivesDirectoryPath());
413 DocXMLFileManager.loadDocXMLFiles(collection_archives_directory,"doc.xml");
414 }
415
416
417 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status, null);
418 }
419
420 else if(type == BUILD) {
421 // download the building directory (if gsdl server is remote)
422 if ((Gatherer.isGsdlRemote) && (!Configuration.fedora_info.isActive())) {
423 if (progress!=null) {
424 progress.messageOnProgressBar("Downloading index data from server");
425 }
426
427 if (!Gatherer.GS3){
428 // Only need to download build.cfg file
429 File build_cfg_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "build.cfg");
430 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
431 }else{
432 // Only need to download buildConfig.xml file
433 File buildConfig_xml_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "buildConfig.xml");
434 Gatherer.remoteGreenstoneServer.downloadCollectionFile(col_name, buildConfig_xml_file);
435 }
436
437 if (progress!=null) {
438 progress.messageOnProgressBar("");
439 }
440 }
441 }
442 else if(type == CDIMAGE) {
443 // download exported files from tmp folder (if gsdl server is remote)
444 if (Gatherer.isGsdlRemote) {
445 if (progress!=null) {
446 progress.messageOnProgressBar("Downloading CD-ROM data from server");
447 }
448
449 // !! TO DO
450
451 if (progress!=null) {
452 progress.messageOnProgressBar("");
453 }
454 }
455 }
456 }
457
458 // We're done.
459 //ystem.err.println("Firing process complete for " + type + "...");
460 fireProcessComplete(type, status);
461 // Close bos
462 if(bos != null) {
463 try {
464 bos.close();
465 bos = null;
466 }
467 catch(Exception error) {
468 DebugStream.printStackTrace(error);
469 }
470 }
471 }
472
473
474 public void fireMessage(String message)
475 {
476 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
477 }
478
479
480 /** Method for firing a message to all interested listeners.
481 * @param type An <strong>int</strong> indicating the process type.
482 * @param message The message as a <strong>String</strong>.
483 * @param status An <strong>int</strong> specifying the current status of the process.
484 */
485 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
486 GShellEvent event = new GShellEvent(this, 0, type, message, status);
487 // 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.
488 ArrayList message_queue = new ArrayList();
489 message_queue.add(event);
490 if(progress != null) {
491 progress.process(message_queue);
492 }
493 for(int j = 0; j < message_queue.size(); j++) {
494 GShellEvent current_event = (GShellEvent) message_queue.get(j);
495 // If the event hasn't been vetoed, pass it on to other listeners
496 if(!current_event.isVetoed()) {
497 Object[] concerned = listeners.getListenerList();
498 for(int i = 0; i < concerned.length ; i++) {
499 if(concerned[i] == GShellListener.class) {
500 ((GShellListener)concerned[i+1]).message(current_event);
501 }
502 }
503 concerned = null;
504 }
505 }
506 // And if we have a buffered output stream from error messages, send the message there
507 if(bos != null) {
508 try {
509 bos.write(message.getBytes(), 0, message.length());
510 }
511 catch(Exception exception) {
512 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
513 DebugStream.printStackTrace(exception);
514 }
515 }
516 message_queue = null;
517 event = null;
518 }
519
520 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
521 * @param type An <strong>int</strong> indicating the process type.
522 * @param status An <strong>int</strong> specifying the current status of the process.
523 */
524 protected void fireProcessBegun(int type, int status) {
525 // Start the progres monitor if available
526 if(progress != null) {
527 //ystem.err.println("About to call progress.start().");
528 progress.start();
529 //ystem.err.println("Called progress.start().");
530 }
531 // Fire an event
532 GShellEvent event = new GShellEvent(this, 0, type, "", status);
533 Object[] concerned = listeners.getListenerList();
534 for(int i = 0; i < concerned.length ; i++) {
535 if(concerned[i] == GShellListener.class) {
536 ((GShellListener)concerned[i+1]).processBegun(event);
537 }
538 }
539 }
540
541
542 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
543 * @param type An <strong>int</strong> indicating the process type.
544 * @param status An <strong>int</strong> specifying the current status of the process.
545 */
546 protected void fireProcessComplete(int type, int status) {
547 // 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).
548 if(progress != null && status != CANCELLED) {
549 progress.stop();
550 }
551
552 // If we were cancelled, and we are lower details modes, fire off one last message.
553 if(status == CANCELLED && Configuration.getMode() <= Configuration.SYSTEMS_MODE) {
554 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
555 Object[] concerned = listeners.getListenerList();
556 for(int i = 0; i < concerned.length ; i++) {
557 if(concerned[i] == GShellListener.class) {
558 ((GShellListener)concerned[i+1]).message(current_event);
559 }
560 }
561 concerned = null;
562 }
563
564 String msg = "";
565 // If we are creating collection and have trouble with permissions, we need more messages
566 if(status == ERROR && type == GShell.NEW){
567 msg = args[args.length-1];
568 }
569 // And firing off an event
570 GShellEvent event = new GShellEvent(this, 0, type, msg, status);
571 Object[] concerned = listeners.getListenerList();
572 for(int i = 0; i < concerned.length ; i++) {
573 if(concerned[i] == GShellListener.class) {
574 ((GShellListener)concerned[i+1]).processComplete(event);
575 }
576 }
577 }
578
579 /** Method to determine if the user, via the progress monitor, has signalled stop.
580 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
581 */
582 public boolean hasSignalledStop() {
583 boolean has_signalled_stop = false;
584 if(progress != null) {
585 has_signalled_stop = progress.hasSignalledStop();
586 }
587 if(has_signalled_stop) {
588 status = CANCELLED;
589 }
590 return has_signalled_stop;
591 }
592
593 /** Converts a type into a text representation.
594 * @param type An <strong>int</strong> which maps to a shell process type.
595 * @return A <strong>String</strong> which is the thread process's text name.
596 */
597 public String typeAsString(int type) {
598 String name = null;
599 switch(type) {
600 case BUILD:
601 name = "buildcol.pl";
602 break;
603 case IMPORT:
604 name = "import.pl";
605 break;
606 case NEW:
607 name = "mkcol.pl";
608 break;
609 case EXPORTAS:
610 name = "export.pl";
611 break;
612 case CDIMAGE:
613 name = "exportcol.pl";
614 break;
615 case CONVERT:
616 name = "convert_coll_from_gs2.pl";
617 break;
618 case EXPLODE:
619 name = "explode_metadata_database.pl";
620 break;
621 case SRCREPLACE: // To work with replace_srcdoc_with_html.pl
622 name = "replace_srcdoc_with_html.pl";
623 break;
624 case SCHEDULE:
625 name = "schedule.pl";
626 break;
627 default:
628 name = "";
629 }
630 return name;
631 }
632}
Note: See TracBrowser for help on using the repository browser.