All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gc.iotools.stream.os.OutputStreamToInputStream Maven / Gradle / Ivy

package com.gc.iotools.stream.os;

/*
 * Copyright (c) 2008,2009 Davide Simonetti.
 * This source code is released under the BSD License.
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;

/**
 * 

* 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()}. *

*

* 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 #getResults()} 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 method.  
 * 		final String result = IOUtils.toString(istream);
 * 		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.getResults();
 * //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 OutputStream { /** * This class executes in the second thread. * * @author dvd.smnt * */ private final class DataConsumer implements Callable { private final InputStream inputstream; DataConsumer(final InputStream istream) { this.inputstream = istream; } public synchronized T call() throws Exception { T processResult; try { // avoid the internal class close the stream. final CloseShieldInputStream istream = new CloseShieldInputStream( this.inputstream); processResult = doRead(istream); } catch (final Exception e) { OutputStreamToInputStream.this.abort = true; throw e; } finally { emptyInputStream(); this.inputstream.close(); } return processResult; } private void emptyInputStream() { try { final byte[] buffer = new byte[EasyStreamConstants.SKIP_BUFFER_SIZE]; while (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. *

* *

* Will be removed in the 1.3 release. Use {@link #setDefaultPipeSize(int)} * instead. *

* * @since 1.2.0 * @param defaultPipeSize * The default pipe buffer size in bytes. * @deprecated * @see #setDefaultPipeSize(int) */ @Deprecated public static void setDefaultBufferSize(final int defaultPipeSize) { OutputStreamToInputStream.defaultPipeSize = defaultPipeSize; } /** * 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 closeCalled = false; private boolean abort = false; private final boolean joinOnClose; private final PipedOutputStream pipedOs; private final Future writingResult; /** *

* 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 IOException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream() throws IOException { this(true, ExecutionModel.THREAD_PER_INSTANCE); } /** *

* 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 * Strategy for allocating threads. * @throws IOException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean joinOnClose, final ExecutionModel executionModel) throws IOException { this(joinOnClose, ExecutorServiceFactory.getExecutor(executionModel)); } /** * * @param joinOnClose * if true the internal thread will be joined when * close is invoked. * @param executorService * Service for executing the internal thread. * @throws IOException * Exception thrown if pipe can't be created. */ public OutputStreamToInputStream(final boolean joinOnClose, final ExecutorService executorService) throws IOException { if (executorService == null) { throw new IllegalArgumentException( "executor service can't be null"); } this.pipedOs = new PipedOutputStream(); final PipedInputStream pipedIS = new MyPipedInputStream( defaultPipeSize); pipedIS.connect(this.pipedOs); final DataConsumer executingProcess = new DataConsumer(pipedIS); this.joinOnClose = joinOnClose; this.writingResult = executorService.submit(executingProcess); } /** * {@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 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); } /** * {@inheritDoc} */ @Override public final void flush() throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedOs.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 ExecutionException * Thrown if the method {@linkplain #doRead(InputStream)} * threw an Exception. The getCause() returns * the original Exception. * @throws IllegalStateException * When it is called before the method {@link #close()} has been * called. * @return the object returned from the doRead() method. */ public final T getResults() throws InterruptedException, ExecutionException { if (!this.closeCalled) { throw new IllegalStateException("Method close() must be called" + " before getResults"); } return this.writingResult.get(); } /** * {@inheritDoc} */ @Override public final void write(final byte[] bytes) throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedOs.write(bytes); } } /** * {@inheritDoc} */ @Override public final void write(final byte[] bytes, final int offset, final int length) throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedOs.write(bytes, offset, length); } } /** * {@inheritDoc} */ @Override public final void write(final int bytetowr) throws IOException { if (this.abort) { // internal thread is already aborting. wait for short time. internalClose(true, TimeUnit.SECONDS, 1); } else { this.pipedOs.write(bytetowr); } } private void internalClose(final boolean join, final TimeUnit timeUnit, final long timeout) throws IOException { if (!this.closeCalled) { this.closeCalled = true; this.pipedOs.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; } } } } /** *

* 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 Exception * If an 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; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy