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

Java tutorial

Introduction

Here is the source code for org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage.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 java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
import org.apache.activemq.artemis.api.core.ICoreMessage;
import org.apache.activemq.artemis.api.core.RefCountMessage;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.message.impl.CoreMessageObjectPools;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.protocol.amqp.converter.AMQPConverter;
import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageIdHelper;
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.utils.DataConstants;
import org.apache.activemq.artemis.utils.collections.TypedProperties;
import org.apache.qpid.proton.amqp.Binary;
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.UnsignedShort;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.apache.qpid.proton.amqp.messaging.DeliveryAnnotations;
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.DecoderImpl;
import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.codec.WritableBuffer;
import org.apache.qpid.proton.message.Message;
import org.apache.qpid.proton.message.impl.MessageImpl;

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

// see https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format
public class AMQPMessage extends RefCountMessage {

    public static final SimpleString ADDRESS_PROPERTY = SimpleString.toSimpleString("_AMQ_AD");

    public static final int DEFAULT_MESSAGE_PRIORITY = 4;
    public static final int MAX_MESSAGE_PRIORITY = 9;

    final long messageFormat;
    ReadableBuffer data;
    boolean bufferValid;
    Boolean durable;
    long messageID;
    SimpleString address;
    MessageImpl protonMessage;
    private volatile int memoryEstimate = -1;
    private long expiration = 0;

    // Records where the Header section ends if present.
    private int headerEnds = 0;

    // Records where the message payload starts, ignoring DeliveryAnnotations if present
    private int messagePaylodStart = 0;

    private boolean parsedHeaders = false;
    private Header _header;
    private DeliveryAnnotations _deliveryAnnotations;
    private MessageAnnotations _messageAnnotations;
    private Properties _properties;
    private int appLocation = -1;
    private ApplicationProperties applicationProperties;
    private long scheduledTime = -1;
    private String connectionID;
    private final CoreMessageObjectPools coreMessageObjectPools;

    Set<Object> rejectedConsumers;

    /** These are properties set at the broker level..
     *  these are properties created by the broker only */
    private volatile TypedProperties extraProperties;

    public AMQPMessage(long messageFormat, byte[] data, TypedProperties extraProperties) {
        this(messageFormat, data, extraProperties, null);
    }

