com.crankuptheamps.client.BookmarkRingBuffer 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-2021 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.fields.BookmarkField;
import com.crankuptheamps.client.fields.Field;
import java.nio.charset.Charset;
import java.util.ArrayList;
/**
* A ring buffer of bookmarks and activation status
* Used by all of the bookmark stores to track state of bookmarks
* we need to hold on to, either because they're active or because
* they're after an active one.
*/
public class BookmarkRingBuffer
{
/**
* Interface that represents all of the information we need about each Bookmark.
*/
public static class Entry
{
BookmarkField _bookmark;
long _index = 0;
boolean _active = false;
public Entry()
{
_bookmark = new BookmarkField();
}
/**
* Sets the bookmark field of the Entry.
* @param f A Field object which contains the fields of the Bookmark.
* @return This instance.
*/
public Entry setBookmark(Field f)
{
_bookmark.copyFrom(f);
return this;
}
/**
* Gets the bookmark field of the Entry.
* @return The bookmark field of the Entry.
*/
public BookmarkField getBookmark()
{
return _bookmark;
}
/**
* Sets the index of the Entry in the Entry array.
* @param index The index to be set.
* @return This instance.
*/
public Entry setIndex(long index)
{
_index = index;
return this;
}
/**
* Gets the index of the Entry in the Entry array.
* @return The index of the Entry.
*/
public long getIndex()
{
return _index;
}
/**
* Gets the active value of the Entry.
* @return The active value of the Entry.
*/
public boolean isActive()
{
return _active;
}
/**
* Sets the active value of the Entry.
* @param active the new value of active
* @return Returns this instance so that various operations can be chained together.
*/
public Entry setActive(boolean active)
{
_active = active;
return this;
}
/**
* WARNING: @deprecated. Do Not Use.
* Old Doc: Gets the persisted value of the Entry.
* @return The persisted value of the Entry.
*/
@Deprecated
public boolean isPersisted()
{
return true;
}
/**
* WARNING: @deprecated. Do Not Use.
* Old Doc: Sets the persisted value of the Entry.
* @param persisted The persisted value of the Entry.
* @return The persisted value of the Entry.
*/
@Deprecated
public Entry setPersisted(boolean persisted)
{
return this;
}
/**
* Converts the Entry to a string.
* @return The converted Entry.
*/
public String toString()
{
return "[ " + _bookmark + ", active=" + _active
+ ", index=" + _index + " ]";
}
}
static final int INITIAL_ENTRY_COUNT = 1000;
public final static int UNSET_INDEX = -1;
Entry _entries[] = new Entry[INITIAL_ENTRY_COUNT];
int _start = 1, _end = 1;
long _index = 1, _indexOfStart = 0;
BookmarkField _lastDiscarded = new BookmarkField();
boolean _empty = true;
BookmarkStoreResizeHandler _resizeHandler;
BookmarkStore _store;
Field _subId;
volatile boolean _canResize = false;
/**
* Initializes the underlying array, and sets
* the "last discarded" value to something reasonable.
*/
public BookmarkRingBuffer()
{
_lastDiscarded.set("0".getBytes(Charset.defaultCharset()));
for(int i = 0; i < _entries.length; ++i)
{
_entries[i] = new Entry();
}
}
/**
* Resets this bookmark ring buffer for reuse by clearing out its state.
*/
public void reset()
{
for(int i = 0; i < _entries.length; ++i)
{
_entries[i].setActive(false).setIndex(0)
.getBookmark().reset();
}
_start = 1;
_end = 1;
_index = 1;
_indexOfStart = 0;
_lastDiscarded.set("0".getBytes(Charset.defaultCharset()));
_empty = true;
_resizeHandler = null;
_store = null;
_subId = null;
_canResize = false;
}
/**
* Size of underlying array.
* @return Current capacity of self.
*/
public int capacity()
{
return _entries.length;
}
/**
* Retrieves an Entry given an index.
* @param index An index returned by .getIndex(), getStartIndex(),
* getEndIndex(), log, etc.
* @return The entry at that index, or null if not found.
*/
public Entry getByIndex(long index)
{
// The index must be higher than the lowest we have stored, which is
// _start + _indexOfStart, and less than _index
int offsetFromStart = 0;
if (index < _indexOfStart + _start || index >= _index)
return null;
offsetFromStart = (int)(index-_indexOfStart) % _entries.length;
return _entries[offsetFromStart];
}
/**
* Returns the "last discarded" bookmark. This may be a
* newer bookmark than the one you last passed to discard(),
* if you're discarding out of order.
*
* @return The last Bookmark discarded.
*/
public BookmarkField getLastDiscarded()
{
return _lastDiscarded;
}
/**
* Returns if the buffer is currently empty.
* @return True if self is empty, false otherwise.
*/
public boolean isEmpty()
{
return _empty;
}
/**
* Returns the index value associated with the first valid
* Entry in self.
* @return Index of first valid Entry.
*/
public long getStartIndex()
{
if (_empty)
return UNSET_INDEX;
return _indexOfStart + _start;
}
/**
* Returns the index value one greater than the last valid
* Entry in self.
* @return Index 1 greater than last valid entry.
*/
public long getEndIndex()
{
if (_empty) return UNSET_INDEX;
return _index;
}
/**
* Logs the bookmark by allocating an Entry, setting
* the Entry to active, copying the bookmark value
* to that entry, and returning the index
* of that entry.
* @param bookmark The Bookmark to log.
* @return The index of the new Entry.
*/
public long log(BookmarkField bookmark)
{
if (_end == _start && !_empty)
{
if (!_canResize) return 0;
resize();
}
_empty = false;
int writeIndex = _end++;
_entries[writeIndex].setBookmark(bookmark).setActive(true).setIndex(_index);
if(_end == _entries.length) _end = 0;
return _index++;
}
/**
* Discards an entry by index. If the discard is completed,
* lastDiscarded will change, otherwise the discard is cached
* in the entry, and the getLastDiscarded() value is unchanged.
*
* @param index The index of the Entry to be discarded.
* @return True if this caused lastDiscarded to change.
*/
public boolean discard(long index)
{
boolean retVal = false;
if (index < (_indexOfStart + _start) || index >= _index)
{
return retVal;
}
int offsetFromStart = (int)(index - _indexOfStart);
int indexInArray = offsetFromStart % _entries.length;
_entries[indexInArray].setActive(false);
if(_start == indexInArray)
{
Entry lastDiscardedEntry = null;
while(!_entries[_start].isActive()
&& !_entries[_start].getBookmark().isNull()
&& (_start != _end || !_empty))
{
if (lastDiscardedEntry != null)
{
lastDiscardedEntry.getBookmark().reset();
}
lastDiscardedEntry = _entries[_start];
if(++_start == _entries.length)
{
_start = 0;
_indexOfStart += _entries.length;
}
_empty = (_start == _end);
}
if(lastDiscardedEntry != null)
{
BookmarkField bookmark = lastDiscardedEntry.getBookmark();
if (bookmark != null && !bookmark.isNull()) {
_lastDiscarded.copyFrom(bookmark);
bookmark.reset();
}
retVal = true;
}
}
return retVal;
}
/**
* Searches valid Entrys for the given bookmark.
* @param field The bookmark that you are searching for.
* @return The Entry containing the given bookmark, or null if the Entry was not found.
*/
public Entry find(BookmarkField field)
{
if (_empty) return null;
long index = _indexOfStart + _start;
int i = _start;
do
{
if(!_entries[i].getBookmark().isNull() &&
_entries[i].getBookmark().equals(field))
{
// hooray! we found it.
return _entries[i].setIndex(index);
}
++index;
i = (i+1)%_entries.length;
} while(i!=_end);
return null;
}
/**
* WARNING: @deprecated. Do Not Use.
* Old Doc: Mark all records up to the and including the provided bookmark as safe for discard and discard all appropriate
* bookmarks. Applies to server versions 3.8x.
* @param bookmark The latest bookmark that can be safely disposed.
*/
@Deprecated
public void persisted(BookmarkField bookmark)
{
return;
}
/**
* WARNING: @deprecated. Do Not Use.
* Old Doc: Called internally by the Client to record the last persisted message in the transaction log of the connected AMPS server.
* @param bookmark The bookmark containing the message.
*/
@Deprecated
public void persisted(long bookmark)
{
return;
}
/**
* Called to see if resize can proceed.
*/
public void checkResize()
{
int oldLength = _entries.length;
_canResize = (_resizeHandler == null ||
_resizeHandler.invoke(_store, _subId,
(int)(oldLength*1.5)));
}
/**
* Called when self is full, assumes all entries are valid.
*/
private void resize()
{
_canResize = false;
int oldLength = _entries.length;
// allocate a new array (1.5x resize factor)
// copy over the data.
Entry[] newEntries = new Entry[(int) (oldLength * 1.5)];
int start = _start;
System.arraycopy(_entries, start, newEntries, 0, oldLength - start);
if(start > 0)
{
System.arraycopy(_entries, 0, newEntries, oldLength - start, start);
}
_indexOfStart += _start;
_start = 0;
_end = oldLength;
for(int i = oldLength; i< newEntries.length; ++i)
{
newEntries[i] = new Entry();
}
_entries = newEntries;
}
/**
* Call this when you want to set a resize handler that is invoked when a store needs a resize.
* @param handler The handler to invoke for the resize.
* @param store The store to be resized.
*/
public void setResizeHandler(BookmarkStoreResizeHandler handler, BookmarkStore store)
{
_resizeHandler = handler;
_store = store;
}
/**
* Sets the Subscription Identifier for the bookmark.
* @param sub The Field object to copy as the new SubId.
*/
public void setSubId(Field sub)
{
_subId = sub.copy();
}
public void getRecoveryEntries(ArrayList entryList_)
{
for (int idx = _start; idx != _end; idx = (++idx%_entries.length))
{
entryList_.add(_entries[idx]);
}
}
/**
* WARNING: @deprecated. Do Not Use. The notion of a separate "recovered bookmark"
* section within the ring buffer and the ability to re-log/re-order those entries
* into the main/active ring buffer section is an obsolete concept that hasn't been
* needed since the AMPS server stopped providing persisted acks for every message
* delivered on a replay (i.e. not needed since AMPS 4.0). The bookmark ring buffer
* has been simplified to remove this concept since a bug was discovered in it (AC-953).
*
* Old Docs:
*
* If there are presently no recovery entries in the ring buffer, this will convert all of the entries in the
* buffer into recovery entries.
* @return The index of the first recovery Entry.
*/
@Deprecated
public long setRecovery()
{
return _start + _indexOfStart;
}
/**
* WARNING: @deprecated. Do Not Use. The notion of a separate "recovered bookmark"
* section within the ring buffer and the ability to re-log/re-order those entries
* into the main/active ring buffer section is an obsolete concept that hasn't been
* needed since the AMPS server stopped providing persisted acks for every message
* delivered on a replay (i.e. not needed since AMPS 4.0). The bookmark ring buffer
* has been simplified to remove this concept since a bug was discovered in it (AC-953).
*
* Old Docs:
*
* Gets all of the recovery entries in a newly allocated ArrayList. If there are no recovery entries,
* this method returns null.
* @return The ArrayList of entries or null if none.
* @see #setRecovery()
*/
@Deprecated
public ArrayList getRecoveryEntries()
{
return null;
}
/**
* WARNING: @deprecated. Do Not Use. The notion of a separate "recovered bookmark"
* section within the ring buffer and the ability to re-log/re-order those entries
* into the main/active ring buffer section is an obsolete concept that hasn't been
* needed since the AMPS server stopped providing persisted acks for every message
* delivered on a replay (i.e. not needed since AMPS 4.0). The bookmark ring buffer
* has been simplified to remove this concept since a bug was discovered in it (AC-953).
*
* Old Docs:
*
* Re-logs the bookmark by copying an Entry to a new location
* and returning the index of that new location.
* @param oldIndex The index that the Entry was previously at.
* @param bookmark The index to move.
* @return The index of the new Entry.
*/
@Deprecated
public long relog(long oldIndex, BookmarkField bookmark)
{
return oldIndex;
}
/**
* Returns a debugging string representing ring buffer state.
*/
public String toString()
{
StringBuilder sb = new StringBuilder(300 + (int) (getEndIndex() - getStartIndex()) * 100);
sb.append(String.format(
"_entries.length = %d%n"
+ "_start = %d%n"
+ "_end = %d%n"
+ "_index = %s%n"
+ "_lastDiscarded = %s%n"
+ "_subId = %s%n"
+ "_canResize = %s%n"
+ "_resizeHandler = %s%n"
+ "_store = %s%n"
+ "%n"
+ "Entries:%n%n",
_entries.length, _start, _end, _index,
_lastDiscarded, _subId,
_canResize, _resizeHandler, _store
));
for (int idx = _start; idx != _end; idx = (++idx%_entries.length))
{
sb.append(String.format("%5d: %s%n", idx, _entries[idx]));
}
return sb.toString();
}
}