1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: ProcessHandler.java,v $
10 * $Revision: 1.14.2.2 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
32 import java
.io
.InputStream
;
34 import java
.io
.PrintWriter
;
35 import java
.io
.PrintStream
;
36 import java
.io
.LineNumberReader
;
37 import java
.io
.InputStreamReader
;
38 import java
.io
.OutputStreamWriter
;
39 import java
.util
.Calendar
;
40 import java
.util
.Date
;
41 import java
.util
.GregorianCalendar
;
42 import lib
.TestParameters
;
43 import util
.PropertyName
;
47 * Class collect information from input stream in
48 * background (sparate thread) and outputs it to
49 * some log stream. I helps to avoid buffer overflow
50 * when output stream has small buffer size (e.g.
51 * in case when handling stdout from external
52 * <code>Process</code>)
54 * This class is currently used by ProcesHandler
57 class Pump
extends Thread
60 private LineNumberReader reader
;
62 private StringBuffer buf
= new StringBuffer(256);
63 private PrintWriter log
;
66 * Creates Pump for specified <code>InputStream</code>.
67 * This Pump also synchronously output text read to
68 * log by prefixed lines. Constructor immediately
69 * starts reading in a separate thread.
71 * @param is Stream which requires permanent reading.
72 * @param log Writer where prefixed text lines to be output
73 * @param outPrefix A prefix which is printed at the
74 * beginning of each output line.
76 public Pump(InputStream is
, PrintWriter log
, String outPrefix
)
78 this.pref
= (outPrefix
== null) ?
"" : outPrefix
;
79 reader
= new LineNumberReader(new InputStreamReader(is
));
88 String line
= reader
.readLine();
91 log
.println(pref
+ line
);
93 buf
.append(line
).append('\n');
94 line
= reader
.readLine();
97 catch (java
.io
.IOException e
)
99 log
.println(pref
+ "Exception occured: " + e
);
104 * Returns the text collected from input stream.
106 public String
getStringBuffer()
108 return buf
.toString();
113 * Class provides convenient way for running external program
114 * handle its standard streams, control execution and check results.
115 * Instance of this class must be created only for a single
116 * execution. If you need to execute the same command again you
117 * should create a new instance for this.
119 public class ProcessHandler
122 private String cmdLine
;
123 private String
[] cmdLineArray
;
124 private String
[] envVars
= null;
125 private File workDir
= null;
126 private PrintWriter log
;
127 private int exitValue
= -1;
128 private boolean isFinished
= false;
129 private boolean isStarted
= false;
130 private boolean mbTimedOut
= false;
131 private long mTimeOut
= 0;
132 private String stdInBuff
= "";
133 private Pump stdout
= null;
134 private Pump stderr
= null;
135 private PrintStream stdIn
= null;
136 private Process m_aProcess
= null;
137 private TestParameters param
= null;
138 private boolean debug
= false;
141 * Creates instance with specified external command.
142 * Debug info and output
143 * of external command is printed to stdout.
146 public ProcessHandler(String cmdLine
)
148 this(cmdLine
, null, null, null, 0);
152 * Creates instance with specified external command
153 * including parameters as an array.
154 * Debug info and output
155 * of external command is printed to stdout.
158 public ProcessHandler(String
[] cmdLines
)
160 this(null, null, null, null, 0);
161 cmdLineArray
= cmdLines
;
165 * Creates instance with specified external command
166 * including parameters as an array, with environment
168 * Debug info and output
169 * of external command is printed to stdout.
172 * @see java.lang.Runtime exec(String[], String[])
174 public ProcessHandler(String
[] cmdLines
, String
[] envVars
)
176 this(null, null, null, envVars
, 0);
177 cmdLineArray
= cmdLines
;
181 * Creates instance with specified external command
182 * including parameters as an array, with environment
183 * variables. The command will be started in workDir.
184 * Debug info and output
185 * of external command is printed to stdout.
189 public ProcessHandler(String
[] cmdLines
, File workDir
)
191 this(null, null, workDir
, null, 0);
192 cmdLineArray
= cmdLines
;
197 * Creates instance with specified external command and
198 * log stream where debug info and output
199 * of external command is printed out. The command will be started in workDir.
204 public ProcessHandler(String
[] cmdLines
, PrintWriter log
, File workDir
)
206 this(null, log
, workDir
, null, 0);
207 cmdLineArray
= cmdLines
;
211 * Creates instance with specified external command and
212 * log stream where debug info and output
213 * of external command is printed out.
217 public ProcessHandler(String cmdLine
, PrintWriter log
)
219 this(cmdLine
, log
, null, null, 0);
223 * Creates instance with specified external command and set the time out for the command.
227 public ProcessHandler(String cmdLine
, int timeOut
)
229 this(cmdLine
, null, null, null, timeOut
);
233 * Creates instance with specified external command which
234 * will be executed in the some work directory.
235 * Debug info and output
236 * of external commandis printed to stdout.
240 public ProcessHandler(String cmdLine
, File workDir
)
242 this(cmdLine
, null, workDir
, null, 0);
246 * Creates instance with specified external command which
247 * will be executed in the some work directory.
248 * Debug info and output printed in log stream.
253 public ProcessHandler(String cmdLine
, PrintWriter log
, File workDir
)
255 this(cmdLine
, log
, workDir
, null, 0);
259 * Creates instance with specified external command which
260 * will be executed in the some work directory and
261 * log stream where debug info and output
262 * of external command is printed .
263 * The specified environment variables are set for the new process.
264 * If log stream is null, logging is printed to stdout.
270 public ProcessHandler(String cmdLine
, PrintWriter log
, File workDir
, String
[] envVars
)
272 this(cmdLine
, log
, workDir
, envVars
, 0);
276 * Creates instance with specified external command which
277 * will be executed in the some work directory and
279 * @param cmdLine the command to be executed
280 * @param log log stream where debug info and output
281 * of external command is printed .
282 * @param workDir The working directory of the new process
283 * @param envVars The specified environment variables are
284 * set for the new process.
285 * If log stream is null, logging is printed to stdout.
286 * @param timeOut When started sychronisly, the maximum time the
287 * process will live. When the process being destroyed
288 * a log will be written out. It can be asked on
289 * <code>isTimedOut()</code> if it has been terminated.
292 * Waits specified time in miliSeconds for
293 * process to exit and return its status.
296 * Waits for the process to end regulary
299 * Kills the process immediately
303 public ProcessHandler(String cmdLine
, PrintWriter log
, File workDir
, String
[] envVars
, long timeOut
)
305 this.cmdLine
= cmdLine
;
306 this.workDir
= workDir
;
308 this.cmdLine
= cmdLine
;
309 this.envVars
= envVars
;
312 this.log
= new PrintWriter(new OutputStreamWriter(System
.out
));
318 this.mTimeOut
= timeOut
;
322 * Creates instance with specified external command which
323 * will be executed in the some work directory and
324 * log stream where debug info and output of external command is printed.
325 * If log stream is null, logging is printed to stdout.
326 * From the <CODE>TestParameters</CODE> the <CODE>OfficeWachter</CODE> get a ping.
330 * @param shortWait If this parameter is ture the <CODE>mTimeOut</CODE> is set to 5000 ms, else it is set to
331 * half of time out from parameter timeout.
332 * @param param the TestParameters
333 * @see lib.TestParameters
334 * @see helper.OfficeWatcher
336 public ProcessHandler(String
[] commands
, PrintWriter log
, File workDir
, int shortWait
, TestParameters param
)
338 this(null, log
, workDir
, null, 0);
339 this.cmdLineArray
= commands
;
343 this.mTimeOut
= shortWait
;
347 this.mTimeOut
= (long) (param
.getInt(PropertyName
.TIME_OUT
) / 1.3);
349 debug
= param
.getBool(PropertyName
.DEBUG_IS_ACTIVE
);
354 * This method do an asynchronous execution of the commands. To avoid a interruption on long running processes
355 * caused by <CODE>OfficeWatcher</CODE>, the OfficeWatcher get frequently a ping.
356 * @see helper.OfficeWatcher
358 public void runCommand()
361 boolean changedText
= true;
365 this.executeAsynchronously();
367 OfficeWatcher ow
= null;
370 ow
= (OfficeWatcher
) param
.get(PropertyName
.OFFICE_WATCHER
);
378 while (!this.isFinished() && changedText
)
381 // dbg("runCommand: waiting " + mTimeOut / 1000 + " seconds while command execution is ongoing... " + count);
382 // shortWait(mTimeOut);
383 // shortWait(2000); // wait 2 seconds.
385 waitFor(2000, false); // wait but don't kill
391 // check for changes in the output stream. If there are no changes, the process maybe hangs
392 if (!this.isFinished())
397 String sOutputText
= getOutputText();
398 if (sOutputText
.length() == memText
.length())
401 // dbg("runCommand Could not detect changes in output stream!!!");
404 memText
= this.getOutputText();
409 if (!this.isFinished())
411 dbg("runCommand Process ist not finished but there are no changes in output stream.");
416 public boolean isTimedOut()
421 private void setTimedOut(boolean bTimedOut
)
423 mbTimedOut
= bTimedOut
;
427 * Executes the command and returns only when the process
430 * @return <code>true</code> if process was successfully
431 * started and correcly exits (exit code doesn't affect
434 public boolean executeSynchronously()
437 return waitFor(mTimeOut
);
441 * Executes the command immediately returns. The process
442 * remains in running state. Control of its state should
443 * be made by <code>waitFor</code> methods.
445 * @return <code>true</code> if process was successfully
448 public boolean executeAsynchronously()
454 public synchronized void kill()
460 boolean exit
= false;
462 while (counter
< 3 && !exit
)
464 m_aProcess
.destroy();
468 Thread
.sleep(1000 * counter
); // 5000
470 catch (java
.lang
.InterruptedException e
)
475 final int exit_Value
= m_aProcess
.exitValue();
484 dbg("kill: process closed with exit code " + exit_Value
);
486 catch (java
.lang
.IllegalThreadStateException e
)
490 dbg("kill: Couldn't close process after " + counter
+ " attempts, trying again");
499 * Returns the time in seconds since 1st January 1970
502 public static long getSystemTime()
504 // Calendar cal = new GregorianCalendar();
505 // final long nTime = cal.getTimeInMillis();
506 final long nTime
= System
.currentTimeMillis();
509 private long m_nExactStartTimeInMillisec
;
511 private void initialExactStartTime()
513 m_nExactStartTimeInMillisec
= getSystemTime();
516 public long getProcessStartTime()
518 return m_nExactStartTimeInMillisec
;
521 protected void execute()
525 throw new RuntimeException(
526 "The process handler has already been executed.");
528 final Runtime runtime
= Runtime
.getRuntime();
533 log
.print(utils
.getDateTime() + "execute: Starting command from array: ");
534 for (int i
= 0; i
< cmdLineArray
.length
; i
++)
536 log
.print(cmdLineArray
[i
]);
540 initialExactStartTime();
541 m_aProcess
= runtime
.exec(cmdLineArray
, envVars
);
547 log
.println(utils
.getDateTime() + "execute: Starting command: " + cmdLine
+ " " +
548 workDir
.getAbsolutePath());
549 m_aProcess
= runtime
.exec(cmdLine
, envVars
, workDir
);
553 log
.println(utils
.getDateTime() + "execute: Starting command: " + cmdLine
);
554 m_aProcess
= runtime
.exec(cmdLine
, envVars
);
559 catch (java
.io
.IOException e
)
563 log
.println(utils
.getDateTime() + "execute: The command array can't be started: " + e
);
567 log
.println(utils
.getDateTime() + "execute: The command " + cmdLine
+ " can't be started: " + e
);
571 dbg("execute: pump io-streams");
572 stdout
= new Pump(m_aProcess
.getInputStream(), log
, "out > ");
573 stderr
= new Pump(m_aProcess
.getErrorStream(), log
, "err > ");
574 stdIn
= new PrintStream(m_aProcess
.getOutputStream());
576 // int nExitValue = m_aProcess.exitValue();
579 dbg("execute: flush io-streams");
585 * This method is useful when the process was executed
586 * asynchronously. Waits for process to exit and return
589 * @return <code>true</code> if process correctly exited
590 * (exit code doesn't affect to this result).
592 public boolean waitFor()
598 * This method is useful when the process was executed
599 * asynchronously. Waits during specified time for process
600 * to exit and return its status.
603 * Waits specified time in miliSeconds for
604 * process to exit and return its status.
607 * Waits for the process to end regulary
610 * Kills the process immediately
612 * @return <code>true</code> if process correctly exited
613 * (exit code doesn't affect to this result).
615 public boolean waitFor(long timeout
)
617 return waitFor(timeout
, true);
620 private boolean waitFor(long timeout
, boolean bKillProcessAfterTimeout
)
635 m_aProcess
.waitFor();
637 catch (InterruptedException e
)
639 log
.println("The process was interrupted: " + e
);
644 exitValue
= m_aProcess
.exitValue();
646 catch (IllegalThreadStateException e
)
654 while (!isFinished
&& timeout
> 0)
661 exitValue
= m_aProcess
.exitValue(); // throws exception if not finished
663 catch (IllegalThreadStateException e
)
671 log
.println("The process has timed out!");
674 catch (InterruptedException ex
)
676 log
.println("The process was interrupted: " + ex
);
680 if (bKillProcessAfterTimeout
== true)
684 log
.println("Going to destroy the process!!");
685 m_aProcess
.destroy();
686 log
.println("Process has been destroyed!");
689 // Removed as hung up in SDK test 'PathSettings'
693 // } catch (InterruptedException e) {}
698 protected void flushInput()
705 synchronized(stdInBuff
)
707 stdIn
.print(stdInBuff
);
714 * Returns the text output by external command to stdout.
715 * @return the text output by external command to stdout
717 public String
getOutputText()
725 return stdout
.getStringBuffer();
730 * Returns the text output by external command to stderr.
731 * @return the text output by external command to stderr
733 public String
getErrorText()
741 return stderr
.getStringBuffer();
746 * Prints the string specified to sdtin of external
747 * command. '\n' is not added so if you need you
748 * should terminate the string with '\n'. <p>
750 * The method can also be called before the command
751 * starts its execution. Then the text is buffered
752 * and transfered to command when it will be started.
755 public void printInputText(String str
)
762 * Returns information about was the command started or
765 * @return <code>true</code> if the external command was
766 * found and successfully started.
768 public boolean isStarted()
774 * Returns the information about the final state of command
777 * @return <code>true</code> if the command correctly starts,
778 * exits and was not interrupted due to timeout.
780 public boolean isFinished()
786 * Returns exit code of the external command.
788 * @return exit code of command if it was finished,
791 public int getExitCode()
795 exitValue
= m_aProcess
.exitValue();
799 //System.out.println("No ExitValue available");
805 /** Causes the thread to sleep some time.
806 * @param milliseconds
808 public static void shortWait(long milliseconds
)
812 Thread
.sleep(milliseconds
);
814 catch (InterruptedException e
)
816 System
.out
.println("While waiting :" + e
);
820 private void dbg(String message
)
824 log
.println(utils
.getDateTime() + "PH." + message
);