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

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

There is a newer version: 5.3.4.0
Show newest version
///////////////////////////////////////////////////////////////////////////
//
// 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.io.IOException;
import java.lang.Long;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.crankuptheamps.client.fields.BookmarkField;
import com.crankuptheamps.client.fields.BookmarkRangeField;
import com.crankuptheamps.client.fields.Field;
import com.crankuptheamps.client.fields.StringField;
import com.crankuptheamps.client.exception.*;


/**
 * Implements a bookmark store useful for handling
 * server failover scenarios, but without a backing store to recover from
 * subscriber failure.
 * 
 * An optional {@link RecoveryPointAdapter} can be specified at construction
 * to persist bookmark subscription recovery state to an external store so the
 * subscription can be resumed if the subscriber fails.
 */
public class MemoryBookmarkStore implements BookmarkStore
{
    /**
     * The Subscription object is used to represent internal bookmark state
     * for the messages received and discarded on a specific subscription
     * within the bookmark store.
     */
    protected static class Subscription implements com.crankuptheamps.client.Subscription
    {
        // This subscription's ID.
        Field _sub;

        // The last persisted bookmark
        BookmarkField _lastPersisted;

        /**
         * The last-modified timestamp, if any, from a RecoveryPointAdapter
         * before recovery is initiated. When this is not null we intend to
         * include it in the list of bookmarks returned by getMostRecentList()
         * until a message is discarded on the sub or the sub state is purged.
         */
        protected volatile String _recoveryTimestamp = null;

        // If this subscription uses a range, hold it here
        BookmarkRangeField _range = new BookmarkRangeField();

        static final Field EPOCH_FIELD = new Field(Client.Bookmarks.EPOCH);


        BookmarkRingBuffer _ring = new BookmarkRingBuffer();

        // The per-subscription memory of what we've seen from publishers
        HashMap _publishers = new HashMap();

        // The Subscription lock
        final Lock _lock = new ReentrantLock();

        // The encoder/decoder for the Subscription
        final CharsetEncoder _encoder = StandardCharsets.UTF_8.newEncoder();
        final CharsetDecoder _decoder = StandardCharsets.UTF_8.newDecoder();

        MemoryBookmarkStore _parent;

        public Subscription(Field subscriptionId)
        {
            _sub = subscriptionId.copy();
            _ring.setSubId(_sub);
            _lastPersisted = new BookmarkField();
            _lastPersisted.copyFrom(EPOCH_FIELD);
        }

        public Subscription()
        {
            _lastPersisted = new BookmarkField();
            _lastPersisted.copyFrom(EPOCH_FIELD);
        }

        /**
         * Reset the state of this subscription object such that it can be
         * returned to the pool for reuse.
         */
        public void reset() {
            _lock.lock();
            try {
                _sub.reset();
                _lastPersisted.copyFrom(EPOCH_FIELD);
                _ring.reset();
                _publishers.clear();
                _range.reset();
            }
            finally {
                _lock.unlock();
            }
        }

        public void init(Field subId, MemoryBookmarkStore parent)
        {
            _lock.lock();
            try {
                _sub = subId.copy();
                _ring.setSubId(_sub);
                _parent = parent;
            }
            finally {
                _lock.unlock();
            }
        }

        public String getRecoveryTimestamp()
        {
            return _recoveryTimestamp;
        }

        protected final void setRecoveryTimestamp(String rts)
        {
            _recoveryTimestamp = rts;
        }