    public AMQPMessage(long messageFormat, byte[] data, TypedProperties extraProperties,
            CoreMessageObjectPools coreMessageObjectPools) {
        this(messageFormat, ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(data)), extraProperties,
                coreMessageObjectPools);
    }

    public AMQPMessage(long messageFormat, ReadableBuffer data, TypedProperties extraProperties,
            CoreMessageObjectPools coreMessageObjectPools) {
        this.data = data;
        this.messageFormat = messageFormat;
        this.bufferValid = true;
        this.coreMessageObjectPools = coreMessageObjectPools;
        this.extraProperties = extraProperties == null ? null : new TypedProperties(extraProperties);
        parseHeaders();
    }

    /** for persistence reload */
    public AMQPMessage(long messageFormat) {
        this.messageFormat = messageFormat;
        this.bufferValid = false;
        this.coreMessageObjectPools = null;
    }

    public AMQPMessage(long messageFormat, Message message) {
        this.messageFormat = messageFormat;
        this.protonMessage = (MessageImpl) message;
        this.bufferValid = false;
        this.coreMessageObjectPools = null;
    }

    public AMQPMessage(Message message) {
        this(0, message);
    }

    public MessageImpl getProtonMessage() {
        if (protonMessage == null) {
            protonMessage = (MessageImpl) Message.Factory.create();

            if (data != null) {
                data.rewind();
                protonMessage.decode(data.duplicate());
                this._header = protonMessage.getHeader();
                protonMessage.setHeader(null);
            }
        }

        return protonMessage;
    }

    private void initalizeObjects() {
        if (protonMessage == null) {
            if (data == null) {
                headerEnds = 0;
                messagePaylodStart = 0;
                _header = new Header();
                _deliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
                _properties = new Properties();
                applicationProperties = new ApplicationProperties(new HashMap<>());
                protonMessage = (MessageImpl) Message.Factory.create();
                protonMessage.setApplicationProperties(applicationProperties);
                protonMessage.setDeliveryAnnotations(_deliveryAnnotations);
            }
        }
    }

    private Map<String, Object> getApplicationPropertiesMap() {
        ApplicationProperties appMap = getApplicationProperties();
        Map<String, Object> map = null;

        if (appMap != null) {
            map = appMap.getValue();
        }

        if (map == null) {
            map = new HashMap<>();
            this.applicationProperties = new ApplicationProperties(map);
        }

        return map;
    }

    private ApplicationProperties getApplicationProperties() {
        parseHeaders();

        if (applicationProperties == null && appLocation >= 0) {
            ReadableBuffer buffer = data.duplicate();
            buffer.position(appLocation);
            TLSEncode.getDecoder().setBuffer(buffer);
            Object section = TLSEncode.getDecoder().readObject();
            if (section instanceof ApplicationProperties) {
                this.applicationProperties = (ApplicationProperties) section;
            }
            this.appLocation = -1;
            TLSEncode.getDecoder().setBuffer(null);
        }

        return applicationProperties;
    }

    private synchronized void parseHeaders() {
        if (!parsedHeaders) {
            if (data == null) {
                initalizeObjects();
            } else {
                partialDecode(data);
            }
            parsedHeaders = true;
        }
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setConnectionID(String connectionID) {
        this.connectionID = connectionID;
        return this;
    }

    @Override
    public String getConnectionID() {
        return connectionID;
    }

    public MessageAnnotations getMessageAnnotations() {
        parseHeaders();
        return _messageAnnotations;
    }

    public Header getHeader() {
        parseHeaders();
        return _header;
    }

    public Properties getProperties() {
        parseHeaders();
        return _properties;
    }

    private Object getSymbol(String symbol) {
        return getSymbol(Symbol.getSymbol(symbol));
    }

    private Object getSymbol(Symbol symbol) {
        MessageAnnotations annotations = getMessageAnnotations();
        Map<Symbol, Object> mapAnnotations = annotations != null ? annotations.getValue() : null;
        if (mapAnnotations != null) {
            return mapAnnotations.get(symbol);
        }

        return null;
    }

    private Object removeSymbol(Symbol symbol) {
        MessageAnnotations annotations = getMessageAnnotations();
        Map<Symbol, Object> mapAnnotations = annotations != null ? annotations.getValue() : null;
        if (mapAnnotations != null) {
            return mapAnnotations.remove(symbol);
        }

        return null;
    }

    private void setSymbol(String symbol, Object value) {
        setSymbol(Symbol.getSymbol(symbol), value);
    }

    private void setSymbol(Symbol symbol, Object value) {
        MessageAnnotations annotations = getMessageAnnotations();
        if (annotations == null) {
            _messageAnnotations = new MessageAnnotations(new HashMap<>());
            annotations = _messageAnnotations;
        }
        Map<Symbol, Object> mapAnnotations = annotations != null ? annotations.getValue() : null;
        if (mapAnnotations != null) {
            mapAnnotations.put(symbol, value);
        }
    }

    @Override
    public RoutingType getRoutingType() {
        Object routingType = getSymbol(AMQPMessageSupport.ROUTING_TYPE);

        if (routingType != null) {
            return RoutingType.getType((byte) routingType);
        } else {
            routingType = getSymbol(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION);
            if (routingType != null) {
                if (AMQPMessageSupport.QUEUE_TYPE == (byte) routingType
                        || AMQPMessageSupport.TEMP_QUEUE_TYPE == (byte) routingType) {
                    return RoutingType.ANYCAST;
                } else if (AMQPMessageSupport.TOPIC_TYPE == (byte) routingType
                        || AMQPMessageSupport.TEMP_TOPIC_TYPE == (byte) routingType) {
                    return RoutingType.MULTICAST;
                }
            } else {
                return null;
            }

            return null;
        }
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setRoutingType(RoutingType routingType) {
        parseHeaders();
        if (routingType == null) {
            removeSymbol(AMQPMessageSupport.ROUTING_TYPE);
        } else {
            setSymbol(AMQPMessageSupport.ROUTING_TYPE, routingType.getType());
        }
        return this;
    }

    @Override
    public SimpleString getGroupID() {
        parseHeaders();

        if (_properties != null && _properties.getGroupId() != null) {
            return SimpleString.toSimpleString(_properties.getGroupId(), coreMessageObjectPools == null ? null
                    : coreMessageObjectPools.getGroupIdStringSimpleStringPool());
        } else {
            return null;
        }
    }

    @Override
    public Long getScheduledDeliveryTime() {

        if (scheduledTime < 0) {
            Object objscheduledTime = getSymbol("x-opt-delivery-time");
            Object objdelay = getSymbol("x-opt-delivery-delay");

            if (objscheduledTime != null && objscheduledTime instanceof Number) {
                this.scheduledTime = ((Number) objscheduledTime).longValue();
            } else if (objdelay != null && objdelay instanceof Number) {
                this.scheduledTime = System.currentTimeMillis() + ((Number) objdelay).longValue();
            } else {
                this.scheduledTime = 0;
            }
        }

        return scheduledTime;
    }

    @Override
    public AMQPMessage setScheduledDeliveryTime(Long time) {
        parseHeaders();
        setSymbol(AMQPMessageSupport.JMS_DELIVERY_TIME, time);
        return this;
    }

    @Override
    public Persister<org.apache.activemq.artemis.api.core.Message> getPersister() {
        return AMQPMessagePersisterV2.getInstance();
    }

    @Override
    public synchronized boolean acceptsConsumer(long consumer) {
        if (rejectedConsumers == null) {
            return true;
        } else {
            return !rejectedConsumers.contains(consumer);
        }
    }

    @Override
    public synchronized void rejectConsumer(long consumer) {
        if (rejectedConsumers == null) {
            rejectedConsumers = new HashSet<>();
        }

        rejectedConsumers.add(consumer);
    }

    private synchronized void partialDecode(ReadableBuffer buffer) {
        DecoderImpl decoder = TLSEncode.getDecoder();
        decoder.setBuffer(buffer.rewind());

        _header = null;
        _deliveryAnnotations = null;
        _messageAnnotations = null;
        _properties = null;
        applicationProperties = null;
        Section section = null;

        try {
            if (buffer.hasRemaining()) {
                section = (Section) decoder.readObject();
            }

            if (section instanceof Header) {
                _header = (Header) section;
                headerEnds = buffer.position();
                messagePaylodStart = headerEnds;
                this.durable = _header.getDurable();

                if (_header.getTtl() != null) {
                    this.expiration = System.currentTimeMillis() + _header.getTtl().intValue();
                }

                if (buffer.hasRemaining()) {
                    section = (Section) decoder.readObject();
                } else {
                    section = null;
                }

            } else {
                // meaning there is no header
                headerEnds = 0;
            }
            if (section instanceof DeliveryAnnotations) {
                _deliveryAnnotations = (DeliveryAnnotations) section;

                // Advance the start beyond the delivery annotations so they are not written
                // out on send of the message.
                messagePaylodStart = buffer.position();

                if (buffer.hasRemaining()) {
                    section = (Section) decoder.readObject();
                } else {
                    section = null;
                }
            }
            if (section instanceof MessageAnnotations) {
                _messageAnnotations = (MessageAnnotations) section;

                if (buffer.hasRemaining()) {
                    section = (Section) decoder.readObject();
                } else {
                    section = null;
                }
            }
            if (section instanceof Properties) {
                _properties = (Properties) section;

                if (_properties.getAbsoluteExpiryTime() != null
                        && _properties.getAbsoluteExpiryTime().getTime() > 0) {
                    this.expiration = _properties.getAbsoluteExpiryTime().getTime();
                }

                // We don't read the next section on purpose, as we will parse ApplicationProperties
                // lazily
                section = null;
            }

            if (section instanceof ApplicationProperties) {
                applicationProperties = (ApplicationProperties) section;
            } else {
                if (buffer.hasRemaining()) {
                    this.appLocation = buffer.position();
                } else {
                    this.appLocation = -1;
                }
            }
        } finally {
            decoder.setByteBuffer(null);
            data.position(0);
        }
    }

    public long getMessageFormat() {
        return messageFormat;
    }

    @Override
    public void messageChanged() {
        bufferValid = false;
        this.data = null;
    }

    @Override
    public ByteBuf getBuffer() {
        if (data == null) {
            return null;
        } else {
            return Unpooled.wrappedBuffer(data.byteBuffer());
        }
    }

    @Override
    public AMQPMessage setBuffer(ByteBuf buffer) {
        this.data = null;
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message copy() {
        checkBuffer();

        ReadableBuffer view = data.duplicate();

        byte[] newData = new byte[view.remaining() - (messagePaylodStart - headerEnds)];

        view.position(0).limit(headerEnds);
        view.get(newData, 0, headerEnds);
        view.clear();
        view.position(messagePaylodStart);
        view.get(newData, headerEnds, view.remaining());

        AMQPMessage newEncode = new AMQPMessage(this.messageFormat, newData, extraProperties,
                coreMessageObjectPools);
        newEncode.setDurable(isDurable()).setMessageID(this.getMessageID());
        return newEncode;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message copy(long newID) {
        checkBuffer();
        return copy().setMessageID(newID);
    }

    @Override
    public long getMessageID() {
        return messageID;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setMessageID(long id) {
        this.messageID = id;
        return this;
    }

    @Override
    public long getExpiration() {
        return expiration;
    }

    @Override
    public AMQPMessage setExpiration(long expiration) {

        Properties properties = getProperties();

        if (properties != null) {
            if (expiration <= 0) {
                properties.setAbsoluteExpiryTime(null);
            } else {
                properties.setAbsoluteExpiryTime(new Date(expiration));
            }
        }
        this.expiration = expiration;
        return this;
    }

    @Override
    public Object getUserID() {
        Properties properties = getProperties();
        if (properties != null && properties.getMessageId() != null) {
            return properties.getMessageId();
        } else {
            return null;
        }
    }

    /**
     * Before we added AMQP into Artemis / Hornetq, the name getUserID was already taken by JMSMessageID.
     * We cannot simply change the names now as it would break the API for existing clients.
     *
     * This is to return and read the proper AMQP userID.
     * @return
     */
    public Object getAMQPUserID() {
        Properties properties = getProperties();
        if (properties != null && properties.getUserId() != null) {
            Binary binary = properties.getUserId();
            return new String(binary.getArray(), binary.getArrayOffset(), binary.getLength(),
                    StandardCharsets.UTF_8);
        } else {
            return null;
        }
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setUserID(Object userID) {
        return null;
    }

    @Override
    public boolean isDurable() {
        if (durable != null) {
            return durable;
        }

        parseHeaders();

        if (getHeader() != null && getHeader().getDurable() != null) {
            durable = getHeader().getDurable();
            return durable;
        } else {
            return durable != null ? durable : false;
        }
    }

    @Override
    public Object getDuplicateProperty() {
        return null;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setDurable(boolean durable) {
        this.durable = durable;
        return this;
    }

    @Override
    public String getAddress() {
        SimpleString addressSimpleString = getAddressSimpleString();
        return addressSimpleString == null ? null : addressSimpleString.toString();
    }

    public SimpleString cachedAddressSimpleString(String address) {
        return CoreMessageObjectPools.cachedAddressSimpleString(address, coreMessageObjectPools);
    }

    @Override
    public AMQPMessage setAddress(String address) {
        setAddress(cachedAddressSimpleString(address));
        return this;
    }

    @Override
    public AMQPMessage setAddress(SimpleString address) {
        this.address = address;
        createExtraProperties().putSimpleStringProperty(ADDRESS_PROPERTY, address);
        return this;
    }

    @Override
    public SimpleString getAddressSimpleString() {
        if (address == null) {
            TypedProperties extraProperties = getExtraProperties();

            // we first check if extraProperties is not null, no need to create it just to check it here
            if (extraProperties != null) {
                address = extraProperties.getSimpleStringProperty(ADDRESS_PROPERTY);
            }

            if (address == null) {
                // if it still null, it will look for the address on the getTo();
                Properties properties = getProperties();
                if (properties != null && properties.getTo() != null) {
                    address = cachedAddressSimpleString(properties.getTo());
                }
            }
        }
        return address;
    }

    @Override
    public long getTimestamp() {
        if (getProperties() != null && getProperties().getCreationTime() != null) {
            return getProperties().getCreationTime().getTime();
        } else {
            return 0L;
        }
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setTimestamp(long timestamp) {
        getProperties().setCreationTime(new Date(timestamp));
        return this;
    }

    @Override
    public byte getPriority() {
        if (getHeader() != null && getHeader().getPriority() != null) {
            return (byte) Math.min(getHeader().getPriority().intValue(), MAX_MESSAGE_PRIORITY);
        } else {
            return DEFAULT_MESSAGE_PRIORITY;
        }
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setPriority(byte priority) {
        getHeader().setPriority(UnsignedByte.valueOf(priority));
        return this;
    }

    @Override
    public void receiveBuffer(ByteBuf buffer) {

    }

    private synchronized void checkBuffer() {
        if (!bufferValid) {
            encodeProtonMessage();
        }
    }

    private void encodeProtonMessage() {
        int estimated = Math.max(1500, data != null ? data.capacity() + 1000 : 0);
        ByteBuf buffer = PooledByteBufAllocator.DEFAULT.heapBuffer(estimated);
        try {
            getProtonMessage().encode(new NettyWritable(buffer));
            byte[] bytes = new byte[buffer.writerIndex()];
            buffer.readBytes(bytes);
            this.data = ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(bytes));
        } finally {
            buffer.release();
        }
    }

    @Override
    public int getEncodeSize() {
        checkBuffer();
        // + 20checkBuffer is an estimate for the Header with the deliveryCount
        return data.remaining() - messagePaylodStart + 20;
    }

    @Override
    public void sendBuffer(ByteBuf buffer, int deliveryCount) {
        checkBuffer();

        int amqpDeliveryCount = deliveryCount - 1;

        // If the re-delivering the message then the header must be re-encoded
        // otherwise we want to write the original header if present.
        if (amqpDeliveryCount > 0) {

            Header header = getHeader();
            if (header == null) {
                header = new Header();
                header.setDurable(durable);
            }

            synchronized (header) {
                header.setDeliveryCount(UnsignedInteger.valueOf(amqpDeliveryCount));
                TLSEncode.getEncoder().setByteBuffer(new NettyWritable(buffer));
                TLSEncode.getEncoder().writeObject(header);
                TLSEncode.getEncoder().setByteBuffer((WritableBuffer) null);
            }
        } else if (headerEnds > 0) {
            buffer.writeBytes(data.duplicate().limit(headerEnds).byteBuffer());
        }

        data.position(messagePaylodStart);
        buffer.writeBytes(data.byteBuffer());
        data.position(0);
    }

    /**
     * Gets a ByteBuf from the Message that contains the encoded bytes to be sent on the wire.
     * <p>
     * When possible this method will present the bytes to the caller without copying them into
     * another buffer copy.  If copying is needed a new Netty buffer is created and returned. The
     * caller should ensure that the reference count on the returned buffer is always decremented
     * to avoid a leak in the case of a copied buffer being returned.
     *
     * @param deliveryCount
     *       The new delivery count for this message.
     *
     * @return a Netty ByteBuf containing the encoded bytes of this Message instance.
     */
    public ReadableBuffer getSendBuffer(int deliveryCount) {
        checkBuffer();

        if (deliveryCount > 1) {
            return createCopyWithNewDeliveryCount(deliveryCount);
        } else if (headerEnds != messagePaylodStart) {
            return createCopyWithoutDeliveryAnnotations();
        } else {
            // Common case message has no delivery annotations and this is the first delivery
            // so no re-encoding or section skipping needed.
            return data.duplicate();
        }
    }

    private ReadableBuffer createCopyWithoutDeliveryAnnotations() {
        assert headerEnds != messagePaylodStart;

        // The original message had delivery annotations and so we must copy into a new
        // buffer skipping the delivery annotations section as that is not meant to survive
        // beyond this hop.
        ReadableBuffer duplicate = data.duplicate();

        final ByteBuf result = PooledByteBufAllocator.DEFAULT.heapBuffer(getEncodeSize());
        result.writeBytes(duplicate.limit(headerEnds).byteBuffer());
        duplicate.clear();
        duplicate.position(messagePaylodStart);
        result.writeBytes(duplicate.byteBuffer());

        return new NettyReadable(result);
    }

    private ReadableBuffer createCopyWithNewDeliveryCount(int deliveryCount) {
        assert deliveryCount > 1;

        final int amqpDeliveryCount = deliveryCount - 1;
        // If the re-delivering the message then the header must be re-encoded
        // (or created if not previously present).  Any delivery annotations should
        // be skipped as well in the resulting buffer.

        final ByteBuf result = PooledByteBufAllocator.DEFAULT.heapBuffer(getEncodeSize());

        Header header = getHeader();
        if (header == null) {
            header = new Header();
            header.setDurable(durable);
        }

        synchronized (header) {
            // Updates or adds a Header section with the correct delivery count
            header.setDeliveryCount(UnsignedInteger.valueOf(amqpDeliveryCount));
            TLSEncode.getEncoder().setByteBuffer(new NettyWritable(result));
            TLSEncode.getEncoder().writeObject(header);
            TLSEncode.getEncoder().setByteBuffer((WritableBuffer) null);
        }

        // This will skip any existing delivery annotations that might have been present
        // in the original message.
        data.position(messagePaylodStart);
        result.writeBytes(data.byteBuffer());
        data.position(0);

        return new NettyReadable(result);
    }

    public TypedProperties createExtraProperties() {
        if (extraProperties == null) {
            extraProperties = new TypedProperties();
        }
        return extraProperties;
    }

    public TypedProperties getExtraProperties() {
        return extraProperties;
    }

    public AMQPMessage setExtraProperties(TypedProperties extraProperties) {
        this.extraProperties = extraProperties;
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putExtraBytesProperty(SimpleString key, byte[] value) {
        createExtraProperties().putBytesProperty(key, value);
        return this;
    }

    @Override
    public byte[] getExtraBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        if (extraProperties == null) {
            return null;
        } else {
            return extraProperties.getBytesProperty(key);
        }
    }

    @Override
    public byte[] removeExtraBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        if (extraProperties == null) {
            return null;
        } else {
            return (byte[]) extraProperties.removeProperty(key);
        }
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putBooleanProperty(String key, boolean value) {
        getApplicationPropertiesMap().put(key, Boolean.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putByteProperty(String key, byte value) {
        getApplicationPropertiesMap().put(key, Byte.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putBytesProperty(String key, byte[] value) {
        getApplicationPropertiesMap().put(key, value);
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putShortProperty(String key, short value) {
        getApplicationPropertiesMap().put(key, Short.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putCharProperty(String key, char value) {
        getApplicationPropertiesMap().put(key, Character.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putIntProperty(String key, int value) {
        getApplicationPropertiesMap().put(key, Integer.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putLongProperty(String key, long value) {
        getApplicationPropertiesMap().put(key, Long.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putFloatProperty(String key, float value) {
        getApplicationPropertiesMap().put(key, Float.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putDoubleProperty(String key, double value) {
        getApplicationPropertiesMap().put(key, Double.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putBooleanProperty(SimpleString key, boolean value) {
        getApplicationPropertiesMap().put(key.toString(), Boolean.valueOf(value));
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putByteProperty(SimpleString key, byte value) {
        return putByteProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putBytesProperty(SimpleString key, byte[] value) {
        return putBytesProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putShortProperty(SimpleString key, short value) {
        return putShortProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putCharProperty(SimpleString key, char value) {
        return putCharProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putIntProperty(SimpleString key, int value) {
        return putIntProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putLongProperty(SimpleString key, long value) {
        return putLongProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putFloatProperty(SimpleString key, float value) {
        return putFloatProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putDoubleProperty(SimpleString key, double value) {
        return putDoubleProperty(key.toString(), value);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putStringProperty(String key, String value) {
        getApplicationPropertiesMap().put(key, value);
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putObjectProperty(String key, Object value)
            throws ActiveMQPropertyConversionException {
        getApplicationPropertiesMap().put(key, value);
        return this;
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putObjectProperty(SimpleString key, Object value)
            throws ActiveMQPropertyConversionException {
        return putObjectProperty(key.toString(), value);
    }

    @Override
    public Object removeProperty(String key) {
        return getApplicationPropertiesMap().remove(key);
    }

    @Override
    public boolean containsProperty(String key) {
        return getApplicationPropertiesMap().containsKey(key);
    }

    @Override
    public Boolean getBooleanProperty(String key) throws ActiveMQPropertyConversionException {
        return (Boolean) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Byte getByteProperty(String key) throws ActiveMQPropertyConversionException {
        return (Byte) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Double getDoubleProperty(String key) throws ActiveMQPropertyConversionException {
        return (Double) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Integer getIntProperty(String key) throws ActiveMQPropertyConversionException {
        return (Integer) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Long getLongProperty(String key) throws ActiveMQPropertyConversionException {
        return (Long) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Object getObjectProperty(String key) {
        if (key.equals(MessageUtil.TYPE_HEADER_NAME.toString())) {
            if (getProperties() != null) {
                return getProperties().getSubject();
            }
        } else if (key.equals(MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString())) {
            return getConnectionID();
        } else if (key.equals(MessageUtil.JMSXGROUPID)) {
            return getGroupID();
        } else if (key.equals(MessageUtil.JMSXUSERID)) {
            return getAMQPUserID();
        } else if (key.equals(MessageUtil.CORRELATIONID_HEADER_NAME.toString())) {
            if (getProperties() != null && getProperties().getCorrelationId() != null) {
                return AMQPMessageIdHelper.INSTANCE.toCorrelationIdString(getProperties().getCorrelationId());
            }
        } else {
            Object value = getApplicationPropertiesMap().get(key);
            if (value instanceof UnsignedInteger || value instanceof UnsignedByte || value instanceof UnsignedLong
                    || value instanceof UnsignedShort) {
                return ((Number) value).longValue();
            } else {
                return value;
            }
        }

        return null;
    }

    @Override
    public Short getShortProperty(String key) throws ActiveMQPropertyConversionException {
        return (Short) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Float getFloatProperty(String key) throws ActiveMQPropertyConversionException {
        return (Float) getApplicationPropertiesMap().get(key);
    }

    @Override
    public String getStringProperty(String key) throws ActiveMQPropertyConversionException {
        if (key.equals(MessageUtil.TYPE_HEADER_NAME.toString())) {
            return getProperties().getSubject();
        } else if (key.equals(MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString())) {
            return getConnectionID();
        } else {
            return (String) getApplicationPropertiesMap().get(key);
        }
    }

    @Override
    public Object removeAnnotation(SimpleString key) {
        return removeSymbol(Symbol.getSymbol(key.toString()));
    }

    @Override
    public Object getAnnotation(SimpleString key) {
        return getSymbol(key.toString());
    }

    @Override
    public AMQPMessage setAnnotation(SimpleString key, Object value) {
        setSymbol(key.toString(), value);
        return this;
    }

    @Override
    public void reencode() {
        parseHeaders();
        getApplicationProperties();
        if (_header != null)
            getProtonMessage().setHeader(_header);
        if (_deliveryAnnotations != null)
            getProtonMessage().setDeliveryAnnotations(_deliveryAnnotations);
        if (_messageAnnotations != null)
            getProtonMessage().setMessageAnnotations(_messageAnnotations);
        if (applicationProperties != null)
            getProtonMessage().setApplicationProperties(applicationProperties);
        if (_properties != null) {
            if (address != null) {
                _properties.setTo(address.toString());
            }
            getProtonMessage().setProperties(this._properties);
        }
        bufferValid = false;
        checkBuffer();
    }

    @Override
    public SimpleString getSimpleStringProperty(String key) throws ActiveMQPropertyConversionException {
        return SimpleString.toSimpleString((String) getApplicationPropertiesMap().get(key),
                getPropertyValuesPool());
    }

    @Override
    public byte[] getBytesProperty(String key) throws ActiveMQPropertyConversionException {
        return (byte[]) getApplicationPropertiesMap().get(key);
    }

    @Override
    public Object removeProperty(SimpleString key) {
        return removeProperty(key.toString());
    }

    @Override
    public boolean containsProperty(SimpleString key) {
        return containsProperty(key.toString());
    }

    @Override
    public Boolean getBooleanProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getBooleanProperty(key.toString());
    }

    @Override
    public Byte getByteProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getByteProperty(key.toString());
    }

    @Override
    public Double getDoubleProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getDoubleProperty(key.toString());
    }

    @Override
    public Integer getIntProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getIntProperty(key.toString());
    }

    @Override
    public Long getLongProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getLongProperty(key.toString());
    }

    @Override
    public Object getObjectProperty(SimpleString key) {
        return getObjectProperty(key.toString());
    }

    @Override
    public Short getShortProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getShortProperty(key.toString());
    }

    @Override
    public Float getFloatProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getFloatProperty(key.toString());
    }

    @Override
    public String getStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getStringProperty(key.toString());
    }

    @Override
    public SimpleString getSimpleStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getSimpleStringProperty(key.toString());
    }

    @Override
    public byte[] getBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
        return getBytesProperty(key.toString());
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putStringProperty(SimpleString key, SimpleString value) {
        return putStringProperty(key.toString(), value.toString());
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message putStringProperty(SimpleString key, String value) {
        return putStringProperty(key.toString(), value);
    }

    @Override
    public Set<SimpleString> getPropertyNames() {
        HashSet<SimpleString> values = new HashSet<>();
        for (Object k : getApplicationPropertiesMap().keySet()) {
            values.add(SimpleString.toSimpleString(k.toString(), getPropertyKeysPool()));
        }
        return values;
    }

    @Override
    public int getMemoryEstimate() {
        if (memoryEstimate == -1) {
            memoryEstimate = memoryOffset + (data != null ? data.capacity() : 0);
        }

        return memoryEstimate;
    }

    @Override
    public ICoreMessage toCore(CoreMessageObjectPools coreMessageObjectPools) {
        try {
            return AMQPConverter.getInstance().toCore(this, coreMessageObjectPools);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public ICoreMessage toCore() {
        return toCore(null);
    }

    @Override
    public SimpleString getLastValueProperty() {
        return getSimpleStringProperty(HDR_LAST_VALUE_NAME);
    }

    @Override
    public org.apache.activemq.artemis.api.core.Message setLastValueProperty(SimpleString lastValueName) {
        return putStringProperty(HDR_LAST_VALUE_NAME, lastValueName);
    }

    @Override
    public SimpleString getReplyTo() {
        if (getProperties() != null) {
            return SimpleString.toSimpleString(getProperties().getReplyTo());
        } else {
            return null;
        }
    }

    @Override
    public AMQPMessage setReplyTo(SimpleString address) {
        if (getProperties() != null) {
            getProperties().setReplyTo(address != null ? address.toString() : null);
        }
        return this;
    }

    @Override
    public int getPersistSize() {
        checkBuffer();
        return DataConstants.SIZE_INT + internalPersistSize();
    }

    private int internalPersistSize() {
        return data.remaining();
    }

    @Override
    public void persist(ActiveMQBuffer targetRecord) {
        checkBuffer();
        targetRecord.writeInt(internalPersistSize());
        if (data.hasArray()) {
            targetRecord.writeBytes(data.array(), data.arrayOffset(), data.remaining());
        } else {
            targetRecord.writeBytes(data.byteBuffer());
        }
    }

    @Override
    public void reloadPersistence(ActiveMQBuffer record) {
        int size = record.readInt();
        byte[] recordArray = new byte[size];
        record.readBytes(recordArray);
        this.messagePaylodStart = 0; // whatever was persisted will be sent
        this.data = ReadableBuffer.ByteBufferReader.wrap(ByteBuffer.wrap(recordArray));
        this.bufferValid = true;
        this.durable = true; // it's coming from the journal, so it's durable
        parseHeaders();
    }

    @Override
    public String toString() {
        return "AMQPMessage [durable=" + isDurable() + ", messageID=" + getMessageID() + ", address=" + getAddress()
                + ", size=" + getEncodeSize() + ", applicationProperties=" + getApplicationProperties()
                + ", properties=" + getProperties() + ", extraProperties = " + getExtraProperties() + "]";
    }

    private SimpleString.StringSimpleStringPool getPropertyKeysPool() {
        return coreMessageObjectPools == null ? null
                : coreMessageObjectPools.getPropertiesStringSimpleStringPools().getPropertyKeysPool();
    }

    private SimpleString.StringSimpleStringPool getPropertyValuesPool() {
        return coreMessageObjectPools == null ? null
                : coreMessageObjectPools.getPropertiesStringSimpleStringPools().getPropertyValuesPool();
    }

    @Override
    public long getPersistentSize() throws ActiveMQException {
        return getEncodeSize();
    }
}