001package co.codewizards.cloudstore.core.util.childprocess; 002 003import static java.util.Objects.*; 004 005import java.io.IOException; 006import java.io.InputStream; 007import java.io.OutputStream; 008 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Thread continuously reading from an {@link InputStream} and writing everything to an {@link OutputStream}. 014 * <p> 015 * While this functionality is useful for multiple purposes, we use {@code DumpStreamThread} primarily for 016 * child processes. When starting another process (e.g. via the 017 * {@link java.lang.ProcessBuilder ProcessBuilder}), it is necessary to read the process' output. Without 018 * doing this, the child process might block forever - especially on Windows (having a very limited 019 * standard-output-buffer), this is a well-known problem. 020 * <p> 021 * Since the main thread (invoking the child process) usually blocks and waits for the child process to 022 * return (i.e. exit), dumping its standard-out and standard-error to a buffer or a log file is done on a 023 * separate thread. 024 * <p> 025 * Please note that {@code DumpStreamThread} can automatically write everything to the log. This is done via 026 * an instance of {@link LogDumpedStreamThread}. 027 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 028 */ 029public class DumpStreamThread extends Thread 030{ 031 private final Logger logger = LoggerFactory.getLogger(DumpStreamThread.class); 032 private final InputStream inputStream; 033 private final OutputStream outputStream; 034 private volatile boolean ignoreErrors = false; 035 private volatile boolean forceInterrupt = false; 036 private final LogDumpedStreamThread logDumpedStreamThread; 037 038 public void setIgnoreErrors(final boolean ignoreErrors) { 039 this.ignoreErrors = ignoreErrors; 040 } 041 042 @Override 043 public void interrupt() { 044 forceInterrupt = true; 045 super.interrupt(); 046 } 047 048 @Override 049 public boolean isInterrupted() { 050 return forceInterrupt || super.isInterrupted(); 051 } 052 053 /** 054 * Creates an instance of {@code DumpStreamThread}. 055 * @param inputStream the stream to read from. Must not be <code>null</code>. 056 * @param outputStream the stream to write to. Must not be <code>null</code>. 057 * @param childProcessLoggerName the name of the logger. May be <code>null</code> if logging is not 058 * desired. In case of <code>null</code>, logging is completely disabled. 059 */ 060 public DumpStreamThread(final InputStream inputStream, final OutputStream outputStream, final String childProcessLoggerName) { 061 this(inputStream, outputStream, null, childProcessLoggerName); 062 } 063 064 /** 065 * Creates an instance of {@code DumpStreamThread}. 066 * @param inputStream the stream to read from. Must not be <code>null</code>. 067 * @param outputStream the stream to write to. Must not be <code>null</code>. 068 * @param childProcessLogger the logger. May be <code>null</code> if logging is not desired. 069 */ 070 public DumpStreamThread(final InputStream inputStream, final OutputStream outputStream, final Logger childProcessLogger) { 071 this(inputStream, outputStream, childProcessLogger, null); 072 } 073 074 private DumpStreamThread(final InputStream inputStream, final OutputStream outputStream, 075 final Logger childProcessLogger, final String childProcessLoggerName) 076 { 077 requireNonNull(inputStream, "inputStream"); 078 requireNonNull(outputStream, "outputStream"); 079 080 this.inputStream = inputStream; 081 this.outputStream = outputStream; 082 if (childProcessLogger != null) 083 this.logDumpedStreamThread = new LogDumpedStreamThread(childProcessLogger); 084 else 085 this.logDumpedStreamThread = childProcessLoggerName == null ? null : new LogDumpedStreamThread(childProcessLoggerName); 086 } 087 088 @Override 089 public synchronized void start() { 090 if (logDumpedStreamThread != null) 091 logDumpedStreamThread.start(); 092 093 super.start(); 094 } 095 096 @Override 097 public void run() { 098 try { 099 final byte[] buffer = new byte[10240]; 100 while (!isInterrupted()) { 101 try { 102 final int bytesRead = inputStream.read(buffer); 103 if (bytesRead > 0) { 104 outputStream.write(buffer, 0, bytesRead); 105 106 if (logDumpedStreamThread != null) 107 logDumpedStreamThread.write(buffer, bytesRead); 108 } 109 } catch (final Throwable e) { 110 if (!ignoreErrors) 111 logger.error("run: " + e, e); //$NON-NLS-1$ 112 else 113 logger.info("run: " + e); //$NON-NLS-1$ 114 115 return; 116 } 117 } 118 } finally { 119 if (logDumpedStreamThread != null) 120 logDumpedStreamThread.interrupt(); 121 122 try { 123 outputStream.close(); 124 } catch (final IOException e) { 125 logger.warn("run: outputStream.close() failed: " + e, e); //$NON-NLS-1$ 126 } 127 } 128 } 129 130 /** 131 * Sets a {@link StringBuffer} for capturing all output. 132 * <p> 133 * Please note, that only data read from the stream after this was set is captured. You normally want to 134 * set this {@code StringBuffer}. 135 * <p> 136 * This feature is only available, if logging is enabled. 137 * @param outputStringBuffer the {@link StringBuffer} used for capturing. May be <code>null</code>. 138 */ 139 public void setOutputStringBuffer(final StringBuffer outputStringBuffer) { 140 if (logDumpedStreamThread == null) 141 throw new IllegalStateException("Not supported, if logging is disabled!"); 142 143 logDumpedStreamThread.setOutputStringBuffer(outputStringBuffer); 144 } 145 public StringBuffer getOutputStringBuffer() { 146 if (logDumpedStreamThread == null) 147 throw new IllegalStateException("Not supported, if logging is disabled!"); 148 149 return logDumpedStreamThread.getOutputStringBuffer(); 150 } 151 public void setOutputStringBufferMaxLength(final int outputStringBufferMaxLength) { 152 if (logDumpedStreamThread == null) 153 throw new IllegalStateException("Not supported, if logging is disabled!"); 154 155 logDumpedStreamThread.setOutputStringBufferMaxLength(outputStringBufferMaxLength); 156 } 157 public int getOutputStringBufferMaxLength() { 158 if (logDumpedStreamThread == null) 159 throw new IllegalStateException("Not supported, if logging is disabled!"); 160 161 return logDumpedStreamThread.getOutputStringBufferMaxLength(); 162 } 163 164 public void flushBuffer() { 165 if (logDumpedStreamThread != null) 166 logDumpedStreamThread.flushBuffer(); 167 } 168}