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

com.crankuptheamps.client.MemorySubscriptionManager 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 com.crankuptheamps.client.exception.AMPSException;
import com.crankuptheamps.client.exception.DisconnectedException;
import com.crankuptheamps.client.exception.TimedOutException;
import com.crankuptheamps.client.fields.Field;
import com.crankuptheamps.client.fields.StringField;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

/**
 * An in-memory implementation of SubscriptionManager. Used by HAClient.
 */
public class MemorySubscriptionManager implements SubscriptionManager
{
    static long DefaultResubscriptionTimeout = 0;
    long _resubscriptionTimeout = 0;

    static class SubscriptionInfo
    {
        public MessageHandler _handler;

        Field   _recent;
        Field   _subId;
        String  _options;
        boolean _hasBookmark;
        String  _filter;
        String  _orderBy;
        String  _queryId;
        String  _sowKeys;
        String  _topic;
        long    _topN;
        int     _command;
        int     _ackType;
        int     _batchSize;
        MemorySubscriptionManager _parent;

        /**
         * Constructs a SubscriptionInfo for the given handler and subscribe message.
         * @param messageHandler The message handler for this subscription.
         * @param message The subscribe message for this subscription.
         */
        SubscriptionInfo(MessageHandler messageHandler, Message message,
                         MemorySubscriptionManager parent)
        {
            _handler = messageHandler;
            saveMessage(message);
            byte[] recentBuf = new byte[512];
            _recent = new Field(recentBuf, 0, 0);
            _parent = parent;
        }

        /**
         * Places this subscription on the given Client.
         * @param client The client to place the subscription on.
         * @throws AMPSException An error occurred while placing the subscription.
         */
        void resubscribe(Client client) throws AMPSException
        {
            // Don't set replace
            if (_options != null)
            {
                _options = _options.replaceAll("replace,?","");
                if(_options.length() < 2) _options = null;
            }
            Message message = client.allocateMessage();
            if(_hasBookmark)
            {
                // Always resubscribe to the most recent.
                if (isPaused() && _recent.length > 0)
                {
                    message.setBookmark(_recent.buffer, _recent.position,
                                        _recent.length);
                }
                else
                {
                    Field recent = client.getBookmarkStore()
                                         .getMostRecent(_subId);
                    message.setBookmark(recent.buffer, recent.position,
                                        recent.length);
                }
            }
            apply(message);
            client.send(_handler, message, _parent._resubscriptionTimeout);
            _recent.length = 0;
        }

        /**
         * Remove the given subscription ID from the list of sub ids on self.
         * @param subId_ The subscription ID to remove
         * @return true if the subscription ID is removed, false otherwise.
         */
        boolean removeSubId(Field subId_)
        {
            int subIdStart = subId_.position;
            int subIdLen = subId_.length;
            while (subId_.buffer[subIdStart + subIdLen - 1] == (byte)',')
                --subIdLen;
            while (subId_.buffer[subIdStart] == (byte)',') {
                ++subIdStart; --subIdLen;
            }
            if (subIdLen > _subId.length) return _subId.isNull();
            boolean match = true;
            int matchStart = 0;
            int matchCount = 0;
            for (int i=0; i<_subId.length; ++i)
            {
                if (_subId.buffer[_subId.position+i] == (byte)',') {
                    if (matchCount == subIdLen) {
                        break;
                    }
                    matchStart = i+1;
                    matchCount = 0;
                    match = true;
                    continue;
                }
                else if (match) {
                    if (matchCount < subIdLen &&
                        _subId.buffer[_subId.position+i] ==
                         subId_.buffer[subIdStart+matchCount]) {
                        ++matchCount;
                    } else {
                        matchCount = 0;
                        match = false;
                    }
                }
            }
            if (match && matchCount == subIdLen) {
                int len = _subId.length - matchCount;
                if (len > 1) // More than just , left
                {
                    byte[] buffer = new byte[len];
                    int offset = 0;
                    int newLen = 0;
                    for (int i=0; i+offset<_subId.length; ++i)
                    {
                        if (i == matchStart) {
                            offset=matchCount;
                            if (i+offset >= _subId.length) break;
                            if (_subId.buffer[_subId.position+offset+i] == (byte)',')
                            {
                                if (++offset+i >= _subId.length) break;
                            }
                        }
                        buffer[i] = _subId.buffer[_subId.position+offset+i];
                        ++newLen;
                    }
                    if (newLen > 0) {
                        _subId = new Field(buffer, 0, newLen);
                        return false;
                    }
                }
                _subId.reset();
                return true;
            }
            return _subId.isNull();
        }

        /**
         * Marks this subscription as paused.
         */
        void pause()
        {
            if (isPaused()) return;
            _options = _options==null ?
                          "pause"             :
                          "pause," + _options;
        }

