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

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

Removed debug comments as it seems my latest change did the trick - looking for output on STDERR while process is writing to STDOUT no longer causes GLI to hang (due to subtle deadlock)

  • Property svn:keywords set to Author Date Id Revision
File size: 14.2 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 /** Element in process type name enumeration. */
84 static public String GSHELL_BUILD = "gshell_build";
85 /** Element in process type name enumeration. */
86 static public String GSHELL_IMPORT = "gshell_import";
87 /** Element in process type name enumeration. */
88 static public String GSHELL_NEW = "gshell_new";
89 /** Element in process type name enumeration */
90 static public String GSHELL_EXPORT = "gshell_export";
91
92 /** 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
93 * @param process the Process to test
94 * @return true if it is still executing, false otherwise
95 */
96 static public boolean processRunning(Process process) {
97 boolean process_running = false;
98 try {
99 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
100 }
101 catch(IllegalThreadStateException itse) {
102 process_running = true;
103 }
104 catch(Exception exception) {
105 Gatherer.printStackTrace(exception);
106 }
107 return process_running;
108 }
109
110 /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
111 * @param args A <strong>String[]</strong> containing the arguments to the process thread, including the name of the executable.
112 * @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).
113 * @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>.
114 * @param caller The default <i>GShellListener</i> that is interested in the progress of this process.
115 * @param progress The <i>GShellProgressMonitor</i> associated with this process.
116 * @param name A <strong>String</strong> identifier given to the process, for convience and debug reasons.
117 */
118 public GShell(String args[], int type, int msg_type, GShellListener caller, GShellProgressMonitor progress, String name) {
119 super(name);
120 this.args = args;
121 this.msg_type = msg_type;
122 this.type = type;
123 this.caller = caller;
124 this.progress = progress;
125 this.status = 0;
126 // Lower this jobs priority
127 this.setPriority(Thread.MIN_PRIORITY);
128 listeners = new EventListenerList();
129 listeners.add(GShellListener.class, caller);
130 }
131 /** This method adds another shell listener to this process.
132 * @param listener The new <i>GShellListener</i>.
133 */
134 public void addGShellListener(GShellListener listener) {
135 listeners.add(GShellListener.class, listener);
136 }
137 /** This method removes a certain shell listener from this process.
138 * @param listener The <i>GShellListener</i> to be removed.
139 */
140 /* private void removeGShellListener(GShellListener listener) {
141 listeners.remove(GShellListener.class, listener);
142 } */
143 /** Any threaded class must include this method to allow the thread body to be run. */
144 public void run() {
145 if(progress != null) {
146 progress.start();
147 }
148 // Determine if the user has asked for an outfile.
149 String out_name = null;
150 BufferedOutputStream bos = null;
151 if(type == IMPORT || type == BUILD) {
152 if(type == IMPORT) {
153 out_name = (String) Gatherer.c_man.getCollection().build_options.getImportValue("out");
154 }
155 else {
156 out_name = (String) Gatherer.c_man.getCollection().build_options.getBuildValue("out");
157 }
158 if(out_name != null && out_name.length() > 0) {
159 try {
160 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
161 }
162 catch (Exception error) {
163 Gatherer.printStackTrace(error);
164 }
165 }
166 }
167 // Issue a processBegun event
168 fireProcessBegun(type, status);
169 try {
170
171 String command = "";
172 for(int i = 0; i < args.length; i++) {
173 command = command + args[i] + " ";
174 }
175 ///ystem.err.println("Command: " + command);
176 fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status);
177
178 Runtime rt = Runtime.getRuntime();
179 Process prcs = rt.exec(args);
180 InputStreamReader eisr = new InputStreamReader( prcs.getErrorStream() );
181 InputStreamReader stdisr = new InputStreamReader( prcs.getInputStream() );
182 //BufferedReader ebr = new BufferedReader( eisr );
183 //BufferedReader stdinbr = new BufferedReader( stdinisr );
184 // Captures the std err of a program and pipes it into std in of java
185
186 StringBuffer eline_buffer = new StringBuffer();
187 StringBuffer stdline_buffer = new StringBuffer();
188 while(processRunning(prcs) && !hasSignalledStop()) {
189 // Hopefully this doesn't block if the process is trying to write to STDOUT.
190 if(eisr.ready()) {
191 int c = eisr.read();
192 if(c == '\n' || c == '\r') {
193 if(eline_buffer.length() > 0) {
194 String eline = eline_buffer.toString();
195 ///ystem.err.print("*");
196 if(progress != null) {
197 progress.parse(eline);
198 }
199 if(bos != null) {
200 try {
201 bos.write(eline.getBytes(), 0, eline.length());
202 }
203 catch(Exception error) {
204 Gatherer.printStackTrace(error);
205 }
206 }
207 fireMessage(type, typeAsString(type) + "> " + eline, status);
208 eline = null;
209 eline_buffer = new StringBuffer();
210 }
211 }
212 else {
213 eline_buffer.append((char)c);
214 }
215 }
216 // Hopefully this won't block if the process is trying to write to STDERR
217 if(stdisr.ready()) {
218 int c = stdisr.read();
219 if(c == '\n' || c == '\r') {
220 if(stdline_buffer.length() > 0) {
221 String stdline = stdline_buffer.toString();
222 ///ystem.err.print("+");
223 fireMessage(type, typeAsString(type) + "> " + stdline, status);
224 stdline = null;
225 stdline_buffer = new StringBuffer();
226 }
227 }
228 else {
229 stdline_buffer.append((char)c);
230 }
231 }
232 }
233
234 /** This causes Phind to deadlock
235 String eline = null;
236 String stdinline = null;
237 while ((( eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !hasSignalledStop()) {
238 if(eline != null) {
239 System.err.print("*");
240 if(progress != null) {
241 progress.parse(eline);
242 }
243 if(bos != null) {
244 try {
245 bos.write(eline.getBytes(), 0, eline.length());
246 }
247 catch(Exception error) {
248 Gatherer.printStackTrace(error);
249 }
250 }
251 fireMessage(type, typeAsString(type) + "> " + eline, status);
252 }
253 if(stdinline != null) {
254 System.err.print("+");
255 fireMessage(type, typeAsString(type) + "> " + stdinline, status);
256 }
257 }
258 */
259
260 if(!hasSignalledStop()) {
261 // Now display final message based on exit value
262 prcs.waitFor();
263
264 if(prcs.exitValue() == 0) {
265 status = OK;
266 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status);
267 }
268 else {
269 status = ERROR;
270 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status);
271 }
272 }
273 else {
274 // 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!)
275 prcs.getInputStream().close();
276 prcs.getErrorStream().close();
277 prcs.getOutputStream().close();
278 prcs.destroy();
279 status = ERROR;
280 }
281 }
282 // Exception
283 catch (Exception error) {
284 Gatherer.printStackTrace(error);
285 status = ERROR;
286 }
287 // If no error occured, and this was an import process we now extract any new metadata from the archive directory.
288 if(status == OK && type == IMPORT) {
289 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status);
290 new GreenstoneArchiveParser(progress, this);
291 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status);
292 }
293 // Tidy up.
294 if(progress != null) {
295 progress.stop();
296 }
297 // We're done.
298 fireProcessComplete(type, status);
299 // Close bos
300 if(bos != null) {
301 try {
302 bos.close();
303 bos = null;
304 }
305 catch(Exception error) {
306 Gatherer.printStackTrace(error);
307 }
308 }
309 }
310 /** Method for firing a message to all interested listeners.
311 * @param type An <strong>int</strong> indicating the process type.
312 * @param message The message as a <strong>String</strong>.
313 * @param status An <strong>int</strong> specifying the current status of the process.
314 */
315 public void fireMessage(int type, String message, int status) {
316 GShellEvent event = new GShellEvent(this, 0, type, message, status);
317 Object[] concerned = listeners.getListenerList();
318 for(int i = 0; i < concerned.length ; i++) {
319 if(concerned[i] == GShellListener.class) {
320 ((GShellListener)concerned[i+1]).message(event);
321 }
322 }
323 }
324
325 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
326 * @param type An <strong>int</strong> indicating the process type.
327 * @param status An <strong>int</strong> specifying the current status of the process.
328 */
329 protected void fireProcessBegun(int type, int status) {
330 GShellEvent event = new GShellEvent(this, 0, type, "", status);
331 Object[] concerned = listeners.getListenerList();
332 for(int i = 0; i < concerned.length ; i++) {
333 if(concerned[i] == GShellListener.class) {
334 ((GShellListener)concerned[i+1]).processBegun(event);
335 }
336 }
337 }
338 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
339 * @param type An <strong>int</strong> indicating the process type.
340 * @param status An <strong>int</strong> specifying the current status of the process.
341 */
342 protected void fireProcessComplete(int type, int status) {
343 GShellEvent event = new GShellEvent(this, 0, type, "", status);
344 Object[] concerned = listeners.getListenerList();
345 for(int i = 0; i < concerned.length ; i++) {
346 if(concerned[i] == GShellListener.class) {
347 ((GShellListener)concerned[i+1]).processComplete(event);
348 }
349 }
350 }
351
352 /** Method to determine if the user, via the progress monitor, has signalled stop.
353 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
354 */
355 private boolean hasSignalledStop() {
356 boolean has_signalled_stop = false;
357 if(progress != null) {
358 return progress.hasSignalledStop();
359 }
360 return has_signalled_stop;
361 }
362
363 /** Converts a type into a text representation.
364 * @param type An <strong>int</strong> which maps to a shell process type.
365 * @return A <strong>String</strong> which is the thread process's text name.
366 */
367 public String typeAsString(int type) {
368 String name = null;
369 switch(type) {
370 case BUILD:
371 name = Dictionary.get("GShell.Build");
372 break;
373 case IMPORT:
374 name = Dictionary.get("GShell.Import");
375 break;
376 case NEW:
377 name = Dictionary.get("GShell.New");
378 break;
379 case EXPORT:
380 name= Dictionary.get("GShell.Export");
381 break;
382 default:
383 name = Dictionary.get("GShell.Other");
384 }
385 return name;
386 }
387}
Note: See TracBrowser for help on using the repository browser.