        public long log(BookmarkField bookmark) throws IOException, CommandException
        {
            _lock.lock();
            try {
                if (!bookmark.isRange()) {
                    // Add this entry onto our list to remember in order.
                    if (!bookmark.isBookmarkList()) {
                        return _log(bookmark);
                    }
                    else {
                        // If we're logging a list, we need to mark all items
                        // in the list as discarded.
                        long seq = 0;
                        for (BookmarkField bm : bookmark.parseBookmarkList()) {
                            isDiscarded(bm);
                            seq = _log(bm);
                            if (seq != 0) {
                                _ring.discard(seq);
                            }
                        }
                        if (_parent._adapter != null) {
                            _parent.adapterUpdate(_sub, bookmark);
                        }
                        return 0;
                    }
                }
                else {
                    _range.copyFrom(bookmark);
                    if (!_range.isValid()) {
                        _range.reset();
                        throw new CommandException("Invalid bookmark range specified");
                    }
                    long seq = 0;
                    if (_range.isStartExclusive()) {
                        // Parse the start of the rannge and log/discard each
                        BookmarkField start = _range.getStart();
                        if (!start.isBookmarkList()) {
                            isDiscarded(start);
                            seq = _log(start);
                            if (seq != 0) {
                                _ring.discard(seq);
                            }
                        }
                        else {
                            for (BookmarkField bm : start.parseBookmarkList()) {
                                isDiscarded(bm);
                                seq = _log(bm);
                                if (seq != 0) {
                                    _ring.discard(seq);
                                }
                            }
                            seq = 0;
                        }
                    }
                    if (_parent._adapter != null) {
                        _parent.adapterUpdate(_sub, _range);
                    }
                    return seq;
                }
            }
            finally {
                _lock.unlock();
            }
        }

        private long _log(BookmarkField bm) throws IOException
        {
            // _lock is already acquired
            long seq = 0;
            if (!bm.isTimestamp()) {
                seq = _ring.log(bm);
                while (seq == 0) {
                    _lock.unlock();
                    try {
                        _ring.checkResize();
                    }
                    finally {
                        _lock.lock();
                    }
                    seq = _ring.log(bm);
                }
            }
            else {
                setLastPersisted(bm);
            }
            return seq;
        }

        public void discard(long index) throws IOException
        {
            _lock.lock();
            try {
                if (_ring.discard(index))
                {
                    if (_parent._adapter != null) {
                        _parent.adapterUpdate(_sub,
                                    (BookmarkField)getMostRecentList(false));
                    }
                    _recoveryTimestamp = null;
                }
            }
            finally {
                _lock.unlock();
            }
        }

        /**
         * Check to see if this message is older than the most recent one seen,
         * and if it is, check if it discarded.
         */
        public boolean isDiscarded(BookmarkField bookmark)
        {
            _lock.lock();
            try {
                long publisher = bookmark.getPublisherId();
                long sequence = bookmark.getSequenceNumber();
                if(!_publishers.containsKey(publisher) ||
                    unsignedLongLess(_publishers.get(publisher), sequence))
                {
                    _publishers.put(publisher, sequence);
                    return false;
                }

                return true; // message is in-flight or discarded
            }
            finally {
                _lock.unlock();
            }
        }

        public BookmarkRangeField getRange() {
            _lock.lock();
            try { return _range.copy(); }
            finally {
                _lock.unlock();
            }
        }

        public Field getMostRecent()
        {
            _lock.lock();
            try {
                return _ring.getLastDiscarded().copy();
            }
            finally {
                _lock.unlock();
            }
        }

        // publisherId_ and sequence are signed, but the actual id's are
        // unsigned, so we have to do some work to create a valid string.
        private static String convertUnsignedLongToString(long publisherId_)
        {
            final BigInteger offset = BigInteger.valueOf(Long.MAX_VALUE)
                    .shiftLeft(1).add(BigInteger.valueOf(2));
            if(publisherId_<0)
            {
                return offset.add(BigInteger.valueOf(publisherId_)).toString();
            }
            else
            {
                return Long.toString(publisherId_);
            }
        }

        /** publisherId_ and sequence are signed, but the actual id's are
         * unsigned, so we have to do some work to compare them.
         */
        private static boolean unsignedLongLess(long seqLeft_, long seqRight_)
        {
            final BigInteger offset = BigInteger.valueOf(Long.MAX_VALUE)
                    .shiftLeft(1).add(BigInteger.valueOf(2));
            if(seqLeft_<0 || seqRight_<0)
            {
                BigInteger left = offset.add(BigInteger.valueOf(seqLeft_));
                BigInteger right = offset.add(BigInteger.valueOf(seqRight_));
                return left.compareTo(right) < 0;
            }
            else
            {
                return seqLeft_ < seqRight_;
            }
        }