        /**
         * Create the most recent bookmark string for this subscription.
         * @param client The Client to check.
         * @return The most recent bookmark string, used when resubscribing.
         * @throws AMPSException An error occurred when determining the most-recent string.
         */
        Field getMostRecent(Client client) throws AMPSException
        {
            if (!_recent.isNull()) return _recent;
            Field subId = new Field();
            int start = 0;
            while (_subId.buffer[_subId.position + start] == (byte)',')
                ++start;
            int end = start + 1;
            while (end < _subId.length) {
                if (_subId.buffer[_subId.position+end] == (byte)',') {
                    subId.set(_subId.buffer, _subId.position+start, end-start);
                    Field mostRecent = client.getBookmarkStore().getMostRecent(subId);
                    if (_recent.length > 0) {
                        if (_recent.length == _recent.buffer.length) {
                            byte[] newRecent = new byte[_recent.buffer.length+512];
                            System.arraycopy(_recent.buffer, 0,
                                             newRecent, 0, _recent.length);
                            _recent.set(newRecent, 0, _recent.length);
                        }
                        _recent.buffer[_recent.length++] = (byte)',';
                    }
                    if (_recent.length + mostRecent.length > _recent.buffer.length) {
                        int newSize = _recent.buffer.length + 512;
                        while (_recent.length + mostRecent.length > newSize)
                            newSize += 512;
                        byte[] newRecent = new byte[newSize];
                        System.arraycopy(_recent.buffer, 0,
                                         newRecent, 0, _recent.length);
                        _recent.set(newRecent, 0, _recent.length);
                    }
                    System.arraycopy(mostRecent.buffer, mostRecent.position,
                                     _recent.buffer, _recent.length,
                                     mostRecent.length);
                    _recent.length += mostRecent.length;
                    while (end < _subId.length &&
                           _subId.buffer[_subId.position + end] == (byte)',')
                        ++end;
                    start = end;
                }
                ++end;
            }
            return _recent;
        }

        /**
         * Records the applicable fields from a Message, for use when resubscribing.
         * @param message The subscribe message to save fields from.
         */
        final void saveMessage(Message message)
        {
            _subId       = message.getSubIdRaw().copy();
            _options     = message.getOptions();
            _hasBookmark = !message.isBookmarkNull();
            _filter      = message.getFilter();
            _orderBy     = message.getOrderBy();
            _queryId     = message.getQueryId();
            _sowKeys     = message.getSowKeys();
            _topic       = message.getTopic();
            _ackType     = message.getAckTypeOutgoing();
            _command     = message.getCommand();

            if(!message.isBatchSizeNull())
            {
                _batchSize   = message.getBatchSize();
            }
            else
            {
                _batchSize = 1;
            }

            if(!message.isTopNNull())
            {
                _topN = message.getTopN();
            }
            else
            {
                _topN = -1;
            }
        }

        /**
         * Applies the saved fields from self to a new message, when resubscribing.
         * @param message The new message to set fields on.
         */
        void apply(Message message)
        {
            message.setSubId(_subId.buffer,0,_subId.length);
            message.setOptions(_options);
            message.setFilter(_filter);
            message.setOrderBy(_orderBy);
            message.setQueryId(_queryId);
            message.setSowKeys(_sowKeys);
            message.setTopic(_topic);
            message.setAckType(_ackType);
            message.setCommand(_command);
            message.setBatchSize(_batchSize);
            if(_topN != -1)
            {
              message.setTopN(_topN);
            }
        }
        /**
         * Returns true if this subscription is in a paused state.
         * @return Returns true if this subscription is in a paused state.
         */
        boolean isPaused()
        {
            return _options != null && _options.contains("pause");
        }
    }

    private final Lock _lock = new ReentrantLock();
    private final Condition _resubscriptionStatus = _lock.newCondition();
    private HashMap _active = new HashMap();
    private HashMap _resumed = new HashMap();
    private ArrayList _resumeList = new ArrayList();
    volatile boolean _resubscribing = false;

    public MemorySubscriptionManager()
    {
        _resubscriptionTimeout = DefaultResubscriptionTimeout;
    }

