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 }