        private static boolean unsignedLongLessEqual(long seqLeft_, long seqRight_)
        {
            final BigInteger offset = BigInteger.valueOf(Long.MAX_VALUE)
                    .shiftLeft(1).add(BigInteger.valueOf(2));
            if(seqLeft_<0 || seqRight_<0)
            {
                BigInteger left = offset.add(BigInteger.valueOf(seqLeft_));
                BigInteger right = offset.add(BigInteger.valueOf(seqRight_));
                return left.compareTo(right) <= 0;
            }
            else
            {
                return seqLeft_ <= seqRight_;
            }
        }

        public Field getMostRecentList(boolean useList)
        {
            _lock.lock();
            try {
                boolean rangeIsValid = _range.isValid();
                BookmarkField lastDiscarded = (BookmarkField)_ring.getLastDiscarded();
                boolean useLastDiscarded = (lastDiscarded != null &&
                                            !lastDiscarded.isNull());
                long lastDiscardedPub = 0;
                long lastDiscardedSeq = 0;
                boolean useLastPersisted = (_lastPersisted != null &&
                                            _lastPersisted.length > 1);
                long lastPersistedPub = 0;
                long lastPersistedSeq = 0;
                if (useLastPersisted)
                {
                    lastPersistedPub = _lastPersisted.getPublisherId();
                    lastPersistedSeq = _lastPersisted.getSequenceNumber();
                }
                if (useLastDiscarded)
                {
                    if (_ring.isEmpty() && useLastPersisted
                        && (!rangeIsValid || _range.getEnd().equals(_lastPersisted)))
                    {
                        useLastDiscarded = false;
                    }
                    else
                    {
                        lastDiscardedPub = lastDiscarded.getPublisherId();
                        lastDiscardedSeq = lastDiscarded.getSequenceNumber();
                        // Only use one if they are the same publisher
                        if (useLastPersisted &&
                            (lastPersistedPub == lastDiscardedPub))
                        {
                            useLastPersisted = (lastPersistedSeq < lastDiscardedSeq);
                            useLastDiscarded = !useLastPersisted;
                        }
                    }
                }

                StringBuilder recentStr = new StringBuilder();
                BookmarkField recentList = new BookmarkField();
                if (_recoveryTimestamp != null)
                {
                    recentStr.append(_recoveryTimestamp);
                }
                if (useLastDiscarded)
                {
                    if (recentStr.length() > 0) { recentStr.append(','); }
                    recentStr.append((lastDiscarded).getValue(_decoder));
                }
                // If we don't have a last persisted or a last discarded OR we
                // are expecting persisted acks but haven't received one yet,
                // then we should try to build a list of bookmarks based on
                // publishers we have seen so far, if any, or return EPOCH.
                if (useList &&
                     ((!useLastPersisted && !useLastDiscarded) ||
                      (_lastPersisted != null &&
                       _lastPersisted.equals(EPOCH_FIELD))
                    ))
                {
                    if (_publishers.isEmpty() && !rangeIsValid)
                    {
                        // Set last persisted to EPOCH and return it
                        if (_lastPersisted == null) {
                            _lastPersisted = new BookmarkField();
                            _lastPersisted.copyFrom(EPOCH_FIELD);
                        }
                        return _lastPersisted;
                    }
                    // If an EPOCH lastDiscarded value was added, remove it.
                    if (useLastDiscarded && lastDiscarded.equals(EPOCH_FIELD))
                    {
                        int len = recentStr.length();
                        if (len == 1) // Only contains EPOCH
                            recentStr.setLength(0);
                        else if (len > 2) // Ends with ",0" (,EPOCH)
                            recentStr.setLength(len - 2);
                    }
                    Iterator it = _publishers.entrySet().iterator();
                    while (it.hasNext())
                    {
                        Map.Entry pairs = (Map.Entry)it.next();
                        long pubId = (Long)pairs.getKey();
                        if (pubId == 0) continue;
                        if (useLastDiscarded && pubId == lastDiscardedPub) continue;
                        long seq = (Long)pairs.getValue();
                        if (recentStr.length() > 0) recentStr.append(',');
                        recentStr.append(convertUnsignedLongToString(pubId))
                                 .append(BookmarkField.SEPARATOR_CHAR)
                                 .append(convertUnsignedLongToString(seq))
                                 .append(BookmarkField.SEPARATOR_CHAR);
                    }
                    recentList.setValue(recentStr.toString(), _encoder);
                    if (rangeIsValid) {
                        if (recentList.length > 1
                            && (_range.isStartInclusive()
                               || !recentList.equals(_range.getStart()))) {
                            _range.replaceStart(recentList, true);
                        }
                        return _range;
                    }
                    return recentList;
                }
                if (useLastPersisted)
                {
                    if (recentStr.length() > 0) recentStr.append(',');
                    recentStr.append(_lastPersisted.getValue(_decoder));
                }
                recentList.setValue(recentStr.toString(), _encoder);
                if (rangeIsValid) {
                    if (recentList.length > 1
                        && (_range.isStartInclusive()
                           || !recentList.equals(_range.getStart()))) {
                        _range.replaceStart(recentList, true);
                    }
                    return _range;
                }
                return recentList;
            }
            finally {
                _lock.unlock();
            }
        }

