org.wso2.andes.mqtt.connectors.InMemoryConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.andes.mqtt.connectors.InMemoryConnector.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.mqtt.connectors;

import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dna.mqtt.wso2.QOSLevel;
import org.wso2.andes.kernel.AndesException;
import org.wso2.andes.kernel.AndesMessageMetadata;
import org.wso2.andes.kernel.DeliverableAndesMetadata;
import org.wso2.andes.kernel.SubscriptionAlreadyExistsException;
import org.wso2.andes.kernel.disruptor.inbound.PubAckHandler;
import org.wso2.andes.mqtt.MQTTException;
import org.wso2.andes.mqtt.MQTTMessageContext;
import org.wso2.andes.mqtt.MQTTopicManager;
import org.wso2.andes.mqtt.utils.MQTTUtils;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Will be used to handle the incoming messages through the in-memory store, this will be supported only for QoS 0
 */
public class InMemoryConnector implements MQTTConnector {

    //Stores the subscriptions relevant for the topic
    //MAP<Topic,Channels> - Key - the list of topics and value - the list of subscribed channels
    private Map<String, List<String>> messageSubscription = new HashMap<String, List<String>>();
    private static Log log = LogFactory.getLog(InMemoryConnector.class);

    /**
     * {@inheritDoc}
     */
    @Override
    public void messageAck(long messageID, UUID subChannelID) throws AndesException {
        //Fully in-memory mode will only be compatible for messages with QoS 0 therefore a message ack will not be,
        //received
        throw new NotImplementedException();
    }

    @Override
    public void messageNack(long messageId, UUID channelID) {

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addMessage(MQTTMessageContext messageContext) throws MQTTException {
        broadcastMessages(messageContext.getTopic(), messageContext.getMessage(),
                messageContext.getMqttLocalMessageID(), messageContext.getQosLevel().getValue(),
                messageContext.isRetain(), messageContext.getPublisherID(), messageContext.getPubAckHandler());
        if (log.isDebugEnabled()) {
            log.debug("Message published to topic " + messageContext.getTopic() + " with publisher id "
                    + messageContext.getMqttLocalMessageID());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addSubscriber(MQTTopicManager channel, String topic, String clientID, String username,
            boolean isCleanSession, QOSLevel qos, UUID subscriptionChannelID)
            throws MQTTException, SubscriptionAlreadyExistsException {

        List<String> subscribers = messageSubscription.get(topic);
        //If this is the first subscriber
        if (null == subscribers) {
            subscribers = new ArrayList<String>();
        }
        subscribers.add(clientID);
        //Will add the final list to the subscription
        messageSubscription.put(topic, subscribers);

        log.info("Subscription with id " + clientID + " registered to topic " + topic);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeSubscriber(MQTTopicManager channel, String subscribedTopic, String username,
            String subscriptionChannelID, UUID subscriberChannel, boolean isCleanSession, String mqttClientID,
            QOSLevel qosLevel) throws MQTTException {

        handleSubscriptionRemoval(subscribedTopic, mqttClientID);
        log.info("Subscription with id " + mqttClientID + " registered to topic " + subscribedTopic);

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void disconnectSubscriber(MQTTopicManager channel, String subscribedTopic, String username,
            String subscriptionChannelID, UUID subscriberChannel, boolean isCleanSession, String mqttClientID,
            QOSLevel qosLevel) throws MQTTException {

        handleSubscriptionRemoval(subscribedTopic, mqttClientID);
        log.info("Subscription with id " + mqttClientID + " removed from " + subscribedTopic);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public UUID removePublisher(String mqttClientChannelID) {
        return null;
    }

    /**
     * Will broadcast a message among its subscribers
     *
     * @param topic        published topic name
     * @param messages     message content
     * @param messageID    unique message identifier
     * @param publishedQoS the level of published QoS
     * @param retain       should the message be retain
     * @param clientID     the client identifier
     * @param ackHandler   the acknowledgment handling engine
     */
    private void broadcastMessages(String topic, ByteBuffer messages, int messageID, int publishedQoS,
            boolean retain, String clientID, PubAckHandler ackHandler) {
        //In case if the message was published at QoS level 0 we send the ack back to the client
        //NOTE : the in-memory mode will only support QoS level 0 subscriptions since a store will not get involved
        sendPublisherAck(publishedQoS, messageID, clientID, ackHandler);
        List<String> subscribers = messageSubscription.get(topic);
        //Will distribute among the subscribers
        for (String subChannel : subscribers) {
            try {
                //We allow only QoS 0 messages to be exchanged in-memory
                int memoryQoSLevel = 0;
                MQTTopicManager.getInstance().distributeMessageToSubscriber(topic, messages, messageID,
                        memoryQoSLevel, retain, subChannel, memoryQoSLevel,
                        new DeliverableAndesMetadata(null, 0L, null, false));
                if (log.isDebugEnabled()) {
                    log.debug("Message " + messageID + " Delivered to subscription " + subChannel + " to topic "
                            + topic);
                }
            } catch (MQTTException e) {
                String message = "Error occurred while sending the message to subscriber";
                log.error(message, e);
                //We do not throw the exception any further, the process should not hand here
            }
        }
    }

    /**
     * Sends acknowledgments to the publisher
     *
     * @param publishedQoS   the level of published qos
     * @param localMessageID the unique message identifier
     * @param clientID       the client identifier
     * @param ackHandler     the acknowledgment handler
     */
    private void sendPublisherAck(int publishedQoS, int localMessageID, String clientID, PubAckHandler ackHandler) {
        AndesMessageMetadata metaData = new AndesMessageMetadata();
        metaData.addProperty(MQTTUtils.QOSLEVEL, publishedQoS);
        metaData.addProperty(MQTTUtils.CLIENT_ID, clientID);
        metaData.addProperty(MQTTUtils.MESSAGE_ID, localMessageID);
        ackHandler.ack(metaData);
        if (log.isDebugEnabled()) {
            log.debug("Publisher ack sent to " + clientID + " for message id " + localMessageID);
        }
    }

    /**
     * handles removal of subscriptions
     *
     * @param subscribedTopic the name of the topic subscription
     * @param mqttClientID    the unique client identifier
     * @throws MQTTException
     */
    private void handleSubscriptionRemoval(String subscribedTopic, String mqttClientID) throws MQTTException {
        List<String> subscribers = messageSubscription.get(subscribedTopic);

        if (null == subscribers) {
            throw new MQTTException("There're no subscribers for topic " + subscribedTopic);
        }

        subscribers.remove(mqttClientID.intern());

        //After removal if the subscriber list is empty let's remove the subscription itself off
        if (subscribers.isEmpty()) {
            messageSubscription.remove(subscribedTopic);
        }

        if (log.isDebugEnabled()) {
            log.debug("Subscriber with id " + mqttClientID + " removed from topic " + subscribedTopic);
        }

    }

    /**
     *
     * Handles retain feature in in-memory mode
     * @param topic topic name
     * @param subscriptionID subscription id of the subscriber
     * @param qos qos level of the subscriber
     */
    public void sendRetainedMessagesToSubscriber(String topic, String subscriptionID, QOSLevel qos,
            UUID subscriptionChannelID) {
        // TODO : Need to implement retain feature for non-persistent MQTT model after inMemoryConnector updated with new delivery model.
    }

}