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.IOException;
013: import java.io.InputStream;
014: import java.io.OutputStream;
015: import java.io.PipedInputStream;
016: import java.io.PipedOutputStream;
017:
018: import org.mmbase.util.logging.Logger;
019: import org.mmbase.util.logging.Logging;
020:
021: /**
022: * The command launcher provides a way to comunicate with a external process
023: *
024: * @author Nico Klasens (Finalist IT Group)
025: * @version $Id:
026: * @since MMBase-1.6
027: */
028: public class CommandLauncher {
029:
030: /** MMBase logging system */
031: private static final Logger log = Logging
032: .getLoggerInstance(CommandLauncher.class);
033:
034: /**
035: * Default buffer size.
036: */
037: private static final int BUFFER_SIZE = 1024;
038:
039: /**
040: * The number of milliseconds to pause between polling.
041: */
042: protected static final long DELAY = 50L;
043:
044: /**
045: * Counts how many comands are launched
046: * Also used for () identification
047: */
048: private static int counter = 0;
049:
050: /**
051: * The process object representing the external process
052: */
053: protected Process process;
054:
055: /**
056: * Command and arguments
057: */
058: protected String[] commandArgs;
059:
060: /**
061: * The internal name of the external process
062: */
063: protected String name = "";
064:
065: /**
066: * System line separator
067: */
068: private String lineSeparator;
069:
070: /**
071: * Creates a new launcher
072: * Fills in stderr and stdout output to the given streams.
073: * Streams can be set to <code>null</code>, if output not required
074: *
075: * @param name internal name of the external process
076: */
077: public CommandLauncher(String name) {
078: process = null;
079: this .name = counter++ + " " + name;
080: lineSeparator = System.getProperty("line.separator", "\n");
081: }
082:
083: /**
084: * get CommandArgs.
085: * @return String[]
086: */
087: public String[] getCommandArgs() {
088: return commandArgs;
089: }
090:
091: /**
092: * Constructs a command array that will be passed to the process
093: *
094: * @param command path of comand
095: * @param commandArgs arguments after the command
096: */
097: protected String[] constructCommandArray(String command,
098: String[] commandArgs) {
099:
100: String[] args = new String[1 + commandArgs.length];
101: args[0] = command;
102: System.arraycopy(commandArgs, 0, args, 1, commandArgs.length);
103: return args;
104: }
105:
106: /**
107: * Execute a command
108: *
109: * @param command command
110: * @throws IOException if an I/O error occurs
111: */
112: public void execute(String command) throws ProcessException {
113:
114: if (log.isDebugEnabled()) {
115: printCommandLine(new String[] { command });
116: }
117: try {
118: process = ProcessFactory.getFactory().exec(command);
119: } catch (IOException e) {
120: throw new ProcessException("An I/O error occured: "
121: + e.getMessage());
122: } catch (SecurityException e) {
123: throw new ProcessException(
124: "A security manager exists and its checkExec method "
125: + "doesn't allow creation of a subprocess.");
126: } catch (NullPointerException e) {
127: throw new ProcessException("Command is null.");
128: } catch (IllegalArgumentException e) {
129: throw new ProcessException("Command is empty.");
130: }
131: }
132:
133: /**
134: * Execute a command
135: *
136: * @param commandArgs command and arguments
137: * @throws IOException if an I/O error occurs
138: */
139: public void execute(String[] commandArgs) throws ProcessException {
140: if (log.isDebugEnabled()) {
141: printCommandLine(commandArgs);
142: }
143: try {
144: process = ProcessFactory.getFactory().exec(commandArgs);
145: } catch (IOException e) {
146: throw new ProcessException("An I/O error occured: "
147: + e.getMessage());
148: } catch (SecurityException e) {
149: throw new ProcessException(
150: "A security manager exists and its checkExec method "
151: + "doesn't allow creation of a subprocess.");
152: } catch (NullPointerException e) {
153: throw new ProcessException("Command is null.");
154: } catch (IllegalArgumentException e) {
155: throw new ProcessException("Command is empty.");
156: }
157: }
158:
159: /**
160: * Execute a command
161: *
162: * @param commandPath path of comand
163: * @param args arguments after the command
164: * @throws IOException if an I/O error occurs
165: */
166: public void execute(String commandPath, String[] args)
167: throws ProcessException {
168: commandArgs = constructCommandArray(commandPath, args);
169: execute(commandArgs);
170: }
171:
172: /**
173: * Execute a command
174: *
175: * @param commandArgs command and arguments
176: * @param env environment name value pairs
177: * @throws IOException if an I/O error occurs
178: */
179: public void execute(String[] commandArgs, String[] env)
180: throws ProcessException {
181:
182: if (log.isDebugEnabled()) {
183: printCommandLine(commandArgs);
184: }
185: try {
186: process = ProcessFactory.getFactory()
187: .exec(commandArgs, env);
188: } catch (IOException e) {
189: throw new ProcessException("An I/O error occured: "
190: + e.getMessage());
191: } catch (SecurityException e) {
192: throw new ProcessException(
193: "A security manager exists and its checkExec method "
194: + "doesn't allow creation of a subprocess.");
195: } catch (NullPointerException e) {
196: throw new ProcessException("Command is null.");
197: } catch (IllegalArgumentException e) {
198: throw new ProcessException("Command is empty.");
199: }
200: }
201:
202: /**
203: * Execute a command
204: *
205: * @param commandPath path of comand
206: * @param args arguments after the comand
207: * @param env environment name value pairs
208: * @throws IOException if an I/O error occurs
209: */
210: public void execute(String commandPath, String[] args, String[] env)
211: throws ProcessException {
212:
213: commandArgs = constructCommandArray(commandPath, args);
214: execute(commandArgs, env);
215: }
216:
217: /**
218: * Execute a command
219: *
220: * @param commandArgs command and arguments
221: * @param env environment name value pairs
222: * @param changeToDirectory working directory
223: * @throws IOException if an I/O error occurs
224: */
225: public void execute(String[] commandArgs, String[] env,
226: String changeToDirectory) throws ProcessException {
227:
228: if (log.isDebugEnabled()) {
229: printCommandLine(commandArgs);
230: }
231: try {
232: process = ProcessFactory.getFactory().exec(commandArgs,
233: env, changeToDirectory);
234: } catch (IOException e) {
235: throw new ProcessException("An I/O error occured: "
236: + e.getMessage());
237: } catch (SecurityException e) {
238: throw new ProcessException(
239: "A security manager exists and its checkExec method "
240: + "doesn't allow creation of a subprocess.");
241: } catch (NullPointerException e) {
242: throw new ProcessException("Command is null.");
243: } catch (IllegalArgumentException e) {
244: throw new ProcessException("Command is empty.");
245: }
246: }
247:
248: /**
249: * Execute a command
250: *
251: * @param commandPath path of comand
252: * @param args arguments after the comand
253: * @param env environment name value pairs
254: * @param changeToDirectory working directory
255: * @throws IOException if an I/O error occurs
256: */
257: public void execute(String commandPath, String[] args,
258: String[] env, String changeToDirectory)
259: throws ProcessException {
260:
261: commandArgs = constructCommandArray(commandPath, args);
262: execute(commandArgs, env, changeToDirectory);
263: }
264:
265: /**
266: * Reads output from the external process to the streams.
267: *
268: * @param out process stdout is written to this stream
269: * @param err process stderr is written to this stream
270: * @throws ProcessException if process not yet executed
271: */
272: public void waitAndRead(OutputStream out, OutputStream err)
273: throws ProcessException {
274: if (process == null) {
275: throw new ProcessException("Process not yet executed");
276: }
277:
278: ProcessClosure reader = new ProcessClosure(name, process, null,
279: out, err);
280: reader.readBlocking(); // a blocking call
281: }
282:
283: /**
284: * Reads output from the external process to the streams. A progress monitor
285: * is polled to test for cancellation. Destroys the process if the monitor
286: * becomes cancelled
287: *
288: * @param output process stdout is written to this stream
289: * @param err process stderr is written to this stream
290: * @param monitor monitor monitor to receive progress info and to cancel
291: * the external process
292: * @throws ProcessException if process not yet executed or if process
293: * cancelled
294: */
295: public void waitAndRead(OutputStream output, OutputStream err,
296: IProgressMonitor monitor) throws ProcessException {
297: if (process == null) {
298: throw new ProcessException("Process not yet executed");
299: }
300:
301: PipedOutputStream errOutPipe = new PipedOutputStream();
302: PipedOutputStream outputPipe = new PipedOutputStream();
303: PipedInputStream errInPipe, inputPipe;
304: try {
305: errInPipe = new PipedInputStream(errOutPipe);
306: inputPipe = new PipedInputStream(outputPipe);
307: } catch (IOException e) {
308: throw new ProcessException("Command canceled");
309: }
310:
311: ProcessClosure closure = new ProcessClosure(name, process,
312: null, outputPipe, errOutPipe);
313: closure.readNonBlocking();
314:
315: processStreams(closure, output, inputPipe, err, errInPipe,
316: monitor);
317: }
318:
319: /**
320: * Writes input to and reads output from the external process to the streams.
321: *
322: * @param in process stdin is read from this stream
323: * @param out process stdout is written to this stream
324: * @param err process stderr is written to this stream
325: * @throws ProcessException if process not yet executed
326: */
327: public void waitAndWrite(InputStream in, OutputStream out,
328: OutputStream err) throws ProcessException {
329: if (process == null) {
330: throw new ProcessException("Process not yet executed");
331: }
332:
333: ProcessClosure reader = new ProcessClosure(name, process, in,
334: out, err);
335: reader.writeBlocking(); // a blocking call
336: }
337:
338: /**
339: * Writes input to and reads output from the external process to the streams.
340: * A progress monitor is polled to test for cancellation. Destroys the
341: * process if the monitor becomes cancelled
342: *
343: * @param in process stdin is read from this stream
344: * @param output process stdout is written to this stream
345: * @param err process stderr is written to this stream
346: * @param monitor monitor monitor to receive progress info and to cancel
347: * the external process
348: * @throws ProcessException if process not yet executed or if process
349: * cancelled
350: */
351: public void waitAndWrite(InputStream in, OutputStream output,
352: OutputStream err, IProgressMonitor monitor)
353: throws ProcessException {
354: if (process == null) {
355: throw new ProcessException("Process not yet executed");
356: }
357:
358: PipedOutputStream errOutPipe = new PipedOutputStream();
359: PipedOutputStream outputPipe = new PipedOutputStream();
360: PipedInputStream errInPipe, inputPipe;
361: try {
362: errInPipe = new PipedInputStream(errOutPipe);
363: inputPipe = new PipedInputStream(outputPipe);
364: } catch (IOException e) {
365: throw new ProcessException("Command canceled");
366: }
367:
368: ProcessClosure closure = new ProcessClosure(name, process, in,
369: outputPipe, errOutPipe);
370: closure.readNonBlocking();
371: closure.writeNonBlocking();
372:
373: processStreams(closure, output, inputPipe, err, errInPipe,
374: monitor);
375: }
376:
377: /**
378: * process the Streams.while the external process returns bytes. Cancellation
379: * is possible by the ProgressMonitor
380: *
381: * @param closure process closure object which handles the interaction with
382: * the external process
383: * @param output process stdout is written to this stream
384: * @param inputPipe piped stream to other thread for the stdout
385: * @param err process stderr is written to this stream
386: * @param errInPipe piped stream to other thread for the stderr
387: * @param monitor monitor to receive progress info and to cancel
388: * the external process
389: * @throws ProcessException if process cancelled
390: */
391: protected void processStreams(ProcessClosure closure,
392: OutputStream output, PipedInputStream inputPipe,
393: OutputStream err, PipedInputStream errInPipe,
394: IProgressMonitor monitor) throws ProcessException {
395:
396: monitor.begin();
397:
398: byte buffer[] = new byte[BUFFER_SIZE];
399: int nbytes;
400: while (!monitor.isCanceled() && closure.isAlive()) {
401: nbytes = 0;
402: try {
403: if (errInPipe.available() > 0) {
404: nbytes = errInPipe.read(buffer);
405: err.write(buffer, 0, nbytes);
406: err.flush();
407: }
408: if (inputPipe.available() > 0) {
409: nbytes = inputPipe.read(buffer);
410: output.write(buffer, 0, nbytes);
411: output.flush();
412: }
413: } catch (IOException e) {
414: }
415: if (nbytes == 0) {
416: try {
417: Thread.sleep(DELAY);
418: } catch (InterruptedException ie) {
419: }
420: } else {
421: monitor.worked();
422: }
423: }
424:
425: // Operation canceled by the user, terminate abnormally.
426: if (monitor.isCanceled()) {
427: closure.terminate();
428: throw new ProcessException("Command canceled");
429: }
430:
431: try {
432: process.waitFor();
433: } catch (InterruptedException e) {
434: //System.err.println("reader exception " +e);
435: //e.printStackTrace();
436: }
437:
438: // Drain the pipes.
439: try {
440: while (errInPipe.available() > 0
441: || inputPipe.available() > 0) {
442: nbytes = 0;
443: if (errInPipe.available() > 0) {
444: nbytes = errInPipe.read(buffer);
445: err.write(buffer, 0, nbytes);
446: err.flush();
447: }
448: if (inputPipe.available() > 0) {
449: nbytes = inputPipe.read(buffer);
450: output.write(buffer, 0, nbytes);
451: output.flush();
452: }
453: if (nbytes != 0) {
454: monitor.worked();
455: }
456: }
457: } catch (IOException e) {
458: } finally {
459: try {
460: errInPipe.close();
461: } catch (IOException e) {
462: }
463: try {
464: inputPipe.close();
465: } catch (IOException e) {
466: }
467: }
468:
469: monitor.done();
470: }
471:
472: /**
473: * print Command Line.
474: *
475: * @param commandArgs array of comand and args
476: */
477: public void printCommandLine(String[] commandArgs) {
478: StringBuffer buf = new StringBuffer();
479: for (String element : commandArgs) {
480: buf.append(element);
481: buf.append(' ');
482: }
483: buf.append(lineSeparator);
484: log.debug(buf.toString());
485: }
486: }
|