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

Last change on this file since 13533 was 13458, checked in by mdewsnip, 18 years ago

Removed an unused function.

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