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

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

Here is the result of sixteen hours work over the weekend. I'm too tired to comment them all separately, but here are some of the highlights:
Rewrote how the 'base on collection' method actually retrieves and updates the collection configuration - ensuring the CDM.CollectionConfiguration class is used instead of the retarded Collection.CollectionConfiguration (which coincidently has had a name change to BasicCollectionConfiguration). Went through code search for places where the two versions had been confused. Rewrote large swathes of GDMDocument so as to differentiate between normal and extracted metadata - an attempt to prevent the snowballing extracted metadata problem. Fixed problem where GLI was correctly recieving the last few lines of an external process. The collection shortname is no longer visible, nor is the confusing double name for metadata elements. Also coloured folders in the trees are kaput. The users email is now saved as part of the GLI configuration and is used as appropriate to fill out collection fields. There are new options on the right click menus over trees to allow the expansion and collapsing of folders. 'Show Files' now shows all types (or at least 6 types) of image properly (arg, the plagues of copy and paste). 'Based On' collections are public, plugin list automatically moves to next entry if plugin removed (I guess we should do the same in every other screen?) and metadata arguments in plugins/classifiers are no longer editable. There are about a dozen other small things, but I can't remember them. Hope I remembered to set all of the files to UNIX line-endings.

  • Property svn:keywords set to Author Date Id Revision
File size: 15.8 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 /** Any threaded class must include this method to allow the thread body to be run. */
145 public void run() {
146 if(progress != null) {
147 progress.start();
148 }
149 // Determine if the user has asked for an outfile.
150 String out_name = null;
151 BufferedOutputStream bos = null;
152 if(type == IMPORT || type == BUILD) {
153 if(type == IMPORT) {
154 out_name = (String) Gatherer.c_man.getCollection().build_options.getImportValue("out");
155 }
156 else {
157 out_name = (String) Gatherer.c_man.getCollection().build_options.getBuildValue("out");
158 }
159 if(out_name != null && out_name.length() > 0) {
160 try {
161 bos = new BufferedOutputStream(new FileOutputStream(new File(out_name), true));
162 }
163 catch (Exception error) {
164 Gatherer.printStackTrace(error);
165 }
166 }
167 }
168 // Issue a processBegun event
169 fireProcessBegun(type, status);
170 try {
171
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 if(progress != null) {
293 progress.parse(eline);
294 }
295 if(bos != null) {
296 try {
297 bos.write(eline.getBytes(), 0, eline.length());
298 }
299 catch(Exception error) {
300 Gatherer.printStackTrace(error);
301 }
302 }
303 fireMessage(type, typeAsString(type) + "> " + eline, status);
304 eline = null;
305 }
306
307 if(stdline_buffer.length() > 0) {
308 String stdline = stdline_buffer.toString();
309 fireMessage(type, typeAsString(type) + "> " + stdline, status);
310 stdline = null;
311 }
312
313 if(!hasSignalledStop()) {
314 // Now display final message based on exit value
315 prcs.waitFor();
316
317 if(prcs.exitValue() == 0) {
318 status = OK;
319 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status);
320 }
321 else {
322 status = ERROR;
323 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status);
324 }
325 }
326 else {
327 // 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!)
328 prcs.getInputStream().close();
329 prcs.getErrorStream().close();
330 prcs.getOutputStream().close();
331 prcs.destroy();
332 status = CANCELLED;
333 }
334 }
335 // Exception
336 catch (Exception error) {
337 Gatherer.printStackTrace(error);
338 status = ERROR;
339 }
340 // If no error occured, and this was an import process we now extract any new metadata from the archive directory.
341 if(status == OK && type == IMPORT) {
342 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Start"), status);
343 new GreenstoneArchiveParser(progress, this);
344 fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Parsing_Metadata_Complete"), status);
345 }
346 // Tidy up.
347 if(progress != null) {
348 progress.stop();
349 }
350 // We're done.
351 fireProcessComplete(type, status);
352 // Close bos
353 if(bos != null) {
354 try {
355 bos.close();
356 bos = null;
357 }
358 catch(Exception error) {
359 Gatherer.printStackTrace(error);
360 }
361 }
362 }
363 /** Method for firing a message to all interested listeners.
364 * @param type An <strong>int</strong> indicating the process type.
365 * @param message The message as a <strong>String</strong>.
366 * @param status An <strong>int</strong> specifying the current status of the process.
367 */
368 public void fireMessage(int type, String message, int status) {
369 GShellEvent event = new GShellEvent(this, 0, type, message, status);
370 Object[] concerned = listeners.getListenerList();
371 for(int i = 0; i < concerned.length ; i++) {
372 if(concerned[i] == GShellListener.class) {
373 ((GShellListener)concerned[i+1]).message(event);
374 }
375 }
376 }
377
378 /** Method for firing a process begun event which is called, strangly enough, when the process begins.
379 * @param type An <strong>int</strong> indicating the process type.
380 * @param status An <strong>int</strong> specifying the current status of the process.
381 */
382 protected void fireProcessBegun(int type, int status) {
383 GShellEvent event = new GShellEvent(this, 0, type, "", status);
384 Object[] concerned = listeners.getListenerList();
385 for(int i = 0; i < concerned.length ; i++) {
386 if(concerned[i] == GShellListener.class) {
387 ((GShellListener)concerned[i+1]).processBegun(event);
388 }
389 }
390 }
391 /** Method for firing a process complete event which is called, no surprise here, when the process ends.
392 * @param type An <strong>int</strong> indicating the process type.
393 * @param status An <strong>int</strong> specifying the current status of the process.
394 */
395 protected void fireProcessComplete(int type, int status) {
396 GShellEvent event = new GShellEvent(this, 0, type, "", status);
397 Object[] concerned = listeners.getListenerList();
398 for(int i = 0; i < concerned.length ; i++) {
399 if(concerned[i] == GShellListener.class) {
400 ((GShellListener)concerned[i+1]).processComplete(event);
401 }
402 }
403 }
404
405 /** Method to determine if the user, via the progress monitor, has signalled stop.
406 * @return A <strong>boolean</strong> indicating if the user wanted to stop.
407 */
408 private boolean hasSignalledStop() {
409 boolean has_signalled_stop = false;
410 if(progress != null) {
411 return progress.hasSignalledStop();
412 }
413 return has_signalled_stop;
414 }
415
416 /** Converts a type into a text representation.
417 * @param type An <strong>int</strong> which maps to a shell process type.
418 * @return A <strong>String</strong> which is the thread process's text name.
419 */
420 public String typeAsString(int type) {
421 String name = null;
422 switch(type) {
423 case BUILD:
424 name = Dictionary.get("GShell.Build");
425 break;
426 case IMPORT:
427 name = Dictionary.get("GShell.Import");
428 break;
429 case NEW:
430 name = Dictionary.get("GShell.New");
431 break;
432 case EXPORT:
433 name= Dictionary.get("GShell.Export");
434 break;
435 default:
436 name = Dictionary.get("GShell.Other");
437 }
438 return name;
439 }
440}
Note: See TracBrowser for help on using the repository browser.