    public void subscribe(MessageHandler messageHandler, Message message)
    {
        _lock.lock();
        try {
            while (_resubscribing) {
                // Keep waiting, even if we're interrupted
                try { _resubscriptionStatus.await(100, TimeUnit.MILLISECONDS); }
                catch (InterruptedException ex) { }
            }
            if (!message.isSubIdNull())
            {
                String subId = message.getSubId();
                if (!message.isOptionsNull())
                {
                    String options = message.getOptions();
                    if (options.contains("resume"))
                    {
                        SubscriptionInfo subInfo = new SubscriptionInfo(messageHandler,
                                                                        message, this);
                        boolean saved = false;
                        for (String sid : subId.split(","))
                        {
                            Field sidField = new Field(sid);
                            if (!_resumed.containsKey(sidField))
                            {
                                _resumed.put(sidField, subInfo);
                                saved = true;
                            }
                        }
                        if (saved) _resumeList.add(subInfo);
                        return;
                    } else if (options.contains("pause"))
                    {
                        for (String sid : subId.split(","))
                        {
                            MessageHandler handler = messageHandler;
                            Field sidField = new Field(sid);
                            SubscriptionInfo existingSubInfo = _resumed.get(sidField);
                            if (existingSubInfo != null)
                            {
                                if (existingSubInfo.removeSubId(sidField))
                                    _resumeList.remove(existingSubInfo);
                                _resumed.remove(sidField);
                            }
                            existingSubInfo = _active.get(sidField);
                            if (existingSubInfo != null)
                            {
                                if (!options.contains("replace"))
                                {
                                    existingSubInfo.pause();
                                    continue;
                                } else {
                                    handler = existingSubInfo._handler;
                                }
                            }
                            Message m = message.copy();
                            m.setSubId(sid);
                            SubscriptionInfo subInfo =
                                                new SubscriptionInfo(handler, m, this);
                            _active.put(subInfo._subId, subInfo);
                        }
                        return;
                    }
                }
                MessageHandler handler = messageHandler;
                Field sidField = new Field(subId);
                SubscriptionInfo existingSubInfo = _active.get(sidField);
                if (existingSubInfo != null)
                {
                        handler = existingSubInfo._handler;
                }
                SubscriptionInfo subInfo = new SubscriptionInfo(handler,
                                                                message, this);
                _active.put(subInfo._subId, subInfo);
            }
        }
        finally { _lock.unlock(); }
    }

    public void unsubscribe(CommandId subId)
    {
        _lock.lock();
        try
        {
            while (_resubscribing) {
                // Keep waiting, even if we're interrupted
                try { _resubscriptionStatus.await(100, TimeUnit.MILLISECONDS); }
                catch (InterruptedException ex) { }
            }
            StringField f = new StringField();
            f.setValue(subId);
            _active.remove(f);
            SubscriptionInfo existingSubInfo = _resumed.get(f);
            if (existingSubInfo != null)
            {
                if (existingSubInfo.removeSubId(f))
                    _resumeList.remove(existingSubInfo);
                _resumed.remove(f);
            }
        }
        finally { _lock.unlock(); }
    }

    public void clear()
    {
        _lock.lock();
        try
        {
            while (_resubscribing) {
                // Keep waiting, even if we're interrupted
                try { _resubscriptionStatus.await(100, TimeUnit.MILLISECONDS); }
                catch (InterruptedException ex) { }
            }
            _active.clear(); _resumed.clear(); _resumeList.clear();
        }
        finally { _lock.unlock(); }
    }

    public void resubscribe(Client client) throws AMPSException
    {
        ArrayList subs = null;
        try
        {
            _lock.lock();
            try
            {
                subs = new ArrayList(_active.size());
                _resubscribing = true;
                for (SubscriptionInfo si : _active.values())
                {
                    subs.add(si);
                }
                for (SubscriptionInfo si : _resumeList)
                {
                    subs.add(si);
                }
            }
            finally
            {
                _lock.unlock();
            }
            for (SubscriptionInfo si : subs)
            {
                si.resubscribe(client);
            }
        }
        finally
        {
            _resubscribeDone();
        }
    }

    private void _resubscribeDone()
    {
        _lock.lock();
        try
        {
            _resubscribing = false;
            _resubscriptionStatus.signalAll();
        }
        finally
        {
            _lock.unlock();
        }
    }

    /**
     * Sets the Default Resubscription Timeout for all instances of
     * this class in milliseconds.
     * @param timeout The default timeout for resubscription calls.
     */
    public static void setDefaultResubscriptionTimeout(long timeout)
    {
        DefaultResubscriptionTimeout = timeout;
    }

    /**
     * Gets the Default Resubscription Timeout for all instances of
     * this class in milliseconds.
     * @return The default timeout for resubscription calls.
     */
    public static long getDefaultResubscriptionTimeout()
    {
        return DefaultResubscriptionTimeout;
    }

    /**
     * Sets the Resubscription timeout in milliseconds. 
     * @param timeout The timeout for resubscription calls.
     */
    public void setResubscriptionTimeout(long timeout)
    {
        _resubscriptionTimeout = timeout;
    }

    /**
     * Gets the Resubscription timeout in milliseconds .
     * @return timeout The timeout for resubscription calls.
     */
    public long getResubscriptionTimeout()
    {
        return _resubscriptionTimeout;
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy