Changeset 31591

Show
Ignore:
Timestamp:
07.04.2017 17:47:14 (2 years ago)
Author:
ak19
Message:

1. SafeProcess?.CustomProcessHandler? replaces SafeProcess?.LineByLineHandler? to allow GS developers more control over the handling of a Process' iostreams and more transparency to the developer about what happens. But SafeProcess? provides default behaviours. 2. Some informative msgs in the GS2Construct method that marks the flat DB for when the oid entry does not occur in the DB.

Location:
main/trunk/greenstone3/src/java/org/greenstone
Files:
3 modified

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone3/src/java/org/greenstone/gsdl3/build/GS2PerlConstructor.java

    r31590 r31591  
    1818import java.io.InputStreamReader; 
    1919import java.io.File; 
     20import java.io.InputStream; 
    2021import java.io.IOException; 
    2122import java.util.ArrayList; 
     
    334335        // http://www.cgi101.com/class/ch3/text.html 
    335336        // setenv QUERY_STRING and REQUEST_METHOD = GET. 
     337        // Run the perl command as a simple process: no logging to the collection's build log 
    336338        if (runPerlCommand(command_str, envvars, new File(cgi_directory))) 
    337339                   //new File(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi"))) 
     
    372374    } 
    373375 
    374     /** returns true if completed correctly, false otherwise */ 
    375     protected boolean runPerlCommand(String[] command) { 
    376     return runPerlCommand(command, null, null); 
    377     } 
    378  
    379          
    380     protected boolean runPerlCommand(String[] command, String[] envvars, File dir) 
    381     { 
    382     boolean success = true; 
    383      
     376    protected SafeProcess createPerlProcess(String[] command, String[] envvars, File dir) { 
    384377    int sepIndex = this.gsdl3home.lastIndexOf(File.separator); 
    385378    String srcHome = this.gsdl3home.substring(0, sepIndex); 
     
    402395        args.add(a + "=" + System.getenv(a)); 
    403396    } 
     397 
     398    SafeProcess perlProcess  
     399        = new SafeProcess(command, args.toArray(new String[args.size()]), dir); //  dir can be null 
     400     
     401    return perlProcess; 
     402    } 
     403 
     404    // ModifyMetadata operations call runSimplePerlCommand which produces no output in build log 
     405    protected boolean runSimplePerlCommand(String[] command) {   
     406    return runSimplePerlCommand(command, null, null); 
     407    } 
     408     
     409    protected boolean runSimplePerlCommand(String[] command, String[] envvars, File dir) { 
     410    boolean success = false;     
    404411     
    405412    String command_str = ""; 
     
    410417    sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str)); 
    411418     
    412     //logger.info("### Running command = " + command_str); 
     419    logger.info("### Running simple command = " + command_str); 
    413420 
    414421    // This is where we create and run our perl process safely 
    415     SafeProcess perlProcess  
    416         = new SafeProcess(command, args.toArray(new String[args.size()]), dir); //  dir can be null 
    417      
     422    SafeProcess perlProcess = createPerlProcess(command, envvars, dir); //  dir can be null 
     423     
     424    perlProcess.setExceptionHandler(this); 
     425 
     426    sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting")); 
     427 
     428    int exitVal = perlProcess.runProcess(); // uses default processing of the perl process' iostreams provided by SafeProcess 
     429 
     430    if (exitVal == 0) { 
     431        success = true; 
     432        sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success")); 
     433    } else { 
     434        sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));       
     435        success = false; // explicit 
     436    } 
     437     
     438    return success; 
     439    } 
     440 
     441 
     442    /** returns true if completed correctly, false otherwise  
     443     * Building operations call runPerlCommand which sends build output to collect/log/build_log.#*.txt 
     444     */ 
     445    protected boolean runPerlCommand(String[] command) { 
     446    return runPerlCommand(command, null, null); 
     447    } 
     448 
     449         
     450    protected boolean runPerlCommand(String[] command, String[] envvars, File dir) 
     451    { 
     452    boolean success = true; 
     453     
     454    String command_str = ""; 
     455    for (int i = 0; i < command.length; i++) { 
     456        command_str = command_str + command[i] + " "; 
     457    } 
     458     
     459    sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str)); 
     460     
     461    logger.info("### Running logged command = " + command_str); 
     462 
     463    // This is where we create and run our perl process safely 
     464    SafeProcess perlProcess = createPerlProcess(command, envvars, dir); //  dir can be null 
    418465     
    419466    sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting")); 
     
    428475    BufferedWriter bw = null; 
    429476    try { 
    430         bw = new BufferedWriter(new FileWriter(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log" + File.separator + "build_log." + (System.currentTimeMillis()) + ".txt"));         
     477 
     478        bw = new BufferedWriter(new FileWriter(new File(logDir, "build_log." + (System.currentTimeMillis()) + ".txt"))); 
    431479     
    432480        bw.write("Document Editor Build \n");        
     
    434482         
    435483        // handle each incoming line from stdout and stderr streams, and any exceptions that occur then 
    436         SynchronizedProcessLineByLineHandler outLineByLineHandler 
    437         = new SynchronizedProcessLineByLineHandler(bw, SynchronizedProcessLineByLineHandler.STDOUT); 
    438         SynchronizedProcessLineByLineHandler errLineByLineHandler 
    439         = new SynchronizedProcessLineByLineHandler(bw, SynchronizedProcessLineByLineHandler.STDERR); 
    440         perlProcess.setStdOutLineByLineHandler(outLineByLineHandler); 
    441         perlProcess.setStdErrLineByLineHandler(errLineByLineHandler); 
     484        SafeProcess.CustomProcessHandler processOutHandler 
     485        = new SynchronizedProcessHandler(bw, SynchronizedProcessHandler.STDOUT); 
     486        SafeProcess.CustomProcessHandler processErrHandler 
     487        = new SynchronizedProcessHandler(bw, SynchronizedProcessHandler.STDERR); 
    442488         
    443489        // GS2PerlConstructor will do further handling of exceptions that may occur during the perl 
     
    450496        // std in of java, as before. 
    451497 
    452         perlProcess.runProcess(); 
     498        perlProcess.runProcess(null, processOutHandler, processErrHandler); // use default procIn handling 
    453499         
    454500    // The original runPerlCommand() code had an ineffective check for whether the cmd had been cancelled 
     
    657703    // Called when an exception happens during the running of our perl process. However, 
    658704    // exceptions when reading from our perl process' stderr and stdout streams are handled by 
    659     // SynchronizedProcessLineByLineHandler.gotException() below. 
     705    // SynchronizedProcessHandler.gotException() below, since they happen in separate threads 
     706    // from this one (the ine from which the perl process is run). 
    660707    public synchronized void gotException(Exception e) { 
    661708 
     
    664711    e.printStackTrace(); 
    665712    sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR,  
    666                         "Exception occurred " + e.toString())); // atomic 
     713                        "Exception occurred " + e.toString())); 
    667714    } 
    668715     
     716    // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler. 
    669717    // This class deals with each incoming line from the perl process' stderr or stdout streams. One 
    670     // instance of this class for each stream. However, since multiple instances of this LineByLineHandler  
    671     // could be (and in fact, are) writing to the same file in their own threads, the writer object needs  
    672     // to be made threadsafe. 
     718    // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler 
     719    // could be (and in fact, are) writing to the same file in their own threads, several objects, not just 
     720    // the bufferedwriter object, needed to be made threadsafe. 
    673721    // This class also handles exceptions during the running of the perl process. 
    674722    // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure 
    675     // we do that here too, to continue original behaviour. 
    676     protected class SynchronizedProcessLineByLineHandler implements SafeProcess.LineByLineHandler 
     723    // we do that here too, to continue original behaviour. These calls are also synchronized to make their 
     724    // use of the EventListeners threadsafe. 
     725    protected class SynchronizedProcessHandler implements SafeProcess.CustomProcessHandler 
    677726    { 
    678727    public static final int STDERR = 0; 
     
    683732         
    684733 
    685     public SynchronizedProcessLineByLineHandler(BufferedWriter bw, int src) { 
    686         this.bwHandle = bw; 
     734    public SynchronizedProcessHandler(BufferedWriter bw, int src) { 
     735        this.bwHandle = bw; // caller will close bw, since many more than one 
     736                            // SynchronizedProcessHandlers are using it 
    687737        this.source = src; // STDERR or STDOUT 
    688738    } 
    689739 
    690     public synchronized void gotLine(String line) { 
    691         //if(this.source == STDERR) { 
    692         ///System.err.println("ERROR: " + line); 
    693         //} else { 
    694         ///System.err.println("OUT: " + line); 
    695         //} 
     740    public void run(Closeable inputStream) { 
     741        InputStream is = (InputStream) inputStream; 
     742 
     743        BufferedReader br = null; 
     744        try { 
     745        br = new BufferedReader(new InputStreamReader(is, "UTF-8")); 
     746        String line=null; 
     747        while ( (line = br.readLine()) != null ) { 
     748             
     749            if(Thread.currentThread().isInterrupted()) { // should we not instead check if SafeProcess thread was interrupted? 
     750            System.err.println("Got interrupted when reading lines from process err/out stream."); 
     751            break; // will go to finally block 
     752            } 
     753             
     754///         System.out.println("@@@ GOT LINE: " + line); 
     755///         GS2PerlConstructor.logger.info("@@@ GOT LINE: " + line + " STDOUT=1: " + source); 
     756 
     757            //if(this.source == STDERR) { 
     758            ///System.err.println("ERROR: " + line); 
     759            //} else { 
     760            ///System.err.println("OUT: " + line); 
     761            //}  
     762             
     763 
     764            this.gotLine(line); // synchronized 
     765             
     766            /* 
     767            try { 
     768            synchronized(bwHandle) { // get a lock on the writer handle, then write 
     769                 
     770                bwHandle.write(line + "\n"); 
     771            }  
     772            } catch(IOException ioe) { 
     773            String msg = (source == STDERR) ? "stderr" : "stdout"; 
     774            msg = "Exception when writing out a line read from perl process' " + msg + " stream."; 
     775            GS2PerlConstructor.logger.error(msg, ioe); 
     776            } 
     777             
     778            // this next method is thread safe since only synchronized methods are invoked. 
     779            // and only immutable (final) vars are used.  
     780            // NO, What about the listeners??? 
     781            sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line));            
     782            */ 
     783        } 
     784        } catch (IOException ioe) { // problem with reading in from process with BufferedReader br 
     785 
     786        String msg = (source == STDERR) ? "stderr" : "stdout"; 
     787        msg = "Got exception when processing the perl process' " + msg + " stream."; 
     788        GS2PerlConstructor.logger.error(msg, ioe); 
     789        // now do what the original runPerlCommand() code always did: 
     790        ioe.printStackTrace(); 
     791        logException(ioe); // synchronized 
     792 
     793        } catch (Exception e) { // problem with BufferedWriter bwHandle on processing each line 
     794        e.printStackTrace(); 
     795        logException(e); // synchronized 
     796        } finally { 
     797        SafeProcess.closeResource(br); 
     798        } 
     799    } 
     800 
     801    // trying to keep synchronized methods as short as possible 
     802    private synchronized void logException(Exception e) { 
     803        sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); 
     804    } 
     805 
     806    // trying to keep synchronized methods as short as possible 
     807    private synchronized void gotLine(String line) throws Exception { 
    696808 
    697809        // BufferedWriter writes may not be atomic 
     
    699811        // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception 
    700812        // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi 
    701         // "All methods on Logger are multi-thread safe", see 
    702         // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization 
    703          
    704         try {        
    705         bwHandle.write(line + "\n"); 
     813        try {                        
     814        bwHandle.write(line + "\n");     
     815         
     816///     System.out.println("@@@ WROTE LINE: " + line); 
     817///     GS2PerlConstructor.logger.info("@@@ WROTE LINE: " + line); 
     818 
     819        // this next method is thread safe since only synchronized methods are invoked. 
     820        // and only immutable (final) vars are used. 
     821        sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line)); 
     822         
     823        } catch(IOException ioe) { // can't throw Exceptions, but are forced to handle Exceptions here 
     824        // since our method definition doesn't specify a throws list. 
     825        // "All methods on Logger are multi-thread safe", see 
     826        // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization 
    706827         
    707         } catch(IOException ioe) { 
    708828        String msg = (source == STDERR) ? "stderr" : "stdout"; 
    709         msg = "Exception when writing out a line read from perl process' " + msg + " stream."; 
    710         GS2PerlConstructor.logger.error(msg, ioe); 
    711         } 
    712          
    713         // this next method is thread safe since only synchronized methods are invoked. 
    714         // and only immutable (final) vars are used. 
    715         sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line)); 
    716     } 
    717  
    718     // This is called when we get an exception during the processing of a perl's 
    719     // input-, err- or output stream 
    720     public synchronized void gotException(Exception e) { 
    721         String msg = (source == STDERR) ? "stderr" : "stdout"; 
    722         msg = "Got exception when processing the perl process' " + msg + " stream."; 
    723  
    724         // now do what the original runPerlCommand() code always did: 
    725         e.printStackTrace(); 
    726         sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); // atomic 
    727     } 
    728  
    729     } // end inner class SynchronizedProcessLineByLineHandler 
     829        msg = "IOException when writing out a line read from perl process' " + msg + " stream."; 
     830        msg += "\nGot line: " + line + "\n"; 
     831        throw new Exception(msg, ioe); 
     832        }            
     833    } 
     834 
     835    } // end inner class SynchronizedProcessHandler 
    730836 
    731837} 
  • main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/GS2Construct.java

    r31546 r31591  
    11871187        // now we know we have an archives folder 
    11881188        String old_value = coll_db.getValue(oid); 
    1189         String new_value = old_value.replace("<index-status>B", "<index-status>" + mark); 
     1189        String new_value = "<index-status>" + mark; 
     1190        if(old_value == null) { 
     1191            logger.error("### null old_value in flat DB for oid " + oid); 
     1192        } else { 
     1193            new_value = old_value.replace("<index-status>B", "<index-status>" + mark); 
     1194            logger.info("### Replacing db entry for oid " + oid + " which has old_value " + old_value); 
     1195            logger.info("### with new value " + new_value); 
     1196                     
     1197        } 
    11901198        // Close database for reading 
    11911199        coll_db.closeDatabase(); 
     1200 
    11921201        if (!coll_db.openDatabase(coll_db_file, SimpleCollectionDatabase.WRITE)) 
    1193         { 
     1202            { 
    11941203            logger.error("Could not open collection archives database. Somebody already using this database!"); 
    11951204            return; 
    1196         } 
     1205            } 
     1206         
    11971207        coll_db.setValue(oid, new_value); 
    1198         coll_db.closeDatabase(); 
    11991208         
     1209        coll_db.closeDatabase();         
    12001210    } 
    12011211} 
  • main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java

    r31588 r31591  
    3535    private int exitValue = -1; 
    3636 
    37     // user can write custom LineByLineHandler to deal with stdout lines as they come out one line at a time 
    38     // and stderr lines as they come out one at a time 
    39     private LineByLineHandler errLineByLineHandler = null; 
    40     private LineByLineHandler outLineByLineHandler = null; 
     37    // allow callers to process exceptions of the main process thread if they want 
    4138    private ExceptionHandler exceptionHandler = null; 
    4239 
     
    7976    } 
    8077 
    81     // register a handler whose gotLine() method will get called as each line is read from the process' stdout 
    82     public void setStdOutLineByLineHandler(LineByLineHandler out_lbl_handler) { 
    83     outLineByLineHandler = out_lbl_handler; 
    84     } 
    85  
    86     // register a handler whose gotLine() method will get called as each line is read from the process' stderr 
    87     public void setStdErrLineByLineHandler(LineByLineHandler err_lbl_handler) { 
    88     errLineByLineHandler = err_lbl_handler; 
    89     } 
    90  
    9178    // register a SafeProcess ExceptionHandler whose gotException() method will 
    9279    // get called for each exception encountered 
     
    10491 
    10592//***************** Copied from gli's gui/FormatConversionDialog.java *************// 
     93 
    10694    public int runProcess() { 
    107  
     95    return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams 
     96    } 
     97 
     98    public int runProcess(CustomProcessHandler procInHandler, 
     99               CustomProcessHandler procOutHandler, 
     100               CustomProcessHandler procErrHandler) 
     101    { 
    108102    Process prcs = null; 
    109103    SafeProcess.OutputStreamGobbler inputGobbler = null; 
     
    112106 
    113107    try {        
     108         
    114109        Runtime rt = Runtime.getRuntime();       
     110         
     111         
    115112        if(this.command != null) { 
    116113        prcs = rt.exec(this.command); 
     
    119116 
    120117        // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java 
    121         ///logger.info("SafeProcess running: " + Arrays.toString(command_args)); 
     118        logger.info("SafeProcess running: " + Arrays.toString(command_args)); 
    122119 
    123120        if(this.envp == null) {  
     
    136133        } 
    137134 
    138         // send inputStr to process. The following constructor can handle inputStr being null 
    139         inputGobbler = // WriterToProcessInputStream 
    140         new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr); 
    141          
    142         // monitor for any error messages 
    143             errorGobbler // ReaderFromProcessOutputStream 
    144         = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines); 
    145  
    146             // monitor for the expected std output line(s) 
    147             outputGobbler 
    148         = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines); 
    149                      
    150         // register line by line handlers, if any were set, for the process stderr and stdout streams 
    151         if(this.outLineByLineHandler != null) { 
    152         outputGobbler.setLineByLineHandler(this.outLineByLineHandler); 
    153         } 
    154         if(this.errLineByLineHandler != null) { 
    155         errorGobbler.setLineByLineHandler(this.errLineByLineHandler); 
    156         } 
    157         if(this.exceptionHandler != null) { 
    158         inputGobbler.setExceptionHandler(this.exceptionHandler); 
    159         }        
     135        logger.info("### Before creating ProcessInGobbler"); 
     136 
     137        // Create the streamgobblers and set any specified handlers on them 
     138 
     139        // PROC INPUT STREAM 
     140        if(procInHandler == null) { 
     141        // send inputStr to process. The following constructor can handle inputStr being null 
     142        inputGobbler = // WriterToProcessInputStream 
     143            new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), this.inputStr); 
     144        } else { // user will do custom handling of process' InputStream  
     145        inputGobbler = new SafeProcess.OutputStreamGobbler(prcs.getOutputStream(), procInHandler); 
     146        } 
     147 
     148        logger.info("### Before creating ProcessErrGobbler"); 
     149         
     150        // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr 
     151        if(procErrHandler == null) { 
     152        errorGobbler // ReaderFromProcessOutputStream 
     153            = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), splitStdErrorNewLines);      
     154        } else { 
     155        errorGobbler 
     156            = new SafeProcess.InputStreamGobbler(prcs.getErrorStream(), procErrHandler); 
     157        } 
     158 
     159        logger.info("### Before creating ProcessOutGobbler"); 
     160 
     161            // PROC OUT STREAM to monitor for the expected std output line(s) 
     162        if(procOutHandler == null) { 
     163        outputGobbler 
     164            = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), splitStdOutputNewLines); 
     165        } else { 
     166        outputGobbler 
     167            = new SafeProcess.InputStreamGobbler(prcs.getInputStream(), procOutHandler); 
     168        } 
     169 
     170         
     171        logger.info("### Before streamgobblers.start()"); 
    160172 
    161173            // kick off the stream gobblers 
     
    163175            errorGobbler.start(); 
    164176            outputGobbler.start(); 
     177 
     178        logger.info("### After streamgobblers.start() - before waitFor"); 
    165179                                     
    166180            // any error??? 
    167             this.exitValue = prcs.waitFor(); // can throw an InterruptedException if process did not terminate               
     181            this.exitValue = prcs.waitFor(); // can throw an InterruptedException if process did not terminate 
     182 
     183            logger.info("Process exitValue: " + exitValue);  
     184 
     185        logger.info("### Before streamgobblers.join()"); 
     186 
    168187        // From the comments of  
    169188        // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2 
     
    175194        inputGobbler.join();  
    176195         
     196        logger.info("### After streamgobblers.join()"); 
     197 
    177198        // set the variables the code that created a SafeProcess object may want to inspect 
    178199        this.outputStr = outputGobbler.getOutput(); 
    179200        this.errorStr = errorGobbler.getOutput(); 
    180  
     201         
    181202        // Since we didn't have an exception, process should have terminated now (waitFor blocks until then) 
    182203        // Set process to null so we don't forcibly terminate it below with process.destroy() 
     
    192213        } 
    193214    } catch(InterruptedException ie) { 
     215 
    194216        if(exceptionHandler != null) { 
    195217        exceptionHandler.gotException(ie); 
     
    197219        logger.error("Process InterruptedException: " + ie.getMessage(), ie); 
    198220        //System.err.println("Process InterruptedException " + ie.getMessage()); 
    199         //ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action 
    200         } 
    201  
     221        ///ie.printStackTrace(); // an interrupt here is not an error, it can be a cancel action 
     222        } 
    202223 
    203224        // propagate interrupts to worker threads here? 
     
    237258        } 
    238259    } 
    239  
     260     
    240261    return this.exitValue; 
    241262    } 
     
    257278} 
    258279 
    259 // When reading from a process' stdout or stderr stream, you can create a LineByLineHandler 
    260 // to do something on a line by line basis, such as sending the line to a log 
    261 public static interface LineByLineHandler { 
    262     public void gotLine(String line); 
    263     public void gotException(Exception e); // for when an exception occurs instead of getting a line 
     280// write your own run() body for any StreamGobbler 
     281// Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams 
     282// example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class. 
     283public static interface CustomProcessHandler { 
     284    public void run(Closeable stream); //InputStream or OutputStream 
    264285} 
    265286 
     
    270291public static class InputStreamGobbler extends Thread 
    271292{ 
    272     InputStream is = null; 
    273     StringBuffer outputstr = new StringBuffer(); 
    274     boolean split_newlines = false; 
    275     LineByLineHandler lineByLineHandler = null; 
    276      
     293    private InputStream is = null; 
     294    private StringBuffer outputstr = new StringBuffer(); 
     295    private boolean split_newlines = false; 
     296    private CustomProcessHandler customHandler = null; 
     297 
    277298    public InputStreamGobbler(InputStream is) 
    278299    { 
    279300    this.is = is; 
    280     split_newlines = false; 
     301    this.split_newlines = false; 
    281302    } 
    282303     
     
    287308    } 
    288309     
    289     public void setLineByLineHandler(LineByLineHandler lblHandler) { 
    290     lineByLineHandler = lblHandler; 
    291     } 
    292  
    293  
    294     public void run() 
     310    public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler) 
     311    { 
     312    this.is = is; 
     313    this.customHandler = customHandler; 
     314    } 
     315 
     316    // default run() behaviour 
     317    public void runDefault() 
    295318    { 
    296319    BufferedReader br = null; 
     
    307330        //System.out.println("@@@ GOT LINE: " + line); 
    308331        outputstr.append(line); 
    309          
    310332        if(split_newlines) { 
    311333            outputstr.append(Misc.NEWLINE); // "\n" is system dependent (Win must be "\r\n") 
    312334        } 
    313  
    314         if(lineByLineHandler != null) { // let handler deal with newlines 
    315             lineByLineHandler.gotLine(line); 
    316         }        
    317         } 
    318     } catch (IOException ioe) { 
    319         if(lineByLineHandler != null) { 
    320         lineByLineHandler.gotException(ioe); 
    321         } else { 
    322         logger.error("Exception when reading from a process' stdout/stderr stream: ", ioe); 
    323         //ioe.printStackTrace(); 
    324         } 
     335        } 
     336    } catch (IOException ioe) {      
     337        logger.error("Exception when reading from a process' stdout/stderr stream: ", ioe); 
     338        //System.err.println("Exception when reading from a process' stdout/stderr stream: "); 
     339        //ioe.printStackTrace();   
    325340         
    326341    } finally { 
     
    329344    } 
    330345     
     346    public void runCustom() { 
     347    this.customHandler.run(is); 
     348    } 
     349     
     350    public void run() { 
     351    if(this.customHandler == null) { 
     352        runDefault(); 
     353    } else { 
     354        runCustom(); 
     355    } 
     356    } 
     357 
    331358    public String getOutput() {  
    332359    return outputstr.toString(); // implicit toString() call anyway. //return outputstr;  
     
    340367public static class OutputStreamGobbler extends Thread 
    341368{ 
    342     OutputStream os = null; 
    343     String inputstr = ""; 
    344     ExceptionHandler exceptionHandler = null; 
     369    private OutputStream os = null; 
     370    private String inputstr = ""; 
     371    private CustomProcessHandler customHandler = null; 
    345372 
    346373    public OutputStreamGobbler(OutputStream os) { 
     
    353380    this.inputstr = inputstr; 
    354381    } 
    355      
    356     public void setExceptionHandler(ExceptionHandler eHandler) { 
    357     exceptionHandler = eHandler; 
    358     } 
    359  
    360     public void run() 
    361     {    
     382 
     383    public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) { 
     384    this.os = os; 
     385    this.customHandler = customHandler; 
     386    } 
     387 
     388    // default run() behaviour 
     389    public void runDefault() { 
     390     
    362391    if (inputstr == null) { 
    363392        return; 
     
    365394     
    366395    BufferedWriter osw = null; 
    367     try { 
     396    try { 
    368397        osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); 
    369398        //System.out.println("@@@ SENDING LINE: " + inputstr); 
     
    385414        */ 
    386415    } catch (IOException ioe) { 
    387         if (this.exceptionHandler != null) { 
    388         this.exceptionHandler.gotException(ioe); 
    389         } else { 
    390         logger.error("Exception writing to SafeProcess' inputstream: ", ioe);        
    391         //ioe.printStackTrace(); 
    392         } 
    393          
     416        logger.error("Exception writing to SafeProcess' inputstream: ", ioe); 
     417        //System.err.println("Exception writing to SafeProcess' inputstream: "); 
     418        //ioe.printStackTrace();     
    394419    } finally { 
    395420        SafeProcess.closeResource(osw); 
    396421    } 
     422    } 
     423 
     424    // call the user's custom handler for the run() method 
     425    public void runCustom() { 
     426    this.customHandler.run(os);  
     427    } 
     428 
     429    public void run() 
     430    { 
     431    if(this.customHandler == null) { 
     432        runDefault(); 
     433    } else { 
     434        runCustom(); 
     435    } 
     436 
    397437    }    
    398438} // end static inner class OutputStreamGobbler