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    }