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-2020 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.beans.ExceptionListener;
import java.lang.Iterable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.crankuptheamps.client.exception.AMPSException;
import com.crankuptheamps.client.exception.DisconnectedException;
import com.crankuptheamps.client.exception.StoreException;
import com.crankuptheamps.client.fields.Field;
import com.crankuptheamps.client.fields.BookmarkField;
/**
* An implementation of the recovery point adapter interface that uses an
* AMPS SOW topic as an external store to persist recovery point state for
* bookmark replay subscriptions.
*/
public class SOWRecoveryPointAdapter implements RecoveryPointAdapter
{
protected String _trackedName;
protected String _topic;
protected String _clientNameField;
protected String _subIdField;
protected String _bookmarkField;
protected Client _client = null;
protected ExceptionListener _exceptionListener = null;
protected Field _serializeField = new Field();
protected long _timeoutMillis = 0;
protected boolean _closeClient = false;
protected boolean _useTimestamp = false;
protected boolean _throwNotListen = false;
protected Command _cmd = new Command(Message.Command.Publish);
protected MessageStream _stream = null;
protected boolean _recovering = true;
protected static final int JSON_START = 11; // {, 7", 2:, 1,
protected static final int JSON_END = 8; // }, 5", 1:, 1,
protected static final int JSON_LEN = JSON_START + JSON_END;
protected static final int SUBID_LEN = 64; // rough typical max
// Allow for a range with 2 ',' 1 ':' and begin/end markers
protected static final int JSON_INIT_LEN = JSON_LEN + SUBID_LEN + 5
+ (4*BookmarkField.MAX_BOOKMARK_LENGTH);
protected String _initStr = null;
protected int _serializeStart = 0;
protected RecoveryPoint _current = new FixedRecoveryPoint();
protected Field EPOCH_FIELD = new Field("0");
/**
* Regular expression pattern to pull the bookmark string from subscription
* state saved in the AMPS SOW.
*/
protected Pattern _bookmarkPattern;
/**
* Regular expression pattern to pull the subscription id from subscription
* state saved in the AMPS SOW.
*/
protected Pattern _subIdPattern;
/**
* A concurrent hash map of subscription id's (known to this store)
* mapped to the subscription's most-recent-for-recovery value
* that was last persisted to the SOW. By caching the last
* value, we can prevent unneeded writes to the SOW.
*/
protected final ConcurrentHashMap _lastValue =
new ConcurrentHashMap();
/**
* A recovery point adapter implementation that uses an AMPS SOW topic
* as an external store to persist recovery point state for bookmark
* replay subscriptions. See full constructor for details on all options.
*
* @see #SOWRecoveryPointAdapter(Client, String, boolean, boolean, boolean,
* long, String, String, String, String)
* @param trackedClientName The unique name of the subscriber AMPS client
* we're tracking bookmark recovery state for. If your client names
* change with each run, this should be a unique session name that
* is stable across application restarts. For example, such a name
* could be formed by concatenating an application name with a
* logical functional area (e.g. "MyApp_OrderReplay"). This name will
* be used as a key in the SOW for tracking the subscriber client's
* bookmark state. For guidance on choosing stable unique AMPS client
* names or session names for use with this adapter, please see our
* support FAQ article entitled Unique Client Naming and AMPS.
* @param closeClient Indicates whether this adapter instance should close
* its internal AMPS client when this adapter is closed. Defaults to
* true. If this flag is true, this adapter considers the internal
* client passed to it during construction as being owned by this
* adapter, such that its life-cycle should end when this adapter is
* closed. If false, this adapter will consider it the responsibility
* of the caller to close the internal client after this adapter is
* closed, allowing that client to be shared, possibly among multiple
* adapters (keeping in mind potential performance impacts of
* multiple threads publishing using the same AMPS client instance).
* @param useTimestamp Indicates if the last updated timestamp of each
* entries should be included in the RecoveryPoint. This is similar
* to useLastModifiedTime in {@link LoggedBookmarkStore}.
* @param throwExceptions Indicates whether exceptions should be thrown
* to the caller (usually the bookmark store), or should instead be
* delivered to this adapter's registered exception listener
* callback. This flag lets the user decide their error handling
* strategy for recovery state write failures. The default is false,
* meaning the exception is absorbed, and sent to the exception
* listener (if any). A value of true means errors in the update()
* method throw exceptions. NOTE: Since this adapter implementation
* publishes to an AMPS SOW and most AMPS publishing failures have
* asynchronous notification, users should consider registering a
* {@link FailedWriteHandler} and publish store on the internal
* client passed above.
*/
public SOWRecoveryPointAdapter(Client client, String trackedClientName,
boolean closeClient, boolean useTimestamp,
boolean throwExceptions) {
this(client, trackedClientName, closeClient, useTimestamp,
throwExceptions, 0L, "/ADMIN/bookmark_store", "clientName",
"subId", "bookmark");
}
/**
* A recovery point adapter implementation that uses an AMPS SOW topic
* as an external store to persist recovery point state for bookmark
* replay subscriptions.
*
* @param client The internal AMPS client instance used to write bookmark
* state to the AMPS SOW. This MUST NOT be the client whose
* registered bookmark store is associated with this recovery point
* adapter. In other words, it must not be the AMPS client used to
* place the bookmark replay subscriptions whose recovery state is
* being persisted by this adapter.
* @param trackedClientName The unique name of the subscriber AMPS client
* we're tracking bookmark recovery state for. If your client names
* change with each run, this should be a unique session name that
* is stable across application restarts. For example, such a name
* could be formed by concatenating an application name with a
* logical functional area (e.g. "MyApp_OrderReplay"). This name will
* be used as a key in the SOW for tracking the subscriber client's
* bookmark state. For guidance on choosing stable unique AMPS client
* names or session names for use with this adapter, please see our
* support FAQ article entitled Unique Client Naming and AMPS.
* @param closeClient Indicates whether this adapter instance should close
* its internal AMPS client when this adapter is closed. Defaults to
* true. If this flag is true, this adapter considers the internal
* client passed to it during construction as being owned by this
* adapter, such that its life-cycle should end when this adapter is
* closed. If false, this adapter will consider it the responsibility
* of the caller to close the internal client after this adapter is
* closed, allowing that client to be shared, possibly among multiple
* adapters (keeping in mind potential performance impacts of
* multiple threads publishing using the same AMPS client instance).
* @param useTimestamp Indicates if the last updated timestamp of each
* entries should be included in the RecoveryPoint. This is similar
* to useLastModifiedTime in {@link LoggedBookmarkStore}.
* @param throwExceptions Indicates whether exceptions should be thrown
* to the caller (usually the bookmark store), or should instead be
* delivered to this adapter's registered exception listener
* callback. This flag lets the user decide their error handling
* strategy for recovery state write failures. The default is false,
* meaning the exception is absorbed, and sent to the exception
* listener (if any). A value of true means errors in the update()
* method throw exceptions. NOTE: Since this adapter implementation
* publishes to an AMPS SOW and most AMPS publishing failures have
* asynchronous notification, users should consider registering a
* {@link FailedWriteHandler} and publish store on the internal
* client passed above.
* @param timeoutMillis The number of milliseconds to wait for the initial
* sow query to complete and for publish_flush to complete during
* {@link #close()}. The default value is 0, which means no timeout.
* @param topic The AMPS SOW topic configured to persist bookmark recovery
* state for this adapter. Usually defaults to "/ADMIN/bookmark_store",
* but this allows users to customize their usage for non-default SOW
* configs. NOTE: The message type of this topic in the AMPS config
* must be "json" unless overriding the operation of this adapter in
* a subclass.
* @param clientNameField The name of the SOW topic field we use for
* persisting the tracked client name or session name. Usually just
* "clientName", but this allows users to customize their usage for
* non-default SOW configs. NOTE: This field must be one of two key
* fields configured on the SOW topic.
* @param subIdField The name of the SOW topic field we use for persisting
* the subscription id. Usually just "subId", but this allows users
* to customize their usage for non-default SOW configs. NOTE: This
* field must be one of two key fields configured on the SOW topic.
* @param bookmarkField The name of the SOW topic field we use for
* persisting the bookmark. Usually just "bookmark", but this
* allows users to customize their usage for non-default SOW configs.
*/
public SOWRecoveryPointAdapter(Client client, String trackedClientName,
boolean closeClient,
boolean useTimestamp,
boolean throwExceptions,
long timeoutMillis,
String topic,
String clientNameField,
String subIdField,
String bookmarkField) {
_client = client;
_trackedName = trackedClientName;
_timeoutMillis = timeoutMillis;
_closeClient = closeClient;
_useTimestamp = useTimestamp;
_throwNotListen = throwExceptions;
_topic = topic;
_clientNameField = clientNameField;
_subIdField = subIdField;
_bookmarkField = bookmarkField;
_cmd.setTopic(_topic);
_subIdPattern = Pattern.compile("\"" + _subIdField + "\" *: *\"([^\"]+)\"");
_bookmarkPattern = Pattern.compile("\"bookmark\" *: *\"([^\"]*)\"");
}
/**
* Sets the {@link java.beans.ExceptionListener} instance used for
* communicating absorbed exceptions.
*
* @param exceptionListener The exception listener instance to invoke for
* internal exceptions.
*/
public void setExceptionListener(ExceptionListener exceptionListener) {
this._exceptionListener = exceptionListener;
}
/**
* Sends an update to the underlying SOW.
* @param recoveryPoint The recovery state to persist in the SOW.
* @throws Exception StoreException wrapper of exception thrown while
* clearing the SOW of recovery information.
*/
public void update(RecoveryPoint recoveryPoint) throws Exception {
try {
// Don't write to the SOW if the current value equals
// the prev value we wrote (i.e. there's been no change)
Field subId = recoveryPoint.getSubId();
BookmarkField currValue = recoveryPoint.getBookmark();
BookmarkField prevValue = _lastValue.get(subId);
if (prevValue == null || !currValue.equals(prevValue)) {
if (!serialize(recoveryPoint)) return;
_cmd.setData(_serializeField.buffer, _serializeField.position,
_serializeField.length);
_client.execute(_cmd);
// Update last value.
if (prevValue == null) {
_lastValue.put(subId.copy(), currValue.copy());
} else {
_lastValue.put(subId, currValue.copy());
}
}
} catch (Exception e) {
if (_throwNotListen) {
throw new StoreException("Sow update exception " + e, e);
} else {
if (this._exceptionListener != null) {
this._exceptionListener.exceptionThrown(new StoreException("Sow update exception " + e.toString(), e));
}
}
}
}
/**
* Purge all recovery information stored in the SOW.
* @throws StoreException Wrapper of exception thrown while clearing the
* SOW of recovery information.
*/
public void purge() throws StoreException {
try {
String filter = String.format("/%s = '%s'", _clientNameField, _trackedName);
_client.sowDelete(_topic, filter, _timeoutMillis);
} catch (AMPSException e) {
throw new StoreException("Error purging recovery state from SOW: " + e, e);
}
}
/**
* Purge all recovery information stored in the SOW.
* @param subId The subId of the subscription to remove.
* @throws StoreException Wrapper of exception thrown while clearing the
* SOW of recovery information.
*/
public void purge(Field subId) throws StoreException {
try {
String filter = String.format("/%s = '%s' AND /%s = '%s'",
_clientNameField, _trackedName, _subIdField, subId);
_client.sowDelete(_topic, filter, _timeoutMillis);
} catch (Exception e) {
throw new StoreException("Error purging recovery state from SOW for subId " + subId + ": " + e, e);
}
}
/**
* Close this adapter making sure all updates are at the server and close
* the internal client if set up to do so.
*/
public void close() throws Exception {
if (_client != null) {
try {
_client.publishFlush(_timeoutMillis);
}
catch (DisconnectedException e) { }
catch (Exception e) {
if (_throwNotListen) {
throw new StoreException("SOWRecvoeryPointAdapter error flushing to store during close", e);
}
else if (_exceptionListener != null) {
_exceptionListener.exceptionThrown(new StoreException("SOWRecvoeryPointAdapter error flushing to store during close", e));
}
}
finally {
if (_closeClient) _client.close();
}
}
}
/**
* Set up the MessageStream for recovery.
*/
protected void runQuery() {
try
{
StringBuilder builder = new StringBuilder();
builder.append('/').append(_clientNameField).append("=\"")
.append(_trackedName).append('"');
Command cmd = new Command("sow");
cmd.setTopic(_topic).setTimeout(_timeoutMillis)
.setFilter(builder.toString());
builder.setLength(0);
builder.append("select=[-/,+/").append(_subIdField)
.append(",+/").append(_bookmarkField).append(']');
if (_useTimestamp) {
builder.append(",timestamp");
cmd.setOptions(builder.toString());
}
else {
cmd.setOptions(builder.toString());
}
_stream = _client.execute(cmd);
}
catch (Exception e) {
if (!_throwNotListen && _exceptionListener != null) {
_exceptionListener.exceptionThrown(new StoreException("Failed to execute SOW query for recovery", e));
_stream = null;
}
}
_recovering = false;
}
/**
* Implements {@link Iterator#hasNext}.
*/
public boolean hasNext()
{
if (_recovering && _stream == null) {
runQuery();
}
return _stream != null && _stream.hasNext();
}
/**
* Implements {@link Iterator#next} for a message stream.
*/
public RecoveryPoint next()
{
if (_recovering && _stream == null) {
runQuery();
}
Message m = _stream.next();
if (m == null) {
_stream = null;
return null;
}
int command = m.getCommand();
if (command == Message.Command.GroupBegin) {
return next();
}
if (command != Message.Command.SOW) {
// Group_End, Ack, or something wacky
_stream = null;
return null;
}
try {
if (!deserialize(m)) {
return next();
}
}
catch(Exception e) {
return null;
}
return _current;
}
/**
* Operation not supported. Implements {@link Iterator#remove} to throw UnsupportedOperationException.
*/
public void remove()
{
throw new UnsupportedOperationException();
}
/**
* Implements {@link Iterable#iterator} to return this instance.
*/
public Iterator iterator()
{
return this;
}
/**
* Serializes a {@link RecoveryPoint} to JSON in the internal buffer for
* publishing to a JSON SOW topic. Used by the
* {@link #update(RecoveryPoint)} method.
*
* This method provides an override point where a recovery point can be
* serialized another message type into the buffer.
*
* @param recoveryPoint The recovery point to serialize.
* @return true upon successful serialization
* @throws Exception If an error occurs during serialization.
*/
protected boolean serialize(RecoveryPoint recoveryPoint)
throws Exception {
BookmarkField bookmark = recoveryPoint.getBookmark();
if (bookmark.equals(EPOCH_FIELD)) {
return false;
}
Field subId = recoveryPoint.getSubId();
int endLength = subId.length + _bookmarkField.length()
+ bookmark.length + JSON_END;
int length = _clientNameField.length() + _trackedName.length()
+ _subIdField.length() + JSON_START + endLength;
if (_serializeField.buffer == null
|| _serializeField.buffer.length < length) {
// Need a new buffer
int newLength = length + (128-(length%128));
byte[] buffer = new byte[newLength];
_serializeField.set(buffer, 0, length);
initSerialization();
}
System.arraycopy(subId.buffer, subId.position, _serializeField.buffer,
_serializeStart, subId.length);
int pos = _serializeStart + subId.length;
_serializeField.buffer[pos++] = (byte)'"';
_serializeField.buffer[pos++] = (byte)',';
_serializeField.buffer[pos++] = (byte)'"';
System.arraycopy(_bookmarkField.getBytes("UTF8"), 0,
_serializeField.buffer, pos, _bookmarkField.length());
pos += _bookmarkField.length();
_serializeField.buffer[pos++] = (byte)'"';
_serializeField.buffer[pos++] = (byte)':';
_serializeField.buffer[pos++] = (byte)'"';
System.arraycopy(bookmark.buffer, bookmark.position,
_serializeField.buffer, pos, bookmark.length);
pos += bookmark.length;
_serializeField.buffer[pos++] = (byte)'"';
_serializeField.buffer[pos] = (byte)'}';
_serializeField.length = length;
return true;
}
/**
* Deserializes a JSON string into a {@link RecoveryPoint} instance for use
* by {@link #next()} during bookmark store creation/initialization.
*
* This method provides an override point where another message type
* can be deserialized into a {@link RecoveryPoint}. This method is only
* called via recover() during bookmark store creation/initialization by a
* single thread.
*
* @param m The `sow` Message from the server to deserialize.
* @return true if the Message was successfully deserialized into the
* _current RecoveryPoint.
* @throws Exception If an error occurs during deserialization.
*/
protected boolean deserialize(Message m) throws Exception {
int subIdIdx = 0;
int bookmarkIdx = 0;
String data = m.getData();
Matcher subIdMatch = _subIdPattern.matcher(data);
if (!subIdMatch.find()) {
throw new StoreException("SubId NOT FOUND in SOW"
+ " record during recovery: data = " + data);
}
Matcher bookmarkMatch = _bookmarkPattern.matcher(data);
if(!bookmarkMatch.find()) {
throw new StoreException("Recovery bookmark NOT FOUND in SOW"
+ " record during recovery: data = " + data);
}
// Get the subId and the bookmark
String subId = subIdMatch.group(1);
String bookmark = bookmarkMatch.group(1);
if (_useTimestamp) {
String timestamp = m.getTimestamp();
if (timestamp != null && !timestamp.isEmpty()
&& bookmark.charAt(0) != '[' && bookmark.charAt(0) != '(') {
bookmark = bookmark + "," + timestamp;
}
}
int subIdLen = subId.length();
int bookmarkLen = bookmark.length();
int len = subIdLen + bookmarkLen;
if (_serializeField.buffer == null
|| _serializeField.buffer.length < len) {
byte[] buffer = new byte[len];
_serializeField.set(buffer, 0, len);
}
System.arraycopy(subId.getBytes("UTF8"), 0, _serializeField.buffer, 0,
subIdLen);
System.arraycopy(bookmark.getBytes("UTF8"), 0, _serializeField.buffer,
subIdLen, bookmarkLen);
_current.getSubId().set(_serializeField.buffer, 0, subIdLen);
_current.getBookmark().set(_serializeField.buffer, subIdLen,
bookmarkLen);
return true;
}
protected void initSerialization() throws Exception {
if (_serializeField.buffer == null
|| _serializeField.buffer.length < JSON_INIT_LEN) {
byte[] buffer = new byte[JSON_INIT_LEN];
_serializeField.set(buffer, 0, JSON_INIT_LEN);
}
if (_initStr == null)
{
StringBuilder builder = new StringBuilder(JSON_INIT_LEN);
builder.append("{\"").append(_clientNameField).append("\":\"")
.append(_trackedName).append("\",\"")
.append(_subIdField).append("\":\"");
_initStr = builder.toString();
_serializeStart = _initStr.length();
}
System.arraycopy(_initStr.getBytes("UTF8"), 0,
_serializeField.buffer, 0, _initStr.length());
}
}