
com.gc.iotools.stream.is.InputStreamFromOutputStream Maven / Gradle / Ivy
Show all versions of easystream Show documentation
package com.gc.iotools.stream.is;
/*
* Copyright (c) 2008, 2015 Gabriele Contini. This source code is released
* under the BSD License.
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.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 the data written to an OutputStream from an
* InputStream.
*
*
* To use this class you must subclass it and implement the abstract method
* {@linkplain #produce(OutputStream)}. The data who is produced inside this
* function can be written to the sink OutputStream
passed as a
* parameter. Later it can be read back from from the
* InputStreamFromOutputStream
class (whose ancestor is
* java.io.InputStream
).
*
*
*
* final String dataId=//id of some data.
* final InputStreamFromOutputStream<String> isos
* = new InputStreamFromOutputStream<String>() {
* @Override
* public String produce(final OutputStream dataSink) throws Exception {
* //call your application function who produces the data here
* //WARNING: we're in another thread here, so this method shouldn't
* //write any class field or make assumptions on the state of the class.
* return produceMydata(dataId,dataSink)
* }
* };
* try {
* //now you can read from the InputStream the data that was written to the
* //dataSink OutputStream
* byte[] read=IOUtils.toByteArray(isos);
* //Use data here
* } catch (final IOException e) {
* //Handle exception here
* } finally {
* isos.close();
* }
* //You can get the result of produceMyData after the stream has been closed.
* String resultOfProduction = isos.getResult();
*
*
* This class encapsulates a pipe and a Thread
, hiding the
* complexity of using them. It is possible to select different strategies for
* allocating the internal thread or even specify the
* {@linkplain ExecutorService} for thread execution.
*
*
* @param
* Optional result returned by the function
* {@linkplain #produce(OutputStream)} after the data has been
* written. It can be obtained calling the {@linkplain #getResult()}
* @see ExecutionModel
* @author dvd.smnt
* @since 1.0
*/
public abstract class InputStreamFromOutputStream extends PipedInputStream {
/**
* This inner class run in another thread and calls the
* {@link #produce(OutputStream)} method.
*
* @author dvd.smnt
*/
private final class DataProducer implements Callable {
@Override
public T call() throws Exception {
final String threadName = Thread.currentThread().getName();
T result;
InputStreamFromOutputStream.ACTIVE_THREAD_NAMES.add(threadName);
InputStreamFromOutputStream.LOG.debug("thread [" + threadName
+ "] started.");
try {
result = produce(InputStreamFromOutputStream.this.pipedOs);
} finally {
closeStream();
InputStreamFromOutputStream.ACTIVE_THREAD_NAMES
.remove(threadName);
InputStreamFromOutputStream.LOG.debug("thread [" + threadName
+ "] closed.");
}
return result;
}
private void closeStream() {
try {
InputStreamFromOutputStream.this.pipedOs.close();
} catch (final IOException e) {
if ((e.getMessage() != null)
&& (e.getMessage().indexOf("closed") > 0)) {
InputStreamFromOutputStream.LOG
.debug("Stream already closed");
} else {
InputStreamFromOutputStream.LOG.error(
"IOException closing OutputStream"
+ " Thread might be locked", e);
}
} catch (final Throwable t) {
InputStreamFromOutputStream.LOG
.error("Error closing InputStream"
+ " Thread might be locked", t);
}
}
}
/**
* Collection for debugging purpose containing names of threads (name is
* calculated from the instantiation line. See getCaller()
.
*/
private static final List ACTIVE_THREAD_NAMES = Collections
.synchronizedList(new ArrayList());
/**
* 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(InputStreamFromOutputStream.DataProducer.class);
/**
* This method can be used for debugging purposes to get a list of the
* currently active threads.
*
* @return Array containing names of the threads currently active.
*/
public static final String[] getActiveThreadNames() {
final String[] result;
synchronized (InputStreamFromOutputStream.ACTIVE_THREAD_NAMES) {
result = InputStreamFromOutputStream.ACTIVE_THREAD_NAMES
.toArray(new String[0]);
}
return result;
}
/**
* Set the size for the pipe buffer for the newly created
* InputStreamFromOutputStream
. Default is 4096 bytes.
*
* @since 1.2.2
* @param defaultPipeSize
* the default pipe buffer size in bytes.
*/
public static void setDefaultPipeSize(final int defaultPipeSize) {
InputStreamFromOutputStream.defaultPipeSize = defaultPipeSize;
}
private final String callerId;
private boolean closeCalled = false;
private final ExecutorService executorService;
private Future futureResult;
private final boolean joinOnClose;
private boolean started = false;
private final PipedOutputStream pipedOs = new PipedOutputStream() {
private boolean outputStreamCloseCalled = false;
//duplicate close hangs the pipeStream see issue #36
@Override
public void close() throws IOException {
synchronized (this) {
if (outputStreamCloseCalled) {
return;
}
outputStreamCloseCalled = true;
}
super.close();
}
};
/**
*
* It creates a InputStreamFromOutputStream
with a
* THREAD_PER_INSTANCE thread strategy.
*
*
* @see ExecutionModel#THREAD_PER_INSTANCE
*/
public InputStreamFromOutputStream() {
this(ExecutionModel.THREAD_PER_INSTANCE);
}
public InputStreamFromOutputStream(final boolean startImmediately,
final boolean joinOnClose, final ExecutorService executor,
final int pipeBufferSize) {
super(pipeBufferSize);
this.callerId = LogUtils.getCaller(this.getClass());
this.joinOnClose = joinOnClose;
this.executorService = executor;
try {
connect(this.pipedOs);
} catch (final IOException e) {
throw new RuntimeException("Error during pipe creaton", e);
}
if (startImmediately) {
checkInitialized();
}
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* choose the thread allocation strategy he likes.
*
*
* This class executes the produce method in a thread created internally.
*
*
* @since 1.2.2
* @see ExecutionModel
* @param executionModel
* Defines how the internal thread is allocated.
* @param joinOnClose
* If true
the {@linkplain #close()} method will
* also wait for the internal thread to finish.
*/
public InputStreamFromOutputStream(final boolean joinOnClose,
final ExecutionModel executionModel) {
this(joinOnClose, ExecutorServiceFactory.getExecutor(executionModel));
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* specify the ExecutorService that will execute the
* {@linkplain #produce(OutputStream)} method.
*
*
* @since 1.2.2
* @see ExecutorService
* @param executor
* Defines the ExecutorService that will allocate the the
* internal thread.
* @param joinOnClose
* If true
the {@linkplain #close()} method will
* also wait for the internal thread to finish.
*/
public InputStreamFromOutputStream(final boolean joinOnClose,
final ExecutorService executor) {
this(joinOnClose, executor, defaultPipeSize);
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* specify the ExecutorService
that will execute the
* {@linkplain #produce(OutputStream)} method and the pipe buffer size.
*
*
* Using this method the default size is ignored.
*
*
* @since 1.2.6
* @see ExecutorService
* @param executor
* Defines the ExecutorService that will allocate the the
* internal thread.
* @param joinOnClose
* If true
the {@linkplain #close()} method will
* also wait for the internal thread to finish.
* @param pipeBufferSize
* The size of the pipe buffer to allocate.
*/
public InputStreamFromOutputStream(final boolean joinOnClose,
final ExecutorService executor, final int pipeBufferSize) {
this(false, joinOnClose, executor, pipeBufferSize);
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* choose the thread allocation strategy he likes.
*
*
* This class executes the produce method in a thread created internally.
*
*
* @see ExecutionModel
* @param executionModel
* Defines how the internal thread is allocated.
*/
public InputStreamFromOutputStream(final ExecutionModel executionModel) {
this(false, executionModel);
}
/**
*
* It creates a InputStreamFromOutputStream
and let the user
* specify the ExecutorService that will execute the
* {@linkplain #produce(OutputStream)} method.
*
*
* @see ExecutorService
* @param executor
* Defines the ExecutorService that will allocate the the
* internal thread.
*/
public InputStreamFromOutputStream(final ExecutorService executor) {
this(false, executor);
}
/**
*
* This method is called just before the {@link #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 InputStream
is closed.
*
*
* @since 1.2.9
* @throws IOException
* threw when the subclass wants to communicate an exception
* during close.
*/
protected void afterClose() throws IOException {
// extension point;
}
private void checkException() throws IOException {
try {
this.futureResult.get(1, TimeUnit.SECONDS);
} catch (final ExecutionException e) {
final Throwable t = e.getCause();
final IOException e1 = new IOException("Exception producing data");
e1.initCause(t);
throw e1;
} catch (final InterruptedException e) {
final IOException e1 = new IOException("Thread interrupted");
e1.initCause(e);
throw e1;
} catch (final TimeoutException e) {
LOG.error("This timeout should never happen, "
+ "the thread should terminate correctly. "
+ "Please contact io-tools support.", e);
}
}
private synchronized void checkInitialized() {
if (!this.started) {
this.started = true;
final Callable executingCallable = new DataProducer();
this.futureResult = this.executorService.submit(executingCallable);
InputStreamFromOutputStream.LOG.debug(
"thread invoked by[{}] queued for start.", this.callerId);
}
}
/** {@inheritDoc} */
@Override
public void close() throws IOException {
checkInitialized();
if (!this.closeCalled) {
this.closeCalled = true;
super.close();
if (this.joinOnClose) {
try {
getResult();
} catch (final Exception e) {
final IOException e1 = new IOException(
"The internal stream threw exception");
e1.initCause(e);
throw e1;
}
}
afterClose();
}
}
/**
*
* Returns the object that was previously returned by the
* {@linkplain #produce(OutputStream)} method. It performs all the
* synchronization operations needed to read the result and waits for the
* internal thread to terminate.
*
*
* This method automatically invokes the {@linkplain #close()} method on the,
* stream.
*
*
* @since 1.2.0
* @return Object that was returned by the
* {@linkplain #produce(OutputStream)} method.
* @throws java.lang.Exception
* If the {@linkplain #produce(OutputStream)} method threw an
* java.lang.Exception this method will throw again the same
* exception.
*/
public T getResult() throws Exception {
if (!this.closeCalled) {
this.closeCalled = true;
super.close();
}
T result;
try {
result = this.futureResult.get();
} catch (final ExecutionException e) {
final Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
}
throw e;
}
return result;
}
/**
*
* This method must be implemented by the user of this class to produce the
* data that must be read from the external InputStream
.
*
*
* Special care must be paid passing arguments to this method or setting
* global fields because it is executed in another thread.
*
*
* The right way to set a field variable is to return a value in the
* produce
and retrieve it in the getResult().
*
*
* @return The implementing class can use this to return a result of data
* production. The result will be available through the method
* {@linkplain #getResult()}.
* @param sink
* the implementing class should write its data to this stream.
* @throws java.lang.Exception
* the exception eventually thrown by the implementing class is
* returned by the {@linkplain #read()} methods.
* @see #getResult()
*/
protected abstract T produce(final OutputStream sink) throws Exception;
/** {@inheritDoc} */
@Override
public int read() throws IOException {
checkInitialized();
final int result = super.read();
if (result < 0) {
checkException();
}
return result;
}
/** {@inheritDoc} */
@Override
public int read(final byte[] b, final int off, final int len)
throws IOException {
checkInitialized();
final int result = super.read(b, off, len);
if (result < 0) {
checkException();
}
return result;
}
/** {@inheritDoc} */
@Override
public int read(byte[] b) throws IOException {
checkInitialized();
final int result = super.read(b);
if (result < 0) {
checkException();
}
return result;
}
}