Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2024 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();
// Represents the epoch field
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;
/**
* Constructor that creates a Subscription object with the provided subscription
* ID and initializes its state.
*
* @param subscriptionId The subscription ID to associate with this
* Subscription.
*/
public Subscription(Field subscriptionId)
{
_sub = subscriptionId.copy();
_ring.setSubId(_sub);
_lastPersisted = new BookmarkField();
_lastPersisted.copyFrom(EPOCH_FIELD);
}
/**
* Default constructor that creates a Subscription object and initializes its
* state.
*/
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();
}
}
/**
* Initialize the Subscription with a new subscription ID and assign a reference
* to the parent bookmark store.
*
* @param subId The new subscription ID to associate with this Subscription.
* @param parent The parent MemoryBookmarkStore to which this Subscription
* belongs.
*/
public void init(Field subId, MemoryBookmarkStore parent) {
_lock.lock();
try {
_sub = subId.copy();
_ring.setSubId(_sub);
_parent = parent;
}
finally {
_lock.unlock();
}
}
/**
* Get the last-modified timestamp used for recovery.
*
* @return The last-modified timestamp used for recovery.
*/
public String getRecoveryTimestamp()
{
return _recoveryTimestamp;
}
/**
* 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.
*
* @param rts The recovery timestamp to set.
*/
protected final void setRecoveryTimestamp(String rts)
{
_recoveryTimestamp = rts;
}
/**
* Log a bookmark, either a single bookmark or a list of bookmarks.
*
* @param bookmark The bookmark to log.
* @return The sequence number associated with the logged bookmark.
* @throws IOException Thrown when an IO operation fails.
* @throws CommandException Thrown when a command operation fails.
*/
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()) {
// Check if the bookmark should be discarded
isDiscarded(bm);
// Log the bookmark and get its sequence
seq = _log(bm);
if (seq != 0) {
_ring.discard(seq); // Discard the logged sequence
}
}
// If a parent adapter exists, update it with the logged bookmark list
if (_parent._adapter != null) {
_parent.adapterUpdate(_sub, bookmark);
}
return 0;
}
}
else {
// Handle bookmark ranges
_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 range 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();
}
}
/**
* Log a single bookmark.
*
* @param bm The bookmark to log.
* @return The sequence number associated with the logged bookmark.
* @throws IOException Thrown when an IO operation fails.
*/
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;
}
/**
* Discard a bookmark at the specified index.
*
* @param index The index of the bookmark to discard.
* @throws IOException Thrown when an IO operation fails.
*/
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.
* @param bookmark The bookmark containing the publisher ID and sequence number.
* @return True if the message is discarded or in-flight, false otherwise.
*/
public boolean isDiscarded(BookmarkField bookmark)
{
_lock.lock();
try {
long publisher = bookmark.getPublisherId();
long sequence = bookmark.getSequenceNumber();
if(!_publishers.containsKey(publisher) ||
BookmarkField.unsignedLongLess(_publishers.get(publisher), sequence))
{
_publishers.put(publisher, sequence);
return false;
}
return true; // message is in-flight or discarded
}
finally {
_lock.unlock();
}
}
/**
* Gets the range of bookmarks stored in this bookmark store.
*
* @return A copy of the bookmark range.
*/
public BookmarkRangeField getRange() {
_lock.lock();
try { return _range.copy(); }
finally {
_lock.unlock();
}
}
/**
* Get the most recent bookmark.
*
* @return A copy of the most recent bookmark.
*/
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_);
}
}
public Field getMostRecentList(boolean useList)
{
_lock.lock();
try {
boolean rangeIsValid = _range.isValid();
BookmarkField lastDiscarded = (BookmarkField)_ring.getLastDiscarded();
boolean useLastDiscarded = (lastDiscarded != null &&
!lastDiscarded.isNull());
// Check if we haven't discarded the first message yet
if (useLastDiscarded && lastDiscarded.equals(EPOCH_FIELD)) {
if (rangeIsValid) {
// Return the unmodified range
return _range;
}
else if (_recoveryTimestamp != null) {
BookmarkField ret = new BookmarkField();
ret.setValue(_recoveryTimestamp, _encoder);
return ret;
}
// Return EPOCH
return lastDiscarded;
}
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();
}
}
/**
* Set the last persisted bookmark.
*
* @param bookmark The bookmark to set as the last persisted.
* @throws IOException If an I/O error occurs.
*/
public void setLastPersisted(BookmarkField bookmark) throws IOException
{
_lock.lock();
try {
// If the bookmark is null, empty, equals the last persisted, or is a range,
// return.
if (bookmark == null || bookmark.isNull()
|| bookmark.equals(_lastPersisted)
|| bookmark.isRange()) {
return;
}
if (bookmark.isTimestamp())
{
_recoveryTimestamp = bookmark.toString();
if (_parent._adapter != null) {
_parent.adapterUpdate(_sub,
(BookmarkField) getMostRecentList(false));
}
return;
}
long publisherId = bookmark.getPublisherId();
if (_lastPersisted != null &&
publisherId == _lastPersisted.getPublisherId() &&
BookmarkField.unsignedLongLessEqual(bookmark.getSequenceNumber(),
_lastPersisted.getSequenceNumber()))
{
return;
}
BookmarkField lastPersisted = _lastPersisted;
_lastPersisted = bookmark.copy();
_recoveryTimestamp = null;
if ((lastPersisted == null || lastPersisted.equals(EPOCH_FIELD))
&& _ring.isEmpty()
&& _ring.getLastDiscarded().equals(EPOCH_FIELD)) {
discard(_log(bookmark));
}
else if (_parent._adapter != null) {
_parent.adapterUpdate(_sub,
(BookmarkField) getMostRecentList(false));
}
if (lastPersisted != null) lastPersisted.reset();
}
finally {
_lock.unlock();
}
}
/**
* Get the oldest bookmark sequence number.
*
* @return The sequence number of the oldest bookmark.
*/
public long getOldestBookmarkSeq()
{
_lock.lock();
try {
return _ring.getStartIndex();
}
finally {
_lock.unlock();
}
}
/**
* Set a resize handler for the bookmark store.
*
* @param handler The resize handler to set.
* @param store The bookmark store to associate with the handler.
*/
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;
/**
* Initializes a new instance of the MemoryBookmarkStore class with default settings.
*/
public MemoryBookmarkStore()
{
this(1);
}
/**
* Constructor for MemoryBookmarkStore with a target number of subscriptions.
*
* @param targetNumberOfSubscriptions The target number of subscriptions to
* pre-initialize.
*/
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 adaptor.
*
* @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_;
}
/**
* Log a message and update the bookmark sequence number.
*
* @param message The message to log.
* @return The updated sequence number.
* @throws AMPSException If an error occurs during the log operation.
*/
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);
}
}
/**
* Discard a message based on the subscription ID and bookmark sequence number.
*
* @param subId The subscription ID.
* @param bookmarkSeqNo The bookmark sequence number to discard.
* @throws AMPSException If an error occurs during the discard operation.
*/
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);
}
}
/**
* Discard a message based on the provided message.
*
* @param message The message to discard.
* @throws AMPSException If an error occurs during the discard operation.
*/
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);
}
}
/**
* Get the most recent bookmark for a specific subscription.
*
* @param subId The subscription ID.
* @return The most recent bookmark.
* @throws AMPSException If an error occurs during the operation.
*/
public Field getMostRecent(Field subId) throws AMPSException
{
return getMostRecent(subId, true);
}
/**
* Get the most recent bookmark for a specific subscription.
*
* @param subId The subscription ID.
* @param useList Indicates whether to use the bookmark list.
* @return The most recent bookmark.
* @throws AMPSException If an error occurs during the operation.
*/
public Field getMostRecent(Field subId, boolean useList) throws AMPSException
{
return find(subId).getMostRecentList(useList).copy();
}
/**
* Check if a message is discarded based on its bookmark.
*
* @param message The message to check.
* @return True if the message is discarded, false otherwise.
* @throws AMPSException If an error occurs during the operation.
*/
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);
}
}
/**
* Persist a bookmark for a subscription.
*
* @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.
*/
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 records of
* messages received and discarded.
*
* @throws AMPSException If an error occurs during the purge operation.
*/
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();
}
}
/**
* Remove entries for a specific subscription ID, clearing its records of
* messages received and discarded.
*
* @param subId_ The subscription ID to purge.
* @throws AMPSException If an error occurs during the purge operation.
*/
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();
}
}
/**
* Get the oldest bookmark sequence number for a specific subscription.
*
* @param subId The subscription ID.
* @return The oldest bookmark sequence number.
* @throws AMPSException If an error occurs during the operation.
*/
public long getOldestBookmarkSeq(Field subId) throws AMPSException
{
_lock.lock();
try {
long retVal = 0;
retVal = find(subId).getOldestBookmarkSeq();
return retVal;
}
finally {
_lock.unlock();
}
}
/**
* Set the resize handler for this bookmark store and all associated
* subscriptions.
*
* @param handler The BookmarkStoreResizeHandler to set.
*/
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);
}
}
/**
* Set the version of the AMPS server.
*
* @param version The server version to set.
*/
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 internally 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);
}
}
}