
org.wisdom.framework.vertx.AsyncInputStream Maven / Gradle / Ivy
/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.wisdom.framework.vertx;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.commons.io.IOUtils;
import org.vertx.java.core.Context;
import org.vertx.java.core.Handler;
import org.vertx.java.core.Vertx;
import org.vertx.java.core.buffer.Buffer;
import org.vertx.java.core.streams.ReadStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
/**
* Reads an input stream in an asynchronous and Vert.X compliant way.
* Instances acts a finite state machine with 3 different states: {@literal ACTIVE, PAUSED,
* CLOSED}. The transition between the states depends on the control flow (i.e. the pump consuming the stream).
*/
public class AsyncInputStream implements ReadStream {
/**
* PAUSED state.
*/
public static final int STATUS_PAUSED = 0;
/**
* ACTIVE state.
*/
public static final int STATUS_ACTIVE = 1;
/**
* CLOSED state.
*/
public static final int STATUS_CLOSED = 2;
/**
* Default chunk size.
*/
static final int DEFAULT_CHUNK_SIZE = 8192;
/**
* An empty byte array.
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/**
* The Vert.X instance.
*/
private final Vertx vertx;
/**
* The executor used to read the chunks.
*/
private final ExecutorService executor;
/**
* A push back input stream wrapping the read input stream.
*/
private final PushbackInputStream in;
/**
* The chunk size.
*/
private final int chunkSize;
/**
* The current state.
*/
private int state = STATUS_ACTIVE;
/**
* The close handler invoked when the stream is completed or closed.
*/
private Handler closeHandler;
/**
* The data handler receiving the data read from the stream.
*/
private Handler dataHandler;
/**
* The failure handler called when an error is encountered while reading the stream.
*/
private Handler failureHandler;
/**
* The number of byte read form the input stream.
*/
private int offset;
private Context context;
/**
* Creates an instance of {@link org.wisdom.framework.vertx.AsyncInputStream}. This constructor uses the default
* chunk size.
*
* @param vertx the Vert.X instance
* @param executor the executor used to read the chunk
* @param in the input stream to read
*/
public AsyncInputStream(Vertx vertx, ExecutorService executor, InputStream in) {
this(vertx, executor, in, DEFAULT_CHUNK_SIZE);
}
/**
* Creates an instance of {@link org.wisdom.framework.vertx.AsyncInputStream}.
*
* @param vertx the Vert.X instance
* @param executor the executor used to read the chunk
* @param in the input stream to read
* @param chunkSize the chunk size
*/
public AsyncInputStream(Vertx vertx, ExecutorService executor, InputStream in, int chunkSize) {
if (in == null) {
throw new NullPointerException("in");
}
if (vertx == null) {
throw new NullPointerException("vertx");
}
this.vertx = vertx;
if (chunkSize <= 0) {
throw new IllegalArgumentException(
"chunkSize: " + chunkSize +
" (expected: a positive integer)");
}
if (in instanceof PushbackInputStream) {
this.in = (PushbackInputStream) in;
} else {
this.in = new PushbackInputStream(in);
}
this.chunkSize = chunkSize;
this.executor = executor;
}
/**
* Gets the current state.
*
* @return the state
*/
public int getState() {
return state;
}
/**
* Sets the end handler.
*
* @param endHandler the handler called when the stream is read completely.
* @return the current {@link org.wisdom.framework.vertx.AsyncInputStream}
*/
@Override
public AsyncInputStream endHandler(Handler endHandler) {
this.closeHandler = endHandler;
return this;
}
/**
* Sets the data handler. This method 'starts' the stream reading.
*
* @param handler the handler called with the read chunks from the backed input stream. Must not be {@code null}.
* @return the current {@link org.wisdom.framework.vertx.AsyncInputStream}
*/
@Override
public AsyncInputStream dataHandler(Handler handler) {
if (handler == null) {
throw new IllegalArgumentException("handler");
}
this.dataHandler = handler;
doRead();
return this;
}
/**
* The method actually reading the stream.
* Except the first calls, this method is executed within an Akka thread.
*/
private void doRead() {
if (context == null) {
context = vertx.currentContext();
}
if (state == STATUS_ACTIVE) {
final Handler dataHandler = this.dataHandler;
final Handler closeHandler = this.closeHandler;
executor.submit(
new Runnable() {
/**
* Reads a chunk and dispatches the read bytes.
*/
@Override
public void run() {
try {
final byte[] bytes = readChunk();
if (bytes == null || bytes.length == 0) {
// null or 0 means we reach the end of the stream, invoke the close handler.
state = STATUS_CLOSED;
IOUtils.closeQuietly(in);
context.runOnContext(new Handler() {
/**
* Invokes the close handler.
* @param event irrelevant
*/
@Override
public void handle(Void event) {
if (closeHandler != null) {
closeHandler.handle(null);
}
}
});
} else {
// We still have data, dispatch it.
context.runOnContext(new Handler() {
/**
* Provides the read data to the data handler and enqueue the reading of the
* next chunk.
* @param event irrelevant
*/
@Override
public void handle(Void event) {
dataHandler.handle(new Buffer(bytes));
// The next chunk will be read in another call, and maybe another thread.
// As the data was already given to the data handler, this is fine.
doRead();
}
});
}
} catch (final Exception e) {
// Error detected, invokes the failure handler.
state = STATUS_CLOSED;
IOUtils.closeQuietly(in);
/**
* Invokes the failure handler.
* @param event irrelevant
*/
context.runOnContext(new Handler() {
@Override
public void handle(Void event) {
if (failureHandler != null) {
failureHandler.handle(e);
}
}
});
}
}
});
}
}
/**
* Pauses the reading.
*
* @return the current {@code AsyncInputStream}
*/
@Override
public AsyncInputStream pause() {
if (state == STATUS_ACTIVE) {
state = STATUS_PAUSED;
}
return this;
}
/**
* Resumes the reading.
*
* @return the current {@code AsyncInputStream}
*/
@Override
public AsyncInputStream resume() {
switch (state) {
case STATUS_CLOSED:
throw new IllegalStateException("Cannot resume, already closed");
case STATUS_PAUSED:
state = STATUS_ACTIVE;
doRead();
}
return this;
}
/**
* Sets the failure handler.
*
* @param handler the failure handler.
* @return the current {@link org.wisdom.framework.vertx.AsyncInputStream}
*/
@Override
public AsyncInputStream exceptionHandler(Handler handler) {
this.failureHandler = handler;
return this;
}
/**
* Retrieves the number of read bytes.
*
* @return the number of read bytes
*/
public long transferredBytes() {
return offset;
}
/**
* Checks whether or not the current stream is closed.
*
* @return {@code true} if the current {@link org.wisdom.framework.vertx.AsyncInputStream} is in the "CLOSED" state.
*/
public boolean isClosed() {
return state == STATUS_CLOSED;
}
/**
* Checks whether or not we reach the end of the stream.
*
* @return {@code true} if we read the end of the stream, {@code false} otherwise
* @throws Exception if the stream cannot be read.
*/
public boolean isEndOfInput() throws Exception {
int b = in.read();
if (b < 0) {
return true;
} else {
in.unread(b);
return false;
}
}
/**
* Reads a chunk.
* @return the read bytes, empty if we reached the end of the stream. The returned array has exactly the sisize
* of the chunk.
* @throws Exception if the stream cannot be read.
*/
private byte[] readChunk() throws Exception {
if (isEndOfInput()) {
return EMPTY_BYTE_ARRAY;
}
try {
// transfer to buffer
byte[] tmp = new byte[chunkSize];
int readBytes = in.read(tmp);
if (readBytes <= 0) {
return null;
}
byte[] buffer = new byte[readBytes];
System.arraycopy(tmp, 0, buffer, 0, readBytes);
offset += readBytes;
return buffer;
} catch (IOException e) {
// Close the stream, and propagate the exception.
IOUtils.closeQuietly(in);
throw e;
}
}
public AsyncInputStream setContext(Context context) {
this.context = context;
return this;
}
private Context context() {
if (this.context == null) {
return vertx.currentContext();
} else {
return this.context;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy