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

org.wisdom.framework.vertx.AsyncInputStream Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/*
 * #%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