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

Last change on this file since 14975 was 14974, checked in by davidb, 16 years ago

Changes to GLI to support export into Fedora. New utility called flisvn diff gems/MetadataSetManager.java

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