001 package toolbus.tool.execution;
002
003 import java.io.BufferedReader;
004 import java.io.IOException;
005 import java.io.InputStreamReader;
006 import java.io.PrintStream;
007
008 import toolbus.logging.ILogger;
009 import toolbus.logging.IToolBusLoggerConstants;
010 import toolbus.logging.LoggerFactory;
011
012 /**
013 * Handles the output and error streams of a tool process.
014 *
015 * @author Arnold Lankamp
016 */
017 public class StreamHandler implements Runnable{
018 private final Process process;
019 private final String toolID;
020
021 /**
022 * Constructor.
023 *
024 * @param process
025 * The tool process.
026 * @param toolID
027 * The tool identifier.
028 */
029 public StreamHandler(Process process, String toolID){
030 this.process = process;
031 this.toolID = toolID;
032 }
033
034 /**
035 * @see java.lang.Runnable#run()
036 */
037 public void run(){
038 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
039
040 String line = null;
041 try{
042 while((line = reader.readLine()) != null){
043 PrintStream ps = System.out;
044 // This is to avoid the silent instantiation and usage of a StringBuffer,
045 // without having to obtain and release the monitor to System.out several times.
046 synchronized(ps){
047 ps.print(toolID);
048 ps.print(" >> ");
049 ps.print(line);
050 ps.println();
051 }
052 }
053 }catch(IOException ioex){
054 LoggerFactory.log("An IOException occured while handling the outputstream of tool: " + toolID, ioex, ILogger.ERROR, IToolBusLoggerConstants.TOOLINSTANCE);
055 LoggerFactory.log("Killing tool: " + toolID + ", because it cause an exception.", ILogger.ERROR, IToolBusLoggerConstants.TOOLINSTANCE);
056 }catch(RuntimeException rex){
057 LoggerFactory.log("An RuntimeException occured during the execution of tool: " + toolID, rex, ILogger.ERROR, IToolBusLoggerConstants.TOOLINSTANCE);
058 LoggerFactory.log("Killing tool: " + toolID + ", because it cause an exception.", ILogger.ERROR, IToolBusLoggerConstants.TOOLINSTANCE);
059 }finally{
060 // Kill the subprocess, even if it's already dead. We won't be handling the streams
061 // anymore from this point on, it'll probably lock up eventually anyway if it's
062 // still running (better kill it ourselfs). It will also close the stream from and
063 // to the process, to make sure the associated file descriptor is removed
064 // immediately and NOT whenever the JVM feels like it.
065 destroy();
066
067 // Close the reader.
068 try{
069 reader.close();
070 }catch(IOException ioex){
071 LoggerFactory.log("Unable to close the input stream from tool: " + toolID, ILogger.FATAL, IToolBusLoggerConstants.TOOLINSTANCE);
072 }
073
074 int exitCode = -1;
075 do{
076 try{
077 process.waitFor();
078 }catch(InterruptedException irex){
079 // Ignore this.
080 }
081 try{
082 exitCode = process.exitValue();
083 }catch(IllegalThreadStateException itsex){
084 // Ignore this too.
085 }
086 }while(exitCode == -1);
087
088 if(exitCode != 0){
089 String error = "Tool "+toolID+" exited unexpectedly with code: "+exitCode;
090 LoggerFactory.log(error, ILogger.ERROR, IToolBusLoggerConstants.TOOLINSTANCE);
091 throw new RuntimeException(error);
092 }
093 }
094 }
095
096 /**
097 * Forcefully terminates the associated tool process.
098 */
099 public void destroy(){
100 process.destroy();
101 }
102 }