io.pravega.shared.protocol.netty.AppendDecoder.java Source code

Java tutorial

Introduction

Here is the source code for io.pravega.shared.protocol.netty.AppendDecoder.java

Source

/**
 * Copyright (c) 2017 Dell Inc., or its subsidiaries. All Rights Reserved.
 *
 * 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
 */
package io.pravega.shared.protocol.netty;

import static io.netty.buffer.Unpooled.wrappedBuffer;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

import com.google.common.annotations.VisibleForTesting;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import lombok.Data;

/**
 * AppendBlocks are decoded specially to avoid having to parse every append individually.
 * This is done by holding onto the AppendBlock command and waiting for the following command
 * which must be an AppendBlockEnd.
 * The AppendBlockEnd command should have all of the information need to construct a single
 * Append object with all of the Events in the block.
 *
 * @see CommandEncoder For details about handling of PartialEvents
 */
public class AppendDecoder extends MessageToMessageDecoder<WireCommand> {

    private final HashMap<UUID, Segment> appendingSegments = new HashMap<>();
    private WireCommands.AppendBlock currentBlock;

    @Data
    private static final class Segment {
        private final String name;
        private long lastEventNumber;
    }

    @Override
    public boolean acceptInboundMessage(Object msg) throws Exception {
        return msg instanceof WireCommands.SetupAppend || msg instanceof WireCommands.AppendBlock
                || msg instanceof WireCommands.AppendBlockEnd || msg instanceof WireCommands.Padding
                || msg instanceof WireCommands.ConditionalAppend;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, WireCommand command, List<Object> out) throws Exception {
        Object result = processCommand(command);
        if (result != null) {
            out.add(result);
        }
    }

    @VisibleForTesting
    public Request processCommand(WireCommand command) throws Exception {
        if (currentBlock != null && command.getType() != WireCommandType.APPEND_BLOCK_END) {
            throw new InvalidMessageException("Unexpected " + command.getType() + " following a append block.");
        }
        Request result;
        Segment segment;
        switch (command.getType()) {
        case PADDING:
            result = null;
            break;
        case SETUP_APPEND:
            WireCommands.SetupAppend append = (WireCommands.SetupAppend) command;
            appendingSegments.put(append.getConnectionId(), new Segment(append.getSegment()));
            result = append;
            break;
        case CONDITIONAL_APPEND:
            WireCommands.ConditionalAppend ca = (WireCommands.ConditionalAppend) command;
            segment = getSegment(ca.getConnectionId());
            if (ca.getEventNumber() < segment.lastEventNumber) {
                throw new InvalidMessageException("Last event number went backwards.");
            }
            segment.lastEventNumber = ca.getEventNumber();
            result = new Append(segment.getName(), ca.getConnectionId(), ca.getEventNumber(), ca.getData(),
                    ca.getExpectedOffset());
            break;
        case APPEND_BLOCK:
            getSegment(((WireCommands.AppendBlock) command).getConnectionId());
            currentBlock = (WireCommands.AppendBlock) command;
            result = null;
            break;
        case APPEND_BLOCK_END:
            WireCommands.AppendBlockEnd blockEnd = (WireCommands.AppendBlockEnd) command;
            if (currentBlock == null) {
                throw new InvalidMessageException("AppendBlockEnd without AppendBlock.");
            }
            UUID connectionId = blockEnd.getWriterId();
            if (!connectionId.equals(currentBlock.getConnectionId())) {
                throw new InvalidMessageException("AppendBlockEnd for wrong connection.");
            }
            segment = getSegment(connectionId);
            if (blockEnd.getLastEventNumber() < segment.lastEventNumber) {
                throw new InvalidMessageException("Last event number went backwards.");
            }
            int sizeOfWholeEventsInBlock = blockEnd.getSizeOfWholeEvents();
            if (sizeOfWholeEventsInBlock > currentBlock.getData().readableBytes() || sizeOfWholeEventsInBlock < 0) {
                throw new InvalidMessageException("Invalid SizeOfWholeEvents in block");
            }
            ByteBuf appendDataBuf = getAppendDataBuf(blockEnd, sizeOfWholeEventsInBlock);
            segment.lastEventNumber = blockEnd.getLastEventNumber();
            currentBlock = null;
            result = new Append(segment.name, connectionId, segment.lastEventNumber, blockEnd.numEvents,
                    appendDataBuf, null);
            break;
        //$CASES-OMITTED$
        default:
            throw new IllegalStateException("Unexpected case: " + command);
        }
        return result;
    }

    private ByteBuf getAppendDataBuf(WireCommands.AppendBlockEnd blockEnd, int sizeOfWholeEventsInBlock)
            throws IOException {
        ByteBuf appendDataBuf = currentBlock.getData().slice(0, sizeOfWholeEventsInBlock);
        int remaining = currentBlock.getData().readableBytes() - sizeOfWholeEventsInBlock;
        if (remaining > 0) {
            ByteBuf dataRemainingInBlock = currentBlock.getData().slice(sizeOfWholeEventsInBlock, remaining);
            WireCommand cmd = CommandDecoder.parseCommand(dataRemainingInBlock);
            if (!(cmd.getType() == WireCommandType.PARTIAL_EVENT || cmd.getType() == WireCommandType.PADDING)) {
                throw new InvalidMessageException("Found " + cmd.getType()
                        + " at end of append block but was expecting a partialEvent or padding.");
            }
            if (cmd.getType() == WireCommandType.PADDING && blockEnd.getData().readableBytes() != 0) {
                throw new InvalidMessageException("Unexpected data in BlockEnd");
            }
            if (cmd.getType() == WireCommandType.PARTIAL_EVENT) {
                // Work around a bug in netty:
                // See https://github.com/netty/netty/issues/5597
                if (appendDataBuf.readableBytes() == 0) {
                    appendDataBuf.release();
                    return wrappedBuffer(((WireCommands.PartialEvent) cmd).getData(), blockEnd.getData());
                } else {
                    return wrappedBuffer(appendDataBuf, ((WireCommands.PartialEvent) cmd).getData(),
                            blockEnd.getData());
                }
            }
        }
        return appendDataBuf;
    }

    private Segment getSegment(UUID connectionId) {
        Segment segment = appendingSegments.get(connectionId);
        if (segment == null) {
            throw new InvalidMessageException("ConnectionID refrenced before SetupAppend");
        }
        return segment;
    }

}