        /**
         * Old style of setting a persisted bookmark no longer used.
         * @deprecated use {@link #setLastPersisted(BookmarkField)} instead.
         * @param bookmark The bookmark to be persisted.
         * @throws IOException if the bookmark was unable to be persisted to the bookmark store.
         */
        @Deprecated
        public void setLastPersisted(long bookmark) throws IOException
        {
            _lock.lock();
            try {
                BookmarkRingBuffer.Entry entry = _ring.getByIndex(bookmark);
                if (entry == null) return;
                if (_lastPersisted != null) _lastPersisted.reset();
                _lastPersisted = entry.getBookmark().copy();
                _recoveryTimestamp = null;
                if (_parent._adapter != null) {
                    _parent.adapterUpdate(_sub,
                                    (BookmarkField)getMostRecentList(false));
                }
            }
            finally {
                _lock.unlock();
            }
        }

        public void setLastPersisted(BookmarkField bookmark) throws IOException
        {
            _lock.lock();
            try {
                if (bookmark == null || bookmark.isNull()
                    || bookmark.equals(_lastPersisted)
                    || bookmark.isRange()) {
                    return;
                }

                if (bookmark.isTimestamp())
                {
                    if (_lastPersisted != null) _lastPersisted.reset();
                    _lastPersisted = bookmark.copy();
                    if (_parent._adapter != null) {
                        _parent.adapterUpdate(_sub,
                                    (BookmarkField)getMostRecentList(false));
                    }
                    return;
                }
                long publisherId = bookmark.getPublisherId();
                if (_lastPersisted != null &&
                    publisherId == _lastPersisted.getPublisherId() &&
                    unsignedLongLessEqual(bookmark.getSequenceNumber(),
                                     _lastPersisted.getSequenceNumber()))
                {
                    return;
                }
                BookmarkField lastPersisted = _lastPersisted;
                _lastPersisted = bookmark.copy();
                _recoveryTimestamp = null;
                if (_parent._adapter != null) {
                    _parent.adapterUpdate(_sub,
                                      (BookmarkField)getMostRecentList(false));
                }
                if (lastPersisted != null) lastPersisted.reset();
            }
            finally {
                _lock.unlock();
            }
        }

        public long getOldestBookmarkSeq()
        {
            _lock.lock();
            try {
                return _ring.getStartIndex();
            }
            finally {
                _lock.unlock();
            }
        }

