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

Last change on this file since 26574 was 26574, checked in by ak19, 11 years ago

Related to commit revision 26567 and 26573. The first mandated the inclusion of the site parameter (and site_name) when building scripts are run over Greenstone 3 collections. The 2nd commit ensures the site param and value are now passed in by GLI to those scripts. This commit avoids including the site arg for the remote case, since RemoteGreenstoneServer.java already automatically adds it in for the remote script commands.

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