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

Last change on this file since 6182 was 6182, checked in by jmt12, 20 years ago

Added then removed debug statements

  • Property svn:keywords set to Author Date Id Revision
File size: 15.9 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 javax.swing.*;
41import javax.swing.event.*;
42import javax.swing.tree.*;
43import org.greenstone.gatherer.Dictionary;
44import org.greenstone.gatherer.Gatherer;
45import org.greenstone.gatherer.msm.GreenstoneArchiveParser;
46import org.greenstone.gatherer.shell.GShellListener;
47import org.greenstone.gatherer.shell.GShellProgressMonitor;
48
49/** 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.
50 */
51public class GShell
52 extends Thread {
53 /** A flag used to determine if this process has been asked to cancel. */
54 private boolean cancel = false;
55 /** The list of listeners associated with this class. */
56 private EventListenerList listeners = null;
57 /** The current status of this shell process. */
58 private int status = -1;
59 /** The type of message being sent. */
60 private int msg_type = -1;
61 /** The type of shell process. */
62 private int type = -1;
63 /** The caller of this process, and thus the class most interested in messages. */
64 private GShellListener caller = null;
65 /** The progress monitor associated with this process. */
66 private GShellProgressMonitor progress = null;
67 /** Arguments to be given to the process (including the executable you are calling. */
68 private String args[] = null;
69 /** Element in process type enumeration. */
70 static final public int BUILD = 0;
71 /** Element in process type enumeration. */
72 static final public int IMPORT = 1;
73 /** Element in process type enumeration. */
74 static final public int NEW = 2;
75 /** Element in process type enumeration. */
76 static final public int EXPORT = 3;
77 /** Element in process type enumeration. */
78 static final public int OTHER = 4;
79 /** Element in status type enumeration. */
80 static final public int ERROR = 0;
81 /** Element in status type enumeration. */
82 static final public int OK = 1;
83 static final public int CANCELLED = 2;
84 /** Element in process type name enumeration. */
85 static public String GSHELL_BUILD = "gshell_build";
86 /** Element in process type name enumeration. */
87 static public String GSHELL_IMPORT = "gshell_import";
88 /** Element in process type name enumeration. */
89 static public String GSHELL_NEW = "gshell_new";
90 /** Element in process type name enumeration */
91 static public String GSHELL_EXPORT = "gshell_export";
92
93 /** 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
94 * @param process the Process to test
95 * @return true if it is still executing, false otherwise
96 */
97 static public boolean processRunning(Process process) {
98 boolean process_running = false;
99 try {
100 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
101 }
102 catch(IllegalThreadStateException itse) {
103 process_running = true;
104 }
105 catch(Exception exception) {
106 Gatherer.printStackTrace(exception);
107 }
108 return process_running;
109 }
110
111 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
112 * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
113 * @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).
114 * @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>.
115 * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
116 * @param progress The <i>GShellProgressMonitor</i> associated with this process.
117 * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
118 */
119 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
120 super(name);
121 this.args = args;
122 this.msg_type = msg_type;
123 this.type = type;
124 this.caller = caller;
125 this.progress = progress;
126 this.status = 0;
127 // Lower this jobs priority
128 this.setPriority(Thread.MIN_PRIORITY);
129 listeners = new EventListenerList();
130 listeners.add(GShellListener.class, caller);
131 }
132 /** This method adds another shell listener to this process.
133 * @param listener The new <i>GShellListener</i>.
134 */
135 public void addGShellListener(GShellListener listener) {
136 listeners.add(GShellListener.class, listener);
137 }
138 /** This method removes a certain shell listener from this process.
139 * @param listener The <i>GShellListener</i> to be removed.
140 */
141 /* private void removeGShellListener(GShellListener listener) {
142 listeners.remove(GShellListener.class, listener);
143 } */
144
145 /** Any threaded class must include this method to allow the thread body to be run. */
146 public void run() {
147 if(progress != null) {
148 progress.start();
149 }
150 // Determine if the user has asked for an outfile.
151 String out_name = null;
152 BufferedOutputStream bos = null;
153 if(type == IMPORT || type == BUILD) {
154 if(type == IMPORT) {
155 out_name = (String) Gatherer.c_man.getCollection().build_options.getImportValue("out");
156 }
157 else {
158 out_name = (String) Gatherer.c_man.getCollection().build_options.getBuildValue("out");
159 }
160 if(out_name != null && out_name.length() > 0) {
161 try {
162 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
163 }
164 catch (Exception error) {
165 Gatherer.printStackTrace(error);
166 }
167 }
168 }
169 // Issue a processBegun event
170 fireProcessBegun(type, status);
171 try {
172 String command = "";
173 for(int i = 0; i < args.length; i++) {
174 command = command + args[i] + " ";
175 }
176 ///ystem.err.println("Command: " + command);
177 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status);
178
179 Runtime rt = Runtime.getRuntime();
180 Process prcs = rt.exec(args);
181 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream() );
182 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream() );
183 //BufferedReader ebr = new BufferedReader( eisr );
184 //BufferedReader stdinbr = new BufferedReader( stdinisr );
185 // Captures the std err of a program and pipes it into std in of java
186
187 StringBuffer eline_buffer = new StringBuffer();
188 StringBuffer stdline_buffer = new StringBuffer();
189 while(type != GShell.NEW && processRunning(prcs) && !hasSignalledStop()) {
190 // Hopefully this doesn't block if the process is trying to write to STDOUT.
191 if(eisr.ready()) {
192 int c = eisr.read();
193 ///atherer.println("eisr: '" + (char) c + "'");
194 if(c == '\n' || c == '\r') {
195 if(eline_buffer.length() > 0) {
196 String eline = eline_buffer.toString();
197 ///atherer.println("* " + eline + " *");
198 if(progress != null) {
199 progress.parse(eline);
200 }
201 if(bos != null) {
202 try {
203 bos.write(eline.getBytes(), 0, eline.length());
204 }
205 catch(Exception error) {
206 Gatherer.printStackTrace(error);
207 }
208 }
209 fireMessage(type, typeAsString(type) + "> " + eline, status);
210 eline = null;
211 eline_buffer = new StringBuffer();
212 }
213 }
214 else {
215 eline_buffer.append((char)c);
216 }
217 }
218 // Hopefully this won't block if the process is trying to write to STDERR
219 else if(stdisr.ready()) {
220 int c = stdisr.read();
221 ///atherer.println("eisr: '" + (char) c + "'");
222 if(c == '\n' || c == '\r') {
223 if(stdline_buffer.length() > 0) {
224 String stdline = stdline_buffer.toString();
225 ///atherer.println("+ " + stdline + " +");
226 fireMessage(type, typeAsString(type) + "> " + stdline, status);
227 stdline = null;
228 stdline_buffer = new StringBuffer();
229 }
230 }
231 else {
232 stdline_buffer.append((char)c);
233 }
234 }
235 else {
236 try {
237 sleep(100);
238 }
239 catch(Exception exception) {
240 }
241 }
242 }
243
244 // Of course, just because the process is finished doesn't mean the incoming streams are empty. Unfortunately I've got no chance of preserving order, so I'll process the error stream first, then the out stream
245 while(eisr.ready()) {
246 int c = eisr.read();
247 ///atherer.println("eisr: '" + (char) c + "'");
248 if(c == '\n' || c == '\r') {
249 if(eline_buffer.length() > 0) {
250 String eline = eline_buffer.toString();
251 ///atherer.println("* " + eline + " *");
252 if(progress != null) {
253 progress.parse(eline);
254 }
255 if(bos != null) {
256 try {
257 bos.write(eline.getBytes(), 0, eline.length());
258 }
259 catch(Exception error) {
260 Gatherer.printStackTrace(error);
261 }
262 }
263 fireMessage(type, typeAsString(type) + "> " + eline, status);
264 eline = null;
265 eline_buffer = new StringBuffer();
266 }
267 }
268 else {
269 eline_buffer.append((char)c);
270 }
271 }
272 while(stdisr.ready()) {
273 int c = stdisr.read();
274 ///atherer.println("eisr: '" + (char) c + "'");
275 if(c == '\n' || c == '\r') {
276 if(stdline_buffer.length() > 0) {
277 String stdline = stdline_buffer.toString();
278 ///atherer.println("+ " + stdline + " +");
279 fireMessage(type, typeAsString(type) + "> " + stdline, status);
280 stdline = null;
281 stdline_buffer = new StringBuffer();
282 }
283 }
284 else {
285 stdline_buffer.append((char)c);
286 }
287 }
288
289 // Ensure that any messages still remaining in the string buffers are fired off.
290 if(eline_buffer.length() > 0) {
291 String eline = eline_buffer.toString();
292 ///atherer.println("Last bit of eline: " + eline);
293 if(progress != null) {
294 progress.parse(eline);
295 }
296 if(bos != null) {
297 try {
298 bos.write(eline.getBytes(), 0, eline.length());
299 }
300 catch(Exception error) {
301 Gatherer.printStackTrace(error);
302 }
303 }
304 fireMessage(type, typeAsString(type) + "> " + eline, status);
305 eline = null;
306 }
307
308 if(stdline_buffer.length() > 0) {
309 String stdline = stdline_buffer.toString();
310 ///atherer.println("Last bit of stdline: " + stdline);
311 fireMessage(type, typeAsString(type) + "> " + stdline, status);
312 stdline = null;
313 }
314
315 if(!hasSignalledStop()) {
316 // Now display final message based on exit value
317 prcs.waitFor();
318
319 if(prcs.exitValue() == 0) {
320 status = OK;
321 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status);
322 }
323 else {
324 status = ERROR;
325 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status);
326 }
327 }
328 else {
329 // I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine (no it doesn't!)
330 prcs.getInputStream().close();
331 prcs.getErrorStream().close();
332 prcs.getOutputStream().close();
333 prcs.destroy();
334 status = CANCELLED;
335 }
336 }
337 // Exception
338 catch (Exception error) {
339 Gatherer.printStackTrace(error);
340 status = ERROR;
341 }
342 // If no error occured, and this was an import process we now extract any new metadata from the archive directory.
343 if(status == OK && type == IMPORT) {
344 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status);
345 new GreenstoneArchiveParser(progress, this);
346 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status);
347 }
348 // Tidy up.
349 if(progress != null) {
350 progress.stop();
351 }
352 // We're done.
353 fireProcessComplete(type, status);
354 // Close bos
355 if(bos != null) {
356 try {
357 bos.close();
358 bos = null;
359 }
360 catch(Exception error) {
361 Gatherer.printStackTrace(error);
362 }
363 }
364 }
365 /** Method for firing a message to all interested listeners.
366 * @param type An <strong>int</strong> indicating the process type.
367 * @param message The message as a <strong>String</strong>.
368 * @param status An <strong>int</strong> specifying the current status of the process.
369 */
370 public void fireMessage(int type, String message, int status) {
371 GShellEvent event = new GShellEvent(this, 0, type, message, status);
372 Object[] concerned = listeners.getListenerList();
373 for(int i = 0; i < concerned.length ; i++) {
374 if(concerned[i] == GShellListener.class) {
375 ((GShellListener)concerned[i+1]).message(event);
376 }
377 }
378 }
379
380 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
381 * @param type An <strong>int</strong> indicating the process type.
382 * @param status An <strong>int</strong> specifying the current status of the process.
383 */
384 protected void fireProcessBegun(int type, int status) {
385 GShellEvent event = new GShellEvent(this, 0, type, "", status);
386 Object[] concerned = listeners.getListenerList();
387 for(int i = 0; i < concerned.length ; i++) {
388 if(concerned[i] == GShellListener.class) {
389 ((GShellListener)concerned[i+1]).processBegun(event);
390 }
391 }
392 }
393 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
394 * @param type An <strong>int</strong> indicating the process type.
395 * @param status An <strong>int</strong> specifying the current status of the process.
396 */
397 protected void fireProcessComplete(int type, int status) {
398 GShellEvent event = new GShellEvent(this, 0, type, "", status);
399 Object[] concerned = listeners.getListenerList();
400 for(int i = 0; i < concerned.length ; i++) {
401 if(concerned[i] == GShellListener.class) {
402 ((GShellListener)concerned[i+1]).processComplete(event);
403 }
404 }
405 }
406
407 /** Method to determine if the user, via the progress monitor, has signalled stop.
408 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
409 */
410 private boolean hasSignalledStop() {
411 boolean has_signalled_stop = false;
412 if(progress != null) {
413 return progress.hasSignalledStop();
414 }
415 return has_signalled_stop;
416 }
417
418 /** Converts a type into a text representation.
419 * @param type An <strong>int</strong> which maps to a shell process type.
420 * @return A <strong>String</strong> which is the thread process's text name.
421 */
422 public String typeAsString(int type) {
423 String name = null;
424 switch(type) {
425 case BUILD:
426 name = Dictionary.get("GShell.Build");
427 break;
428 case IMPORT:
429 name = Dictionary.get("GShell.Import");
430 break;
431 case NEW:
432 name = Dictionary.get("GShell.New");
433 break;
434 case EXPORT:
435 name= Dictionary.get("GShell.Export");
436 break;
437 default:
438 name = Dictionary.get("GShell.Other");
439 }
440 return name;
441 }
442}
Note: See TracBrowser for help on using the repository browser.