com.crankuptheamps.client.MemorySubscriptionManager 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.exception.DisconnectedException;
import com.crankuptheamps.client.exception.TimedOutException;
import com.crankuptheamps.client.fields.Field;
import com.crankuptheamps.client.fields.StringField;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
/**
* An in-memory implementation of SubscriptionManager. Used by HAClient.
*/
public class MemorySubscriptionManager implements SubscriptionManager
{
static long DefaultResubscriptionTimeout = 0;
long _resubscriptionTimeout = 0;
static class SubscriptionInfo
{
public MessageHandler _handler;
Field _recent;
Field _subId;
String _options;
boolean _hasBookmark;
String _filter;
String _orderBy;
String _queryId;
String _sowKeys;
String _topic;
long _topN;
int _command;
int _ackType;
int _batchSize;
MemorySubscriptionManager _parent;
/**
* Constructs a SubscriptionInfo for the given handler and subscribe message.
* @param messageHandler The message handler for this subscription.
* @param message The subscribe message for this subscription.
*/
SubscriptionInfo(MessageHandler messageHandler, Message message,
MemorySubscriptionManager parent)
{
_handler = messageHandler;
saveMessage(message);
byte[] recentBuf = new byte[512];
_recent = new Field(recentBuf, 0, 0);
_parent = parent;
}
/**
* Places this subscription on the given Client.
* @param client The client to place the subscription on.
* @throws AMPSException An error occurred while placing the subscription.
*/
void resubscribe(Client client) throws AMPSException
{
// Don't set replace
if (_options != null)
{
_options = _options.replaceAll("replace,?","");
if(_options.length() < 2) _options = null;
}
Message message = client.allocateMessage();
if(_hasBookmark)
{
// Always resubscribe to the most recent.
if (isPaused() && _recent.length > 0)
{
message.setBookmark(_recent.buffer, _recent.position,
_recent.length);
}
else
{
Field recent = client.getBookmarkStore()
.getMostRecent(_subId);
message.setBookmark(recent.buffer, recent.position,
recent.length);
}
}
apply(message);
client.send(_handler, message, _parent._resubscriptionTimeout);
_recent.length = 0;
}
/**
* Remove the given subscription ID from the list of sub ids on self.
* @param subId_ The subscription ID to remove
* @return true if the subscription ID is removed, false otherwise.
*/
boolean removeSubId(Field subId_)
{
int subIdStart = subId_.position;
int subIdLen = subId_.length;
while (subId_.buffer[subIdStart + subIdLen - 1] == (byte)',')
--subIdLen;
while (subId_.buffer[subIdStart] == (byte)',') {
++subIdStart; --subIdLen;
}
if (subIdLen > _subId.length) return _subId.isNull();
boolean match = true;
int matchStart = 0;
int matchCount = 0;
for (int i=0; i<_subId.length; ++i)
{
if (_subId.buffer[_subId.position+i] == (byte)',') {
if (matchCount == subIdLen) {
break;
}
matchStart = i+1;
matchCount = 0;
match = true;
continue;
}
else if (match) {
if (matchCount < subIdLen &&
_subId.buffer[_subId.position+i] ==
subId_.buffer[subIdStart+matchCount]) {
++matchCount;
} else {
matchCount = 0;
match = false;
}
}
}
if (match && matchCount == subIdLen) {
int len = _subId.length - matchCount;
if (len > 1) // More than just , left
{
byte[] buffer = new byte[len];
int offset = 0;
int newLen = 0;
for (int i=0; i+offset<_subId.length; ++i)
{
if (i == matchStart) {
offset=matchCount;
if (i+offset >= _subId.length) break;
if (_subId.buffer[_subId.position+offset+i] == (byte)',')
{
if (++offset+i >= _subId.length) break;
}
}
buffer[i] = _subId.buffer[_subId.position+offset+i];
++newLen;
}
if (newLen > 0) {
_subId = new Field(buffer, 0, newLen);
return false;
}
}
_subId.reset();
return true;
}
return _subId.isNull();
}
/**
* Marks this subscription as paused.
*/
void pause()
{
if (isPaused()) return;
_options = _options==null ?
"pause" :
"pause," + _options;
}
/**
* Create the most recent bookmark string for this subscription.
* @param client The Client to check.
* @return The most recent bookmark string, used when resubscribing.
* @throws AMPSException An error occurred when determining the most-recent string.
*/
Field getMostRecent(Client client) throws AMPSException
{
if (!_recent.isNull()) return _recent;
Field subId = new Field();
int start = 0;
while (_subId.buffer[_subId.position + start] == (byte)',')
++start;
int end = start + 1;
while (end < _subId.length) {
if (_subId.buffer[_subId.position+end] == (byte)',') {
subId.set(_subId.buffer, _subId.position+start, end-start);
Field mostRecent = client.getBookmarkStore().getMostRecent(subId);
if (_recent.length > 0) {
if (_recent.length == _recent.buffer.length) {
byte[] newRecent = new byte[_recent.buffer.length+512];
System.arraycopy(_recent.buffer, 0,
newRecent, 0, _recent.length);
_recent.set(newRecent, 0, _recent.length);
}
_recent.buffer[_recent.length++] = (byte)',';
}
if (_recent.length + mostRecent.length > _recent.buffer.length) {
int newSize = _recent.buffer.length + 512;
while (_recent.length + mostRecent.length > newSize)
newSize += 512;
byte[] newRecent = new byte[newSize];
System.arraycopy(_recent.buffer, 0,
newRecent, 0, _recent.length);
_recent.set(newRecent, 0, _recent.length);
}
System.arraycopy(mostRecent.buffer, mostRecent.position,
_recent.buffer, _recent.length,
mostRecent.length);
_recent.length += mostRecent.length;
while (end < _subId.length &&
_subId.buffer[_subId.position + end] == (byte)',')
++end;
start = end;
}
++end;
}
return _recent;
}
/**
* Records the applicable fields from a Message, for use when resubscribing.
* @param message The subscribe message to save fields from.
*/
final void saveMessage(Message message)
{
_subId = message.getSubIdRaw().copy();
_options = message.getOptions();
_hasBookmark = !message.isBookmarkNull();
_filter = message.getFilter();
_orderBy = message.getOrderBy();
_queryId = message.getQueryId();
_sowKeys = message.getSowKeys();
_topic = message.getTopic();
_ackType = message.getAckTypeOutgoing();
_command = message.getCommand();
if(!message.isBatchSizeNull())
{
_batchSize = message.getBatchSize();
}
else
{
_batchSize = 1;
}
if(!message.isTopNNull())
{
_topN = message.getTopN();
}
else
{
_topN = -1;
}
}
/**
* Applies the saved fields from self to a new message, when resubscribing.
* @param message The new message to set fields on.
*/
void apply(Message message)
{
message.setSubId(_subId.buffer,0,_subId.length);
message.setOptions(_options);
message.setFilter(_filter);
message.setOrderBy(_orderBy);
message.setQueryId(_queryId);
message.setSowKeys(_sowKeys);
message.setTopic(_topic);
message.setAckType(_ackType);
message.setCommand(_command);
message.setBatchSize(_batchSize);
if(_topN != -1)
{
message.setTopN(_topN);
}
}
/**
* Returns true if this subscription is in a paused state.
* @return Returns true if this subscription is in a paused state.
*/
boolean isPaused()
{
return _options != null && _options.contains("pause");
}
}
private final Lock _lock = new ReentrantLock();
private final Condition _resubscriptionStatus = _lock.newCondition();
private HashMap _active = new HashMap();
private HashMap _resumed = new HashMap();
private ArrayList _resumeList = new ArrayList();
volatile boolean _resubscribing = false;
public MemorySubscriptionManager()
{
_resubscriptionTimeout = DefaultResubscriptionTimeout;
}
public void subscribe(MessageHandler messageHandler, Message message)
{
_lock.lock();
try {
while (_resubscribing) {
// Keep waiting, even if we're interrupted
try { _resubscriptionStatus.await(100, TimeUnit.MILLISECONDS); }
catch (InterruptedException ex) { }
}
if (!message.isSubIdNull())
{
String subId = message.getSubId();
if (!message.isOptionsNull())
{
String options = message.getOptions();
if (options.contains("resume"))
{
SubscriptionInfo subInfo = new SubscriptionInfo(messageHandler,
message, this);
boolean saved = false;
for (String sid : subId.split(","))
{
Field sidField = new Field(sid);
if (!_resumed.containsKey(sidField))
{
_resumed.put(sidField, subInfo);
saved = true;
}
}
if (saved) _resumeList.add(subInfo);
return;
} else if (options.contains("pause"))
{
for (String sid : subId.split(","))
{
MessageHandler handler = messageHandler;
Field sidField = new Field(sid);
SubscriptionInfo existingSubInfo = _resumed.get(sidField);
if (existingSubInfo != null)
{
if (existingSubInfo.removeSubId(sidField))
_resumeList.remove(existingSubInfo);
_resumed.remove(sidField);
}
existingSubInfo = _active.get(sidField);
if (existingSubInfo != null)
{
if (!options.contains("replace"))
{
existingSubInfo.pause();
continue;
} else {
handler = existingSubInfo._handler;
}
}
Message m = message.copy();
m.setSubId(sid);
SubscriptionInfo subInfo =
new SubscriptionInfo(handler, m, this);
_active.put(subInfo._subId, subInfo);
}
return;
}
}
MessageHandler handler = messageHandler;
Field sidField = new Field(subId);
SubscriptionInfo existingSubInfo = _active.get(sidField);
if (existingSubInfo != null)
{
handler = existingSubInfo._handler;
}
SubscriptionInfo subInfo = new SubscriptionInfo(handler,
message, this);
_active.put(subInfo._subId, subInfo);
}
}
finally { _lock.unlock(); }
}
public void unsubscribe(CommandId subId)
{
_lock.lock();
try
{
while (_resubscribing) {
// Keep waiting, even if we're interrupted
try { _resubscriptionStatus.await(100, TimeUnit.MILLISECONDS); }
catch (InterruptedException ex) { }
}
StringField f = new StringField();
f.setValue(subId);
_active.remove(f);
SubscriptionInfo existingSubInfo = _resumed.get(f);
if (existingSubInfo != null)
{
if (existingSubInfo.removeSubId(f))
_resumeList.remove(existingSubInfo);
_resumed.remove(f);
}
}
finally { _lock.unlock(); }
}
public void clear()
{
_lock.lock();
try
{
while (_resubscribing) {
// Keep waiting, even if we're interrupted
try { _resubscriptionStatus.await(100, TimeUnit.MILLISECONDS); }
catch (InterruptedException ex) { }
}
_active.clear(); _resumed.clear(); _resumeList.clear();
}
finally { _lock.unlock(); }
}
public void resubscribe(Client client) throws AMPSException
{
ArrayList subs = null;
try
{
_lock.lock();
try
{
subs = new ArrayList(_active.size());
_resubscribing = true;
for (SubscriptionInfo si : _active.values())
{
subs.add(si);
}
for (SubscriptionInfo si : _resumeList)
{
subs.add(si);
}
}
finally
{
_lock.unlock();
}
for (SubscriptionInfo si : subs)
{
si.resubscribe(client);
}
}
finally
{
_resubscribeDone();
}
}
private void _resubscribeDone()
{
_lock.lock();
try
{
_resubscribing = false;
_resubscriptionStatus.signalAll();
}
finally
{
_lock.unlock();
}
}
/**
* Sets the Default Resubscription Timeout for all instances of
* this class in milliseconds.
* @param timeout The default timeout for resubscription calls.
*/
public static void setDefaultResubscriptionTimeout(long timeout)
{
DefaultResubscriptionTimeout = timeout;
}
/**
* Gets the Default Resubscription Timeout for all instances of
* this class in milliseconds.
* @return The default timeout for resubscription calls.
*/
public static long getDefaultResubscriptionTimeout()
{
return DefaultResubscriptionTimeout;
}
/**
* Sets the Resubscription timeout in milliseconds.
* @param timeout The timeout for resubscription calls.
*/
public void setResubscriptionTimeout(long timeout)
{
_resubscriptionTimeout = timeout;
}
/**
* Gets the Resubscription timeout in milliseconds .
* @return timeout The timeout for resubscription calls.
*/
public long getResubscriptionTimeout()
{
return _resubscriptionTimeout;
}
}