org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessageTest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessageTest.java

Source

/**
 * 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 org.apache.activemq.artemis.protocol.amqp.broker;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ICoreMessage;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageSupport;
import org.apache.activemq.artemis.protocol.amqp.util.NettyReadable;
import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
import org.apache.activemq.artemis.reader.MessageUtil;
import org.apache.activemq.artemis.spi.core.protocol.EmbedMessageUtil;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.utils.collections.TypedProperties;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.UnsignedByte;
import org.apache.qpid.proton.amqp.UnsignedInteger;
import org.apache.qpid.proton.amqp.UnsignedLong;
import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.messaging.DeliveryAnnotations;
import org.apache.qpid.proton.amqp.messaging.Footer;
import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.Properties;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.EncodingCodes;
import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.message.Message;
import org.apache.qpid.proton.message.impl.MessageImpl;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

public class AMQPMessageTest {

    private static final String TEST_TO_ADDRESS = "someAddress";

    private static final String TEST_MESSAGE_ANNOTATION_KEY = "x-opt-test-annotation";
    private static final String TEST_MESSAGE_ANNOTATION_VALUE = "test-annotation";

    private static final String TEST_APPLICATION_PROPERTY_KEY = "key-1";
    private static final String TEST_APPLICATION_PROPERTY_VALUE = "value-1";

    private static final String TEST_STRING_BODY = "test-string-body";

    private byte[] encodedProtonMessage;

    @Before
    public void setUp() {
        encodedProtonMessage = encodeMessage(createProtonMessage());
    }

    //----- Test Message Creation ---------------------------------------------//

    @Test
    public void testCreateMessageFromEncodedByteArrayData() {
        // Constructor 1
        AMQPMessage decoded = new AMQPMessage(0, encodedProtonMessage, null);

        assertTrue(decoded.isDurable());
        assertEquals(TEST_TO_ADDRESS, decoded.getAddress());

        // Constructor 2
        decoded = new AMQPMessage(0, encodedProtonMessage, null, null);

        assertTrue(decoded.isDurable());
        assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
    }

    @Test
    public void testCreateMessageFromEncodedReadableBuffer() {
        AMQPMessage decoded = new AMQPMessage(0, ReadableBuffer.ByteBufferReader.wrap(encodedProtonMessage), null,
                null);

        assertEquals(true, decoded.getHeader().getDurable());
        assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
    }

    @Test
    public void testCreateMessageFromEncodedByteArrayDataWithExtraProperties() {
        AMQPMessage decoded = new AMQPMessage(0, encodedProtonMessage, new TypedProperties(), null);

        assertEquals(true, decoded.getHeader().getDurable());
        assertEquals(TEST_TO_ADDRESS, decoded.getAddress());
        assertNotNull(decoded.getExtraProperties());
    }

    @Test
    public void testCreateMessageForPersistenceDataReload() throws ActiveMQException {
        MessageImpl protonMessage = createProtonMessage();
        ActiveMQBuffer encoded = encodeMessageAsPersistedBuffer(protonMessage);

        AMQPMessage message = new AMQPMessage(0);
        try {
            message.getProtonMessage();
            fail("Should throw NPE due to not being initialized yet");
        } catch (NullPointerException npe) {
        }

        final long persistedSize = (long) encoded.readableBytes();

        // Now reload from encoded data
        message.reloadPersistence(encoded);

        assertEquals(persistedSize, message.getPersistSize());
        assertEquals(persistedSize - Integer.BYTES, message.getPersistentSize());
        assertEquals(persistedSize - Integer.BYTES, message.getEncodeSize());
        assertEquals(true, message.getHeader().getDurable());
        assertEquals(TEST_TO_ADDRESS, message.getAddress());
    }

    //----- Test Memory Estimate access ---------------------------------------//

    @Test
    public void testGetMemoryEstimate() {
        AMQPMessage decoded = new AMQPMessage(0, encodedProtonMessage, new TypedProperties(), null);

        int estimate = decoded.getMemoryEstimate();
        assertTrue(encodedProtonMessage.length < decoded.getMemoryEstimate());
        assertEquals(estimate, decoded.getMemoryEstimate());

        decoded.putStringProperty(new SimpleString("newProperty"), "newValue");
        decoded.reencode();

        assertNotEquals(estimate, decoded.getMemoryEstimate());
    }

    //----- Test Connection ID access -----------------------------------------//

    @Test
    public void testDecodeMultiThreaded() throws Exception {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        Properties properties = new Properties();
        properties.setTo("someNiceLocal");
        protonMessage.setProperties(properties);
        protonMessage.getHeader().setDeliveryCount(new UnsignedInteger(7));
        protonMessage.getHeader().setDurable(Boolean.TRUE);
        protonMessage.setApplicationProperties(new ApplicationProperties(new HashMap<>()));

        final AtomicInteger failures = new AtomicInteger(0);

        for (int testTry = 0; testTry < 100; testTry++) {
            AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
            Thread[] threads = new Thread[100];

            CountDownLatch latchAlign = new CountDownLatch(threads.length);
            CountDownLatch go = new CountDownLatch(1);

            Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {

                        latchAlign.countDown();
                        go.await();

                        Assert.assertNotNull(decoded.getHeader());
                        // this is a method used by Core Converter
                        decoded.getProtonMessage();
                        Assert.assertNotNull(decoded.getHeader());

                    } catch (Throwable e) {
                        e.printStackTrace();
                        failures.incrementAndGet();
                    }
                }
            };

            for (int i = 0; i < threads.length; i++) {
                threads[i] = new Thread(run);
                threads[i].start();
            }

            Assert.assertTrue(latchAlign.await(10, TimeUnit.SECONDS));
            go.countDown();

            for (Thread thread : threads) {
                thread.join(5000);
                Assert.assertFalse(thread.isAlive());
            }

            Assert.assertEquals(0, failures.get());
        }
    }

    @Test
    public void testGetConnectionID() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(null, decoded.getConnectionID());
    }

    @Test
    public void testSetConnectionID() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        final String ID = UUID.randomUUID().toString();

        assertEquals(null, decoded.getConnectionID());
        decoded.setConnectionID(ID);
        assertEquals(ID, decoded.getConnectionID());
    }

    @Test
    public void testGetConnectionIDFromProperties() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        final String ID = UUID.randomUUID().toString();

        assertEquals(null, decoded.getConnectionID());
        decoded.setConnectionID(ID);
        assertEquals(ID, decoded.getConnectionID());
        assertEquals(ID, decoded.getStringProperty(MessageUtil.CONNECTION_ID_PROPERTY_NAME));
    }

    //----- Test LastValueProperty access -------------------------------//

    @Test
    public void testGetLastValueFromMessageWithNone() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getLastValueProperty());
    }

    @Test
    public void testSetLastValueFromMessageWithNone() {
        SimpleString lastValue = new SimpleString("last-address");

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getLastValueProperty());
        decoded.setLastValueProperty(lastValue);
        assertEquals(lastValue, decoded.getLastValueProperty());
    }

    //----- Test User ID access -----------------------------------------//

    @Test
    public void getUserIDWhenNoPropertiesExists() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getUserID());
        decoded.setUserID(UUID.randomUUID().toString());
        assertNull(decoded.getUserID());
    }

    @Test
    public void testSetUserIDHasNoEffectOnMessagePropertiesWhenNotPresent() {
        final String ID = UUID.randomUUID().toString();

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getUserID());
        assertNull(decoded.getProperties());

        decoded.setUserID(ID);
        decoded.reencode();

        assertNull(decoded.getUserID());
        assertNull(decoded.getProperties());
    }

    @Test
    public void testSetUserIDHasNoEffectOnMessagePropertiesWhenPresentButNoMessageID() {
        final String ID = UUID.randomUUID().toString();

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getUserID());
        assertNotNull(decoded.getProperties());
        assertNull(decoded.getProperties().getMessageId());

        decoded.setUserID(ID);
        decoded.reencode();

        assertNull(decoded.getUserID());
        assertNotNull(decoded.getProperties());
        assertNull(decoded.getProperties().getMessageId());
    }

    @Test
    public void testSetUserIDHasNoEffectOnMessagePropertiesWhenPresentWithMessageID() {
        final String ID = UUID.randomUUID().toString();

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        protonMessage.setMessageId(ID);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNotNull(decoded.getUserID());
        assertNotNull(decoded.getProperties());
        assertNotNull(decoded.getProperties().getMessageId());
        assertEquals(ID, decoded.getUserID());

        decoded.setUserID(ID);
        decoded.reencode();

        assertNotNull(decoded.getUserID());
        assertNotNull(decoded.getProperties());
        assertNotNull(decoded.getProperties().getMessageId());
        assertEquals(ID, decoded.getUserID());
    }

    //----- Test the getDuplicateProperty methods -----------------------------//

    @Test
    public void testGetDuplicateProperty() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(null, decoded.getDuplicateProperty());
    }

    //----- Test the getAddress methods ---------------------------------------//

    @Test
    public void testGetAddressFromMessage() {
        final String ADDRESS = "myQueue";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setAddress(ADDRESS);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(ADDRESS, decoded.getAddress());
    }

    @Test
    public void testGetAddressSimpleStringFromMessage() {
        final String ADDRESS = "myQueue";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setAddress(ADDRESS);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(ADDRESS, decoded.getAddressSimpleString().toString());
    }

    @Test
    public void testGetAddressFromMessageWithNoValueSet() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getAddress());
        assertNull(decoded.getAddressSimpleString());
    }

    @Test
    public void testSetAddressFromMessage() {
        final String ADDRESS = "myQueue";
        final SimpleString NEW_ADDRESS = new SimpleString("myQueue-1");

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setAddress(ADDRESS);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(ADDRESS, decoded.getAddress());
        decoded.setAddress(NEW_ADDRESS);
        assertEquals(NEW_ADDRESS, decoded.getAddressSimpleString());
    }

    @Test
    public void testSetAddressFromMessageUpdatesPropertiesOnReencode() {
        final String ADDRESS = "myQueue";
        final SimpleString NEW_ADDRESS = new SimpleString("myQueue-1");

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setAddress(ADDRESS);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(ADDRESS, decoded.getAddress());
        decoded.setAddress(NEW_ADDRESS);
        decoded.reencode();

        assertEquals(NEW_ADDRESS.toString(), decoded.getProperties().getTo());
        assertEquals(NEW_ADDRESS, decoded.getAddressSimpleString());
    }

    //----- Test the durability set and get methods ---------------------------//

    @Test
    public void testIsDurableFromMessageWithHeaderTaggedAsTrue() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setDurable(true);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertTrue(decoded.isDurable());
    }

    @Test
    public void testIsDurableFromMessageWithHeaderTaggedAsFalse() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setDurable(false);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertFalse(decoded.isDurable());
    }

    @Test
    public void testIsDurableFromMessageWithNoValueSet() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertFalse(decoded.isDurable());
    }

    @Test
    public void testIsDuranleReturnsTrueOnceUpdated() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertFalse(decoded.isDurable());
        decoded.setDurable(true);
        assertTrue(decoded.isDurable());
    }

    @Test
    public void testNonDurableMessageReencodedToDurable() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertFalse(decoded.isDurable());

        // Underlying message data not updated yet
        assertNull(decoded.getHeader().getDurable());

        decoded.setDurable(true);
        decoded.reencode();
        assertTrue(decoded.isDurable());

        // Underlying message data now updated
        assertTrue(decoded.getHeader().getDurable());
    }

    @Test
    public void testMessageWithNoHeaderGetsOneWhenDurableSetAndReencoded() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertFalse(decoded.isDurable());

        // Underlying message data not updated yet
        assertNull(decoded.getHeader());

        decoded.setDurable(true);
        decoded.reencode();
        assertTrue(decoded.isDurable());

        // Underlying message data now updated
        Header header = decoded.getHeader();
        assertNotNull(header);
        assertTrue(header.getDurable());
    }

    //----- Test RoutingType access -------------------------------------------//

    @Test
    public void testGetRoutingTypeFromMessageWithoutIt() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getRoutingType());
    }

    @Test
    public void testSetRoutingType() {
        RoutingType type = RoutingType.ANYCAST;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getRoutingType());
        decoded.setRoutingType(type);
        assertEquals(type, decoded.getRoutingType());
    }

    @Test
    public void testSetRoutingTypeToClear() {
        RoutingType type = RoutingType.ANYCAST;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getRoutingType());
        decoded.setRoutingType(type);
        assertEquals(type, decoded.getRoutingType());
        decoded.setRoutingType(null);
        assertNull(decoded.getRoutingType());
    }

    @Test
    public void testRemoveRoutingTypeFromMessageEncodedWithOne() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.ROUTING_TYPE, RoutingType.ANYCAST.getType());
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
        decoded.setRoutingType(null);
        decoded.reencode();
        assertNull(decoded.getRoutingType());

        assertTrue(decoded.getMessageAnnotations().getValue().isEmpty());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithAnyCastType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.ROUTING_TYPE, RoutingType.ANYCAST.getType());
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithMulticastType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.ROUTING_TYPE, RoutingType.MULTICAST.getType());
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.MULTICAST, decoded.getRoutingType());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithQueueType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, AMQPMessageSupport.QUEUE_TYPE);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithTempQueueType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION,
                AMQPMessageSupport.TEMP_QUEUE_TYPE);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.ANYCAST, decoded.getRoutingType());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithTopicType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, AMQPMessageSupport.TOPIC_TYPE);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.MULTICAST, decoded.getRoutingType());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithTempTopicType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION,
                AMQPMessageSupport.TEMP_TOPIC_TYPE);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(RoutingType.MULTICAST, decoded.getRoutingType());
    }

    @Test
    public void testGetRoutingTypeFromMessageWithUnknownType() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION, (byte) 32);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getRoutingType());
    }

    //----- Test access to message Group ID -----------------------------------//

    @Test
    public void testGetGroupIDFromMessage() {
        final String GROUP_ID = "group-1";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setGroupId(GROUP_ID);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(GROUP_ID, decoded.getGroupID().toString());
    }

    @Test
    public void testGetGroupIDFromMessageWithNoGroupId() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertNull(decoded.getGroupID());
    }

    @Test
    public void testGetGroupIDFromMessageWithNoProperties() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertNull(decoded.getGroupID());
    }

    //----- Test access to message Group ID -----------------------------------//

    @Test
    public void testGetReplyToFromMessage() {
        final String REPLY_TO = "address-1";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setReplyTo(REPLY_TO);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(REPLY_TO, decoded.getReplyTo().toString());
    }

    @Test
    public void testGetReplyToFromMessageWithNoReplyTo() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertNull(decoded.getReplyTo());
    }

    @Test
    public void testGetReplyToFromMessageWithNoProperties() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertNull(decoded.getReplyTo());
    }

    @Test
    public void testSetReplyToFromMessageWithProperties() {
        final String REPLY_TO = "address-1";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertNull(decoded.getReplyTo());

        decoded.setReplyTo(new SimpleString(REPLY_TO));
        decoded.reencode();

        assertEquals(REPLY_TO, decoded.getReplyTo().toString());
        assertEquals(REPLY_TO, decoded.getProperties().getReplyTo());
    }

    @Test
    public void testSetReplyToFromMessageWithNoProperties() {
        final String REPLY_TO = "address-1";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertNull(decoded.getReplyTo());

        decoded.setReplyTo(new SimpleString(REPLY_TO));
        decoded.reencode();

        assertEquals(REPLY_TO, decoded.getReplyTo().toString());
        assertEquals(REPLY_TO, decoded.getProperties().getReplyTo());
    }

    @Test
    public void testSetReplyToFromMessageWithPropertiesCanClear() {
        final String REPLY_TO = "address-1";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        protonMessage.setReplyTo(REPLY_TO);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertEquals(REPLY_TO, decoded.getReplyTo().toString());

        decoded.setReplyTo(null);
        decoded.reencode();

        assertEquals(null, decoded.getReplyTo());
        assertEquals(null, decoded.getProperties().getReplyTo());
    }

    //----- Test access to User ID --------------------------------------------//

    @Test
    public void testGetUserIDFromMessage() {
        final String USER_NAME = "foo";

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setUserId(USER_NAME.getBytes(StandardCharsets.UTF_8));
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(USER_NAME, decoded.getAMQPUserID());
    }

    @Test
    public void testGetUserIDFromMessageWithNoProperties() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getAMQPUserID());
    }

    @Test
    public void testGetUserIDFromMessageWithNoUserID() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getAMQPUserID());
    }

    //----- Test access message priority --------------------------------------//

    @Test
    public void testGetPriorityFromMessage() {
        final short PRIORITY = 7;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setPriority(PRIORITY);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(PRIORITY, decoded.getPriority());
    }

    @Test
    public void testGetPriorityFromMessageWithNoHeader() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
    }

    @Test
    public void testGetPriorityFromMessageWithNoPrioritySet() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());
    }

    @Test
    public void testSetPriorityOnMessageWithHeader() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());

        decoded.setPriority((byte) 9);
        decoded.reencode();

        assertEquals(9, decoded.getPriority());
        assertEquals(9, decoded.getHeader().getPriority().byteValue());
    }

    @Test
    public void testSetPriorityOnMessageWithoutHeader() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(AMQPMessage.DEFAULT_MESSAGE_PRIORITY, decoded.getPriority());

        decoded.setPriority((byte) 9);
        decoded.reencode();

        assertEquals(9, decoded.getPriority());
        assertEquals(9, decoded.getHeader().getPriority().byteValue());
    }

    //----- Test access message expiration ------------------------------------//

    @Test
    public void testGetExpirationFromMessageWithNoHeader() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
    }

    @Test
    public void testGetExpirationFromMessageWithNoTTLInHeader() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
    }

    @Test
    public void testGetExpirationFromMessageWithNoTTLInHeaderOrExpirationInProperties() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
    }

    @Test
    public void testGetExpirationFromMessageUsingTTL() {
        final long ttl = 100000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setTtl(ttl);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertTrue(decoded.getExpiration() > System.currentTimeMillis());
    }

    @Test
    public void testGetExpirationFromMessageUsingAbsoluteExpiration() {
        final Date expirationTime = new Date(System.currentTimeMillis());

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        Properties properties = new Properties();
        properties.setAbsoluteExpiryTime(expirationTime);
        protonMessage.setProperties(properties);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(expirationTime.getTime(), decoded.getExpiration());
    }

    @Test
    public void testGetExpirationFromMessageUsingAbsoluteExpirationNegative() {
        final Date expirationTime = new Date(-1);

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        Properties properties = new Properties();
        properties.setAbsoluteExpiryTime(expirationTime);
        protonMessage.setProperties(properties);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
    }

    @Test
    public void testGetExpirationFromMessageAbsoluteExpirationOVerrideTTL() {
        final Date expirationTime = new Date(System.currentTimeMillis());
        final long ttl = 100000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setTtl(ttl);
        Properties properties = new Properties();
        properties.setAbsoluteExpiryTime(expirationTime);
        protonMessage.setProperties(properties);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(expirationTime.getTime(), decoded.getExpiration());
    }

    @Test
    public void testSetExpiration() {
        final Date expirationTime = new Date(System.currentTimeMillis());

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
        decoded.setExpiration(expirationTime.getTime());
        assertEquals(expirationTime.getTime(), decoded.getExpiration());
    }

    @Test
    public void testSetExpirationUpdatesProperties() {
        final Date originalExpirationTime = new Date(System.currentTimeMillis());
        final Date expirationTime = new Date(System.currentTimeMillis());

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        protonMessage.setExpiryTime(originalExpirationTime.getTime());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(originalExpirationTime.getTime(), decoded.getExpiration());
        decoded.setExpiration(expirationTime.getTime());
        assertEquals(expirationTime.getTime(), decoded.getExpiration());

        decoded.reencode();
        assertEquals(expirationTime, decoded.getProperties().getAbsoluteExpiryTime());
    }

    @Test
    public void testSetExpirationAddsPropertiesWhenNonePresent() {
        final Date expirationTime = new Date(System.currentTimeMillis());

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
        decoded.setExpiration(expirationTime.getTime());
        assertEquals(expirationTime.getTime(), decoded.getExpiration());

        decoded.reencode();
        assertEquals(expirationTime, decoded.getProperties().getAbsoluteExpiryTime());
    }

    @Test
    public void testSetExpirationToClearUpdatesPropertiesWhenPresent() {
        final Date expirationTime = new Date(System.currentTimeMillis());

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        protonMessage.setExpiryTime(expirationTime.getTime());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(expirationTime.getTime(), decoded.getExpiration());
        decoded.setExpiration(-1);
        assertEquals(0, decoded.getExpiration());

        decoded.reencode();
        assertEquals(0, decoded.getExpiration());
        assertNull(decoded.getProperties().getAbsoluteExpiryTime());
    }

    @Test
    public void testSetExpirationToClearDoesNotAddPropertiesWhenNonePresent() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getExpiration());
        decoded.setExpiration(-1);
        assertEquals(0, decoded.getExpiration());

        decoded.reencode();
        assertEquals(0, decoded.getExpiration());
        assertNull(decoded.getProperties());
    }

    @Test
    public void testSetExpirationToClearUpdateHeader() {
        final long ttl = 100000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        protonMessage.setTtl(ttl);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertTrue(decoded.getExpiration() > System.currentTimeMillis());

        decoded.setExpiration(-1);
        decoded.reencode();

        assertEquals(0, decoded.getExpiration());
        assertNull(decoded.getHeader().getTtl());
    }

    //----- Test access message time stamp ------------------------------------//

    @Test
    public void testGetTimestampFromMessage() {
        Date timestamp = new Date(System.currentTimeMillis());

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        Properties properties = new Properties();
        properties.setCreationTime(timestamp);
        protonMessage.setProperties(properties);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(timestamp.getTime(), decoded.getTimestamp());
    }

    @Test
    public void testGetTimestampFromMessageWithNoCreateTimeSet() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setHeader(new Header());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0L, decoded.getTimestamp());
    }

    @Test
    public void testGetTimestampFromMessageWithNoHeader() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0L, decoded.getTimestamp());
    }

    @Test
    public void testSetTimestampOnMessage() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        protonMessage.setProperties(new Properties());
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0L, decoded.getTimestamp());

        Date createTime = new Date(System.currentTimeMillis());

        decoded.setTimestamp(createTime.getTime());
        decoded.reencode();

        assertEquals(createTime.getTime(), decoded.getTimestamp());
        assertEquals(createTime, decoded.getProperties().getCreationTime());
    }

    @Test
    public void testSetTimestampOnMessageWithNoPropertiesSection() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0L, decoded.getTimestamp());

        Date createTime = new Date(System.currentTimeMillis());

        decoded.setTimestamp(createTime.getTime());
        decoded.reencode();

        assertNotNull(decoded.getProperties());
        assertEquals(createTime.getTime(), decoded.getTimestamp());
        assertEquals(createTime, decoded.getProperties().getCreationTime());
    }

    //----- Test access to message scheduled delivery time --------------------//

    @Test
    public void testGetScheduledDeliveryTimeMessageSentWithFixedTime() {
        final long scheduledTime = System.currentTimeMillis();

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());
    }

    @Test
    public void testGetScheduledDeliveryTimeMessageSentWithFixedTimeAndDelay() {
        final long scheduledTime = System.currentTimeMillis();
        final long scheduledDelay = 100000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());
    }

    @Test
    public void testGetScheduledDeliveryTimeMessageSentWithFixedDelay() {
        final long scheduledDelay = 100000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertTrue(decoded.getScheduledDeliveryTime().longValue() > System.currentTimeMillis());
    }

    @Test
    public void testGetScheduledDeliveryTimeWhenMessageHasNoSetValue() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        assertEquals(0, decoded.getScheduledDeliveryTime().longValue());
    }

    @Test
    public void testSetScheduledDeliveryTimeWhenNonPresent() {
        final long scheduledTime = System.currentTimeMillis() + 5000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(0, decoded.getScheduledDeliveryTime().longValue());
        decoded.setScheduledDeliveryTime(scheduledTime);
        assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());

        decoded.reencode();

        assertEquals(scheduledTime,
                decoded.getMessageAnnotations().getValue().get(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME));
    }

    @Test
    public void testSetScheduledDeliveryTimeMessageSentWithFixedTime() {
        final long scheduledTime = System.currentTimeMillis();
        final long newScheduledTime = System.currentTimeMillis() + 1000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());

        decoded.setScheduledDeliveryTime(newScheduledTime);
        assertEquals(newScheduledTime, decoded.getScheduledDeliveryTime().longValue());
        decoded.reencode();
        assertEquals(newScheduledTime,
                decoded.getMessageAnnotations().getValue().get(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME));
    }

    @Test
    public void testSetScheduledDeliveryTimeMessageSentWithFixedDelay() {
        final long scheduledDelay = 100000;
        final long newScheduledTime = System.currentTimeMillis() + 1000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertTrue(decoded.getScheduledDeliveryTime().longValue() > System.currentTimeMillis());

        decoded.setScheduledDeliveryTime(newScheduledTime);
        assertEquals(newScheduledTime, decoded.getScheduledDeliveryTime().longValue());
        decoded.reencode();
        assertEquals(newScheduledTime,
                decoded.getMessageAnnotations().getValue().get(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME));
    }

    @Test
    public void testSetScheduledDeliveryTimeToNoneClearsDelayAndTimeValues() {
        final long scheduledTime = System.currentTimeMillis();
        final long scheduledDelay = 100000;

        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY, scheduledDelay);
        annotations.getValue().put(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME, scheduledTime);
        protonMessage.setMessageAnnotations(annotations);
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertEquals(scheduledTime, decoded.getScheduledDeliveryTime().longValue());

        decoded.setScheduledDeliveryTime((long) 0);
        decoded.reencode();
        assertNull(decoded.getMessageAnnotations().getValue().get(AMQPMessageSupport.SCHEDULED_DELIVERY_TIME));
        assertNull(decoded.getMessageAnnotations().getValue().get(AMQPMessageSupport.SCHEDULED_DELIVERY_DELAY));
    }

    //----- Tests access to Message Annotations -------------------------------//

    @Test
    public void testGetAnnotation() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null);

        Object result = message.getAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY));
        String stringResult = message.getAnnotationString(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY));

        assertEquals(result, stringResult);
    }

    @Test
    public void testRemoveAnnotation() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null);

        assertNotNull(message.getAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY)));
        message.removeAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY));
        assertNull(message.getAnnotation(new SimpleString(TEST_MESSAGE_ANNOTATION_KEY)));

        message.reencode();

        assertTrue(message.getMessageAnnotations().getValue().isEmpty());
    }

    @Test
    public void testSetAnnotation() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null);

        final SimpleString newAnnotation = new SimpleString("testSetAnnotation");
        final String newValue = "newValue";

        message.setAnnotation(newAnnotation, newValue);
        assertEquals(newValue, message.getAnnotation(newAnnotation));

        message.reencode();

        assertEquals(newValue,
                message.getMessageAnnotations().getValue().get(Symbol.valueOf(newAnnotation.toString())));
    }

    //----- Tests accessing Proton Sections from encoded data -----------------//

    @Test
    public void testGetProtonMessage() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        assertProtonMessageEquals(protonMessage, message.getProtonMessage());

        message.setAnnotation(new SimpleString("testGetProtonMessage"), "1");
        message.messageChanged();

        assertProtonMessageNotEquals(protonMessage, message.getProtonMessage());
    }

    @Test
    public void testGetHeader() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        Header decoded = message.getHeader();
        assertNotSame(decoded, protonMessage.getHeader());
        assertHeaderEquals(protonMessage.getHeader(), decoded);

        // Update the values
        decoded.setDeliveryCount(UnsignedInteger.ZERO);
        decoded.setTtl(UnsignedInteger.valueOf(255));
        decoded.setFirstAcquirer(true);

        // Check that the message is unaffected.
        assertHeaderNotEquals(protonMessage.getHeader(), decoded);
    }

    @Test
    public void testGetProperties() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        Properties decoded = message.getProperties();
        assertNotSame(decoded, protonMessage.getProperties());
        assertPropertiesEquals(protonMessage.getProperties(), decoded);

        // Update the values
        decoded.setAbsoluteExpiryTime(new Date(System.currentTimeMillis()));
        decoded.setGroupSequence(UnsignedInteger.valueOf(255));
        decoded.setSubject(UUID.randomUUID().toString());

        // Check that the message is unaffected.
        assertPropertiesNotEquals(protonMessage.getProperties(), decoded);
    }

    @Test
    public void testGetDeliveryAnnotations() {
        MessageImpl protonMessage = createProtonMessage();
        DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
        deliveryAnnotations.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test-1");
        protonMessage.setDeliveryAnnotations(deliveryAnnotations);

        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        DeliveryAnnotations decoded = message.getDeliveryAnnotations();
        assertNotSame(decoded, protonMessage.getDeliveryAnnotations());
        assertDeliveryAnnotationsEquals(protonMessage.getDeliveryAnnotations(), decoded);

        // Update the values
        decoded.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test-2");

        // Check that the message is unaffected.
        assertDeliveryAnnotationsNotEquals(protonMessage.getDeliveryAnnotations(), decoded);
    }

    @Test
    public void testGetMessageAnnotations() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        MessageAnnotations decoded = message.getMessageAnnotations();
        assertNotSame(decoded, protonMessage.getMessageAnnotations());
        assertMessageAnnotationsEquals(protonMessage.getMessageAnnotations(), decoded);

        // Update the values
        decoded.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test");

        // Check that the message is unaffected.
        assertMessageAnnotationsNotEquals(protonMessage.getMessageAnnotations(), decoded);
    }

    @Test
    public void testGetApplicationProperties() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        ApplicationProperties decoded = message.getApplicationProperties();
        assertNotSame(decoded, protonMessage.getApplicationProperties());
        assertApplicationPropertiesEquals(protonMessage.getApplicationProperties(), decoded);

        // Update the values
        decoded.getValue().put(UUID.randomUUID().toString(), "test");

        // Check that the message is unaffected.
        assertApplicationPropertiesNotEquals(protonMessage.getApplicationProperties(), decoded);
    }

    @Test
    public void testGetBody() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        Object body = message.getBody();
        assertTrue(body instanceof AmqpValue);
        AmqpValue amqpValueBody = (AmqpValue) body;

        assertNotNull(amqpValueBody.getValue());
        assertNotSame(((AmqpValue) protonMessage.getBody()).getValue(), amqpValueBody.getValue());
        assertEquals(((AmqpValue) protonMessage.getBody()).getValue(), amqpValueBody.getValue());
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testGetFooter() {
        MessageImpl protonMessage = createProtonMessage();
        Footer footer = new Footer(new HashMap<>());
        footer.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test-1");
        protonMessage.setFooter(footer);

        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        Footer decoded = message.getFooter();
        assertNotSame(decoded, protonMessage.getFooter());
        assertFootersEquals(protonMessage.getFooter(), decoded);

        // Update the values
        decoded.getValue().put(Symbol.valueOf(UUID.randomUUID().toString()), "test-2");

        // Check that the message is unaffected.
        assertFootersNotEquals(protonMessage.getFooter(), decoded);
    }

    //----- Test re-encode of updated message sections ------------------------//

    @Test
    public void testApplicationPropertiesReencodeAfterUpdate() {
        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertProtonMessageEquals(protonMessage, decoded.getProtonMessage());

        decoded.putStringProperty("key-2", "value-2");
        decoded.reencode();

        assertProtonMessageNotEquals(protonMessage, decoded.getProtonMessage());

        assertEquals(decoded.getStringProperty(TEST_APPLICATION_PROPERTY_KEY), TEST_APPLICATION_PROPERTY_VALUE);
        assertEquals(decoded.getStringProperty("key-2"), "value-2");
    }

    @Test
    public void testMessageAnnotationsReencodeAfterUpdate() {
        final SimpleString TEST_ANNOTATION = new SimpleString("testMessageAnnotationsReencodeAfterUpdate");

        MessageImpl protonMessage = createProtonMessage();
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertProtonMessageEquals(protonMessage, decoded.getProtonMessage());

        decoded.setAnnotation(TEST_ANNOTATION, "value-2");
        decoded.reencode();

        assertProtonMessageNotEquals(protonMessage, decoded.getProtonMessage());

        assertEquals(decoded.getAnnotation(TEST_ANNOTATION), "value-2");
    }

    //----- Test handling of message extra properties -------------------------//

    @Test
    public void testExtraByteProperty() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();

        byte[] value = RandomUtil.randomBytes();
        SimpleString name = SimpleString.toSimpleString("myProperty");

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        assertNull(decoded.getExtraProperties());
        assertNull(decoded.getExtraBytesProperty(name));
        assertNull(decoded.removeExtraBytesProperty(name));

        decoded.putExtraBytesProperty(name, value);
        assertFalse(decoded.getExtraProperties().isEmpty());

        assertTrue(Arrays.equals(value, decoded.getExtraBytesProperty(name)));
        assertTrue(Arrays.equals(value, decoded.removeExtraBytesProperty(name)));
    }

    @Test
    public void testExtraProperty() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();

        byte[] original = RandomUtil.randomBytes();
        SimpleString name = SimpleString.toSimpleString("myProperty");
        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);
        decoded.setAddress("someAddress");
        decoded.setMessageID(33);
        decoded.putExtraBytesProperty(name, original);

        ICoreMessage coreMessage = decoded.toCore();
        Assert.assertEquals(original, coreMessage.getBytesProperty(name));

        ActiveMQBuffer buffer = ActiveMQBuffers.pooledBuffer(10 * 1024);
        try {
            decoded.getPersister().encode(buffer, decoded);
            Assert.assertEquals(AMQPMessagePersisterV2.getInstance().getID(), buffer.readByte()); // the journal reader will read 1 byte to find the persister
            AMQPMessage readMessage = (AMQPMessage) decoded.getPersister().decode(buffer, null);
            Assert.assertEquals(33, readMessage.getMessageID());
            Assert.assertEquals("someAddress", readMessage.getAddress());
            assertArrayEquals(original, readMessage.getExtraBytesProperty(name));
        } finally {
            buffer.release();
        }

        {
            ICoreMessage embeddedMessage = EmbedMessageUtil.embedAsCoreMessage(decoded);
            AMQPMessage readMessage = (AMQPMessage) EmbedMessageUtil.extractEmbedded(embeddedMessage);
            Assert.assertEquals(33, readMessage.getMessageID());
            Assert.assertEquals("someAddress", readMessage.getAddress());
            assertArrayEquals(original, readMessage.getExtraBytesProperty(name));
        }
    }

    //----- Test that message decode ignores unused sections ------------------//

    private static final UnsignedLong AMQPVALUE_DESCRIPTOR = UnsignedLong.valueOf(0x0000000000000077L);
    private static final UnsignedLong APPLICATION_PROPERTIES_DESCRIPTOR = UnsignedLong.valueOf(0x0000000000000074L);
    private static final UnsignedLong DELIVERY_ANNOTATIONS_DESCRIPTOR = UnsignedLong.valueOf(0x0000000000000071L);

    @Test
    public void testPartialDecodeIgnoresDeliveryAnnotationsByDefault() {
        Header header = new Header();
        header.setDurable(true);
        header.setPriority(UnsignedByte.valueOf((byte) 6));

        ByteBuf encodedBytes = Unpooled.buffer(1024);
        NettyWritable writable = new NettyWritable(encodedBytes);

        EncoderImpl encoder = TLSEncode.getEncoder();
        encoder.setByteBuffer(writable);
        encoder.writeObject(header);

        // Signal body of AmqpValue but write corrupt underlying type info
        encodedBytes.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
        encodedBytes.writeByte(EncodingCodes.SMALLULONG);
        encodedBytes.writeByte(DELIVERY_ANNOTATIONS_DESCRIPTOR.byteValue());
        encodedBytes.writeByte(EncodingCodes.MAP8);
        encodedBytes.writeByte(2); // Size
        encodedBytes.writeByte(2); // Elements
        // Use bad encoding code on underlying type of map key which will fail the decode if run
        encodedBytes.writeByte(255);

        ReadableBuffer readable = new NettyReadable(encodedBytes);

        AMQPMessage message = null;
        try {
            message = new AMQPMessage(0, readable, null, null);
        } catch (Exception decodeError) {
            fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
        }

        try {
            // This should perform the lazy decode of the DeliveryAnnotations portion of the message
            message.getDeliveryAnnotations();
            fail("Should have thrown an error when attempting to decode the ApplicationProperties which are malformed.");
        } catch (Exception ex) {
            // Expected decode to fail when building full message.
        }
    }

    @Test
    public void testPartialDecodeIgnoresApplicationPropertiesByDefault() {
        Header header = new Header();
        header.setDurable(true);
        header.setPriority(UnsignedByte.valueOf((byte) 6));

        ByteBuf encodedBytes = Unpooled.buffer(1024);
        NettyWritable writable = new NettyWritable(encodedBytes);

        EncoderImpl encoder = TLSEncode.getEncoder();
        encoder.setByteBuffer(writable);
        encoder.writeObject(header);

        // Signal body of AmqpValue but write corrupt underlying type info
        encodedBytes.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
        encodedBytes.writeByte(EncodingCodes.SMALLULONG);
        encodedBytes.writeByte(APPLICATION_PROPERTIES_DESCRIPTOR.byteValue());
        encodedBytes.writeByte(EncodingCodes.MAP8);
        encodedBytes.writeByte(2); // Size
        encodedBytes.writeByte(2); // Elements
        // Use bad encoding code on underlying type of map key which will fail the decode if run
        encodedBytes.writeByte(255);

        ReadableBuffer readable = new NettyReadable(encodedBytes);

        AMQPMessage message = null;
        try {
            message = new AMQPMessage(0, readable, null, null);
        } catch (Exception decodeError) {
            fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
        }

        assertTrue(message.isDurable());

        try {
            // This should perform the lazy decode of the ApplicationProperties portion of the message
            message.getStringProperty("test");
            fail("Should have thrown an error when attempting to decode the ApplicationProperties which are malformed.");
        } catch (Exception ex) {
            // Expected decode to fail when building full message.
        }
    }

    @Test
    public void testPartialDecodeIgnoresBodyByDefault() {
        Header header = new Header();
        header.setDurable(true);
        header.setPriority(UnsignedByte.valueOf((byte) 6));

        ByteBuf encodedBytes = Unpooled.buffer(1024);
        NettyWritable writable = new NettyWritable(encodedBytes);

        EncoderImpl encoder = TLSEncode.getEncoder();
        encoder.setByteBuffer(writable);
        encoder.writeObject(header);

        // Signal body of AmqpValue but write corrupt underlying type info
        encodedBytes.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
        encodedBytes.writeByte(EncodingCodes.SMALLULONG);
        encodedBytes.writeByte(AMQPVALUE_DESCRIPTOR.byteValue());
        // Use bad encoding code on underlying type
        encodedBytes.writeByte(255);

        ReadableBuffer readable = new NettyReadable(encodedBytes);

        AMQPMessage message = null;
        try {
            message = new AMQPMessage(0, readable, null, null);
        } catch (Exception decodeError) {
            fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
        }

        assertTrue(message.isDurable());

        try {
            // This will decode the body section if present in order to present it as a Proton Message object
            message.getBody();
            fail("Should have thrown an error when attempting to decode the body which is malformed.");
        } catch (Exception ex) {
            // Expected decode to fail when building full message.
        }
    }

    //----- Tests for message copy correctness --------------------------------//

    @Test
    public void testCopyMessage() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
        message.setMessageID(127);
        AMQPMessage copy = (AMQPMessage) message.copy();

        assertEquals(message.getMessageID(), copy.getMessageID());
        assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
    }

    @Test
    public void testCopyMessageWithNewArtemisMessageID() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);
        message.setMessageID(127);
        AMQPMessage copy = (AMQPMessage) message.copy(255);

        assertNotEquals(message.getMessageID(), copy.getMessageID());
        assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
    }

    @Test
    public void testCopyMessageDoesNotRemovesMessageAnnotations() {
        MessageImpl protonMessage = createProtonMessage();
        DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
        deliveryAnnotations.getValue().put(Symbol.valueOf("testCopyMessageRemovesMessageAnnotations"), "1");
        protonMessage.setDeliveryAnnotations(deliveryAnnotations);

        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);
        message.setMessageID(127);
        AMQPMessage copy = (AMQPMessage) message.copy();

        assertEquals(message.getMessageID(), copy.getMessageID());
        assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
        assertNotNull(copy.getDeliveryAnnotations());
    }

    @Test
    public void testDecodeCopyUpdateReencodeAndThenDecodeAgain() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);

        // Sanity checks
        assertTrue(message.isDurable());
        assertEquals(TEST_STRING_BODY, ((AmqpValue) message.getBody()).getValue());

        // Copy the message
        message = (AMQPMessage) message.copy();

        // Sanity checks
        assertTrue(message.isDurable());
        assertEquals(TEST_STRING_BODY, ((AmqpValue) message.getBody()).getValue());

        // Update the message
        message.setAnnotation(new SimpleString("x-opt-extra-1"), "test-1");
        message.setAnnotation(new SimpleString("x-opt-extra-2"), "test-2");
        message.setAnnotation(new SimpleString("x-opt-extra-3"), "test-3");

        // Reencode and then decode the message again
        message.reencode();

        // Sanity checks
        assertTrue(message.isDurable());
        assertEquals(TEST_STRING_BODY, ((AmqpValue) message.getBody()).getValue());
    }

    //----- Test sendBuffer method --------------------------------------------//

    @Test
    public void testSendBuffer() {
        ByteBuf buffer = Unpooled.buffer(255);
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);

        message.sendBuffer(buffer, 1);

        assertNotNull(buffer);

        AMQPMessage copy = new AMQPMessage(0, new NettyReadable(buffer), null, null);

        assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
    }

    //----- Test getSendBuffer variations -------------------------------------//

    @Test
    public void testGetSendBuffer() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);

        ReadableBuffer buffer = message.getSendBuffer(1);
        assertNotNull(buffer);
        assertTrue(buffer.hasArray());

        assertTrue(Arrays.equals(encodedProtonMessage, buffer.array()));

        AMQPMessage copy = new AMQPMessage(0, buffer, null, null);

        assertProtonMessageEquals(message.getProtonMessage(), copy.getProtonMessage());
    }

    @Test
    public void testGetSendBufferAddsDeliveryCountOnlyToSendMessage() {
        AMQPMessage message = new AMQPMessage(0, encodedProtonMessage, null, null);

        ReadableBuffer buffer = message.getSendBuffer(7);
        assertNotNull(buffer);
        message.reencode(); // Ensures Header is current if accidentally updated

        AMQPMessage copy = new AMQPMessage(0, buffer, null, null);

        MessageImpl originalsProtonMessage = message.getProtonMessage();
        MessageImpl copyProtonMessage = copy.getProtonMessage();
        assertProtonMessageNotEquals(originalsProtonMessage, copyProtonMessage);

        assertNull(originalsProtonMessage.getHeader().getDeliveryCount());
        assertEquals(6, copyProtonMessage.getHeader().getDeliveryCount().intValue());
    }

    @Test
    public void testGetSendBufferAddsDeliveryCountOnlyToSendMessageOriginalHadNoHeader() {
        MessageImpl protonMessage = (MessageImpl) Proton.message();
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        ReadableBuffer buffer = message.getSendBuffer(7);
        assertNotNull(buffer);
        message.reencode(); // Ensures Header is current if accidentally updated

        AMQPMessage copy = new AMQPMessage(0, buffer, null, null);

        MessageImpl originalsProtonMessage = message.getProtonMessage();
        MessageImpl copyProtonMessage = copy.getProtonMessage();
        assertProtonMessageNotEquals(originalsProtonMessage, copyProtonMessage);

        assertNull(originalsProtonMessage.getHeader());
        assertEquals(6, copyProtonMessage.getHeader().getDeliveryCount().intValue());
    }

    @Test
    public void testGetSendBufferRemoveDeliveryAnnotations() {
        MessageImpl protonMessage = createProtonMessage();
        DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
        deliveryAnnotations.getValue().put(Symbol.valueOf("testGetSendBufferRemoveDeliveryAnnotations"), "X");
        protonMessage.setDeliveryAnnotations(deliveryAnnotations);
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        ReadableBuffer buffer = message.getSendBuffer(1);
        assertNotNull(buffer);

        AMQPMessage copy = new AMQPMessage(0, buffer, null, null);

        MessageImpl copyProtonMessage = copy.getProtonMessage();
        assertProtonMessageNotEquals(message.getProtonMessage(), copyProtonMessage);
        assertNull(copyProtonMessage.getDeliveryAnnotations());
    }

    @Test
    public void testGetSendBufferAddsDeliveryCountOnlyToSendMessageAndTrimsDeliveryAnnotations() {
        MessageImpl protonMessage = createProtonMessage();
        DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
        deliveryAnnotations.getValue().put(Symbol.valueOf("testGetSendBufferRemoveDeliveryAnnotations"), "X");
        protonMessage.setDeliveryAnnotations(deliveryAnnotations);
        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        ReadableBuffer buffer = message.getSendBuffer(7);
        assertNotNull(buffer);
        message.reencode(); // Ensures Header is current if accidentally updated

        AMQPMessage copy = new AMQPMessage(0, buffer, null, null);

        MessageImpl originalsProtonMessage = message.getProtonMessage();
        MessageImpl copyProtonMessage = copy.getProtonMessage();
        assertProtonMessageNotEquals(originalsProtonMessage, copyProtonMessage);

        assertNull(originalsProtonMessage.getHeader().getDeliveryCount());
        assertEquals(6, copyProtonMessage.getHeader().getDeliveryCount().intValue());
        assertNull(copyProtonMessage.getDeliveryAnnotations());
    }

    //----- Test reencode method ----------------------------------------------//

    @Test
    public void testReencodeOnMessageWithNoPayoad() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, false, false, false, false);
    }

    @Test
    public void testReencodeOnMessageWithFullPayoad() {
        doTestMessageReencodeProducesEqualMessage(true, true, true, true, true, true, true);
    }

    @Test
    public void testReencodeOnMessageWithHeadersOnly() {
        doTestMessageReencodeProducesEqualMessage(true, false, false, false, false, false, false);
    }

    @Test
    public void testReencodeOnMessageWithDeliveryAnnotationsOnly() {
        doTestMessageReencodeProducesEqualMessage(false, true, false, false, false, false, false);
    }

    @Test
    public void testReencodeOnMessageWithMessageAnnotationsOnly() {
        doTestMessageReencodeProducesEqualMessage(false, false, true, false, false, false, false);
    }

    @Test
    public void testReencodeOnMessageWithPropertiesOnly() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, true, false, false, false);
    }

    @Test
    public void testReencodeOnMessageWithApplicationPropertiesOnly() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, false, true, false, false);
    }

    @Test
    public void testReencodeOnMessageWithBodyOnly() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, false, false, true, false);
    }

    @Test
    public void testReencodeOnMessageWithFooterOnly() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, false, false, false, true);
    }

    @Test
    public void testReencodeOnMessageWithApplicationPropertiesAndBody() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, false, true, true, false);
    }

    @Test
    public void testReencodeOnMessageWithApplicationPropertiesAndBodyAndFooter() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, false, true, true, true);
    }

    @Test
    public void testReencodeOnMessageWithPropertiesApplicationPropertiesAndBodyAndFooter() {
        doTestMessageReencodeProducesEqualMessage(false, false, false, true, true, true, true);
    }

    @Test
    public void testReencodeOnMessageWithMessageAnnotationsPropertiesApplicationPropertiesAndBodyAndFooter() {
        doTestMessageReencodeProducesEqualMessage(false, false, true, true, true, true, true);
    }

    @Test
    public void testReencodeOnMessageWithDeliveryAnnotationsMessageAnnotationsPropertiesApplicationPropertiesBodyFooter() {
        doTestMessageReencodeProducesEqualMessage(false, true, true, true, true, true, true);
    }

    @SuppressWarnings("unchecked")
    private void doTestMessageReencodeProducesEqualMessage(boolean header, boolean deliveryAnnotations,
            boolean messageAnnotations, boolean properties, boolean applicationProperties, boolean body,
            boolean footer) {

        MessageImpl protonMessage = (MessageImpl) Proton.message();

        if (header) {
            Header headers = new Header();
            headers.setDurable(true);
            headers.setPriority(UnsignedByte.valueOf((byte) 9));
            protonMessage.setHeader(headers);
        }

        if (properties) {
            Properties props = new Properties();
            props.setCreationTime(new Date(System.currentTimeMillis()));
            props.setTo(TEST_TO_ADDRESS);
            props.setMessageId(UUID.randomUUID());
            protonMessage.setProperties(props);
        }

        if (deliveryAnnotations) {
            DeliveryAnnotations annotations = new DeliveryAnnotations(new HashMap<>());
            annotations.getValue().put(Symbol.valueOf(TEST_MESSAGE_ANNOTATION_KEY + "_DA"),
                    TEST_MESSAGE_ANNOTATION_VALUE);
            protonMessage.setDeliveryAnnotations(annotations);
        }

        if (messageAnnotations) {
            MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
            annotations.getValue().put(Symbol.valueOf(TEST_MESSAGE_ANNOTATION_KEY), TEST_MESSAGE_ANNOTATION_VALUE);
            protonMessage.setMessageAnnotations(annotations);
        }

        if (applicationProperties) {
            ApplicationProperties appProps = new ApplicationProperties(new HashMap<>());
            appProps.getValue().put(TEST_APPLICATION_PROPERTY_KEY, TEST_APPLICATION_PROPERTY_VALUE);
            protonMessage.setApplicationProperties(appProps);
        }

        if (body) {
            AmqpValue text = new AmqpValue(TEST_STRING_BODY);
            protonMessage.setBody(text);
        }

        if (footer) {
            Footer foot = new Footer(new HashMap<>());
            foot.getValue().put(Symbol.valueOf(TEST_MESSAGE_ANNOTATION_KEY + "_FOOT"),
                    TEST_MESSAGE_ANNOTATION_VALUE);
            protonMessage.setFooter(foot);
        }

        AMQPMessage message = new AMQPMessage(0, encodeMessage(protonMessage), null, null);

        message.reencode();

        if (deliveryAnnotations) {
            assertProtonMessageNotEquals(protonMessage, message.getProtonMessage());
        } else {
            assertProtonMessageEquals(protonMessage, message.getProtonMessage());
        }
    }

    @Test
    public void testGetSendBufferWithoutDeliveryAnnotations() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        Header header = new Header();
        header.setDeliveryCount(new UnsignedInteger(1));
        protonMessage.setHeader(header);
        Properties properties = new Properties();
        properties.setTo("someNiceLocal");
        protonMessage.setProperties(properties);
        protonMessage.setBody(new AmqpValue("Sample payload"));

        DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
        final String annotationKey = "annotationKey";
        final String annotationValue = "annotationValue";
        deliveryAnnotations.getValue().put(Symbol.getSymbol(annotationKey), annotationValue);
        protonMessage.setDeliveryAnnotations(deliveryAnnotations);

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        ReadableBuffer sendBuffer = decoded.getSendBuffer(1);
        assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
        AMQPMessage msgFromSendBuffer = new AMQPMessage(0, sendBuffer, null, null);
        assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
        assertNull(msgFromSendBuffer.getDeliveryAnnotations());

        // again with higher deliveryCount
        ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5);
        assertEquals(decoded.getEncodeSize(), sendBuffer2.capacity());
        AMQPMessage msgFromSendBuffer2 = new AMQPMessage(0, sendBuffer2, null, null);
        assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
        assertNull(msgFromSendBuffer2.getDeliveryAnnotations());
    }

    @Test
    public void testGetSendBufferWithDeliveryAnnotations() {
        MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
        Header header = new Header();
        header.setDeliveryCount(new UnsignedInteger(1));
        protonMessage.setHeader(header);
        Properties properties = new Properties();
        properties.setTo("someNiceLocal");
        protonMessage.setProperties(properties);
        protonMessage.setBody(new AmqpValue("Sample payload"));

        AMQPMessage decoded = encodeAndDecodeMessage(protonMessage);

        DeliveryAnnotations newDeliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
        final String annotationKey = "annotationKey";
        final String annotationValue = "annotationValue";
        newDeliveryAnnotations.getValue().put(Symbol.getSymbol(annotationKey), annotationValue);
        decoded.setDeliveryAnnotationsForSendBuffer(newDeliveryAnnotations);

        ReadableBuffer sendBuffer = decoded.getSendBuffer(1);
        assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
        AMQPMessage msgFromSendBuffer = new AMQPMessage(0, sendBuffer, null, null);
        assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
        assertNotNull(msgFromSendBuffer.getDeliveryAnnotations());
        assertEquals(1, msgFromSendBuffer.getDeliveryAnnotations().getValue().size());
        assertEquals(annotationValue,
                msgFromSendBuffer.getDeliveryAnnotations().getValue().get(Symbol.getSymbol(annotationKey)));

        // again with higher deliveryCount
        DeliveryAnnotations newDeliveryAnnotations2 = new DeliveryAnnotations(new HashMap<>());
        final String annotationKey2 = "annotationKey2";
        final String annotationValue2 = "annotationValue2";
        newDeliveryAnnotations2.getValue().put(Symbol.getSymbol(annotationKey2), annotationValue2);
        decoded.setDeliveryAnnotationsForSendBuffer(newDeliveryAnnotations2);

        ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5);
        assertEquals(decoded.getEncodeSize(), sendBuffer2.capacity());
        AMQPMessage msgFromSendBuffer2 = new AMQPMessage(0, sendBuffer2, null, null);
        assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
        assertNotNull(msgFromSendBuffer2.getDeliveryAnnotations());
        assertEquals(1, msgFromSendBuffer2.getDeliveryAnnotations().getValue().size());
        assertEquals(annotationValue2,
                msgFromSendBuffer2.getDeliveryAnnotations().getValue().get(Symbol.getSymbol(annotationKey2)));
    }

    //----- Test Support ------------------------------------------------------//

    private MessageImpl createProtonMessage() {
        MessageImpl message = (MessageImpl) Proton.message();

        Header header = new Header();
        header.setDurable(true);
        header.setPriority(UnsignedByte.valueOf((byte) 9));

        Properties properties = new Properties();
        properties.setCreationTime(new Date(System.currentTimeMillis()));
        properties.setTo(TEST_TO_ADDRESS);
        properties.setMessageId(UUID.randomUUID());

        MessageAnnotations annotations = new MessageAnnotations(new HashMap<>());
        annotations.getValue().put(Symbol.valueOf(TEST_MESSAGE_ANNOTATION_KEY), TEST_MESSAGE_ANNOTATION_VALUE);

        ApplicationProperties applicationProperties = new ApplicationProperties(new HashMap<>());
        applicationProperties.getValue().put(TEST_APPLICATION_PROPERTY_KEY, TEST_APPLICATION_PROPERTY_VALUE);

        AmqpValue body = new AmqpValue(TEST_STRING_BODY);

        message.setHeader(header);
        message.setMessageAnnotations(annotations);
        message.setProperties(properties);
        message.setApplicationProperties(applicationProperties);
        message.setBody(body);

        return message;
    }

    private void assertProtonMessageEquals(MessageImpl left, MessageImpl right) {
        if (!isEquals(left, right)) {
            fail("MessageImpl values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertProtonMessageNotEquals(MessageImpl left, MessageImpl right) {
        if (isEquals(left, right)) {
            fail("MessageImpl values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(MessageImpl left, MessageImpl right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        try {
            assertHeaderEquals(left.getHeader(), right.getHeader());
            assertDeliveryAnnotationsEquals(left.getDeliveryAnnotations(), right.getDeliveryAnnotations());
            assertMessageAnnotationsEquals(left.getMessageAnnotations(), right.getMessageAnnotations());
            assertPropertiesEquals(left.getProperties(), right.getProperties());
            assertApplicationPropertiesEquals(left.getApplicationProperties(), right.getApplicationProperties());
            assertTrue(isEquals(left.getBody(), right.getBody()));
            assertFootersEquals(left.getFooter(), right.getFooter());
        } catch (Throwable e) {
            return false;
        }

        return true;
    }

    private void assertHeaderEquals(Header left, Header right) {
        if (!isEquals(left, right)) {
            fail("Header values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertHeaderNotEquals(Header left, Header right) {
        if (isEquals(left, right)) {
            fail("Header values should not be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(Header left, Header right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        try {
            assertEquals(left.getDurable(), right.getDurable());
            assertEquals(left.getDeliveryCount(), right.getDeliveryCount());
            assertEquals(left.getFirstAcquirer(), right.getFirstAcquirer());
            assertEquals(left.getPriority(), right.getPriority());
            assertEquals(left.getTtl(), right.getTtl());
        } catch (Throwable e) {
            return false;
        }

        return true;
    }

    private void assertPropertiesEquals(Properties left, Properties right) {
        if (!isEquals(left, right)) {
            fail("Properties values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertPropertiesNotEquals(Properties left, Properties right) {
        if (isEquals(left, right)) {
            fail("Properties values should not be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(Properties left, Properties right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        try {
            assertEquals(left.getAbsoluteExpiryTime(), right.getAbsoluteExpiryTime());
            assertEquals(left.getContentEncoding(), right.getAbsoluteExpiryTime());
            assertEquals(left.getContentType(), right.getContentType());
            assertEquals(left.getCorrelationId(), right.getCorrelationId());
            assertEquals(left.getCreationTime(), right.getCreationTime());
            assertEquals(left.getGroupId(), right.getGroupId());
            assertEquals(left.getGroupSequence(), right.getGroupSequence());
            assertEquals(left.getMessageId(), right.getMessageId());
            assertEquals(left.getReplyTo(), right.getReplyTo());
            assertEquals(left.getReplyToGroupId(), right.getReplyToGroupId());
            assertEquals(left.getSubject(), right.getSubject());
            assertEquals(left.getUserId(), right.getUserId());
            assertEquals(left.getTo(), right.getTo());
        } catch (Throwable e) {
            return false;
        }

        return true;
    }

    private void assertMessageAnnotationsEquals(MessageAnnotations left, MessageAnnotations right) {
        if (!isEquals(left, right)) {
            fail("MessageAnnotations values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertMessageAnnotationsNotEquals(MessageAnnotations left, MessageAnnotations right) {
        if (isEquals(left, right)) {
            fail("MessageAnnotations values should not be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(MessageAnnotations left, MessageAnnotations right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        return isEquals(left.getValue(), right.getValue());
    }

    private void assertDeliveryAnnotationsEquals(DeliveryAnnotations left, DeliveryAnnotations right) {
        if (!isEquals(left, right)) {
            fail("DeliveryAnnotations values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertDeliveryAnnotationsNotEquals(DeliveryAnnotations left, DeliveryAnnotations right) {
        if (isEquals(left, right)) {
            fail("DeliveryAnnotations values should not be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(DeliveryAnnotations left, DeliveryAnnotations right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        return isEquals(left.getValue(), right.getValue());
    }

    private void assertApplicationPropertiesEquals(ApplicationProperties left, ApplicationProperties right) {
        if (!isEquals(left, right)) {
            fail("ApplicationProperties values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertApplicationPropertiesNotEquals(ApplicationProperties left, ApplicationProperties right) {
        if (isEquals(left, right)) {
            fail("ApplicationProperties values should not be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(ApplicationProperties left, ApplicationProperties right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        return isEquals(left.getValue(), right.getValue());
    }

    private void assertFootersEquals(Footer left, Footer right) {
        if (!isEquals(left, right)) {
            fail("Footer values should be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private void assertFootersNotEquals(Footer left, Footer right) {
        if (isEquals(left, right)) {
            fail("Footer values should not be equal: left{" + left + "} right{" + right + "}");
        }
    }

    private boolean isEquals(Footer left, Footer right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        return isEquals(left.getValue(), right.getValue());
    }

    private boolean isEquals(Map<?, ?> left, Map<?, ?> right) {
        if (left == null && right == null) {
            return true;
        }

        if (!isNullnessEquals(left, right)) {
            return false;
        }

        if (left.size() != right.size()) {
            return false;
        }

        for (Object leftKey : left.keySet()) {
            assertEquals(right.get(leftKey), left.get(leftKey));
        }

        for (Object rightKey : right.keySet()) {
            assertEquals(left.get(rightKey), right.get(rightKey));
        }

        return true;
    }

    @SuppressWarnings("unchecked")
    private boolean isEquals(Section left, Section right) {
        if (left == null && right == null) {
            return true;
        }
        if (!isNullnessEquals(left, right)) {
            return false;
        }

        assertTrue(left.getClass().equals(right.getClass()));

        if (left instanceof AmqpValue) {
            AmqpValue leftValue = (AmqpValue) left;
            AmqpValue rightValue = (AmqpValue) right;

            if (leftValue.getValue() == null && rightValue.getValue() == null) {
                return true;
            }
            if (!isNullnessEquals(leftValue.getValue(), rightValue.getValue())) {
                return false;
            }

            assertEquals(leftValue.getValue(), rightValue.getValue());
        } else if (left instanceof AmqpSequence) {
            AmqpSequence leftValue = (AmqpSequence) left;
            AmqpSequence rightValue = (AmqpSequence) right;

            if (leftValue.getValue() == null && rightValue.getValue() == null) {
                return true;
            }
            if (!isNullnessEquals(leftValue.getValue(), rightValue.getValue())) {
                return false;
            }

            List<Object> leftList = leftValue.getValue();
            List<Object> rightList = leftValue.getValue();

            assertEquals(leftList.size(), rightList.size());

            for (int i = 0; i < leftList.size(); ++i) {
                assertEquals(leftList.get(i), rightList.get(i));
            }
        } else if (left instanceof Data) {
            Data leftValue = (Data) left;
            Data rightValue = (Data) right;

            if (leftValue.getValue() == null && rightValue.getValue() == null) {
                return true;
            }
            if (!isNullnessEquals(leftValue.getValue(), rightValue.getValue())) {
                return false;
            }

            byte[] leftArray = leftValue.getValue().getArray();
            byte[] rightArray = rightValue.getValue().getArray();

            if (leftArray == null && rightArray == null) {
                return true;
            }
            if (!isNullnessEquals(leftArray, rightArray)) {
                return false;
            }

            assertArrayEquals(leftArray, rightArray);
        } else {
            return false;
        }

        return true;
    }

    private boolean isNullnessEquals(Object left, Object right) {
        if (left == null && right != null) {
            return false;
        }
        if (left != null && right == null) {
            return false;
        }

        return true;
    }

    private ActiveMQBuffer encodeMessageAsPersistedBuffer(MessageImpl message) {
        ByteBuf nettyBuffer = Unpooled.buffer(1500);

        message.encode(new NettyWritable(nettyBuffer));
        byte[] bytes = new byte[nettyBuffer.writerIndex() + Integer.BYTES];
        nettyBuffer.readBytes(bytes, Integer.BYTES, nettyBuffer.readableBytes());

        ActiveMQBuffer buffer = ActiveMQBuffers.wrappedBuffer(bytes);
        buffer.writerIndex(0);
        buffer.writeInt(bytes.length - Integer.BYTES);
        buffer.setIndex(0, bytes.length);

        return buffer;
    }

    private byte[] encodeMessage(MessageImpl message) {
        ByteBuf nettyBuffer = Unpooled.buffer(1500);

        message.encode(new NettyWritable(nettyBuffer));
        byte[] bytes = new byte[nettyBuffer.writerIndex()];
        nettyBuffer.readBytes(bytes);

        return bytes;
    }

    private AMQPMessage encodeAndDecodeMessage(MessageImpl message) {
        ByteBuf nettyBuffer = Unpooled.buffer(1500);

        message.encode(new NettyWritable(nettyBuffer));
        byte[] bytes = new byte[nettyBuffer.writerIndex()];
        nettyBuffer.readBytes(bytes);

        return new AMQPMessage(0, bytes, null);
    }
}