com.byteatebit.nbserver.task.ReadDelimitedMessageTask.java Source code

Java tutorial

Introduction

Here is the source code for com.byteatebit.nbserver.task.ReadDelimitedMessageTask.java

Source

/*
 * Copyright (c) 2016 byteatebit
 *
 * 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.
 */

package com.byteatebit.nbserver.task;

import com.byteatebit.common.builder.BaseBuilder;
import com.byteatebit.nbserver.IChannelSelectorRegistrar;
import com.byteatebit.nbserver.ISelectorRegistrar;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.EOFException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

public class ReadDelimitedMessageTask {

    private static final Logger LOG = LoggerFactory.getLogger(ReadDelimitedMessageTask.class);

    protected final ByteBuffer byteBuffer;
    protected final int maxMessageSize;
    protected final Charset charset;
    protected final Splitter splitter;
    protected final ByteArrayOutputStream messageBuffer;

    public ReadDelimitedMessageTask(ObjectBuilder builder) {
        this.byteBuffer = builder.byteBuffer;
        this.maxMessageSize = builder.maxMessageSize;
        this.charset = builder.charset;
        this.splitter = Splitter.onPattern(builder.delimiter);
        this.messageBuffer = new ByteArrayOutputStream(byteBuffer.capacity());
    }

    public void readMessages(IChannelSelectorRegistrar channelSelectorRegistrar, Consumer<List<String>> callback,
            Consumer<Exception> exceptionHandler) {
        try {
            channelSelectorRegistrar.register(SelectionKey.OP_READ,
                    (selectionKey -> read(selectionKey, callback, exceptionHandler)),
                    (selectionKey, ops) -> exceptionHandler.accept(new TimeoutException("read timed out")));
        } catch (Exception e) {
            exceptionHandler.accept(e);
        }
    }

    public void readMessages(ISelectorRegistrar selectorRegistrar, SelectableChannel channel,
            Consumer<List<String>> callback, Consumer<Exception> exceptionHandler) {
        readMessages(
                (ops, ioTask, ioTimeoutTask) -> selectorRegistrar.register(channel, ops, ioTask, ioTimeoutTask),
                callback, exceptionHandler);
    }

    protected void read(SelectionKey selectionKey, Consumer<List<String>> callback,
            Consumer<Exception> exceptionHandler) {
        try {
            if (!selectionKey.isValid() || !selectionKey.isReadable())
                return;
            byteBuffer.clear();
            int bytesRead = ((ReadableByteChannel) selectionKey.channel()).read(byteBuffer);
            if (bytesRead < 0) {
                LOG.warn(
                        "End of stream reached.  Deregistering interest in reads on the selection key invoking exception handler.");
                invokeExceptionHandler(selectionKey, exceptionHandler, new EOFException("End of stream"));
                return;
            }
            byteBuffer.flip();
            if (byteBuffer.remaining() + messageBuffer.size() > maxMessageSize) {
                LOG.error("Max message size of " + maxMessageSize
                        + " bytes exceeded.  Deregistering interest in reads on the selection key invoking exception handler.");
                invokeExceptionHandler(selectionKey, exceptionHandler,
                        new IllegalStateException("Max message size of " + maxMessageSize + " bytes exceeded"));
                return;
            }
            while (byteBuffer.hasRemaining())
                messageBuffer.write(byteBuffer.get());
            String messagesString = messageBuffer.toString(charset);
            messageBuffer.reset();
            List<String> messages = new ArrayList<>();
            for (String message : splitter.split(messagesString))
                messages.add(message);
            messageBuffer.write(messages.remove(messages.size() - 1).getBytes(charset));
            if (!messages.isEmpty()) {
                selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_READ);
                callback.accept(messages);
            }
        } catch (Exception e) {
            LOG.error("ReadDelimitedMessage task failed", e);
            invokeExceptionHandler(selectionKey, exceptionHandler, e);
        }
    }

    protected void invokeExceptionHandler(SelectionKey selectionKey, Consumer<Exception> exceptionHandler,
            Exception exception) {
        selectionKey.interestOps(selectionKey.interestOps() & ~SelectionKey.OP_READ);
        try {
            exceptionHandler.accept(exception);
        } catch (Exception e) {
            LOG.error("Read exception handler failed", e);
        }
    }

    public static abstract class ObjectBuilder<T extends ObjectBuilder<T>> extends BaseBuilder<T> {

        protected ByteBuffer byteBuffer;
        protected Integer maxMessageSize = 1024 * 1024;
        protected String delimiter;
        protected Charset charset = StandardCharsets.UTF_8;

        public T withByteBuffer(ByteBuffer byteBuffer) {
            this.byteBuffer = byteBuffer;
            return self();
        }

        public T withMaxMessageSize(Integer maxMessageSize) {
            this.maxMessageSize = maxMessageSize;
            return self();
        }

        public T withDelimiter(String delimiter) {
            this.delimiter = delimiter;
            return self();
        }

        public T withCharset(Charset charset) {
            this.charset = charset;
            return self();
        }

        public ReadDelimitedMessageTask build() {
            Preconditions.checkNotNull(byteBuffer, "byteBuffer cannot be null");
            Preconditions.checkArgument(byteBuffer.capacity() > 0, "bufferSize.capacity must be > 0");
            Preconditions.checkNotNull(maxMessageSize, "maxMessageSize cannot be null");
            Preconditions.checkArgument(maxMessageSize > 0, "maxMessageSize must be > 0");
            Preconditions.checkNotNull(charset, "charset cannot be null");
            Preconditions.checkNotNull(delimiter, "delimiter cannot be null");
            return new ReadDelimitedMessageTask(this);
        }
    }

    public static class Builder extends ObjectBuilder<Builder> {
        @Override
        public Builder self() {
            return this;
        }

        public static Builder builder() {
            return new Builder();
        }

    }

}