001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util.externalprocess;
011:
012: import java.io.InputStream;
013: import java.io.OutputStream;
014:
015: import org.mmbase.util.logging.Logger;
016: import org.mmbase.util.logging.Logging;
017:
018: /**
019: * ProcessClosure handles the reading of the stdout and stderr of a external
020: * process. A reader can block the calling thread until it is finished reading
021: * (readBlocking). Or the reader can immidiately return when reading of the
022: * stdout and stderr begins (readNonBlocking).
023: *
024: * ProcessClosure handles the writing of the stdin of a external process. A
025: * writer can block the calling thread until it is finished writing to the stdin
026: * and reading the stdout and stderr (writeBlocking). Or the writer can
027: * imidiately return when writing to stdin and reading of the stdout and sterr
028: * begins (writeNonBlocking)
029: *
030: * @author Nico Klasens (Finalist IT Group)
031: * @version $Id: ProcessClosure.java,v 1.4 2006/04/18 13:11:06 michiel Exp $
032: * @since MMBase-1.6
033: */
034: public class ProcessClosure {
035:
036: private static final Logger log = Logging
037: .getLoggerInstance(ProcessClosure.class);
038:
039: /**
040: * the name of the process closure
041: */
042: protected String name;
043:
044: /**
045: * the process object representing the external process
046: */
047: protected Process process;
048:
049: /**
050: * The stream where data is read from to pipe it to stdin
051: */
052: protected InputStream input;
053: /**
054: * The stream where data is written to when piped from stdout
055: */
056: protected OutputStream output;
057: /**
058: * The stream where data is written to when piped from stderr
059: */
060: protected OutputStream error;
061:
062: /**
063: * Thread for copying bytes from input to stdin
064: */
065: protected StreamCopyThread inputWriter;
066: /**
067: * Thread for copying bytes from stdout to output
068: */
069: protected StreamCopyThread outputReader;
070: /**
071: * Thread for copying bytes from stderr to error
072: */
073: protected StreamCopyThread errorReader;
074:
075: /**
076: * Creates a process reader
077: * .
078: * @param name the name of the reader
079: * @param inputStream process stdin is read from this stream. Can be
080: * <code>null</code>, if not interested in writing the input
081: * @param outputStream process stdout is written to this stream. Can be
082: * <code>null</code>, if not interested in reading the output
083: * @param errorStream porcess stderr is written to this stream. Can be
084: * <code>null</code>, if not interested in reading the output
085: */
086: public ProcessClosure(String name, Process process,
087: InputStream inputStream, OutputStream outputStream,
088: OutputStream errorStream) {
089: this .name = name;
090: this .process = process;
091: this .input = inputStream;
092: this .output = outputStream;
093: this .error = errorStream;
094: }
095:
096: /**
097: * read data from the external process without blocking the calling thread
098: */
099: public void readNonBlocking() {
100: log.debug(name + " read Non Blocking");
101:
102: ThreadGroup group = new ThreadGroup(name + " ThreadGroup");
103:
104: //Reading both stdout and stderr is required to prevent deadlocks from
105: //the external process.
106:
107: //External process Streams are seen from the point of view of the java process
108: InputStream stdout = process.getInputStream();
109: InputStream stderr = process.getErrorStream();
110:
111: outputReader = new StreamCopyThread(group, name
112: + "OutputReader", stdout, output, false);
113: errorReader = new StreamCopyThread(group, name + "ErrorReader",
114: stderr, error, false);
115:
116: outputReader.start();
117: errorReader.start();
118: }
119:
120: /**
121: * write data to the external process without blocking the calling thread
122: */
123: public void writeNonBlocking() {
124: log.debug(name + " write Non Blocking");
125: //External process Streams are seen from the point of view of the java process
126: if (input != null) {
127: OutputStream stdin = process.getOutputStream();
128:
129: inputWriter = new StreamCopyThread(name + "InputWriter",
130: input, stdin, true);
131:
132: inputWriter.start();
133: }
134: }
135:
136: /**
137: * read data from the external process and block the calling thread until
138: * reading is finished
139: */
140: public void readBlocking() {
141: log.debug(name + " read Blocking");
142: readNonBlocking();
143:
144: log.debug(name + " wait for process");
145: waitForProcess();
146: waitForReaders();
147:
148: // it seems that thread termination and stream closing is working without
149: // any help
150: process = null;
151: outputReader = null;
152: errorReader = null;
153: log.debug(name + " read done");
154: }
155:
156: /**
157: * write data to the external process and block the calling thread until
158: * writing and reading is finished
159: */
160: public void writeBlocking() {
161: log.debug(name + " write Blocking");
162:
163: //Reading both stdout and stderr is required to prevent deadlocks from
164: //the external process.
165: readNonBlocking();
166: writeNonBlocking();
167:
168: log.debug(name + " wait for process");
169: waitForWriter();
170: waitForProcess();
171: waitForReaders();
172:
173: // it seems that thread termination and stream closing is working without
174: // any help
175: process = null;
176: outputReader = null;
177: errorReader = null;
178: inputWriter = null;
179: log.debug(name + " write done");
180: }
181:
182: /**
183: * wait for the external process.to end
184: */
185: protected void waitForProcess() {
186: boolean finished = false;
187: while (!finished) {
188: try {
189: process.waitFor();
190: } catch (InterruptedException e) {
191: //System.err.println("exception " +e);
192: }
193: try {
194: process.exitValue();
195: finished = true;
196: } catch (IllegalThreadStateException e) {
197: //System.err.println("exception " +e);
198: }
199: }
200: }
201:
202: /**
203: * wait for the reading threads to finish copying
204: */
205: protected void waitForReaders() {
206: // double-check using output threads
207: if (!outputReader.finished()) {
208: outputReader.waitFor();
209: }
210:
211: if (!errorReader.finished()) {
212: errorReader.waitFor();
213: }
214: }
215:
216: /**
217: * wait for the writing thread to finish copying
218: */
219: protected void waitForWriter() {
220: // double-check using input threads
221: if (!inputWriter.finished()) {
222: inputWriter.waitFor();
223: }
224: }
225:
226: /**
227: * Process closure is alive when the external process and writer/reader
228: * threads are still busy
229: * @return <code>true</code> if is alive and <code>false</code> otherwise
230: */
231: public boolean isAlive() {
232: if (process != null) {
233: if (outputReader.isAlive() || errorReader.isAlive()) {
234: return true;
235: } else {
236: process = null;
237: outputReader = null;
238: errorReader = null;
239: }
240: }
241: return false;
242: }
243:
244: /**
245: * Forces the termination of the launched process
246: */
247: public void terminate() {
248: if (process != null) {
249: process.destroy();
250: process = null;
251: }
252: }
253:
254: }
|