All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.crankuptheamps.client.MessageRouter Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2022 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties.  This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights.  This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////
package com.crankuptheamps.client;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * MessageRouter is used to register and manage a list of handler objects for messages,
 * as well as routing messages to those handlers as messages arrive.
 * MessageRouter also "knows" about the meaning of AMPS acks and can use them to automatically
 * clean up routes as acks arrive.
 */
public class MessageRouter
{
    /**
     * Adds a route to self.
     * @param commandId_ The command, query, or subid used for this route.
     * @param messageHandler_ The message handler to route to
     * @param requestedAcks_ The acks requested by the user for this command
     * @param systemAcks_ The acks not requested by the end user, but requested
     *                    by AMPS. These will not be delivered to the message
     *                    handler, but are still processed for auto-removal.
     * @param isSubscribe_ True if this route is for an ongoing subscription
     */
    public void addRoute(CommandId commandId_, MessageHandler messageHandler_, int requestedAcks_, int systemAcks_, boolean isSubscribe_)
    {
        _routes.put(commandId_, new MessageRoute(messageHandler_, requestedAcks_, systemAcks_, isSubscribe_));
    }

    /**
     * Remove a route from self.
     * @param commandId_ The route to remove
     * @return true if the route was removed.
     */
    public boolean removeRoute(CommandId commandId_)
    {
        return _routes.remove(commandId_) != null;
    }

    /**
     * Find and return a route
     * @param commandId_ The command id for this route
     * @return The MessageHandler registered for this route, or null if none is registered.
     */
    public MessageHandler findRoute(CommandId commandId_)
    {
        MessageRoute route = _routes.get(commandId_);
        if(route != null)
            return route.getMessageHandler();
        else
            return null;
    }

    /**
     * Return whether or not a command id has a route
     * @param commandId_ The command id for this route
     * @return If a route is registered for the command id.
     */
    public boolean hasRoute(CommandId commandId_)
    {
        return _routes.containsKey(commandId_);
    }

    /**
     * Removes all routes for subscriptions.
     */
    public void unsubscribeAll()
    {
        ArrayList subs = new ArrayList(_routes.size());
        for (Map.Entry entry : _routes.entrySet())
        {
            if (entry.getValue().isTerminationAck(0)) subs.add(entry.getKey());
        }
        for (CommandId subId : subs)
        {
            removeRoute(subId);
        }
    }

    /**
     * Removes all routes from self.
     */
    public void clear()
    {
        _routes.clear();
    }
    /**
     * Deliver a message that is known already to be an Ack. Coordinates the
     * removal of routes based on the ack received and the original message type.
     * @param ackMessage_ The Message to deliver.
     * @param ackType_ The ack type from that message.
     * @return The number of message deliveries that occurred
     * @throws Exception Any exception from user message handlers.
     */
    public int deliverAck(Message ackMessage_, int ackType_) throws Exception
    {
        assert(ackMessage_.getCommand() == Message.Command.Ack);
        assert(ackType_ != Message.AckType.None);
        int messagesDelivered = 0;
        if(ackMessage_.getCommandId(_key))
        {
            messagesDelivered += _deliverAck(ackMessage_, ackType_, _key);
        }
        if(ackMessage_.getQueryId(_key))
        {
            if(messagesDelivered==0)
            {
                messagesDelivered += _deliverAck(ackMessage_, ackType_, _key);
            } else {
                _processAckForRemoval(ackType_, _key);
            }
        }
        if(ackMessage_.getSubId(_key))
        {
            if(messagesDelivered == 0)
            {
                messagesDelivered += _deliverAck(ackMessage_, ackType_, _key);
            } else {
                _processAckForRemoval(ackType_, _key);
            }
        }
        return messagesDelivered;
    }