        public void setResizeHandler(BookmarkStoreResizeHandler handler, BookmarkStore store)
        {
            _ring.setResizeHandler(handler, store);
        }
    }

    HashMap _subs = new HashMap();
    BookmarkStoreResizeHandler _resizeHandler = null;
    private int _serverVersion = Client.MIN_MULTI_BOOKMARK_VERSION;
    final CharsetEncoder _encoder = StandardCharsets.UTF_8.newEncoder();
    final CharsetDecoder _decoder = StandardCharsets.UTF_8.newDecoder();
    // The Store lock
    final Lock _lock = new ReentrantLock();

    /**
     * Optional recovery point adapter used to persist bookmark replay recovery
     * state for each subscription.
     */
    protected RecoveryPointAdapter _adapter = null;
    protected RecoveryPointFactory _factory = null;

    // Subscriptions take a while to initialize, so we avoid doing this in the message handler.
    // number of subscriptions we pre-initialize.
    Pool _pool;

    public MemoryBookmarkStore()
    {
        this(1);
    }

    public MemoryBookmarkStore(int targetNumberOfSubscriptions)
    {
        _pool = new Pool(Subscription.class, targetNumberOfSubscriptions);
    }

    /**
     * Initialize self with a target number of subscriptions to pool and the
     * specified recovery point adapter. 
     * 
     * @param targetNumberOfSubscriptions The number of subscriptions this store
     *        will initially create in its pool.
     * @param adapter Recovery point adapter this instance will
     *        call with recovery point state for each of its subscriptions.
     *        The adapter can persist this state to an external store and
     *        retrieve it on application restart so that bookmark replay
     *        subscriptions can be continued approximately where they left off
     *        (with the possible delivery of some duplicate messages). The
     *        adapter will be given FixedRecoveryPoints.
     * @throws AMPSException If the given adapter throws an exception during
     *         recovery.
     */
    public MemoryBookmarkStore(int targetNumberOfSubscriptions,
                               RecoveryPointAdapter adapter) throws AMPSException
    {
        this(targetNumberOfSubscriptions, adapter, new FixedRecoveryPointFactory());
    }

    /**
     * Initialize self with a target number of subscriptions to pool and the
     * specified recovery point adapter. 
     * 
     * @param targetNumberOfSubscriptions The number of subscriptions this store
     *        will initially create in its pool.
     * @param adapter Recovery point adapter this instance will
     *        call with recovery point state for each of its subscriptions.
     *        The adapter can persist this state to an external store and
     *        retrieve it on application restart so that bookmark replay
     *        subscriptions can be continued approximately where they left off
     *        (with the possible delivery of some duplicate messages).
     * @param factory RecoveryPointFactory that will produce RecoveryPoints
     *        for the adapter.
     * @throws AMPSException If the given adapter throws an exception during
     *         recovery.
     */
    public MemoryBookmarkStore(int targetNumberOfSubscriptions,
                               RecoveryPointAdapter adapter,
                               RecoveryPointFactory factory) throws AMPSException
    {
        _pool = new Pool(Subscription.class, targetNumberOfSubscriptions);

        // Try to recover subscription state if a recovery point adapter
        // was specified.
        Message m = new JSONMessage(_encoder, _decoder);
        for (RecoveryPoint rp : adapter) {
            if (rp == null) break;
            Field subId = rp.getSubId();
            m.reset();
            m.setSubId(subId.buffer, subId.position, subId.length);
            BookmarkField bookmark = rp.getBookmark();
            if (bookmark.isRange()) {
                m.setBookmark(bookmark.buffer, bookmark.position, bookmark.length);
                log(m);
            }
            else {
                for (BookmarkField bm : bookmark.parseBookmarkList()) {
                    if (!bm.isTimestamp()) {
                        try {
                            m.setBookmark(bm.buffer, bm.position, bm.length);
                            isDiscarded(m);
                            if (log(m) > 0)
                                discard(m);
                        } catch(AMPSException e) { } // Always valid
                    }
                    else {
                        find(subId).setRecoveryTimestamp(bm.toString());
                    }
                }
            }
        }
        // Set after recovery so no updates sent back to adapter
        _adapter = adapter;
        _factory = factory;
    }

