/* * JBoss, Home of Professional Open Source. * Copyright 2016, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.remoting3.remote; import static org.jboss.remoting3._private.Messages.conn; import static org.xnio.IoUtils.safeClose; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Iterator; import org.xnio.Buffers; import org.xnio.ByteBufferPool; import org.xnio.ChannelListener; import org.xnio.Pooled; import org.xnio.conduits.ConduitStreamSourceChannel; /** * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ final class MessageReader { private final ConduitStreamSourceChannel sourceChannel; private final ArrayDeque<ByteBuffer> queue = new ArrayDeque<>(); private final Object lock; private final ByteBuffer[] array = new ByteBuffer[16]; static final Pooled<ByteBuffer> EOF_MARKER = Buffers.emptyPooledByteBuffer(); MessageReader(final ConduitStreamSourceChannel sourceChannel, final Object lock) { this.sourceChannel = sourceChannel; this.lock = lock; } ConduitStreamSourceChannel getSourceChannel() { return sourceChannel; } Pooled<ByteBuffer> getMessage() throws IOException { synchronized (lock) { for (;;) { ByteBuffer first = queue.peekFirst(); if (first != null) { if (first.remaining() >= 4) { int size = first.getInt(first.position()); if (remaining(size + 4)) { ByteBuffer message = ByteBufferPool.MEDIUM_HEAP.allocate(); first.getInt(); int cnt = 0; while (cnt < size) { cnt += Buffers.copy(size - cnt, message, first); if (! first.hasRemaining()) { ByteBufferPool.free(first); queue.pollFirst(); first = queue.peekFirst(); } } message.flip(); if (first != null && first.position() + 4 > first.limit()) { // compact & reflip just to make sure there's space for next time first.compact(); first.flip(); } conn.tracef("Received message %s", message); return Buffers.globalPooledWrapper(message); } else { if (conn.isTraceEnabled()) { conn.tracef("Not enough buffered bytes for message of size %d+4 (%s)", Integer.valueOf(size), first); } } } else { if (queue.peekLast() == first) { // ready for re-filling first.compact().flip(); } else { // first can never be full enough; we have to copy a few bytes out of the next one first.compact(); try { final Iterator<ByteBuffer> iterator = queue.iterator(); iterator.next(); // skip first assert iterator.hasNext(); // at least one more buffer is present because last != first do { final ByteBuffer next = iterator.next(); if (next.remaining() > 4) { first.putInt(next.getInt()); } else { Buffers.copy(first, next); } } while (first.position() < 4 && iterator.hasNext()); } finally { first.flip(); } } } } else { conn.trace("No buffers in queue for message header"); } ByteBuffer[] b = array; ByteBuffer last = queue.pollLast(); if (last != null) { last.compact(); b[0] = last; ByteBufferPool.MEDIUM_DIRECT.allocate(b, 1); conn.tracef("Compacted existing buffer %s", last); } else { ByteBufferPool.MEDIUM_DIRECT.allocate(b, 0); conn.tracef("Allocated fresh buffers"); } try { long res = sourceChannel.read(b); if (res == -1) { conn.trace("Received EOF"); return EOF_MARKER; } if (res == 0) { conn.trace("No read bytes available"); return null; } if (conn.isTraceEnabled()) { conn.tracef("Received %d bytes", Long.valueOf(res)); } } finally { for (int i = 0; i < b.length; i++) { final ByteBuffer buffer = b[i]; if (buffer.position() > 0) { buffer.flip(); queue.addLast(buffer); } else { ByteBufferPool.free(buffer); } b[i] = null; } } } } } private boolean remaining(int cnt) { int rem = 0; for (ByteBuffer buffer : queue) { rem += buffer.remaining(); if (rem >= cnt) return true; } return false; } public void close() { synchronized (lock) { safeClose(sourceChannel); ByteBuffer buffer; while ((buffer = queue.pollFirst()) != null) { ByteBufferPool.free(buffer); } } } public void setReadListener(final ChannelListener<? super ConduitStreamSourceChannel> readListener) { synchronized (lock) { sourceChannel.setReadListener(readListener); } } public void suspendReads() { synchronized (lock) { getSourceChannel().suspendReads(); } } public void resumeReads() { synchronized (lock) { getSourceChannel().resumeReads(); } } public void wakeupReads() { synchronized (lock) { getSourceChannel().wakeupReads(); } } public void shutdownReads() throws IOException { synchronized (lock) { getSourceChannel().shutdownReads(); } } }