
com.gc.iotools.stream.os.OutputStreamToInputStream Maven / Gradle / Ivy
Show all versions of easystream Show documentation
package com.gc.iotools.stream.os;
/*
* Copyright (c) 2008, 2015 Gabriele Contini. This source code is released
* under the BSD License.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gc.iotools.stream.base.EasyStreamConstants;
import com.gc.iotools.stream.base.ExecutionModel;
import com.gc.iotools.stream.base.ExecutorServiceFactory;
import com.gc.iotools.stream.utils.LogUtils;
/**
*
* This class allow to read from an InputStream
the data who has
* been written to an OutputStream
(performs an
* OutputStream
-> InputStream
conversion).
*
*
* More detailiy it is an OutputStream
that, when extended, allows
* to read the data written to it from the InputStream
inside the
* method {@linkplain #doRead(InputStream)}.
*
*
* To use this class you must extend it and implement the method
* {@linkplain #doRead(InputStream)}. Inside this method place the logic that
* needs to read the data from the InputStream
. Then the data can
* be written to this class that implements OutputStream
. When
* {@linkplain #close()} method is called on the outer OutputStream
* an EOF is generated in the InputStream
passed in the
* {@linkplain #doRead(InputStream)}.
*
*
* The {@linkplain #doRead(InputStream)} call executes in another thread, so
* there is no warranty on when it will start and when it will end. Special care
* must be taken in passing variables to it: all the arguments must be final and
* inside {@linkplain #doRead(InputStream)} you shouldn't change the variables
* of the outer class.
*
*
* Any Exception threw inside the {@linkplain #doRead(InputStream)} method is
* propagated to the outer OutputStream
on the next
* write
operation.
*
*
* The method {@link #getResult()} suspend the outer thread and wait for the
* read from the internal stream is over. It returns when the
* doRead()
terminates and has produced its result.
*
*
* Some sample code:
*
*
*
* OutputStreamToInputStream<String> oStream2IStream =
* new OutputStreamToInputStream<String>() {
* protected String doRead(final InputStream istream) throws Exception {
* // Users of this class should place all the code that need to read data
* // from the InputStream in this method. Data available through the
* // InputStream passed as a parameter is the data that is written to the
* // OutputStream oStream2IStream through its write() methods.
* final String result = IOUtils.toString(istream);
* //"result" is here only for example of how returning data to the external OutputStream.
* //Real implementations might return "null" if they doesn't need to return a value.
* return result + " was processed.";
* }
* };
* try {
* // some data is written to the OutputStream, will be passed to the method
* // doRead(InputStream i) above and after close() is called the results
* // will be available through the getResults() method.
* oStream2IStream.write("test".getBytes());
* } finally {
* // don't miss the close (or a thread would not terminate correctly).
* oStream2IStream.close();
* }
* String result = oStream2IStream.getResult();
* //result now contains the string "test was processed."
*
*
* @param
* Type returned by the method {@link #getResults()} after the thread
* has finished.
* @since 1.0
* @author dvd.smnt
*/
public abstract class OutputStreamToInputStream extends PipedOutputStream {
/**
* This class executes in the second thread.
*
* @author dvd.smnt
*/
private final class DataConsumer implements Callable {
@Override
public synchronized T call() throws Exception {
T processResult;
try {
// avoid the internal class close the stream.
final CloseShieldInputStream istream = new CloseShieldInputStream(
OutputStreamToInputStream.this.inputstream);
processResult = doRead(istream);
} catch (final Exception e) {
OutputStreamToInputStream.this.abort = true;
throw e;
} finally {
// empty the internal inputstream so the outer thread doesn't
// lock
emptyInputStream();
OutputStreamToInputStream.this.inputstream.close();
}
return processResult;
}
private void emptyInputStream() {
try {
final byte[] buffer = new byte[EasyStreamConstants.SKIP_BUFFER_SIZE];
while (OutputStreamToInputStream.this.inputstream.read(buffer) >= 0) {
;
// empty block: just throw bytes away
}
} catch (final IOException e) {
if ((e.getMessage() != null)
&& (e.getMessage().indexOf("closed") > 0)) {
OutputStreamToInputStream.LOG
.debug("Stream already closed");
} else {
OutputStreamToInputStream.LOG.error(
"IOException while empty InputStream a "
+ "thread can be locked", e);
}
} catch (final Throwable e) {
OutputStreamToInputStream.LOG.error(
"IOException while empty InputStream a "
+ "thread can be locked", e);
}
}
}
/**
* Extends PipedInputStream to allow set the default buffer size.
*/
private final class MyPipedInputStream extends PipedInputStream {
MyPipedInputStream(final int bufferSize) {
super.buffer = new byte[bufferSize];
}
}
// Default timeout in milliseconds.
private static final int DEFAULT_TIMEOUT = 15 * 60 * 1000;
/**
* The default pipe buffer size for the newly created pipes.
*/
private static int defaultPipeSize = EasyStreamConstants.DEFAULT_PIPE_SIZE;
private static final Logger LOG = LoggerFactory
.getLogger(OutputStreamToInputStream.class);
/**
* Set the size for the pipe circular buffer. This setting has effect for
* the newly created OutputStreamToInputStream
. Default is 4096
* bytes.
*
* @since 1.2.3
* @param defaultPipeSize
* The default pipe buffer size in bytes.
*/
public static void setDefaultPipeSize(final int defaultPipeSize) {
OutputStreamToInputStream.defaultPipeSize = defaultPipeSize;
}
private boolean abort = false;
private boolean closeCalled = false;
private final ExecutorService executorService;
private final InputStream inputstream;
private final boolean joinOnClose;
private Future writingResult = null;
/**
*
* Creates a new OutputStreamToInputStream
. It uses the default
* {@link ExecutionModel#THREAD_PER_INSTANCE} thread instantiation strategy.
* This means that a new thread is created for every instance of
* OutputStreamToInputStream
.
*
*
* When the {@linkplain #close()} method is called this class wait for the
* internal thread to terminate.
*
*
* @throws java.lang.IllegalStateException
* Exception thrown if pipe can't be created.
*/
public OutputStreamToInputStream() {
this(true, ExecutionModel.THREAD_PER_INSTANCE);
}
/**
*
* Creates a new OutputStreamToInputStream
. It uses the default
* {@link ExecutionModel#THREAD_PER_INSTANCE} thread instantiation strategy.
* This means that a new thread is created for every instance of
* OutputStreamToInputStream
.
*
*
* If startImmediately
is true
the internal thread
* will start before the constructor completes. This is the best way if
* you're doing anonymous subclassing. While if you do explicit sublcassing
* you should set this parameter to false to allow the constructor of the
* superclass to complete before the threads are started.
*
*
* When the {@linkplain #close()} method is called this class wait for the
* internal thread to terminate.
*
*
* @since 1.2.13
* @throws java.lang.IllegalStateException
* Exception thrown if pipe can't be created.
*/
public OutputStreamToInputStream(final boolean startImmediately) {
this(startImmediately, true, ExecutorServiceFactory
.getExecutor(ExecutionModel.THREAD_PER_INSTANCE),
defaultPipeSize);
}
/**
*
* Creates a new OutputStreamToInputStream
. It let the user
* specify the thread instantiation service and what will happen upon the
* invocation of close()
method.
*
*
* If startImmediately
is true
the internal thread
* will start before the constructor completes. This is the best way if
* you're doing anonymous subclassing. While if you do explicit sublcassing
* you should set this parameter to false to allow the constructor of the
* superclass to complete before the threads are started.
*
* If joinOnClose
is true
when the
* close()
method is invoked this class will wait for the
* internal thread to terminate.
*
* It also let the user specify the size of the pipe buffer to allocate.
*
*
* @since 1.2.13
* @param startImmediately
* if true
the internal thread will start
* immediately after this constructor completes.
* @param joinOnClose
* if true
the internal thread will be joined when
* close is invoked.
* @param executorService
* Service for executing the internal thread.
* @param pipeBufferSize
* The size of the pipe buffer to allocate.
* @throws java.lang.IllegalStateException
* Exception thrown if pipe can't be created.
*/
public OutputStreamToInputStream(final boolean startImmediately,
final boolean joinOnClose, final ExecutorService executorService,
final int pipeBufferSize) {
if (executorService == null) {
throw new IllegalArgumentException("executor service can't be null");
}
final String callerId = LogUtils.getCaller(getClass());
final PipedInputStream pipedIS = new MyPipedInputStream(pipeBufferSize);
try {
pipedIS.connect(this);
} catch (final IOException e) {
throw new IllegalStateException("Error during pipe creaton", e);
}
this.joinOnClose = joinOnClose;
this.inputstream = pipedIS;
this.executorService = executorService;
LOG.debug("invoked by[{}] queued for start.", callerId);
if (startImmediately) {
initializeIfNecessary();
}
}
/**
*
* Creates a new OutputStreamToInputStream
. It let the user
* specify the thread instantiation strategy and what will happen upon the
* invocation of close()
method.
*
*
* If joinOnClose
is true
when the
* close()
method is invoked this class will wait for the
* internal thread to terminate.
*
*
* @see ExecutionModel
* @param joinOnClose
* if true
the internal thread will be joined when
* close is invoked.
* @param executionModel
* The strategy for allocating threads.
* @throws java.lang.IllegalStateException
* Exception thrown if pipe can't be created.
*/
public OutputStreamToInputStream(final boolean joinOnClose,
final ExecutionModel executionModel) {
this(joinOnClose, ExecutorServiceFactory.getExecutor(executionModel));
}
/**
*
* Creates a new OutputStreamToInputStream
. It let the user
* specify the thread instantiation service and what will happen upon the
* invocation of close()
method.
*
*
* If joinOnClose
is true
when the
* close()
method is invoked this class will wait for the
* internal thread to terminate.
*
*
* @since 1.2.6
* @param joinOnClose
* if true
the internal thread will be joined when
* close is invoked.
* @param executorService
* Service for executing the internal thread.
* @throws java.lang.IllegalStateException
* Exception thrown if pipe can't be created.
*/
public OutputStreamToInputStream(final boolean joinOnClose,
final ExecutorService executorService) {
this(joinOnClose, executorService, defaultPipeSize);
}
/**
*
* Creates a new OutputStreamToInputStream
. It let the user
* specify the thread instantiation service and what will happen upon the
* invocation of close()
method.
*
*
* If joinOnClose
is true
when the
* close()
method is invoked this class will wait for the
* internal thread to terminate.
*
*
* It also let the user specify the size of the pipe buffer to allocate.
*
*
* @since 1.2.6
* @param joinOnClose
* if true
the internal thread will be joined when
* close is invoked.
* @param executorService
* Service for executing the internal thread.
* @param pipeBufferSize
* The size of the pipe buffer to allocate.
* @throws java.lang.IllegalStateException
* Exception thrown if pipe can't be created.
*/
public OutputStreamToInputStream(final boolean joinOnClose,
final ExecutorService executorService, final int pipeBufferSize) {
this(false, joinOnClose, executorService, pipeBufferSize);
}
/**
*
* This method is called just before the close method completes, and after
* the eventual join with the internal thread.
*
*
* It is an extension point designed for applications that need to perform
* some operation when the OutputStream
is closed.
*
*
* @since 1.2.9
* @throws IOException
* threw when the extension point wants to launch an exception.
*/
protected void afterClose() throws IOException {
// extension point;
}
/** {@inheritDoc} */
@Override
public final void close() throws IOException {
internalClose(this.joinOnClose, TimeUnit.MILLISECONDS, DEFAULT_TIMEOUT);
}
/**
* When this method is called the internal thread is always waited for
* completion.
*
* @param timeout
* maximum time to wait for the internal thread to finish.
* @param tu
* Time unit for the timeout.
* @throws java.io.IOException
* Threw if some problem (timeout or internal exception) occurs.
* see the getCause()
method for the explanation.
*/
public final void close(final long timeout, final TimeUnit tu)
throws IOException {
internalClose(true, tu, timeout);
}
/**
*
* This method has to be implemented to use this class. It allows to
* retrieve the data written to the outer OutputStream
from the
* InputStream
passed as a parameter.
*
*
* Any exception eventually threw inside this method will be propagated to
* the external OutputStream
. When the next
* {@linkplain #write(byte[])} operation is called an
* IOException
will be thrown and the original exception can be
* accessed calling the getCause() method on the IOException. It will also
* be available by calling the method {@link #getResults()}.
*
*
* @param istream
* The InputStream where the data can be retrieved.
* @return Optionally returns a result of the elaboration.
* @throws java.lang.Exception
* If an java.lang.Exception
occurs during the
* elaboration it can be thrown. It will be propagated to the
* external OutputStream
and will be available
* calling the method {@link #getResults()}.
*/
protected abstract T doRead(InputStream istream) throws Exception;
/** {@inheritDoc} */
@Override
public final void flush() throws IOException {
initializeIfNecessary();
if (this.abort) {
// internal thread is already aborting. wait for short time.
internalClose(true, TimeUnit.SECONDS, 1);
} else {
super.flush();
}
}
/**
*
* This method returns the result of the method {@link #doRead(InputStream)}
* and ensure the previous method is over.
*
*
* This method suspend the calling thread and waits for the function
* {@link #doRead(InputStream)} to finish. It returns when the
* doRead()
terminates and has produced its result.
*
*
* It must be called after the method {@link #close()} otherwise a
* IllegalStateException
is thrown.
*
*
* @exception InterruptedException
* Thrown when the thread is interrupted.
* @exception Exception
* Thrown if the method {@linkplain #doRead(InputStream)}
* threw an Exception.
*
* @return the object returned from the doRead() method.
*/
public final T getResult() throws Exception {
initializeIfNecessary();
if (!this.closeCalled) {
this.closeCalled = true;
super.close();
}
T result;
try {
result = this.writingResult.get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception) {
throw (Exception)e.getCause();
} else {
throw e;
}
}
return result;
}
private void initializeIfNecessary() {
if (this.writingResult == null) {
final DataConsumer executingProcess = new DataConsumer();
this.writingResult = this.executorService.submit(executingProcess);
}
}
private void internalClose(final boolean join, final TimeUnit timeUnit,
final long timeout) throws IOException {
if (!this.closeCalled) {
initializeIfNecessary();
this.closeCalled = true;
super.close();
if (join) {
// waiting for thread to finish..
try {
this.writingResult.get(timeout, timeUnit);
} catch (final ExecutionException e) {
final IOException e1 = new IOException(
"The doRead() threw exception. Use "
+ "getCause() for details.");
e1.initCause(e.getCause());
throw e1;
} catch (final InterruptedException e) {
final IOException e1 = new IOException(
"Waiting of the thread has been interrupted");
e1.initCause(e);
throw e1;
} catch (final TimeoutException e) {
if (!this.writingResult.isDone()) {
this.writingResult.cancel(true);
}
final IOException e1 = new IOException(
"Waiting for the internal "
+ "thread to finish took more than ["
+ timeout + "] " + timeUnit);
e1.initCause(e);
throw e1;
}
}
afterClose();
}
}
/** {@inheritDoc} */
@Override
public final void write(final byte[] bytes) throws IOException {
initializeIfNecessary();
if (this.abort) {
// internal thread is already aborting. wait for short time.
internalClose(true, TimeUnit.SECONDS, 1);
} else {
super.write(bytes);
}
}
/** {@inheritDoc} */
@Override
public final void write(final byte[] bytes, final int offset,
final int length) throws IOException {
initializeIfNecessary();
if (this.abort) {
// internal thread is already aborting. wait for short time.
internalClose(true, TimeUnit.SECONDS, 1);
} else {
super.write(bytes, offset, length);
}
}
/** {@inheritDoc} */
@Override
public final void write(final int bytetowr) throws IOException {
initializeIfNecessary();
if (this.abort) {
// internal thread is already aborting. wait for short time.
internalClose(true, TimeUnit.SECONDS, 1);
} else {
super.write(bytetowr);
}
}
}