    /**
     *  Change the RecoveryPointFactory used by this store for its adapter.
     *  @param factory_ The new RecoveryPointFactory
	 *  @throws AMPSException if the factory_ parameter is null or the recovery point adapter is null.
     */
    public void setRecoveryPointFactory(RecoveryPointFactory factory_) throws AMPSException
    {
        if (factory_ == null || _adapter == null) {
            throw new CommandException("Factory and Adapter must not be null.");
        }
        _factory = factory_;
    }

    public long log(Message message) throws AMPSException
    {
        BookmarkField bookmark = (BookmarkField)message.getBookmarkRaw();
        if (bookmark.equals(Subscription.EPOCH_FIELD)) return 0;
        Subscription sub = (MemoryBookmarkStore.Subscription)message.getSubscription();
        if (sub == null)
        {
            Field subId = message.getSubIdRaw();
            if (subId == null || subId.isNull())
                subId = message.getSubIdsRaw();
            sub = find(subId);
            message.setSubscription(sub);
        }
        try {
            long seqNo = sub.log(bookmark);
            message.setBookmarkSeqNo(seqNo);
            return seqNo;
        }
        catch (IOException e) {
            throw new StoreException("Failed to save range in RecoveryPointAdapter", e);
        }
    }

    public void discard(Field subId, long bookmarkSeqNo) throws AMPSException
    {
        try {
            find(subId).discard(bookmarkSeqNo);
        }
        catch (IOException e) {
            throw new StoreException("Error discarding from bookmark store: " + e, e);
        }
    }

    public void discard(Message message) throws AMPSException
    {
        long bookmark = message.getBookmarkSeqNo();
        Subscription sub = (MemoryBookmarkStore.Subscription)message.getSubscription();
        if (sub == null)
        {
            BookmarkField bmField = message.getBookmarkRaw();
            if (bmField.equals(Subscription.EPOCH_FIELD)
                || bmField.isTimestamp() || bmField.isRange()
                || bmField.isBookmarkList())
            {
                return;
            }
            Field subId = message.getSubIdRaw();
            if (subId == null || subId.isNull())
                subId = message.getSubIdsRaw();
            sub = find(subId);
            message.setSubscription(sub);
        }
        try {
            sub.discard(bookmark);
        }
        catch (IOException e) {
            throw new StoreException("Error discarding from bookmark store: " + e, e);
        }
    }

    public Field getMostRecent(Field subId) throws AMPSException
    {
        return getMostRecent(subId, true);
    }

    public Field getMostRecent(Field subId, boolean useList) throws AMPSException
    {
        return find(subId).getMostRecentList(useList).copy();
    }

    public boolean isDiscarded(Message message) throws AMPSException
    {
        BookmarkField bookmark = (BookmarkField)message.getBookmarkRaw();
        if (bookmark.equals(Subscription.EPOCH_FIELD)) return true;
        if (bookmark.isTimestamp() || bookmark.isBookmarkList()) return false;
        Field subId = message.getSubIdRaw();
        if (subId == null || subId.isNull())
            subId = message.getSubIdsRaw();
        Subscription sub = find(subId);
        message.setSubscription(sub);
        return sub.isDiscarded(bookmark);
    }

    /**
     * Old style of setting a persisted bookmark no longer used.
     * @deprecated use {@link #persisted(Field, BookmarkField)} instead.
     * @param subId The subId associated with the bookmark to be persisted.
     * @param bookmark The bookmark to be persisted.
     * @throws AMPSException If the bookmark was unable to be persisted to the bookmark store.
     */
    @Deprecated
    public void persisted(Field subId, long bookmark) throws AMPSException
    {
        try {
            find(subId).setLastPersisted(bookmark);
        }
        catch (IOException e) {
            throw new StoreException("Error logging persisted to bookmark store: " + e, e);
        }
    }

