com.crankuptheamps.client.MemoryBookmarkStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amps-client Show documentation
Show all versions of amps-client Show documentation
AMPS Java client by 60East Technologies, Inc.
///////////////////////////////////////////////////////////////////////////
//
// 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);
}
}
}