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

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

download 'buildConfig.xml' if Gatherer.GS3

  • Property svn:keywords set to Author Date Id Revision
File size: 20.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.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 = null;
237 if (Utility.isWindows()){
238 prcs = rt.exec(args);
239 }else{
240 prcs = rt.exec(command);
241 }
242
243 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream(), "UTF-8" );
244 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream(), "UTF-8" );
245
246 StringBuffer eline_buffer = new StringBuffer();
247 StringBuffer stdline_buffer = new StringBuffer();
248
249 while(/*type != GShell.NEW &&*/ processRunning(prcs) && !hasSignalledStop()) {
250 // Hopefully this doesn't block if the process is trying to write to STDOUT.
251 if((eisr!=null) && eisr.ready()) {
252 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
253 }
254 // Hopefully this won't block if the process is trying to write to STDERR
255 else if(stdisr.ready()) {
256 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
257 }
258 else {
259 try {
260 sleep(100);
261 }
262 catch(Exception exception) {
263 }
264 }
265 }
266
267 if(!hasSignalledStop()) {
268 // Of course, just because the process is finished doesn't
269 // mean the incoming streams are empty. Unfortunately I've
270 // got no chance of preserving order, so I'll process the
271 // error stream first, then the out stream
272 while(eisr.ready()) {
273 eline_buffer = get_stream_char(eisr,eline_buffer,bos);
274 }
275
276 while(stdisr.ready()) {
277 stdline_buffer = get_stream_char(stdisr,stdline_buffer,bos);
278 }
279
280 // Ensure that any messages still remaining in the string buffers are fired off.
281 if(eline_buffer.length() > 0) {
282 String eline = eline_buffer.toString();
283 //DebugStream.println("Last bit of eline: " + eline);
284 fireMessage(type, typeAsString(type) + "> " + eline, status, bos);
285 eline = null;
286 }
287
288 if(stdline_buffer.length() > 0) {
289 String stdline = stdline_buffer.toString();
290 //DebugStream.println("Last bit of stdline: " + stdline);
291 fireMessage(type, typeAsString(type) + "> " + stdline, status, null);
292 stdline = null;
293 }
294 }
295 else {
296 DebugStream.println("We've been asked to stop.");
297 }
298
299
300 if(!hasSignalledStop()) {
301 // Now display final message based on exit value
302
303 prcs.waitFor();
304
305 if(prcs.exitValue() == 0) {
306 status = OK;
307 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
308 }
309 else {
310 status = ERROR;
311 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
312 }
313
314 eisr.close();
315 stdisr.close();
316 }
317 else {
318 // I need to somehow kill the child process. Unfortunately
319 // Thread.stop() and Process.destroy() both fail to do
320 // this. But now, thankx to the magic of Michaels 'close the
321 // stream suggestion', it works fine (no it doesn't!)
322 prcs.getInputStream().close();
323 prcs.getErrorStream().close();
324 prcs.getOutputStream().close();
325 prcs.destroy();
326 status = CANCELLED;
327 }
328 }
329 // Exception
330 catch (Exception exception) {
331 DebugStream.println("Exception in GShell.runLocal() - unexpected");
332 DebugStream.printStackTrace(exception);
333 status = ERROR;
334 }
335 }
336
337
338
339 /** Any threaded class must include this method to allow the thread body to be run. */
340 public void run() {
341 String col_name = args[args.length-1];
342
343 // Determine if the user has asked for an outfile.
344 String out_name = null;
345 BufferedOutputStream bos = null;
346 if(type == IMPORT || type == BUILD) {
347 if(type == IMPORT) {
348 out_name = (String) Gatherer.c_man.getCollection().import_options.getValue("out");
349 }
350 else {
351 out_name = (String) Gatherer.c_man.getCollection().build_options.getValue("out");
352 }
353 if(out_name != null && out_name.length() > 0) {
354 try {
355 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
356 }
357 catch (Exception error) {
358 DebugStream.printStackTrace(error);
359 }
360 }
361 }
362
363 // Issue a processBegun event
364 //ystem.err.println("\nFiring process begun for " + type + "...");
365 fireProcessBegun(type, status);
366 //ystem.err.println("Done process begun.");
367 if (Gatherer.isGsdlRemote) {
368 runRemote(args,bos);
369 }
370 else {
371 runLocal(args,bos);
372 }
373 //ystem.err.println("Done runLocal().");
374
375 if(status == OK) {
376 if (type == NEW) {
377 if (Gatherer.isGsdlRemote) {
378 RemoteGreenstoneServer.downloadCollection(col_name);
379 }
380 }
381 else if(type == IMPORT) {
382
383 // download the archives directory (if gsdl server is remote)
384 if (Gatherer.isGsdlRemote) {
385
386 if (progress!=null) {
387 progress.messageOnProgressBar("Downloading archive data from server");
388 }
389
390 RemoteGreenstoneServer.downloadCollectionArchives(col_name);
391
392 if (progress!=null) {
393 progress.messageOnProgressBar("");
394 }
395 }
396
397 // Refresh the DocXMLFileManager
398 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status, null);
399 DocXMLFileManager.clearDocXMLFiles();
400 DocXMLFileManager.loadDocXMLFiles(new File(CollectionManager.getLoadedCollectionArchivesDirectoryPath()));
401 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status, null);
402 }
403
404 else if(type == BUILD) {
405 // download the building directory (if gsdl server is remote)
406 if (Gatherer.isGsdlRemote) {
407 if (progress!=null) {
408 progress.messageOnProgressBar("Downloading index data from server");
409 }
410
411 if (!Gatherer.GS3){
412 // Only need to download build.cfg file
413 File build_cfg_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "build.cfg");
414 RemoteGreenstoneServer.downloadCollectionFile(col_name, build_cfg_file);
415 }else{
416 // Only need to download buildConfig.xml file
417 File buildConfig_xml_file = new File(CollectionManager.getLoadedCollectionBuildingDirectoryPath(), "buildConfig.xml");
418 RemoteGreenstoneServer.downloadCollectionFile(col_name, buildConfig_xml_file);
419 }
420
421 if (progress!=null) {
422 progress.messageOnProgressBar("");
423 }
424 }
425 }
426 else if(type == CDIMAGE) {
427 // download exported files from tmp folder (if gsdl server is remote)
428 if (Gatherer.isGsdlRemote) {
429 if (progress!=null) {
430 progress.messageOnProgressBar("Downloading CD-ROM data from server");
431 }
432
433 // !! TO DO
434
435 if (progress!=null) {
436 progress.messageOnProgressBar("");
437 }
438 }
439 }
440 }
441
442 // We're done.
443 //ystem.err.println("Firing process complete for " + type + "...");
444 fireProcessComplete(type, status);
445 // Close bos
446 if(bos != null) {
447 try {
448 bos.close();
449 bos = null;
450 }
451 catch(Exception error) {
452 DebugStream.printStackTrace(error);
453 }
454 }
455 }
456
457
458 public void fireMessage(String message)
459 {
460 fireMessage(type, typeAsString(type) + "> " + message, status, buffered_output_stream);
461 }
462
463
464 /** Method for firing a message to all interested listeners.
465 * @param type An <strong>int</strong> indicating the process type.
466 * @param message The message as a <strong>String</strong>.
467 * @param status An <strong>int</strong> specifying the current status of the process.
468 */
469 public void fireMessage(int type, String message, int status, BufferedOutputStream bos) {
470 GShellEvent event = new GShellEvent(this, 0, type, message, status);
471 // 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.
472 ArrayList message_queue = new ArrayList();
473 message_queue.add(event);
474 if(progress != null) {
475 progress.process(message_queue);
476 }
477 for(int j = 0; j < message_queue.size(); j++) {
478 GShellEvent current_event = (GShellEvent) message_queue.get(j);
479 // If the event hasn't been vetoed, pass it on to other listeners
480 if(!current_event.isVetoed()) {
481 Object[] concerned = listeners.getListenerList();
482 for(int i = 0; i < concerned.length ; i++) {
483 if(concerned[i] == GShellListener.class) {
484 ((GShellListener)concerned[i+1]).message(current_event);
485 }
486 }
487 concerned = null;
488 }
489 }
490 // And if we have a buffered output stream from error messages, send the message there
491 if(bos != null) {
492 try {
493 bos.write(message.getBytes(), 0, message.length());
494 }
495 catch(Exception exception) {
496 DebugStream.println("Exception in GShell.fireMessage() - unexpected");
497 DebugStream.printStackTrace(exception);
498 }
499 }
500 message_queue = null;
501 event = null;
502 }
503
504 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
505 * @param type An <strong>int</strong> indicating the process type.
506 * @param status An <strong>int</strong> specifying the current status of the process.
507 */
508 protected void fireProcessBegun(int type, int status) {
509 // Start the progres monitor if available
510 if(progress != null) {
511 //ystem.err.println("About to call progress.start().");
512 progress.start();
513 //ystem.err.println("Called progress.start().");
514 }
515 // Fire an event
516 GShellEvent event = new GShellEvent(this, 0, type, "", status);
517 Object[] concerned = listeners.getListenerList();
518 for(int i = 0; i < concerned.length ; i++) {
519 if(concerned[i] == GShellListener.class) {
520 ((GShellListener)concerned[i+1]).processBegun(event);
521 }
522 }
523 }
524
525
526 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
527 * @param type An <strong>int</strong> indicating the process type.
528 * @param status An <strong>int</strong> specifying the current status of the process.
529 */
530 protected void fireProcessComplete(int type, int status) {
531 // 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).
532 if(progress != null && status != CANCELLED) {
533 progress.stop();
534 }
535
536 // If we were cancelled, and we are lower details modes, fire off one last message.
537 if(status == CANCELLED && Configuration.getMode() <= Configuration.SYSTEMS_MODE) {
538 GShellEvent current_event = new GShellEvent(this, 0, type, Dictionary.get("GShell.Build.BuildCancelled"), status);
539 Object[] concerned = listeners.getListenerList();
540 for(int i = 0; i < concerned.length ; i++) {
541 if(concerned[i] == GShellListener.class) {
542 ((GShellListener)concerned[i+1]).message(current_event);
543 }
544 }
545 concerned = null;
546 }
547 // And firing off an event
548 GShellEvent event = new GShellEvent(this, 0, type, "", status);
549 Object[] concerned = listeners.getListenerList();
550 for(int i = 0; i < concerned.length ; i++) {
551 if(concerned[i] == GShellListener.class) {
552 ((GShellListener)concerned[i+1]).processComplete(event);
553 }
554 }
555 }
556
557 /** Method to determine if the user, via the progress monitor, has signalled stop.
558 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
559 */
560 public boolean hasSignalledStop() {
561 boolean has_signalled_stop = false;
562 if(progress != null) {
563 has_signalled_stop = progress.hasSignalledStop();
564 }
565 if(has_signalled_stop) {
566 status = CANCELLED;
567 }
568 return has_signalled_stop;
569 }
570
571 /** Converts a type into a text representation.
572 * @param type An <strong>int</strong> which maps to a shell process type.
573 * @return A <strong>String</strong> which is the thread process's text name.
574 */
575 public String typeAsString(int type) {
576 String name = null;
577 switch(type) {
578 case BUILD:
579 name = "buildcol.pl";
580 break;
581 case IMPORT:
582 name = "import.pl";
583 break;
584 case NEW:
585 name = "mkcol.pl";
586 break;
587 case EXPORTAS:
588 name = "export.pl";
589 break;
590 case CDIMAGE:
591 name = "exportcol.pl";
592 break;
593 case CONVERT:
594 name = "convert_coll_from_gs2.pl";
595 break;
596 case EXPLODE:
597 name = "explode_metadata_database.pl";
598 break;
599 default:
600 name = "";
601 }
602 return name;
603 }
604}
Note: See TracBrowser for help on using the repository browser.