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

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