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

Last change on this file since 14110 was 14110, checked in by qq6, 17 years ago

change rt.exec(args) to rt.exec(command) in runLocal()

  • Property svn:keywords set to Author Date Id Revision
File size: 20.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.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
92 /** Elements in status type enumeration. */
93 static final public int ERROR = 0;
94 static final public int OK = 1;
95 static final public int CANCELLED = 2;
96
97 /** Elements in process type name enumeration. */
98 static public String GSHELL_BUILD = "gshell_build";
99 static public String GSHELL_IMPORT = "gshell_import";
100 static public String GSHELL_NEW = "gshell_new";
101 static public String GSHELL_EXPORTAS = "gshell_exportas";
102 static public String GSHELL_CDIMAGE = "gshell_cdimage";
103 static public String GSHELL_CONVERT = "gshell_convert";
104 static public String GSHELL_EXPLODE = "gshell_explode";
105
106 /** 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
107 * @param process the Process to test
108 * @return true if it is still executing, false otherwise
109 */
110 static public boolean processRunning(Process process) {
111 boolean process_running = false;
112
113 try {
114 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
115 }
116 catch(IllegalThreadStateException itse) {
117 process_running = true;
118 }
119 catch(Exception exception) {
120 DebugStream.printStackTrace(exception);
121 }
122 return process_running;
123 }
124
125 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
126 * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
127 * @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).
128 * @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>.
129 * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
130 * @param progress The <i>GShellProgressMonitor</i> associated with this process.
131 * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
132 */
133 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
134 super(name);
135 this.args = args;
136 this.msg_type = msg_type;
137 this.type = type;
138 this.caller = caller;
139 this.progress = progress;
140 this.status = 0;
141 // Lower this jobs priority
142 this.setPriority(Thread.MIN_PRIORITY);
143 listeners = new EventListenerList();
144 listeners.add(GShellListener.class, caller);
145 }
146 /** This method adds another shell listener to this process.
147 * @param listener The new <i>GShellListener</i>.
148 */
149 public void addGShellListener(GShellListener listener) {
150 listeners.add(GShellListener.class, listener);
151 }
152 /** This method removes a certain shell listener from this process.
153 * @param listener The <i>GShellListener</i> to be removed.
154 */
155 /* private void removeGShellListener(GShellListener listener) {
156 listeners.remove(GShellListener.class, listener);
157 } */
158
159 protected StringBuffer get_stream_char(InputStreamReader isr, StringBuffer line_buffer,
160 BufferedOutputStream bos) throws IOException
161 {
162 int c = isr.read();
163 ///atherer.println("isr: '" + (char) c + "'");
164 if(c == '\n' || c == '\r') {
165 if(line_buffer.length() > 0) {
166 String line = line_buffer.toString();
167 // DebugStream.println("* " + line + " *");
168 fireMessage(type, typeAsString(type) + "> " + line, status, bos);
169 line_buffer = new StringBuffer();
170 }
171 }
172 else {
173 line_buffer.append((char)c);
174 }
175
176 return line_buffer;
177 }
178
179
180 private void runRemote(String[] args, BufferedOutputStream bos)
181 {
182 // Make sure the process hasn't been cancelled
183 if (hasSignalledStop()) {
184 return;
185 }
186
187 try {
188 int directory_name_end = args[0].lastIndexOf(File.separator);
189 String script_name = ((directory_name_end != -1) ? args[0].substring(directory_name_end + 1) : args[0]);
190 System.err.println("Script name: " + script_name);
191 String collection_name = args[args.length - 1];
192 System.err.println("Collection name: " + collection_name);
193
194 String script_args = "";
195 for (int i = 1; i < (args.length - 1); i++) {
196 // Skip arguments that won't make sense on the server
197 if (args[i].equals("-collectdir") || args[i].equals("-importdir") || args[i].equals("-builddir")) {
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 = RemoteGreenstoneServer.runScript(collection_name, script_name, script_args, this);
215 status = (command_output.equals("") ? CANCELLED : OK);
216 }
217 catch (Exception exception) {
218 DebugStream.printStackTrace(exception);
219 status = ERROR;
220 }
221 }
222
223
224 private void runLocal(String[] args, BufferedOutputStream bos)
225 {
226 try {
227 String command = "";
228 for(int i = 0; i < args.length; i++) {
229 command = command + args[i] + " ";
230 }
231
232 ///ystem.err.println("Command: " + command);
233 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
234
235 Runtime rt = Runtime.getRuntime();
236 Process prcs = rt.exec(command);
237
238 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream(), "UTF-8" );
239 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream(), "UTF-8" );
240
241 StringBuffer eline_buffer = new StringBuffer();
242 StringBuffer stdline_buffer = new StringBuffer();
243
244 while(/*type != GShell.NEW &&*/ processRunning(prcs) && !hasSignalledStop()) {
245 // Hopefully this doesn't block if the process is trying to write to STDOUT.
246 if((eisr!=null) && eisr.ready()) {
247 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
248 }
249 // Hopefully this won't block if the process is trying to write to STDERR
250 else if(stdisr.ready()) {
251 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
252 }
253 else {
254 try {
255 sleep(100);
256 }
257 catch(Exception exception) {
258 }
259 }
260 }
261
262 if(!hasSignalledStop()) {
263 // Of course, just because the process is finished doesn't
264 // mean the incoming streams are empty. Unfortunately I've
265 // got no chance of preserving order, so I'll process the
266 // error stream first, then the out stream
267 while(eisr.ready()) {
268 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
269 }
270
271 while(stdisr.ready()) {
272 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
273 }
274
275 // Ensure that any messages still remaining in the string buffers are fired off.
276 if(eline_buffer.length() > 0) {
277 String eline = eline_buffer.toString();
278 //DebugStream.println("Last bit of eline: " + eline);
279 fireMessage(type, typeAsString(type) + "> " + eline, status, bos);
280 eline = null;
281 }
282
283 if(stdline_buffer.length() > 0) {
284 String stdline = stdline_buffer.toString();
285 //DebugStream.println("Last bit of stdline: " + stdline);
286 fireMessage(type, typeAsString(type) + "> " + stdline, status, null);
287 stdline = null;
288 }
289 }
290 else {
291 DebugStream.println("We've been asked to stop.");
292 }
293
294
295 if(!hasSignalledStop()) {
296 // Now display final message based on exit value
297
298 prcs.waitFor();
299
300 if(prcs.exitValue() == 0) {
301 status = OK;
302 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
303 }
304 else {
305 status = ERROR;
306 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
307 }
308
309 eisr.close();
310 stdisr.close();
311 }
312 else {
313 // I need to somehow kill the child process. Unfortunately
314 // Thread.stop() and Process.destroy() both fail to do
315 // this. But now, thankx to the magic of Michaels 'close the
316 // stream suggestion', it works fine (no it doesn't!)
317 prcs.getInputStream().close();
318 prcs.getErrorStream().close();
319 prcs.getOutputStream().close();
320 prcs.destroy();
321 status = CANCELLED;
322 }
323 }
324 // Exception
325 catch (Exception exception) {
326 DebugStream.println("Exception in GShell.runLocal() - unexpected");
327 DebugStream.printStackTrace(exception);
328 status = ERROR;
329 }
330 }
331
332
333
334 /** Any threaded class must include this method to allow the thread body to be run. */
335 public void run() {
336 String col_name = args[args.length-1];
337
338 // Determine if the user has asked for an outfile.
339 String out_name = null;
340 BufferedOutputStream bos = null;
341 if(type == IMPORT || type == BUILD) {
342 if(type == IMPORT) {
343 out_name = (String) Gatherer.c_man.getCollection().import_options.getValue("out");
344 }
345 else {
346 out_name = (String) Gatherer.c_man.getCollection().build_options.getValue("out");
347 }
348 if(out_name != null && out_name.length() > 0) {
349 try {
350 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
351 }
352 catch (Exception error) {
353 DebugStream.printStackTrace(error);
354 }
355 }
356 }
357
358 // Issue a processBegun event
359 //ystem.err.println("\nFiring process begun for " + type + "...");
360 fireProcessBegun(type, status);
361 //ystem.err.println("Done process begun.");
362 if (Gatherer.isGsdlRemote) {
363 runRemote(args,bos);
364 }
365 else {
366 runLocal(args,bos);
367 }
368 //ystem.err.println("Done runLocal().");
369
370 if(status == OK) {
371 if (type == NEW) {
372 if (Gatherer.isGsdlRemote) {
373 RemoteGreenstoneServer.downloadCollection(col_name);
374 }
375 }
376 else if(type == IMPORT) {
377
378 // download the archives directory (if gsdl server is remote)
379 if (Gatherer.isGsdlRemote) {
380
381 if (progress!=null) {
382 progress.messageOnProgressBar("Downloading archive data from server");
383 }
384
385 RemoteGreenstoneServer.downloadCollectionArchives(col_name);
386
387 if (progress!=null) {
388 progress.messageOnProgressBar("");
389 }
390 }
391
392 // Refresh the DocXMLFileManager
393 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status, null);
394 DocXMLFileManager.clearDocXMLFiles();
395 DocXMLFileManager.loadDocXMLFiles(new File(CollectionManager.getLoadedCollectionArchivesDirectoryPath()));
396 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status, null);
397 }
398
399 else if(type == BUILD) {
400 // download the building directory (if gsdl server is remote)
401 if (Gatherer.isGsdlRemote) {
402 if (progress!=null) {
403 progress.messageOnProgressBar("Downloading index data from server");
404 }
405
406 // Only need to download build.cfg file
407 File build_cfg_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "build.cfg");
408 RemoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
409
410 if (progress!=null) {
411 progress.messageOnProgressBar("");
412 }
413 }
414 }
415 else if(type == CDIMAGE) {
416 // download exported files from tmp folder (if gsdl server is remote)
417 if (Gatherer.isGsdlRemote) {
418 if (progress!=null) {
419 progress.messageOnProgressBar("Downloading CD-ROM data from server");
420 }
421
422 // !! TO DO
423
424 if (progress!=null) {
425 progress.messageOnProgressBar("");
426 }
427 }
428 }
429 }
430
431 // We're done.
432 //ystem.err.println("Firing process complete for " + type + "...");
433 fireProcessComplete(type, status);
434 // Close bos
435 if(bos != null) {
436 try {
437 bos.close();
438 bos = null;
439 }
440 catch(Exception error) {
441 DebugStream.printStackTrace(error);
442 }
443 }
444 }
445
446
447 public void fireMessage(String message)
448 {
449 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
450 }
451
452
453 /** Method for firing a message to all interested listeners.
454 * @param type An <strong>int</strong> indicating the process type.
455 * @param message The message as a <strong>String</strong>.
456 * @param status An <strong>int</strong> specifying the current status of the process.
457 */
458 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
459 GShellEvent event = new GShellEvent(this, 0, type, message, status);
460 // 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.
461 ArrayList message_queue = new ArrayList();
462 message_queue.add(event);
463 if(progress != null) {
464 progress.process(message_queue);
465 }
466 for(int j = 0; j < message_queue.size(); j++) {
467 GShellEvent current_event = (GShellEvent) message_queue.get(j);
468 // If the event hasn't been vetoed, pass it on to other listeners
469 if(!current_event.isVetoed()) {
470 Object[] concerned = listeners.getListenerList();
471 for(int i = 0; i < concerned.length ; i++) {
472 if(concerned[i] == GShellListener.class) {
473 ((GShellListener)concerned[i+1]).message(current_event);
474 }
475 }
476 concerned = null;
477 }
478 }
479 // And if we have a buffered output stream from error messages, send the message there
480 if(bos != null) {
481 try {
482 bos.write(message.getBytes(), 0, message.length());
483 }
484 catch(Exception exception) {
485 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
486 DebugStream.printStackTrace(exception);
487 }
488 }
489 message_queue = null;
490 event = null;
491 }
492
493 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
494 * @param type An <strong>int</strong> indicating the process type.
495 * @param status An <strong>int</strong> specifying the current status of the process.
496 */
497 protected void fireProcessBegun(int type, int status) {
498 // Start the progres monitor if available
499 if(progress != null) {
500 //ystem.err.println("About to call progress.start().");
501 progress.start();
502 //ystem.err.println("Called progress.start().");
503 }
504 // Fire an event
505 GShellEvent event = new GShellEvent(this, 0, type, "", status);
506 Object[] concerned = listeners.getListenerList();
507 for(int i = 0; i < concerned.length ; i++) {
508 if(concerned[i] == GShellListener.class) {
509 ((GShellListener)concerned[i+1]).processBegun(event);
510 }
511 }
512 }
513
514
515 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
516 * @param type An <strong>int</strong> indicating the process type.
517 * @param status An <strong>int</strong> specifying the current status of the process.
518 */
519 protected void fireProcessComplete(int type, int status) {
520 // 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).
521 if(progress != null && status != CANCELLED) {
522 progress.stop();
523 }
524
525 // If we were cancelled, and we are lower details modes, fire off one last message.
526 if(status == CANCELLED && Configuration.getMode() <= Configuration.SYSTEMS_MODE) {
527 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
528 Object[] concerned = listeners.getListenerList();
529 for(int i = 0; i < concerned.length ; i++) {
530 if(concerned[i] == GShellListener.class) {
531 ((GShellListener)concerned[i+1]).message(current_event);
532 }
533 }
534 concerned = null;
535 }
536 // And firing off an event
537 GShellEvent event = new GShellEvent(this, 0, type, "", status);
538 Object[] concerned = listeners.getListenerList();
539 for(int i = 0; i < concerned.length ; i++) {
540 if(concerned[i] == GShellListener.class) {
541 ((GShellListener)concerned[i+1]).processComplete(event);
542 }
543 }
544 }
545
546 /** Method to determine if the user, via the progress monitor, has signalled stop.
547 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
548 */
549 public boolean hasSignalledStop() {
550 boolean has_signalled_stop = false;
551 if(progress != null) {
552 has_signalled_stop = progress.hasSignalledStop();
553 }
554 if(has_signalled_stop) {
555 status = CANCELLED;
556 }
557 return has_signalled_stop;
558 }
559
560 /** Converts a type into a text representation.
561 * @param type An <strong>int</strong> which maps to a shell process type.
562 * @return A <strong>String</strong> which is the thread process's text name.
563 */
564 public String typeAsString(int type) {
565 String name = null;
566 switch(type) {
567 case BUILD:
568 name = "buildcol.pl";
569 break;
570 case IMPORT:
571 name = "import.pl";
572 break;
573 case NEW:
574 name = "mkcol.pl";
575 break;
576 case EXPORTAS:
577 name = "export.pl";
578 break;
579 case CDIMAGE:
580 name = "exportcol.pl";
581 break;
582 case CONVERT:
583 name = "convert_coll_from_gs2.pl";
584 break;
585 case EXPLODE:
586 name = "explode_metadata_database.pl";
587 break;
588 default:
589 name = "";
590 }
591 return name;
592 }
593}
Note: See TracBrowser for help on using the repository browser.