/* * Copyright 2007-2010 Sun Microsystems, Inc. * * This file is part of Project Darkstar Server. * * Project Darkstar Server is free software: you can redistribute it * and/or modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation and * distributed hereunder to you. * * Project Darkstar Server 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * -- */ package com.sun.sgs.impl.protocol.simple; import com.sun.sgs.impl.nio.DelegatingCompletionHandler; import com.sun.sgs.impl.sharedutil.LoggerWrapper; import com.sun.sgs.nio.channels.AsynchronousByteChannel; import com.sun.sgs.nio.channels.CompletionHandler; import com.sun.sgs.nio.channels.IoFuture; import com.sun.sgs.nio.channels.ReadPendingException; import com.sun.sgs.nio.channels.WritePendingException; import java.io.EOFException; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; /** * A wrapper channel that reads and writes complete messages by framing * messages with a 2-byte message length, and masking (and re-issuing) partial * I/O operations. Also enforces a fixed buffer size when reading. */ public class AsynchronousMessageChannel implements Channel { /** The number of bytes used to represent the message length. */ public static final int PREFIX_LENGTH = 2; /** The logger for this class. */ static final LoggerWrapper logger = new LoggerWrapper( Logger.getLogger(AsynchronousMessageChannel.class.getName())); /** * The underlying channel (possibly another layer of abstraction, * e.g. compression, retransmission...) */ final AsynchronousByteChannel channel; /** Whether there is a read underway. */ final AtomicBoolean readPending = new AtomicBoolean(); /** Whether there is a write underway. */ final AtomicBoolean writePending = new AtomicBoolean(); /** The read buffer. */ final ByteBuffer readBuffer; /** * Creates a new instance of this class with the given channel and read * buffer size. * * @param channel a channel * @param readBufferSize the number of bytes in the read buffer * @throws IllegalArgumentException if {@code readBufferSize} is smaller * than {@value #PREFIX_LENGTH} */ public AsynchronousMessageChannel(AsynchronousByteChannel channel, int readBufferSize) { if (readBufferSize < PREFIX_LENGTH) { throw new IllegalArgumentException( "The readBufferSize must not be smaller than " + PREFIX_LENGTH); } this.channel = channel; readBuffer = ByteBuffer.allocateDirect(readBufferSize); } /* -- Methods for reading and writing -- */ /** * Initiates reading a complete message from this channel. Returns a * future which will contain a read-only view of a buffer containing the * complete message. Calls {@code handler} when the read operation has * completed, if {@code handler} is not {@code null}. The buffer's * position will be set to {@code 0} and it's limit will be set to the * length of the complete message. The contents of the buffer will remain * valid until the next call to {@code read}. * * @param handler the completion handler object; can be {@code null} * @return a future representing the result of the operation * @throws BufferOverflowException if the buffer does not contain enough * space to read the next message * @throws ReadPendingException if a read is in progress */ public IoFuture<ByteBuffer, Void> read(CompletionHandler<ByteBuffer, Void> handler) { if (!readPending.compareAndSet(false, true)) { throw new ReadPendingException(); } return new Reader(handler).start(); } /** * Initiates writing a complete message from the given buffer to the * underlying channel, and returns a future for controlling the operation. * Writes bytes starting at the buffer's current position and up to its * limit. * * @param src the buffer from which bytes are to be retrieved * @param handler the completion handler object; can be {@code null} * @return a future representing the result of the operation * @throws WritePendingException if a write is in progress */ public IoFuture<Void, Void> write(ByteBuffer src, CompletionHandler<Void, Void> handler) { if (!writePending.compareAndSet(false, true)) { throw new WritePendingException(); } return new Writer(handler, src).start(); } /* -- Implement Channel -- */ /** {@inheritDoc} */ @Override public void close() throws IOException { channel.close(); } /** {@inheritDoc} */ @Override public boolean isOpen() { return channel.isOpen(); } /* -- Other methods and classes -- */ /** * Returns the length of the complete message, including the length prefix, * based on the data read into the buffer between position 0 and the * current position, or {@code -1} if the length cannot be determined. * * @return the length, or {@code -1} */ int getMessageLength() { return (readBuffer.position() >= PREFIX_LENGTH) ? (readBuffer.getShort(0) & 0xffff) + PREFIX_LENGTH : -1; } /** * Implement a completion handler for reading a complete message from the * underlying byte stream. */ private final class Reader extends DelegatingCompletionHandler<ByteBuffer, Void, Integer, Void> { /** The length of the message, or -1 if not yet known. */ private int messageLen = -1; /** Creates an instance with the specified attachment and handler. */ Reader(CompletionHandler<ByteBuffer, Void> handler) { super(null, handler); } /** Clear the readPending flag. */ @Override protected void done() { readPending.set(false); super.done(); } /** Start reading into the buffer. */ @Override protected IoFuture<Integer, Void> implStart() { int position = readBuffer.position(); if (position > 0) { /* Skip previous message, moving remaining bytes to front */ int len = getMessageLength(); assert len > 0; if (position > len) { readBuffer.position(len); readBuffer.limit(position); readBuffer.compact(); } else { readBuffer.clear(); } } return processBuffer(); } /** Process the results of reading so far and read more if needed. */ @Override protected IoFuture<Integer, Void> implCompleted( IoFuture<Integer, Void> result) throws ExecutionException, EOFException { int bytesRead = result.getNow(); if (bytesRead < 0) { throw new EOFException("The message was incomplete"); } return processBuffer(); } /** * Process the results of reading into the buffer, and return a future * to read more if needed. */ private IoFuture<Integer, Void> processBuffer() { if (messageLen < 0) { messageLen = getMessageLength(); if (messageLen >= 0) { if (readBuffer.limit() < messageLen) { /* Buffer is too small to hold complete message */ throw new BufferOverflowException(); } } } if (messageLen >= 0 && readBuffer.position() >= messageLen) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "{0} read complete {1}:{2}", this, messageLen, readBuffer.position()); } /* * Return a read-only buffer containing just the message bytes * without the length prefix. */ ByteBuffer result = readBuffer.duplicate(); result.limit(messageLen); result.position(PREFIX_LENGTH); set(result.slice().asReadOnlyBuffer()); return null; } else { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "{0} read incomplete {1}:{2}", this, messageLen, readBuffer.position()); } return channel.read(readBuffer, this); } } } /** * Implement a completion handler for writing a complete message to the * underlying byte stream. */ private final class Writer extends DelegatingCompletionHandler<Void, Void, Integer, Void> { /** * The byte buffer containing the bytes to send, with the size * prepended. */ private final ByteBuffer srcWithSize; /** * Creates an instance with the specified attachment and handler, and * sending the bytes in the specified buffer. */ Writer(CompletionHandler<Void, Void> handler, ByteBuffer src) { super(null, handler); int size = src.remaining(); assert size < Short.MAX_VALUE; /* Prepend the size as a short. */ /* * XXX: Maybe avoid copying by doing two writes? -tjb@sun.com * (02/29/2008) */ srcWithSize = ByteBuffer.allocate(2 + size); srcWithSize.putShort((short) size) .put(src) .flip(); } /** Clear the writePending flag. */ @Override protected void done() { writePending.set(false); super.done(); } /** Start writing from the buffer. */ @Override protected IoFuture<Integer, Void> implStart() { return channel.write(srcWithSize, this); } /** Process the results of writing so far and write more if needed. */ @Override protected IoFuture<Integer, Void> implCompleted( IoFuture<Integer, Void> result) throws ExecutionException { /* See if computation already failed. */ result.getNow(); if (srcWithSize.hasRemaining()) { /* Write some more */ return channel.write(srcWithSize, this); } else { /* Finished */ return null; } } } }