/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 io.jafka.message; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.GatheringByteChannel; import java.util.Iterator; import io.jafka.common.ErrorMapping; import io.jafka.common.InvalidMessageSizeException; import io.jafka.common.MessageSizeTooLargeException; import io.jafka.utils.IteratorTemplate; /** * A sequence of messages stored in a byte buffer * * There are two ways to create a ByteBufferMessageSet * * Option 1: From a ByteBuffer which already contains the serialized * message set. Consumers will use this method. * * Option 2: Give it a list of messages along with instructions relating to * serialization format. Producers will use this method. * * @author adyliu (imxylz@gmail.com) * @since 1.0 */ public class ByteBufferMessageSet extends MessageSet{ private final ByteBuffer buffer; private final long initialOffset; private final ErrorMapping errorCode; // private long shallowValidByteCount = -1L; // private long validBytes; public ByteBufferMessageSet(ByteBuffer buffer) { this(buffer,0L,ErrorMapping.NoError); } public ByteBufferMessageSet(ByteBuffer buffer,long initialOffset,ErrorMapping errorCode) { this.buffer = buffer; this.initialOffset = initialOffset; this.errorCode = errorCode; this.validBytes = shallowValidBytes(); } public ByteBufferMessageSet(CompressionCodec compressionCodec,Message...messages) { this(MessageSet.createByteBuffer(compressionCodec, messages),0L,ErrorMapping.NoError); } public ByteBufferMessageSet(Message...messages) { this(CompressionCodec.NoCompressionCodec,messages); } /** get valid bytes of buffer * <p> * The size of buffer is equal or larger than the size of valid messages. * The last message maybe is not integrate. * </p> * @return the validBytes */ public long getValidBytes() { return validBytes; } private long shallowValidBytes() { if (shallowValidByteCount < 0) { Iterator<MessageAndOffset> iter = this.internalIterator(true); while (iter.hasNext()) { shallowValidByteCount = iter.next().offset; } } if (shallowValidByteCount < initialOffset) { return 0; } else { return shallowValidByteCount - initialOffset; } } /** * @return the initialOffset */ public long getInitialOffset() { return initialOffset; } /** * @return the buffer */ public ByteBuffer getBuffer() { return buffer; } /** * @return the errorCode */ public ErrorMapping getErrorCode() { return errorCode; } public ByteBuffer serialized() { return buffer; } public Iterator<MessageAndOffset> iterator() { return internalIterator(false); } public Iterator<MessageAndOffset> internalIterator(boolean isShallow){ return new Iter(isShallow); } class Iter extends IteratorTemplate<MessageAndOffset> { boolean isShallow; ByteBuffer topIter = buffer.slice(); long currValidBytes = initialOffset; Iterator<MessageAndOffset> innerIter = null; long lastMessageSize = 0L; Iter(boolean isShallow) { this.isShallow = isShallow; } private boolean innerDone() { return innerIter == null || !innerIter.hasNext(); } private MessageAndOffset makeNextOuter() { if(topIter.remaining() <4)return allDone(); int size = topIter.getInt(); lastMessageSize = size; if(size<0||topIter.remaining()<size) { if(currValidBytes == initialOffset||size<0) { throw new InvalidMessageSizeException("invalid message size: " + size + " only received bytes: " + topIter.remaining() + " at " + currValidBytes + "( possible causes (1) a single message larger than " + "the fetch size; (2) log corruption )"); } return allDone(); } // ByteBuffer message = topIter.slice(); message.limit(size); topIter.position(topIter.position() + size); Message newMessage = new Message(message); if(isShallow) { currValidBytes += 4 +size; return new MessageAndOffset(newMessage, currValidBytes); } if(newMessage.compressionCodec() == CompressionCodec.NoCompressionCodec) { if(!newMessage.isValid())throw new InvalidMessageException("Uncompressed essage is invalid"); innerIter = null; currValidBytes += 4 +size; return new MessageAndOffset(newMessage, currValidBytes); } //compress message if(!newMessage.isValid()) { throw new InvalidMessageException("Compressed message is invalid"); } innerIter = CompressionUtils.decompress(newMessage).internalIterator(false); if(!innerIter.hasNext()) { currValidBytes += 4 + lastMessageSize; innerIter = null; } return makeNext(); } @Override protected MessageAndOffset makeNext() { if (isShallow) return makeNextOuter(); if (innerDone()) return makeNextOuter(); MessageAndOffset messageAndOffset = innerIter.next(); if (!innerIter.hasNext()) { currValidBytes += 4 + lastMessageSize; } return new MessageAndOffset(messageAndOffset.message, currValidBytes); } } @Override public long writeTo(GatheringByteChannel channel, long offset, long maxSize) throws IOException { buffer.mark(); int written = channel.write(buffer); buffer.reset(); return written; } public long getSizeInBytes() { return buffer.limit(); } /** * check max size of each message * @param maxMessageSize the max size for each message */ public void verifyMessageSize(int maxMessageSize) { Iterator<MessageAndOffset> shallowIter = internalIterator(true); while(shallowIter.hasNext()) { MessageAndOffset messageAndOffset = shallowIter.next(); int payloadSize = messageAndOffset.message.payloadSize(); if(payloadSize > maxMessageSize) { throw new MessageSizeTooLargeException("payload size of " + payloadSize + " larger than " + maxMessageSize); } } } }