org.wso2.andes.kernel.AndesMessageMetadata.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.andes.kernel.AndesMessageMetadata.java

Source

/*
 * Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.wso2.andes.kernel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.andes.amqp.AMQPUtils;
import org.wso2.andes.mqtt.MQTTMessageMetaData;
import org.wso2.andes.mqtt.MQTTMetaDataHandler;
import org.wso2.andes.mqtt.utils.MQTTUtils;
import org.wso2.andes.server.message.MessageMetaData;
import org.wso2.andes.server.store.MessageMetaDataType;
import org.wso2.andes.server.store.StorableMessageMetaData;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

public class AndesMessageMetadata implements Comparable<AndesMessageMetadata> {

    private static Log log = LogFactory.getLog(AndesMessageMetadata.class);

    /**
     * Unique identifier of the message
     */
    long messageID;
    /**
     * AMQ metadata of the message
     */
    byte[] metadata;
    /**
     * The timestamp at which the message is set to expire.
     */
    long expirationTime;
    /**
     * true if the message is addressed to a topic exchange.
     */
    boolean isTopic;

    /**
     * Name of the exchange message is addressed to
     */
    String messageRouterName;

    /**
     * The timestamp at which the message arrived at the first gates of the broker.
     */
    long arrivalTime;

    /**
     * Destination (routing key) of message
     */
    private String destination;

    /**
     * Queue name in store in which message
     * should be saved
     */
    private String storageQueueName;

    /**
     * True if the message is sent with JMS persistent mode.
     */
    private boolean isPersistent;

    /**
     * Added for MQTT usage
     */
    private int messageContentLength;
    private int qosLevel;

    /**
     * The meta data type which specify which protocol this meta data belongs to``
     */
    private MessageMetaDataType metaDataType;

    private boolean isCompressed;

    /**
     * Properties that are not directly relevant to Andes but to protocols can be stored
     * in this map. But non of the data is persisted
     */
    private Map<String, Object> propertyMap;

    /**
     * MQTT Retain
     * Topic message should retained if true.
     *
     * By setting the retain flag, the message is held onto by the broker, so when the late arrivals
     * connect to the broker or clients create a new subscription they get all the relevant retained
     * messages based on subscribed topic.
     *
     * When MQTT message received it's header will be converted to AndesMessageMetadata header. This
     * boolean state holds retain state of given andes message.
     * @see org.wso2.andes.mqtt.utils.MQTTUtils#convertToAndesHeader(long, String, int, int, boolean,
     * org.wso2.andes.mqtt.MQTTPublisherChannel, boolean)
     *
     * This boolean state will be checked each time andes message received in MessagePreProcessor.
     * @see org.wso2.andes.kernel.disruptor.inbound.MessagePreProcessor#handleTopicRoutine(
     * org.wso2.andes.kernel.disruptor.inbound.InboundEventContainer, AndesMessage, AndesChannel)
     *
     */
    private boolean retain;

    public AndesMessageMetadata() {
        propertyMap = new HashMap<>();
        this.retain = false;
    }

    /**
     * Set retain flag for current message
     *
     * @see org.wso2.andes.kernel.AndesMessageMetadata#retain
     * @param retain boolean retain flag
     */
    public void setRetain(boolean retain) {
        this.retain = retain;
    }

    public AndesMessageMetadata(long messageID, byte[] metadata, boolean parse) {
        super();
        propertyMap = new HashMap<>();
        this.messageID = messageID;
        this.metadata = metadata;
        if (parse) {
            parseMetaData();
        }

    }

    public long getMessageID() {
        return messageID;
    }

    /**
     * Return retained status of the current message.
     *
     * @see org.wso2.andes.kernel.AndesMessageMetadata#retain
     * @return boolean retain flag for the current message
     */
    public boolean isRetain() {
        return retain;
    }

    public void setMessageID(long messageID) {
        this.messageID = messageID;
    }

    /**
     * Will retive the level of QOS of the message. This will be applicable only if the message is MQTT
     *
     * @return the level of qos it can be either 0,1 or 2
     */
    public int getQosLevel() {
        return qosLevel;
    }

    /**
     * The level of qos the message is published at
     *
     * @param qosLevel the qos level can be of value 0,1 or 2
     */
    public void setQosLevel(int qosLevel) {
        this.qosLevel = qosLevel;
    }

    public byte[] getMetadata() {
        return metadata;
    }

    public void setMetadata(byte[] metadata) {
        this.metadata = metadata;
    }

    public long getExpirationTime() {
        return expirationTime;
    }

    public void setExpirationTime(long expirationTime) {
        this.expirationTime = expirationTime;
    }

    public boolean isTopic() {
        return isTopic;
    }

    public String getMessageRouterName() {
        return messageRouterName;
    }

    public void setMessageRouterName(String messageRouterName) {
        this.messageRouterName = messageRouterName;
    }

    public void setTopic(boolean isTopic) {
        this.isTopic = isTopic;
    }

    /**
     * Get the routing key of the message. This is set
     * when parsing metadata.
     *
     * @return routing key of the message
     */
    public String getDestination() {
        return destination;
    }

    /**
     * Set the routing key of the message. If you
     * need to override the value set by parsing metadata
     * this method should be used.
     *
     * @param destination routing key to set
     */
    public void setDestination(String destination) {
        this.destination = destination;
    }

    /**
     * Get the name of storage queue message is saved to. When messages
     * are read from persistent storage and deliver this is used.
     *
     * @return name of storage queue message is persisted to
     */
    public String getStorageQueueName() {
        return storageQueueName;
    }

    /**
     * Set name of the storage queue this message is saved to
     *
     * @param storageQueueName name of storage queue
     */
    public void setStorageQueueName(String storageQueueName) {
        this.storageQueueName = storageQueueName;
    }

    public boolean isPersistent() {
        return isPersistent;
    }

    public void setPersistent(boolean persistent) {
        isPersistent = persistent;
    }

    public long getArrivalTime() {
        return arrivalTime;
    }

    public void setArrivalTime(long arrivalTime) {
        this.arrivalTime = arrivalTime;
    }

    /**
     * Create a clone, with new message ID
     *
     * @param messageId message id
     * @return returns AndesMessageMetadata
     */
    public AndesMessageMetadata shallowCopy(long messageId) {
        AndesMessageMetadata clone = new AndesMessageMetadata();
        clone.messageID = messageId;
        clone.retain = retain;
        clone.metadata = metadata;
        clone.expirationTime = expirationTime;
        clone.isTopic = isTopic;
        clone.messageRouterName = messageRouterName;
        clone.destination = destination;
        clone.storageQueueName = storageQueueName;
        clone.isPersistent = isPersistent;
        clone.arrivalTime = arrivalTime;
        clone.metaDataType = metaDataType;
        clone.propertyMap = propertyMap;
        clone.messageContentLength = messageContentLength;
        clone.isCompressed = isCompressed;
        return clone;
    }

    /**
     * Update metadata of message, after a change of the routing key and the exchange of a message. This will change
     * AMQP bytes representing metadata. Routing key and exchange name will be set to the given values.
     *
     * @param newDestination  new routing key to set
     * @param newExchangeName new exchange name to set
     */
    public void updateMetadata(String newDestination, String newExchangeName, long newArrivalTime) {
        this.metadata = createNewMetadata(this.metadata, newDestination, newExchangeName, newArrivalTime);
        this.destination = newDestination;
        if (log.isDebugEnabled()) {
            log.debug("updated andes message metadata id= " + messageID + " new destination = " + newDestination);
        }
    }

    /**
     * Update metadata of message, after a change of the compression state of the message. This will change AMQP bytes
     * representing metadata. IsCompressed will be set to the given value.
     *
     * @param isCompressedMessage new value to indicate if the message is compressed or not
     */
    public void updateMetadata(boolean isCompressedMessage) {
        this.metadata = createNewMetadata(this.metadata, isCompressedMessage);
        this.isCompressed = isCompressedMessage;
        if (log.isDebugEnabled()) {
            log.debug("updated andes message metadata id = " + messageID + ", compression state of the message is "
                    + isCompressedMessage);
        }
    }

    public boolean isExpired() {
        if (expirationTime != 0L) {
            long now = System.currentTimeMillis();
            return (now > expirationTime);
        }
        return false;
    }

    private void parseMetaData() {
        ByteBuffer buf = ByteBuffer.wrap(metadata);
        buf.position(1);
        buf = buf.slice();
        MessageMetaDataType type = MessageMetaDataType.values()[metadata[0]];
        metaDataType = type;
        StorableMessageMetaData mdt = type.getFactory().createMetaData(buf);
        //todo need to discuss on making the flow more generic
        if (type.equals(MessageMetaDataType.META_DATA_0_10) || type.equals(MessageMetaDataType.META_DATA_0_8)) {
            this.isPersistent = ((MessageMetaData) mdt).isPersistent();
            this.expirationTime = ((MessageMetaData) mdt).getMessageHeader().getExpiration();
            this.arrivalTime = ((MessageMetaData) mdt).getArrivalTime();
            this.destination = ((MessageMetaData) mdt).getMessagePublishInfo().getRoutingKey().toString();
            this.messageContentLength = ((MessageMetaData) mdt).getContentSize();
            this.isTopic = ((MessageMetaData) mdt).getMessagePublishInfo().getExchange()
                    .equals(AMQPUtils.TOPIC_EXCHANGE_NAME);
            this.messageRouterName = ((MessageMetaData) mdt).getMessagePublishInfo().getExchange().toString();
            this.isCompressed = ((MessageMetaData) mdt).isCompressed();
        }
        //For MQTT Specific Types
        if (type.equals(MessageMetaDataType.META_DATA_MQTT)) {
            this.arrivalTime = ((MQTTMessageMetaData) mdt).getMessageArrivalTime();
            this.isTopic = ((MQTTMessageMetaData) mdt).isTopic();
            this.messageRouterName = MQTTUtils.MQTT_EXCHANGE_NAME;
            this.destination = ((MQTTMessageMetaData) mdt).getDestination();
            this.isPersistent = ((MQTTMessageMetaData) mdt).isPersistent();
            this.messageContentLength = ((MQTTMessageMetaData) mdt).getContentSize();
            this.qosLevel = ((MQTTMessageMetaData) mdt).getQosLevel();
            this.isCompressed = ((MQTTMessageMetaData) mdt).isCompressed();
        }

    }

    /**
     * Create a copy of updated metadata, for durable topic subscriptions
     *
     * @param originalMetadata source metadata that needs to be copied
     * @param routingKey       routing key of the message
     * @param exchangeName     exchange of the message
     * @return copy of the metadata as a byte array
     */
    private byte[] createNewMetadata(byte[] originalMetadata, String routingKey, String exchangeName,
            long arrivalTime) {
        ByteBuffer buf = ByteBuffer.wrap(originalMetadata);
        buf.position(1);
        buf = buf.slice();
        MessageMetaDataType type = MessageMetaDataType.values()[originalMetadata[0]];
        metaDataType = type;
        StorableMessageMetaData originalMessageMetadata = type.getFactory().createMetaData(buf);

        byte[] underlying;
        //TODO need to implement factory pattern here
        if ((MessageMetaDataType.META_DATA_MQTT).equals(type)) {
            underlying = MQTTMetaDataHandler.constructMetadata(routingKey, buf, originalMessageMetadata,
                    exchangeName);
        } else {
            underlying = AMQPMetaDataHandler.constructMetadata(routingKey, buf, originalMessageMetadata,
                    exchangeName, arrivalTime);
        }

        return underlying;
    }

    /**
     * Create a copy of metadata
     *
     * @param originalMetadata          source metadata that needs to be copied
     * @param isCompressed Value to indicate if the message is compressed or not
     * @return copy of the metadata as a byte array
     */
    private byte[] createNewMetadata(byte[] originalMetadata, boolean isCompressed) {
        ByteBuffer buf = ByteBuffer.wrap(originalMetadata);
        buf.position(1);
        buf = buf.slice();
        MessageMetaDataType type = MessageMetaDataType.values()[originalMetadata[0]];
        metaDataType = type;
        StorableMessageMetaData originalMessageMetadata = type.getFactory().createMetaData(buf);

        byte[] underlying;
        //TODO need to implement factory pattern here
        if ((MessageMetaDataType.META_DATA_MQTT).equals(type)) {
            underlying = MQTTMetaDataHandler.constructMetadata(buf, originalMessageMetadata, isCompressed);
        } else {
            underlying = AMQPMetaDataHandler.constructMetadata(buf, originalMessageMetadata, isCompressed);
        }

        return underlying;
    }

    public int getMessageContentLength() {
        return messageContentLength;
    }

    public void setMessageContentLength(int messageContentLength) {
        this.messageContentLength = messageContentLength;
    }

    public boolean isCompressed() {
        return isCompressed;
    }

    public void setCompressed(boolean isCompressed) {
        this.isCompressed = isCompressed;
    }

    @Override
    public int compareTo(AndesMessageMetadata other) {
        if (this.getMessageID() == other.getMessageID()) {
            return 0;
        } else {
            return this.getMessageID() > other.getMessageID() ? 1 : -1;
        }
    }

    public MessageMetaDataType getMetaDataType() {
        return metaDataType;
    }

    public void setMetaDataType(MessageMetaDataType metaDataType) {
        this.metaDataType = metaDataType;
    }

    /**
     * Add a property that is not directly relevant to Andes. The properties are not persistent. Lost when the object is
     * deleted
     * @param key String Key
     * @param value Object. Value of the property
     */
    public void addProperty(String key, Object value) {
        propertyMap.put(key, value);
    }

    /**
     * Returns the property for the given
     * @param key String
     * @return value of the property. Null if not found
     */
    public Object getProperty(String key) {
        return propertyMap.get(key);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof AndesMessageMetadata)) {
            return false;
        }
        AndesMessageMetadata andesMessageMetadata = (AndesMessageMetadata) o;
        return getMessageID() == andesMessageMetadata.getMessageID();
    }

    @Override
    public int hashCode() {
        return (int) (getMessageID() ^ (getMessageID() >>> 32));
    }

    /**
     * True if the expiration time is defined for the message.
     */
    public boolean isExpirationDefined() {
        return 0L < expirationTime;
    }

    public String toString() {
        return "messageID : " + messageID + "\nmetadata array length (bytes) : " + metadata.length
                + "\nexpirationTime : " + expirationTime + "\nisTopic : " + isTopic + "\nmessageRouterName : "
                + messageRouterName + "\narrivalTime : " + arrivalTime + "\ndestination : " + destination
                + "\nstorageQueueName : " + storageQueueName + "\nisPersistent : " + isPersistent
                + "\nmessageContentLength : " + messageContentLength + "\nqosLevel : " + qosLevel
                + "\nmetaDataType : " + metaDataType + "\nisCompressed : " + isCompressed + "\npropertyMap : "
                + propertyMap.toString() + "\nretain : " + retain;
    }

}