    public void persisted(Field subId, BookmarkField bookmark) throws AMPSException
    {
        try {
            find(subId).setLastPersisted(bookmark);
        }
        catch (IOException e) {
            throw new StoreException("Error logging persisted to bookmark store: " + e, e);
        }
    }

    /**
     * Finds and returns the Subscription object for the specified
     * subscription id (subId).
     * @param subId The subId to find or create a Subscription for.
     * @return The Subscription for the given subId.
     */
    protected Subscription find(Field subId)
    {
        _lock.lock();
        try {
            Subscription s = _subs.get(subId);
            if(s==null)
            {
                s = _pool.get();
                s.init(subId, this);
                s.setResizeHandler(_resizeHandler, this);
                _subs.put(subId.copy(), s);
            }
            return s;
        }
        finally {
            _lock.unlock();
        }
    }

    /**
     *  Remove all entries in the bookmark store, completely
     *  clearing all record of messages received and discarded.
     */
    public void purge() throws AMPSException
    {
        _lock.lock();
        try {
            for (Subscription sub: _subs.values())
            {
                sub.reset();
                _pool.returnToPool(sub);
            }
            _subs.clear();

            // Also purge recovery point adapter if it exists.
            if (_adapter != null) _adapter.purge();
        }
        catch (Exception e) {
            throw new StoreException("An error occurred while purging recovery" 
                    + "state from recovery point adapter: " + e, e);
        }
        finally {
            _lock.unlock();
        }
    }

    public void purge(Field subId_) throws AMPSException
    {
        _lock.lock();
        try {
            Subscription sub = _subs.remove(subId_);
            if (sub == null) return;
            sub.reset();
            _pool.returnToPool(sub);

            // Also purge recovery point adapter if it exists.
            if (_adapter != null) _adapter.purge(subId_);
        }
        catch (Exception e) {
            throw new StoreException("An error occurred while purging recovery" 
                            + "state from recovery point adapter for subId '"
                            + subId_.toString() + "': " + e, e);
        }
        finally {
            _lock.unlock();
        }
    }

    public long getOldestBookmarkSeq(Field subId) throws AMPSException
    {
        _lock.lock();
        try {
            long retVal = 0;
            retVal = find(subId).getOldestBookmarkSeq();
            return retVal;
        }
        finally {
            _lock.unlock();
        }
    }

    public void setResizeHandler(BookmarkStoreResizeHandler handler)
    {
        _resizeHandler = handler;
        Iterator it = _subs.entrySet().iterator();
        while (it.hasNext())
        {
            Map.Entry pairs = (Map.Entry)it.next();
            ((Subscription)pairs.getValue()).setResizeHandler(handler, this);
        }
    }

    public void setServerVersion(int version)
    {
        _serverVersion = version;
    }

    /**
    * Called by Client when connected to an AMPS server in order to retrieve the version
    * number of the server. Returns retrieved version number.
    * @return the server version, represented as an integer
    */
    public int getServerVersion()
    {
        return _serverVersion;
    }

    /**
     * Closes down the bookmark store. If a recovery point adapter was
     * specified during construction, this will call close() on it.
     */
    public void close() throws AMPSException
    {
        _lock.lock();
        try {
            if (_adapter != null) {
                _adapter.close();
                _adapter = null;
            }
        }
        catch (Exception e) {
            throw new StoreException("An error occurred while closing the underlying " 
                    + "recovery point adapter: " + e, e);
        }
        finally {
            _lock.unlock();
        }
    }

    /**
     * Used inernally to update the RecoveryPointAdapter if there is one.
     * @param subId The subId to update.
     * @param bookmark The latest bookmark.
     * @throws IOException If there is an exception from the adapter.
     */
    protected void adapterUpdate(Field subId, BookmarkField bookmark) throws IOException {
            try {
                _adapter.update(_factory.createRecoveryPoint(subId, bookmark));
            }
            catch (Exception e) {
                throw new IOException("Exception in LoggedBookmarkStore updating the RecoveryPointAdapter", e);
            }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy