com.kolich.curacao.examples.controllers.NonBlockingIOExampleController.java Source code

Java tutorial

Introduction

Here is the source code for com.kolich.curacao.examples.controllers.NonBlockingIOExampleController.java

Source

/**
 * Copyright (c) 2015 Mark S. Kolich
 * http://mark.koli.ch
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.kolich.curacao.examples.controllers;

import static java.nio.channels.Channels.newChannel;
import static org.apache.commons.codec.binary.StringUtils.getBytesUtf8;
import static org.apache.commons.io.IOUtils.LINE_SEPARATOR_UNIX;
import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;

import com.kolich.common.functional.option.None;
import com.kolich.common.functional.option.Option;
import com.kolich.common.functional.option.Some;
import com.kolich.curacao.annotations.Controller;
import com.kolich.curacao.annotations.methods.PUT;

@Controller
public final class NonBlockingIOExampleController {

    private static final Logger logger__ = getLogger(NonBlockingIOExampleController.class);

    // curl --noproxy localhost -vvv -k -X PUT -d "@enormous.out" --limit-rate 1K http://localhost:8080/api/nonblocking

    // curl --noproxy localhost -vvv -k -X POST -d "here are some bytes" http://localhost:8080/api/nonblocking

    // http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java?h=jetty-9.1

    @PUT("/api/nonblocking")
    public final void nonblocking(final AsyncContext context, final HttpServletRequest request,
            final HttpServletResponse response, final ServletInputStream input, final ServletOutputStream output)
            throws Exception {

        final Queue<Option<String>> queue = new ConcurrentLinkedQueue<Option<String>>();

        final WriteListener writer = new StupidWriteListener(context, queue, output);

        final ReadListener reader = new StupidReadListener(queue, input, output, writer,
                request.getContentLength());

        // Producer
        input.setReadListener(reader);
        logger__.info("Tomcat, is input ready?: " + input.isReady());
        reader.onDataAvailable();

        // Consumer
        output.setWriteListener(writer);
        logger__.info("Tomcat, is input ready?: " + output.isReady());
        writer.onWritePossible();

    }

    private class StupidReadListener implements ReadListener, Runnable {
        private final Queue<Option<String>> queue_;
        private final ServletInputStream input_;
        private final ServletOutputStream output_;
        private final WriteListener writer_;
        private final ServletInputStreamBuffer buffer_;

        public StupidReadListener(final Queue<Option<String>> queue, final ServletInputStream input,
                final ServletOutputStream output, final WriteListener writer, final int contentLength) {
            queue_ = queue;
            input_ = input;
            output_ = output;
            writer_ = writer;
            buffer_ = new StaticByteBuffer(contentLength);
        }

        @Override
        public void onDataAvailable() throws IOException {
            buffer_.fill(input_);
            final String msg = "Read " + buffer_.getTotalBytesRead() + "-bytes!";
            logger__.info(msg);
            queue_.add(Some.some(msg));
            //logger__.info("Queue has " + queue.size() + "-elements.");
            /*if(output_.isReady()) {
               logger__.info("onDataAvailable: Output is ready!");
               writer_.onWritePossible();
            } else {
               logger__.info("onDataAvailable: Output is NOT ready!");
            }*/
        }

        @Override
        public void onAllDataRead() throws IOException {
            if (input_.isFinished()) {
                queue_.add(None.<String>none());
                logger__.info("All data is read! Added NONE to queue.");
            }
            if (output_.isReady()) {
                logger__.info("onDataAvailable: Output is ready!");
                writer_.onWritePossible();
            } else {
                logger__.info("onDataAvailable: Output is NOT ready!");
            }
        }

        @Override
        public void onError(Throwable t) {
            logger__.error("Failure while reading input stream.", t);
        }

        @Override
        public void run() {
            try {
                onDataAvailable();
            } catch (IOException e) {
                logger__.error("Failed miserably.", e);
            }
        }
    }

    private class StupidWriteListener implements WriteListener, Runnable {
        private final AsyncContext context_;
        private final Queue<Option<String>> queue_;
        private final ServletOutputStream output_;

        public StupidWriteListener(final AsyncContext context, final Queue<Option<String>> queue,
                final ServletOutputStream output) {
            context_ = context;
            queue_ = queue;
            output_ = output;
        }

        @Override
        public void onWritePossible() throws IOException {
            if (!output_.isReady()) {
                return;
            }
            logger__.info("onWritePossible(): Output is ready!!!");
            Option<String> o = null;
            while ((o = queue_.poll()) != null && o.isSome()) {
                logger__.info("onWritePossible(): Got SOME: " + o.get());
                output_.write(getBytesUtf8(o.get() + LINE_SEPARATOR_UNIX));
            }
            if (output_.isReady()) {
                output_.flush();
            }
            if (o != null && o.isNone()) {
                logger__.info("onWritePossible(): Got NONE... " + "completing context!");
                context_.complete();
            }
        }

        @Override
        public void onError(Throwable t) {
            logger__.error("Failure in write listener.", t);
        }

        @Override
        public void run() {
            try {
                onWritePossible();
            } catch (IOException e) {
                logger__.error("Failed miserably.", e);
            }
        }
    }

    interface ServletInputStreamBuffer {
        public static final int EOF = -1;

        public void fill(final ServletInputStream input) throws IOException;

        public int getTotalBytesRead();

        public byte[] getBuffer();
    }

    static final class StaticByteBuffer implements ServletInputStreamBuffer {
        private final byte[] bytes_;
        private final ByteBuffer buffer_;
        private final AtomicInteger total_;

        public StaticByteBuffer(final int contentLength) {
            bytes_ = new byte[contentLength];
            buffer_ = ByteBuffer.wrap(bytes_);
            total_ = new AtomicInteger(0);
        }

        @Override
        public void fill(final ServletInputStream input) throws IOException {
            ReadableByteChannel readable = null;
            try {
                readable = newChannel(input);
                int read = 0;
                while (input.isReady() && (read = readable.read(buffer_)) != EOF) {
                    total_.addAndGet(read);
                }
            } finally {
                closeQuietly(readable);
            }
        }

        @Override
        public byte[] getBuffer() {
            return bytes_;
        }

        @Override
        public int getTotalBytesRead() {
            return total_.get();
        }
    }

    static final class DynamicBuffer implements ServletInputStreamBuffer {
        private final ByteArrayOutputStream baos_;
        private final AtomicInteger total_;

        public DynamicBuffer() {
            baos_ = new ByteArrayOutputStream();
            total_ = new AtomicInteger(0);
        }

        @Override
        public void fill(final ServletInputStream input) throws IOException {
            final byte[] buffer = new byte[4096];
            int read = 0;
            while (input.isReady() && (read = input.read(buffer)) != EOF) {
                baos_.write(buffer, 0, read);
                total_.addAndGet(read);
            }
        }

        @Override
        public byte[] getBuffer() {
            return baos_.toByteArray();
        }

        @Override
        public int getTotalBytesRead() {
            return total_.get();
        }
    }

    static final class ByteBufferServletInputStream extends ServletInputStream {

        private ByteBuffer buffer_;
        private ServletInputStream delegate_;

        public ByteBufferServletInputStream(final ServletInputStream delegate, final int size) {
            delegate_ = delegate;
            buffer_ = ByteBuffer.allocateDirect(size);
            buffer_.mark();
        }

        public ByteBufferServletInputStream(final ServletInputStream delegate) {
            this(delegate, 4096);
        }

        @Override
        public int available() throws IOException {
            return delegate_.available();
        }

        @Override
        public void close() throws IOException {
            delegate_.close();
        }

        @Override
        public void mark(int readlimit) {
            delegate_.mark(readlimit);
        }

        @Override
        public boolean markSupported() {
            return delegate_.markSupported();
        }

        @Override
        public void reset() throws IOException {
            delegate_.reset();
        }

        @Override
        public long skip(long n) throws IOException {
            return delegate_.skip(n);
        }

        @Override
        public int read() throws IOException {
            int b = delegate_.read();
            if (!bufferIsFull()) {
                buffer_.put((byte) b);
            }
            return b;
        }

        @Override
        public int read(byte[] b) throws IOException {
            int n = delegate_.read(b);
            fill(b, 0, n);
            return n;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int n = delegate_.read(b, off, len);
            fill(b, off, n);
            return n;
        }

        @Override
        public int readLine(byte[] b, int off, int len) throws IOException {
            int n = delegate_.readLine(b, off, len);
            fill(b, off, n);
            return n;
        }

        void fill(byte[] b, int off, int len) {
            if (len < 0)
                return;
            if (!bufferIsFull()) {
                int m = Math.min(len, buffer_.capacity() - buffer_.position());
                buffer_.put(b, off, m);
            }
        }

        boolean bufferIsFull() {
            return buffer_.position() == buffer_.capacity();
        }

        public byte[] getData() {
            if (bufferIsFull()) {
                return buffer_.array();
            }
            byte[] data = new byte[buffer_.position()];
            ((ByteBuffer) buffer_.duplicate().reset()).get(data);
            return data;
        }

        public byte[] array() {
            return buffer_.array();
        }

        public void dispose() {
            buffer_ = null;
            delegate_ = null;
        }

        @Override
        public boolean isFinished() {
            return delegate_.isFinished();
        }

        @Override
        public boolean isReady() {
            return delegate_.isReady();
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            delegate_.setReadListener(readListener);
        }

    }

}