    /**
     * Delivers a data message (not an Ack) to the registered route.
     * Uses the commandID, subID, and queryID to find a route and
     * deliver to the first one found. This method is optimized for speed
     * and does not attempt to examine ack types for removal of routes.
     * @param dataMessage_ The non-ack message to deliver.
     * @return The number of deliveries performed
     * @throws Exception Any exception thrown by the user message handler.
     */
    public int deliverData(Message dataMessage_) throws Exception
    {
        assert(dataMessage_.getCommand() != Message.Command.Ack);
        int messagesDelivered = 0;
        if(messagesDelivered==0 && dataMessage_.getQueryId(_key))
        {
            messagesDelivered += deliverData(dataMessage_, _key);
        }
        if(dataMessage_.getCommandId(_key))
        {
            messagesDelivered += deliverData(dataMessage_, _key);
        }
        if(messagesDelivered==0 && dataMessage_.getSubId(_key))
        {
            messagesDelivered += deliverData(dataMessage_, _key);
        }
        return messagesDelivered;
    }
    /**
     * Delivers a data message using a specific command ID from the message. Optimized
     * for speed and does not attempt to examine the message for auto-removal of routes
     * @param dataMessage_ The message to deliver.
     * @param commandId_ The command ID which will be used to lookup the delivery route
     * @return The number of deliveries performed
     * @throws Exception Any exception returned thrown by the message handler.
     */
    public int deliverData(Message dataMessage_, CommandId commandId_) throws Exception
    {
        assert(dataMessage_.getCommand() != Message.Command.Ack);
        int messagesDelivered = 0;
        MessageRoute route = _routes.get(commandId_);
        if(route!=null)
        {
            messagesDelivered += route.deliverData(dataMessage_);
        }
        return messagesDelivered;
    }
    //////////////// INTERNAL METHODS AND CLASSES ///////////////////
    static private class MessageRoute
    {
        MessageHandler _messageHandler;
        int _systemAcks, _requestedAcks, _terminationAck;
        public MessageRoute(MessageHandler messageHandler_, int requestedAcks_,
                int systemAcks_, boolean isSubscribe_)
        {
            _messageHandler = messageHandler_;
            _systemAcks = systemAcks_;
            _requestedAcks = requestedAcks_;
            // For non-subscriptions we autoremove a handler when the termination ack comes in.
            if(!isSubscribe_)
            {
                // compute the termination ack we're looking for
                // make terminationAck the highest bit set in all acks
                int bitCounter = requestedAcks_ | systemAcks_;
                while(bitCounter > 0) { bitCounter>>=1; _terminationAck = _terminationAck>0?2*_terminationAck:1; }
            }
        }
        public int deliverAck(Message message_, int ackType_) throws Exception
        {
            // If it was a not a requested ack, do not deliver.
            if( (_requestedAcks & ackType_) == 0) return 0;
            _messageHandler.invoke(message_);
            return 1;
        }
        public boolean isTerminationAck(int ackType_)
        {
            return ackType_ == _terminationAck;
        }
        public int deliverData(Message message_) throws Exception
        {
            _messageHandler.invoke(message_);
            return 1;
        }
        public MessageHandler getMessageHandler()
        {
            return _messageHandler;
        }
    }
    private int _deliverAck(Message ackMessage_, int ackType_, CommandId commandId_) throws Exception
    {
        int messagesDelivered = 0;
        MessageRoute route = _routes.get(commandId_);
        if(route!=null)
        {
            messagesDelivered += route.deliverAck(ackMessage_, ackType_);
            if(route.isTerminationAck(ackType_))
            {
                _routes.remove(commandId_);
            }
        }
        return messagesDelivered;
    }
    private void _processAckForRemoval(int ackType_, CommandId commandId_)
    {
        MessageRoute route = _routes.get(commandId_);
        if(route != null && route.isTerminationAck(ackType_))
        {
            _routes.remove(commandId_);
        }
    }

    /////////// MEMBER VARIABLES FOR MessageRouter /////////////
    // Storage for current routes.
    ConcurrentHashMap _routes = new ConcurrentHashMap();

    // Cached CommandId object used for retrieval of command Ids.
    CommandId _key = new CommandId();

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy