/*
* Citrusleaf Aerospike Client - Java Library
*
* Copyright 2009-2010 by Citrusleaf, Inc. All rights reserved.
*
* Availability of this source code to partners and customers includes
* redistribution rights covered by individual contract. Please check your
* contract for exact rights and responsibilities.
*/
package net.citrusleaf;
import gnu.crypto.hash.HashFactory;
import gnu.crypto.hash.IMessageDigest;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.LockSupport;
/**
* Instantiate a <code>CitrusleafClient</code> object to access a Citrusleaf
* database cluster and perform database operations.
* <p>
* Your application uses this class API to perform database operations such as
* writing and reading records, and scanning sets of records. Write operations
* include specialized functionality such as append/prepend and arithmetic
* addition.
* <p>
* Records are stored and identified using a specified namespace, an optional
* set name, and a key which must be unique within a set.
* <p>
* Each record may have multiple bins, unless the Citrusleaf server nodes are
* configured as "single-bin". In "multi-bin" mode, partial records may be
* written or read by specifying the relevant subset of bins.
*/
public class CitrusleafClient {
//=================================================================
// Constants
//
/**
* Database operation result codes.
*/
public enum ClResultCode {
/**
* Operation was successful.
*/
OK,
/**
* Initial "empty" value, operation has not started.
*/
NOT_SET,
/**
* Unknown server failure.
*/
SERVER_ERROR,
/**
* Operation timed out.
*/
TIMEOUT,
/**
* Memory or other client fault.
*/
CLIENT_ERROR,
/**
* On retrieving, touching or replacing a record that doesn't exist.
*/
KEY_NOT_FOUND_ERROR,
/**
* On modifying a record with unexpected generation.
*/
GENERATION_ERROR,
/**
* Bad parameter(s) were passed in database operation call.
*/
PARAMETER_ERROR,
/**
* On create-only (write unique) operations on a record that already
* exists.
*/
KEY_EXISTS_ERROR,
/**
* On create-only (write unique) operations on a bin that already
* exists.
*/
BIN_EXISTS_ERROR,
/**
* Object could not be serialized.
*/
SERIALIZE_ERROR,
/**
* Expected cluster ID was not received.
*/
CLUSTER_KEY_MISMATCH,
/**
* Server has run out of memory.
*/
SERVER_MEM_ERROR,
/**
* XDS product is not available.
*/
NO_XDS,
/**
* Server is not accepting requests.
*/
SERVER_NOT_AVAILABLE,
/**
* Operation is not supported with configured bin type (single-bin or
* multi-bin).
*/
BIN_TYPE_ERROR,
/**
* Record size exceeds limit.
*/
RECORD_TOO_BIG,
/**
* Too many concurrent operations on the same record.
*/
KEY_BUSY
}
/**
* Priority of scan operations on database server.
*/
public enum ClScanningPriority {
/**
* A server node autonomously decides how many scan threads to use based
* on factors such as the number of storage devices.
*/
AUTO,
/**
* Currently this means a server node will use one scan thread.
*/
LOW,
/**
* Currently this means a server node will use three scan threads.
*/
MEDIUM,
/**
* Currently this means a server node will use five scan threads.
*/
HIGH
}
/**
* Log escalation level.
*/
public enum ClLogLevel {
/**
* Error condition has occurred.
*/
ERROR,
/**
* Unusual non-error condition has occurred.
*/
WARN,
/**
* Normal information message.
*/
INFO,
/**
* Message used for debugging purposes.
*/
DEBUG,
/**
* Detailed message also used for debugging purposes.
*/
VERBOSE
}
// Package-Private
static final int DEFAULT_TIMEOUT = 5000;
private enum PendType {
PREPEND,
APPEND
}
private enum RetryPolicy {
ONE_SHOT,
RETRY
}
private static final int MAX_TIMEOUT_MS_WAIT = 100;
private static final int MIN_TIMEOUT_MS_WAIT = 1;
private static final int RETRY_COUNT = 3;
private static final String DEFAULT_NAMESPACE = "ns";
//=================================================================
// Globals
//
private static SimpleDateFormat gSdf = null;
private static ClLogLevel gLogLevel = ClLogLevel.INFO;
private static LogCallback gLogCallback = null;
private static ConcurrentHashMap<String,CLConnectionManager> gConnMgrMap =
new ConcurrentHashMap<String,CLConnectionManager>();
//=================================================================
// Member Data
//
private CLConnectionManager mConnMgr = null;
private String mDefaultNamespace = DEFAULT_NAMESPACE;
//=================================================================
// Public Functions
//
//-------------------------------------------------------
// Constructors
//-------------------------------------------------------
/**
* Constructor, creates <code>CitrusleafClient</code> object and initializes
* logging framework.
* <p>
* This constructor does not add hosts.
*/
public CitrusleafClient() {
ClLogInit();
}
/**
* Constructor, combines default constructor {@link #CitrusleafClient()} and
* {@link #addHost(String, int) addHost()}.
*
* @param hostname host name
* @param port host port
*/
public CitrusleafClient(String hostname, int port) {
ClLogInit();
addHost(hostname, port);
}
//-------------------------------------------------------
// Do Logging via Callback
//-------------------------------------------------------
/**
* Set logging level filter and optional log callback implementation.
* <p>
* This method may only be called once, at startup.
*
* @param level only show logs at this or more urgent level
* @param logCb {@link LogCallback} implementation, pass
* <code>null</code> to let Citrusleaf client write
* logs to <code>System.err</code>
*/
public static void setLogging(ClLogLevel level, LogCallback logCb) {
gLogLevel = level;
gLogCallback = logCb;
}
//-------------------------------------------------------
// Cluster Connection Management
//-------------------------------------------------------
/**
* Add a server database host to the client's cluster map.
* <p>
* The following actions occur upon the first invocation of this method:
* <p>
* - create new cluster map <br>
* - add host to cluster map <br>
* - connect to host server <br>
* - request host's list of other nodes in cluster <br>
* - add these nodes to cluster map <br>
* <p>
* Further invocations will add hosts to the cluster map if they don't
* already exist. In most cases, only one <code>addHost()</code> call is
* necessary. When this call succeeds, the client is ready to process
* database requests.
*
* @param hostname host name
* @param port host port
* @return {@link ClResultCode result status}
*/
public ClResultCode addHost(String hostname, int port) {
// If cluster has been initialized, add host to cluster.
if (mConnMgr != null) {
return mConnMgr.addHost(hostname, port);
}
// Lookup host in global cluster map, return if host already exists.
String key = String.format("%s:%d", hostname, port);
mConnMgr = gConnMgrMap.get(key);
if (mConnMgr != null) {
return ClResultCode.OK;
}
// Create new cluster.
try {
mConnMgr = new CLConnectionManager(hostname, port, 200);
}
catch (Exception ex) {
// Failed to establish connection to host, bail on cluster creation.
warn(ex.getMessage());
return ClResultCode.SERVER_NOT_AVAILABLE;
}
// Check to see if another thread created cluster first.
CLConnectionManager prevConnMgr =
gConnMgrMap.putIfAbsent(key, mConnMgr);
if (prevConnMgr != null) {
// Don't use the one we allocated, use the one from the map.
mConnMgr.close();
mConnMgr = prevConnMgr;
}
return ClResultCode.OK;
}
/**
* Determine if we are ready to talk to the database server cluster.
*
* @return <code>true</code> if cluster is ready,
* <code>false</code> if not
*/
public boolean isConnected() {
return mConnMgr != null ? mConnMgr.connected() : false;
}
/**
* Use {@link #isConnected()}.
*/
@Deprecated
public boolean connect() {
return isConnected();
}
/**
* Close client connections to database server nodes.
*/
public void close() {
// Some may linger if they haven't responded or timed out.
mConnMgr.closeAllConnections();
}
/**
* Specify namespace to use in database operation calls that do not have a
* namespace parameter.
*
* @param namespace namespace to use in calls that have no namepsace
* parameter
*/
public void setDefaultNamespace(String namespace) {
mDefaultNamespace = namespace;
}
//-------------------------------------------------------
// Key Digest Computation
//-------------------------------------------------------
/**
* Generate digest from key and set name.
*
* @param set set name
* @param key record identifier, unique within set
* @return unique identifier generated from key and set
* name
*/
public static byte[] computeDigest(String set, Object key) {
CLBuffer cb = new CLBuffer();
return cb.getDigest(set, key);
}
//-------------------------------------------------------
// Set Operations
//-------------------------------------------------------
/**
* For server nodes configured as "single-bin" only - set record value for
* specified key, using default namespace, no set name, and default options.
* <p>
* If the record does not exist, it will be created. If it exists its value
* is replaced.
*
* @param key record identifier, unique within set
* @param value single-bin value
* @return {@link ClResultCode result status}
*/
public ClResultCode set(Object key, Object value) {
return set(key, value, null, null);
}
/**
* For server nodes configured as "single-bin" only - set record value for
* specified key, using default namespace and no set name.
* <p>
* If the record does not exist, it will be created. If it exists its value
* is replaced.
*
* @param key record identifier, unique within set
* @param value single-bin value
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode set(Object key, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return set(mDefaultNamespace, "", key, binAsCollection("", value),
opts, wOpts);
}
/**
* Set record bin value for specified key and bin name.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binName bin name, pass <code>""</code> if server nodes
* are configured as "single-bin"
* @param value bin value
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode set(String namespace, String set, Object key,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return set(namespace, set, key, binAsCollection(binName, value), opts,
wOpts);
}
/**
* Set record bin values for specified key and bin names.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode set(String namespace, String set, Object key,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("set", namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addWrite(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
/**
* Set record bin values for specified key and bin names.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode set(String namespace, String set, Object key,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("set", namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addWrite(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
//-------------------------------------------------------
// Set Operations - by digest
//-------------------------------------------------------
/**
* Set record bin value for specified key digest and bin name.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param binName bin name, pass <code>""</code> if server nodes
* are configured as "single-bin"
* @param value bin value
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode setDigest(String namespace, byte[] digest,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return setDigest(namespace, digest, binAsMap(binName, value), opts,
wOpts);
}
/**
* Set record bin value for specified key digest and bin name.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param bin bin name/value pair
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode setDigest(String namespace, byte[] digest, ClBin bin,
ClOptions opts, ClWriteOptions wOpts) {
return setDigest(namespace, digest, binAsMap(bin.name, bin.value), opts,
wOpts);
}
/**
* Set record bin values for specified key digest and bin names.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode setDigest(String namespace, byte[] digest,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
HashMap<String, Object> binsAsMap = new HashMap<String, Object>();
for (ClBin bin: bins) {
binsAsMap.put(bin.name, bin.value);
}
return setDigest(namespace, digest, binsAsMap, opts, wOpts);
}
/**
* Set record bin values for specified key digest and bin names.
* <p>
* If the record or bin does not exist, it will be created. If the bin
* exists its value is replaced. Others bins are ignored.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode setDigest(String namespace, byte[] digest,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("setDigest", namespace, digest, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, digest, wOpts);
asb.addWrite(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
//-------------------------------------------------------
// Append & Prepend Operations
//-------------------------------------------------------
/**
* For server nodes configured as "single-bin" only - append value to
* existing record value for specified key, using default namespace, no set
* name, and default options.
* <p>
* If the record does not exist, it will be created with the specified
* value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param key record identifier, unique within set
* @param value value to append
* @return {@link ClResultCode result status}
*/
public ClResultCode append(Object key, Object value) {
return pend(PendType.APPEND, key, value);
}
/**
* For server nodes configured as "single-bin" only - append value to
* existing record value for specified key, using default namespace and no
* set name.
* <p>
* If the record does not exist, it will be created with the specified
* value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param key record identifier, unique within set
* @param value value to append
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode append(Object key, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return pend(PendType.APPEND, key, value, opts, wOpts);
}
/**
* Append value to existing bin value for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binName bin name, pass <code>""</code> if server nodes
* are configured as "single-bin"
* @param value value to append
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode append(String namespace, String set, Object key,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return pend(PendType.APPEND, namespace, set, key, binName, value, opts,
wOpts);
}
/**
* Append value to existing bin value for each bin for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode append(String namespace, String set, Object key,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
return pend(PendType.APPEND, namespace, set, key, bins, opts, wOpts);
}
/**
* Append value to existing bin value for each bin for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode append(String namespace, String set, Object key,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
return pend(PendType.APPEND, namespace, set, key, bins, opts, wOpts);
}
/**
* For server nodes configured as "single-bin" only - prepend value to
* existing record value for specified key, using default namespace, no set
* name, and default options.
* <p>
* If the record does not exist, it will be created with the specified
* value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param key record identifier, unique within set
* @param value value to prepend
* @return {@link ClResultCode result status}
*/
public ClResultCode prepend(Object key, Object value) {
return pend(PendType.PREPEND, key, value);
}
/**
* For server nodes configured as "single-bin" only - prepend value to
* existing record value for specified key, using default namespace and no
* set name.
* <p>
* If the record does not exist, it will be created with the specified
* value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param key record identifier, unique within set
* @param value value to prepend
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode prepend(Object key, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return pend(PendType.PREPEND, key, value, opts, wOpts);
}
/**
* Prepend value to existing bin value for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binName bin name, pass <code>""</code> if server nodes
* are configured as "single-bin"
* @param value value to prepend
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode prepend(String namespace, String set, Object key,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return pend(PendType.PREPEND, namespace, set, key, binName, value, opts,
wOpts);
}
/**
* Prepend value to existing bin value for each bin for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode prepend(String namespace, String set, Object key,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
return pend(PendType.PREPEND, namespace, set, key, bins, opts, wOpts);
}
/**
* Prepend value to existing bin value for each bin for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* This call works only if the specified value's type matches the existing
* value's type, and the type is not an integer (i.e. <code>Integer</code>
* or <code>Long</code>.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode prepend( String namespace, String set, Object key,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
return pend(PendType.PREPEND, namespace, set, key, bins, opts, wOpts);
}
//-------------------------------------------------------
// Arithmetic Operations
//-------------------------------------------------------
/**
* For integer values, and server nodes configured as "single-bin" only -
* add value to existing record value for specified key, using default
* namespace, no set name, and default options.
* <p>
* If the record does not exist, it will be created with the specified
* value.
*
* @param key record identifier, unique within set
* @param value bin value
* @return {@link ClResultCode result status}
*/
public ClResultCode add(Object key, Object value) {
return add(key, value, null, null);
}
/**
* For integer values, and server nodes configured as "single-bin" only -
* add value to existing record value for specified key, using default
* namespace and no set name.
* <p>
* If the record does not exist, it will be created with the specified
* value.
*
* @param key record identifier, unique within set
* @param value bin value
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode add(Object key, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return add(mDefaultNamespace, "", key, binAsCollection("", value), opts,
wOpts);
}
/**
* For integer values only - add value to existing bin value for specified
* key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binName bin name, pass <code>""</code> if server nodes
* are configured as "single-bin"
* @param value bin value
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode add(String namespace, String set, Object key,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return add(namespace, set, key, binAsCollection(binName, value), opts,
wOpts);
}
/**
* For integer values only - add value to existing bin value for each bin
* for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode add(String namespace, String set, Object key,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("add", namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAdd(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
/**
* For integer values only - add value to existing bin value for each bin
* for specified key.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode add(String namespace, String set, Object key,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("add", namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAdd(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
/**
* For integer values only - add value to existing bin value for each bin
* for specified key, and return the results.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
* with new values
*/
public ClResult addAndGet(String namespace, String set, Object key,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("addAndGet", namespace, set, key, bins)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAdd(bins);
ArrayList<String> binNames = new ArrayList<String>(bins.size());
for (ClBin bin : bins) {
binNames.add(bin.name);
}
asb.addRead(binNames);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
/**
* For integer values only - add value to existing bin value for each bin
* for specified key, and return the results.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
* with new values
*/
public ClResult addAndGet(String namespace, String set, Object key,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("addAndGet", namespace, set, key, bins)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAdd(bins);
ArrayList<String> binNames = new ArrayList<String>(bins.size());
for (String binName : bins.keySet()) {
binNames.add(binName);
}
asb.addRead(binNames);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
//-------------------------------------------------------
// Arithmetic Operations - by digest
//-------------------------------------------------------
/**
* For integer values only - add value to existing bin value for specified
* key digest.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param binName bin name, pass <code>""</code> if server nodes
* are configured as "single-bin"
* @param value bin value
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode addDigest(String namespace, byte[] digest,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return addDigest(namespace, digest, binAsMap(binName, value), opts,
wOpts);
}
/**
* For integer values only - add value to existing bin value for specified
* key digest.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param bin bin name/value pair
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode addDigest(String namespace, byte[] digest, ClBin bin,
ClOptions opts, ClWriteOptions wOpts) {
return addDigest(namespace, digest, binAsMap(bin.name, bin.value), opts,
wOpts);
}
/**
* For integer values only - add value to existing bin value for each bin
* for specified key digest.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param bins bin name/value pairs as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode addDigest(String namespace, byte[] digest,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
HashMap<String, Object> binsAsMap = new HashMap<String, Object>();
for (ClBin bin: bins) {
binsAsMap.put(bin.name, bin.value);
}
return addDigest(namespace, digest, binsAsMap, opts, wOpts);
}
/**
* For integer values only - add value to existing bin value for each bin
* for specified key digest.
* <p>
* If the record or bin does not exist, it will be created with the
* specified value.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param bins bin name/value pairs as <code>Map</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode addDigest(String namespace, byte[] digest,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("addDigest", namespace, digest, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, digest, wOpts);
asb.addAdd(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
//-------------------------------------------------------
// Delete Operations
//-------------------------------------------------------
/**
* Delete record for specified key, using default namespace and no set name.
*
* @param key record identifier, unique within set
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode delete(Object key, ClOptions opts,
ClWriteOptions wOpts) {
return delete(mDefaultNamespace, "", key, opts, wOpts);
}
/**
* Delete record for specified key.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode delete(String namespace, String set, Object key,
ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("delete", namespace, set, key)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.setDelete();
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
//-------------------------------------------------------
// Delete Operations - by digest
//-------------------------------------------------------
/**
* Delete record for specified key digest.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode deleteDigest(String namespace, byte[] digest,
ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("deleteDigest", namespace, digest)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, digest, wOpts);
asb.setDelete();
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
//-------------------------------------------------------
// Get Operations
//-------------------------------------------------------
/**
* For server nodes configured as "single-bin" only - get record value for
* specified key, using default namespace, no set name, and default options.
*
* @param key record identifier, unique within set
* @return {@link ClResult} containing single-bin value
*/
public Object get(Object key) {
ClResult r =
get(mDefaultNamespace, "", key, binNameAsCollection(""), null);
// Single-bin result is in results Map, also put it in result Object:
return r != null && r.results != null ? r.results.get("") : null;
}
/**
* Get bin value for specified key and bin name.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binName bin name filter, pass <code>""</code> if server
* nodes are configured as "single-bin"
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin value
*/
public ClResult get(String namespace, String set, Object key,
String binName, ClOptions opts) {
ClResult r =
get(namespace, set, key, binNameAsCollection(binName), opts);
// Result for one bin is in results Map, also put it in result Object:
if (r != null && r.results != null) {
r.result = r.results.values().toArray()[0];
}
return r;
}
/**
* Get bin name/value pairs for specified key and list of bin names.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binNames bin names filter as array
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
*/
public ClResult get(String namespace, String set, Object key,
String[] binNames, ClOptions opts) {
return get(namespace, set, key, Arrays.asList(binNames), opts);
}
/**
* Get bin name/value pairs for specified key and list of bin names.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binNames bin names filter as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
*/
public ClResult get(String namespace, String set, Object key,
Collection<String> binNames, ClOptions opts) {
if (! isValidArgs("get", namespace, set, key, binNames)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, null);
asb.addRead(binNames);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
/**
* Get all bin name/value pairs for specified key.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
*/
public ClResult getAll(String namespace, String set, Object key,
ClOptions opts) {
if (! isValidArgs("getAll", namespace, set, key)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, null);
asb.readAll();
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
/**
* Get bin name/value pairs for specified key and list of bin names, and if
* the record exists, reset record time to expiration.
* <p>
* This operation will increment the record generation.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param binNames bin names filter as <code>Collection</code>
* @param expiration new record expiration (see
* {@link ClWriteOptions#expiration})
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
*/
public ClResult getWithTouch(String namespace, String set, Object key,
Collection<String> binNames, int expiration, ClOptions opts) {
if (! isValidArgs("getWithTouch", namespace, set, key, binNames)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
ClWriteOptions wOpts = new ClWriteOptions();
wOpts.expiration = expiration;
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addRead(binNames);
asb.addTouch();
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
/**
* Batch multiple <code>get()</code> requests into a single call.
* <p>
* Elements of <code>keys</code> are positionally matched with elements of
* result array.
*
* @param namespace namespace
* @param set optional set name
* @param keys batch of keys as <code>Collection</code>
* @param binName bin name filter, pass <code>""</code> if server
* nodes are configured as "single-bin"
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult result} array where each element
* contains bin name/value pairs
*/
public ClResult[] batchGet(String namespace, String set,
Collection<Object> keys, String binName, ClOptions opts) {
return batchGetGeneric(namespace, set, keys,
binNameAsCollection(binName), opts, false, true);
}
/**
* Batch multiple <code>get()</code> requests into a single call.
* <p>
* Elements of <code>keys</code> are positionally matched with elements of
* result array.
*
* @param namespace namespace
* @param set optional set name
* @param keys batch of keys as <code>Collection</code>
* @param binNames bin names filter as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult result} array where each element
* contains bin name/value pairs
*/
public ClResult[] batchGet(String namespace, String set,
Collection<Object> keys, Collection<String> binNames,
ClOptions opts) {
return batchGetGeneric(namespace, set, keys, binNames, opts, false,
true);
}
/**
* Batch multiple <code>getAll()</code> requests into a single call.
* <p>
* Elements of <code>keys</code> are positionally matched with elements of
* result array.
*
* @param namespace namespace
* @param set optional set name
* @param keys batch of keys as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult result} array where each element
* contains bin name/value pairs
*/
public ClResult[] batchGetAll(String namespace, String set,
Collection<Object> keys, ClOptions opts) {
return batchGetGeneric(namespace, set, keys, null, opts, true, true);
}
//-------------------------------------------------------
// Get Operations - by digest
//-------------------------------------------------------
/**
* Get bin value for specified key digest and bin name.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param binName bin name filter, pass <code>""</code> if server
* nodes are configured as "single-bin"
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin value
*/
public ClResult getDigest(String namespace, byte[] digest, String binName,
ClOptions opts) {
return getDigest(namespace, digest, binNameAsCollection(binName), opts);
}
/**
* Get bin name/value pairs for specified key digest and list of bin names.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param binNames bin names filter as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
*/
public ClResult getDigest(String namespace, byte[] digest,
Collection<String> binNames, ClOptions opts) {
if (! isValidArgs("getDigest", namespace, digest, binNames)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, digest, null);
asb.addRead(binNames);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
/**
* Get all bin name/value pairs for specified key digest.
* <p>
* Non-Digest database methods send the normal key and set name to the
* server which converts them to a digest. Digest methods send the digest
* directly.
*
* @param namespace namespace
* @param digest unique identifier generated from key and set
* name
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} containing bin name/value pairs
*/
public ClResult getAllDigest(String namespace, byte[] digest,
ClOptions opts) {
if (! isValidArgs("getAllDigest", namespace, digest)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, digest, null);
asb.readAll();
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
//-------------------------------------------------------
// Existence-Check Operations
//-------------------------------------------------------
/**
* Check if specified key exists, using default namespace, no set name, and
* default options.
*
* @param key record identifier, unique within set
* @return {@link ClResultCode result status}
*/
public ClResultCode exists(Object key) {
return exists(mDefaultNamespace, "", key, null);
}
/**
* Check if specified key exists.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResultCode result status}
*/
public ClResultCode exists(String namespace, String set, Object key,
ClOptions opts) {
if (! isValidArgs("exists", namespace, set, key)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, null);
asb.readExists();
asb.readNoBindata();
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
// TODO - verify result format
/**
* Batch multiple <code>exists()</code> requests into a single call.
* <p>
* Elements of <code>keys</code> are positionally matched with elements of
* result array.
*
* @param namespace namespace
* @param set optional set name
* @param keys batch of keys as <code>Collection</code>
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult result} array where each element
* indicates whether its corresponding key exists
*/
public ClResult[] batchExists(String namespace, String set,
Collection<Object> keys, ClOptions opts) {
return batchGetGeneric(namespace, set, keys, null, opts, false, false);
}
//-------------------------------------------------------
// Scan Operations
//-------------------------------------------------------
/**
* Scan the database, retrieving all records in specified namespace.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResult} for final record
*/
@Deprecated
public ClResult scan(String namespace, ClOptions opts, ScanCallback scanCb,
Object userData) {
return scan(namespace, null, opts, scanCb, userData, false);
}
/**
* Scan the database, retrieving all records in specified namespace.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param set set name, not supported as scan filter
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResult} for final record
*/
@Deprecated
public ClResult scan(String namespace, String set, ClOptions opts,
ScanCallback scanCb, Object userData) {
return scan(namespace, set, opts, scanCb, userData, false);
}
/**
* Scan the database using specified namespace filter, retrieving records or
* digests only, as specified.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResult} for final record
*/
@Deprecated
public ClResult scan(String namespace, ClOptions opts, ScanCallback scanCb,
Object userData, boolean noBinData) {
return scan(namespace, null, opts, scanCb, userData, noBinData);
}
/**
* Scan the database using specified namespace filter, retrieving records or
* digests only, as specified.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param set set name, not supported as scan filter
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @param noBinData <code>true</code> to retrieve digests only,
* <code>false</code> to retrieve bin data
* @return {@link ClResult} for final record
*/
@Deprecated
public ClResult scan(String namespace, String set, ClOptions opts,
ScanCallback scanCb, Object userData, boolean noBinData) {
if (opts == null) {
opts = new ClOptions(DEFAULT_TIMEOUT);
opts.setOneShot();
}
else {
if (opts.mRetryPolicy != RetryPolicy.ONE_SHOT) {
warn("scan: must not have retry set");
return new ClResult(ClResultCode.CLIENT_ERROR);
}
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, null, null);
asb.readIterate(scanCb, userData);
if (noBinData) {
asb.readNoBindata();
}
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
/**
* Scan a single database node, using specified namespace and set filters,
* retrieving records or digests only, as specified.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param nodeName node to scan
* @param namespace namespace
* @param set set name, pass <code>null</code> to retrieve all
* records in the namespace
* @param noBinData <code>true</code> to retrieve digests only,
* <code>false</code> to retrieve bin data
* @param scanPercent fraction of data to scan - not yet supported
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanOpts parallel scan policy attributes, pass
* <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResultCode result status}
*/
public ClResultCode scanNode(String nodeName, String namespace, String set,
boolean noBinData, int scanPercent, ClOptions opts,
ClScanningOptions scanOpts, ScanCallback scanCb, Object userData) {
if (scanOpts == null) {
scanOpts = new ClScanningOptions();
}
if (scanPercent > 100 || scanPercent < 0) {
warn("invalid scan percent");
return ClResultCode.PARAMETER_ERROR;
}
if (opts == null) {
opts = new ClOptions(DEFAULT_TIMEOUT);
opts.setOneShot();
}
else if (opts.mRetryPolicy != RetryPolicy.ONE_SHOT) {
warn("scan must be one-shot");
return ClResultCode.PARAMETER_ERROR;
}
CLNode node = mConnMgr.getNodeFromNodeName(nodeName);
if (node == null) {
warn("cannot start scanning node " + nodeName +
" - does not exist (did it die?)");
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, null, null);
asb.readIterate(scanCb, userData);
if (noBinData) {
asb.readNoBindata();
}
asb.scan_options(scanOpts, scanPercent);
try {
asb.prepare();
}
catch (SerializeException e) {
asb.destroy();
return ClResultCode.SERIALIZE_ERROR;
}
ClResult r = doRequestUsingNode(asb, node, opts);
asb.destroy();
return r.resultCode;
}
/**
* Scan the database nodes in parallel, using specified namespace and set
* filters, and default options, returning a single result code.
* <p>
* This call is the same as
* {@link #scanAllNodes(String, String, ScanCallback, Object)
* scanAllNodes()} but returns only one result code.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param set set name, pass <code>null</code> to retrieve all
* records in the namespace
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResultCode result status},
* {@link ClResultCode#OK OK} if all nodes
* succeeded, else first error code encountered
*/
public ClResultCode scan(String namespace, String set, ScanCallback scanCb,
Object userData) {
Map<String, ClResultCode> resultMap = scanAllNodes(namespace, set,
false, 100, null, null, scanCb, userData);
for (ClResultCode rc : resultMap.values()) {
if (rc != ClResultCode.OK) {
return rc;
}
}
return ClResultCode.OK;
}
/**
* Scan the database nodes in parallel, using specified namespace and set
* filters, and default options.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param set set name, pass <code>null</code> to retrieve all
* records in the namespace
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResultCode result status} for each node
*/
public Map<String, ClResultCode> scanAllNodes(String namespace, String set,
ScanCallback scanCb, Object userData) {
return scanAllNodes(namespace, set, false, 100, null, null, scanCb,
userData);
}
/**
* Scan the database nodes in parallel, using specified namespace and set
* filters.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param set set name, pass <code>null</code> to retrieve all
* records in the namespace
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanOpts parallel scan policy attributes, pass
* <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResultCode result status} for each node
*/
public Map<String, ClResultCode> scanAllNodes(String namespace, String set,
ClOptions opts, ClScanningOptions scanOpts, ScanCallback scanCb,
Object userData) {
return scanAllNodes(namespace, set, false, 100, opts, scanOpts, scanCb,
userData);
}
/**
* Scan the database nodes in parallel, using specified namespace and set
* filters, retrieving records or digests only, as specified.
* <p>
* This call will block until the scan is complete - callbacks are made
* within the scope of this call.
*
* @param namespace namespace
* @param set set name, pass <code>null</code> to retrieve all
* records in the namespace
* @param noBinData <code>true</code> to retrieve digests only,
* <code>false</code> to retrieve bin data
* @param scanPercent fraction of data to scan - not yet supported
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param scanOpts parallel scan policy attributes, pass
* <code>null</code> to use defaults
* @param scanCb {@link ScanCallback} implementation
* @param userData pass-through user-defined data
* @return {@link ClResultCode result status} for each node
*/
public Map<String, ClResultCode> scanAllNodes(String namespace, String set,
boolean noBinData, int scanPercent, ClOptions opts,
ClScanningOptions scanOpts, ScanCallback scanCb, Object userData) {
if (scanOpts == null) {
scanOpts = new ClScanningOptions();
}
if (scanPercent > 100 || scanPercent < 0) {
warn("invalid scan percent");
return null;
}
if (opts == null) {
opts = new ClOptions(DEFAULT_TIMEOUT);
opts.setOneShot();
}
else if (opts.mRetryPolicy != RetryPolicy.ONE_SHOT) {
warn("scan must be one-shot");
return null;
}
// Figure out how many nodes we should go to.
List<String> nodeNames = mConnMgr.getNodeNameList();
if (nodeNames == null) {
warn("no node names in cluster");
return null;
}
// Initialize all the results to be NOT_SET.
Map<String, ClResultCode> resultMap =
new HashMap<String, ClResultCode>(nodeNames.size());
for (String nodeName : nodeNames) {
resultMap.put(nodeName, ClResultCode.NOT_SET);
}
// Do the job.
if (scanOpts.isConcurrentNodes()) {
debug("starting concurrent node scan for " + namespace + " " +
resultMap.size() + " node(s)");
List<CLScanNodeThread> nodeThreads =
new ArrayList<CLScanNodeThread>(nodeNames.size());
for (String nodeName : nodeNames) {
CLScanNodeThread t = new CLScanNodeThread(this, nodeName,
namespace, set, noBinData, scanPercent, scanCb, opts,
scanOpts, userData);
nodeThreads.add(t);
t.start();
}
try {
for (Thread t : nodeThreads) {
t.join();
}
for (CLScanNodeThread t : nodeThreads) {
resultMap.put(t.getNodeName(), t.getResult());
}
}
catch (InterruptedException e) {
info("interrupted");
}
}
else {
debug("starting serial node scan for " + namespace + " " +
resultMap.size() + " node(s)");
for (String nodeName : nodeNames) {
ClResultCode rc = scanNode(nodeName, namespace, set, noBinData,
scanPercent, opts, scanOpts, scanCb, userData);
resultMap.put(nodeName, rc);
}
}
return resultMap;
}
//-------------------------------------------------------
// Low-level Operations
//-------------------------------------------------------
/**
* Combine <code>get()</code> and <code>set()</code> operations for a
* specified record.
*
* @param namespace namespace
* @param set optional set name
* @param key record identifier, unique within set
* @param readBinNames bin names filter for read
* @param writeBins bin name/value pairs to write
* @param opts {@link ClOptions transaction policy attributes},
* pass <code>null</code> to use defaults
* @param wOpts {@link ClWriteOptions write policy attributes},
* pass <code>null</code> to use defaults
* @return {@link ClResult} for read, containing bin
* name/value pairs
*/
public ClResult operate(String namespace, String set, Object key,
Collection<String> readBinNames, Collection<ClBin> writeBins,
ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("operate", namespace, set, key, readBinNames,
writeBins)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, null);
if (readBinNames != null) {
asb.addRead(readBinNames);
}
if (writeBins != null) {
asb.addWrite(writeBins);
}
ClResult r = doRequest(asb, opts);
asb.destroy();
return r;
}
//-------------------------------------------------------
// RTA Operations
//-------------------------------------------------------
// TODO - is there a way to hide specific public functions in javadoc ???
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResultCode appendSegment(Object key, Object value) {
return appendSegment(key, value, null, null);
}
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResultCode appendSegment(Object key, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return appendSegment(mDefaultNamespace, "", key,
binAsCollection("", value), opts, wOpts);
}
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResultCode appendSegment(String namespace, String set, Object key,
String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return appendSegment(namespace, set, key,
binAsCollection(binName, value), opts, wOpts);
}
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResultCode appendSegment(String namespace, String set, Object key,
Collection<ClBin> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("appendSegment", namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAppendSegment(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResultCode appendSegment(String namespace, String set, Object key,
Map<String, Object> bins, ClOptions opts, ClWriteOptions wOpts) {
if (! isValidArgs("appendSegment", namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAppendSegment(bins);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResultCode appendSegmentAs(String namespace, String set,
Object key, String binName, Object value,
ClAppendSegmentWriteOptions appendSegmentOpts, ClOptions opts,
ClWriteOptions wOpts) {
if (! isValidArgs("appendSegmentAs", namespace, set, key, value)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
asb.addAppendSegmentExt(binName, value, appendSegmentOpts);
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
/**
* For use with special RTA servers only - not generally supported.
*/
public ClResult getUsing(String namespace, String set, Object key,
String binName, ClOptions opts,
Collection<ClAppendSegmentQueryFilter> filters, int maxResults) {
if (! isValidArgs("getUsing", namespace, set, key)) {
return new ClResult(ClResultCode.PARAMETER_ERROR);
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, null);
asb.addAppendSegmentQuery(binName, filters, maxResults);
ClResult r = doRequest(asb, opts);
if (r != null && r.results != null) {
r.result = r.results.values().toArray()[0];
}
asb.destroy();
return r;
}
//=================================================================
// Public Nested Interfaces & Classes
//
//-------------------------------------------------------
// Callbacks
//-------------------------------------------------------
/**
* An object implementing this interface may be passed in
* <code>setLogging()</code>, so the caller can channel Citrusleaf client
* logs as desired.
*/
public static interface LogCallback {
/**
* This method will be called for each Citrusleaf client log statement.
*
* @param level {@link ClLogLevel log level}
* @param msg log message
*/
public void logCallback(ClLogLevel level, String msg);
}
/**
* An object implementing this interface is passed in <code>scan...()</code>
* calls, so the caller can be notified with scan results.
*/
public static interface ScanCallback {
/**
* This method will be called for each record returned from a scan.
*
* @param namespace namespace
* @param set set name
* @param digest unique ID generated from key and set name
* @param bins bin name/value pairs as <code>Map</code>
* @param generation how many times the record has been modified
* @param expirationDate date this record will expire, in seconds
* from Jan 01 2010 00:00:00 GMT
* @param userData pass-through user-defined data
*/
public void scanCallback(String namespace, String set, byte[] digest,
Map<String, Object> bins, int generation, int expirationDate,
Object userData);
}
//-------------------------------------------------------
// Transaction Options
//-------------------------------------------------------
/**
* Container object for transaction policy attributes used in all database
* operation calls.
* <p>
* This object is passed in all database operation calls to specify options.
* <code>null</code> may be passed to use defaults for all options.
*/
public static class ClOptions {
// Package-Private
int mTimeout;
RetryPolicy mRetryPolicy;
// TODO - is there a way to show a named constant's value in javadoc ???
/**
* Constructor, sets default options.
* <p>
* The object is constructed with timeout set to <code>5000</code>
* milliseconds and default transaction retry policy.
*/
public ClOptions() {
mTimeout = DEFAULT_TIMEOUT;
mRetryPolicy = RetryPolicy.RETRY;
}
/**
* Constructor, specifying timeout.
* <p>
* The object is constructed with specified timeout and default
* transaction retry policy.
*
* @param timeoutMillisec transaction duration limit, milliseconds
*/
public ClOptions(int timeoutMillisec) {
mTimeout = timeoutMillisec;
mRetryPolicy = RetryPolicy.RETRY;
}
/**
* Specify no transaction retries.
*/
public void setOneShot() {
mRetryPolicy = RetryPolicy.ONE_SHOT;
}
/**
* Specify timeout.
*
* @param timeoutMillisec transaction duration limit, milliseconds
*/
public void setTimeout(int timeoutMillisec) {
mTimeout = timeoutMillisec;
}
// Package-Private
static boolean wantsRetry(ClOptions opts) {
return opts != null && opts.mRetryPolicy == RetryPolicy.ONE_SHOT ?
false : true;
}
}
/**
* Container object for policy attributes used in write operations.
* <p>
* This object is passed in <code>set()</code>, <code>append()</code>,
* <code>prepend()</code>, <code>add()</code>, and <code>delete()</code>
* calls to specify write options. <code>null</code> may be passed to use
* defaults for all options.
* <p>
* Set <code>expiration</code> and <code>unique</code> directly, and call
* <code>set_generation...()</code> methods to override defaults.
* <p>
* Generation is the number of times a record has been modified (including
* creation) on the server. Therefore if a write operation is creating a
* record, the expected generation would be <code>0</code>.
*/
public static class ClWriteOptions {
/**
* Record is automatically removed from server this many seconds after
* write operation.
*/
public int expiration;
/**
* Write operation is create-only, will fail if record already exists.
*/
public boolean unique;
// Package-Private
boolean mUseGeneration;
boolean mUseGenerationGt;
boolean mUseGenerationDup;
int mGeneration;
/**
* Constructor, sets default write options.
* <p>
* The object is constructed with <code>expiration</code> of
* <code>0</code>, <code>unique</code> set <code>false</code>, and no
* expected generation.
*/
public ClWriteOptions() {
expiration = 0;
unique = false;
mUseGeneration = false;
mUseGenerationGt = false;
mUseGenerationDup = false;
mGeneration = 0;
}
/**
* Specify expected generation.
* <p>
* Write operation will fail if generation is set and it's not equal to
* the generation on the server.
*
* @param generation expected generation
*/
public void set_generation(int generation) {
mUseGeneration = true;
mGeneration = generation;
}
/**
* Specify highest expected generation.
* <p>
* Write operation will fail if generation is set and it's less than the
* generation on the server. (Useful for restore after backup.)
*
* @param generation highest expected generation
*/
public void set_generation_gt(int generation) {
mUseGenerationGt = true;
mGeneration = generation;
}
/**
* Specify expected generation, and flag to duplicate if generation is
* not equal to that on server.
* <p>
* Write operation will generate duplicate record if expected generation
* is set and it's not the generation on the server.
*
* @param generation expected generation
*/
public void set_generation_dup(int generation) {
mUseGenerationDup = true;
mGeneration = generation;
}
}
/**
* Container object for optional parameters used in scan operations.
* <p>
* This object is passed in <code>scan...()</code> calls to specify options.
* <code>null</code> may be passed to use defaults for all options.
*/
public static class ClScanningOptions {
// Honored by client - work on nodes in parallel or serially:
private boolean mConcurrentNodes;
// Honored by client - have multiple threads per node:
private int mThreadsPerNode;
// Honored by server - priority of scan:
private ClScanningPriority mPriority;
// Honored by server - terminate scan if cluster in fluctuating state:
private boolean mFailOnClusterChange;
/**
* Constructor, sets default scan options.
* <p>
* The object is constructed with concurrent nodes scan enabled, one
* thread per node, scan priority {@link ClScanningPriority#AUTO}, and
* scan termination on cluster changes disabled.
*/
public ClScanningOptions() {
mConcurrentNodes = true;
mThreadsPerNode = 1;
mPriority = ClScanningPriority.AUTO;
mFailOnClusterChange = false;
}
/**
* Get concurrent nodes scan setting.
*
* @return <code>true</code> if concurrent nodes scan
* is enabled, <code>false</code> if not
*/
public boolean isConcurrentNodes() {
return mConcurrentNodes;
}
/**
* Specify concurrent nodes scan setting.
*
* @param concurrentNodes <code>true</code> to enable concurrent nodes
* scan, <code>false</code> to disable
*/
public void setConcurrentNodes(boolean concurrentNodes) {
mConcurrentNodes = concurrentNodes;
}
/**
* Get number of client scan threads per node.
*
* @return number of client threads used to scan a node
*/
public int getThreadsPerNode() {
return mThreadsPerNode;
}
/**
* Specify number of client scan threads per node - not yet supported.
*
* @param threadsPerNode number of client threads used to scan a node
*/
public void setThreadsPerNode(int threadsPerNode) {
mThreadsPerNode = threadsPerNode;
}
/**
* Get server scan priority setting.
*
* @return {@link ClScanningPriority scan priority} to
* be used by server
*/
public ClScanningPriority getPriority() {
return mPriority;
}
/**
* Specify server scan priority setting.
*
* @param priority {@link ClScanningPriority scan priority} to
* be used by server
*/
public void setPriority(ClScanningPriority priority) {
mPriority = priority;
}
/**
* Get scan termination setting.
*
* @return <code>true</code> if scan will terminate on
* cluster change, <code>false</code> if not
*/
public boolean isFailOnClusterChange() {
return mFailOnClusterChange;
}
/**
* Specify scan termination setting.
*
* @param failOnClusterChange <code>true</code> to terminate scan on
* cluster change, <code>false</code> to
* continue scan
*/
public void setFailOnClusterChange(boolean failOnClusterChange) {
mFailOnClusterChange = failOnClusterChange;
}
}
//-------------------------------------------------------
// Results of Operations
//-------------------------------------------------------
/**
* Container object for database operation results.
*/
public static class ClResult {
/**
* {@link ClResultCode Result code} for operation.
*/
public ClResultCode resultCode = ClResultCode.NOT_SET;
/**
* How many times the record has been modified (including creation) on
* the server.
*/
public int generation = -1;
/**
* For scans, to notify that the data is corrupted.
*/
public boolean corruptedData = false;
/**
* When only one bin is requested, value is returned here.
*/
public Object result = null;
/**
* Requested bins are returned here, as name/value pairs.
*/
public Map<String, Object> results = null;
/**
* When conflicting versions are encountered, every version's bins are
* returned as an element of this <code>List</code>.
* <p>
* @see ClWriteOptions#mUseGenerationDup
*/
public List<Map<String, Object>> results_dup = null;
// Package-Private
// Used internally by batch operations to store per-node results.
ClResult[] resultArray = null;
/**
* Constructor, creates "empty" object.
*/
public ClResult() {
}
/**
* Constructor, specifies result code.
*
* @param resultCode operation {@link ClResultCode result code}
*/
public ClResult(ClResultCode resultCode) {
this.resultCode = resultCode;
}
/**
* Convert result code to meaningful <code>String</code>.
*
* @param resultCode operation {@link ClResultCode result code}
* @return interpretation of <code>resultCode</code>
*/
public static String resultCodeToString(ClResultCode resultCode) {
switch (resultCode) {
case OK:
return "OK";
case NOT_SET:
return "result code has not been set";
case SERVER_ERROR:
return "server error";
case TIMEOUT:
return "timeout";
case CLIENT_ERROR:
return "client error";
case KEY_NOT_FOUND_ERROR:
return "key not found error";
case GENERATION_ERROR:
return "generation error";
case PARAMETER_ERROR:
return "parameter error";
case KEY_EXISTS_ERROR:
return "key exists error";
case BIN_EXISTS_ERROR:
return "bin exists error";
case SERIALIZE_ERROR:
return "serialize error";
case CLUSTER_KEY_MISMATCH:
return "cluster key mismatch";
case SERVER_MEM_ERROR:
return "server memory error";
case NO_XDS:
return "XDS product not available";
case SERVER_NOT_AVAILABLE:
return "server not available";
case BIN_TYPE_ERROR:
return "bin type error";
case RECORD_TOO_BIG:
return "record too big";
case KEY_BUSY:
return "key busy";
default:
return "unknown error " + resultCode;
}
}
}
//-------------------------------------------------------
// Container for Bin Name & Value
//-------------------------------------------------------
/**
* Container object for record bin name/value pair.
*/
public static class ClBin {
/**
* Bin name. Current limit is 14 characters.
*/
public String name;
/**
* Bin value.
*/
public Object value;
/**
* Constructor, sets <code>null</code> bin name and value.
*/
public ClBin() {
name = null;
value = null;
}
/**
* Constructor, specifying bin name and value.
*
* @param name bin name, current limit is 14 characters
* @param value bin value
*/
public ClBin(String name, Object value) {
this.name = name;
this.value = value;
}
}
//-------------------------------------------------------
// RTA Options and Filters
//-------------------------------------------------------
/**
* For use with special RTA servers only - not generally supported.
*/
public static class ClAppendSegmentWriteOptions {
// Segment expires this many seconds from now, 0 means use default.
public int expiration;
// User-defined, null means no tag.
public String tag;
// null means creation time is when server receives segment.
public Date creation_time;
public ClAppendSegmentWriteOptions(int expiration, String tag,
Date creation_time) {
this.expiration = expiration;
this.tag = tag;
this.creation_time = creation_time;
}
}
/**
* For use with special RTA servers only - not generally supported.
*/
public static class ClAppendSegmentQueryFilter {
// For range queries - return only segments created at or after
// start_time, null means not used.
public Date start_time;
// For range queries - return only segments created before end_time,
// null means not used.
public Date end_time;
// User-defined, matches if tag parameter is contained in segment tag,
// null means not used.
public String tag;
public ClAppendSegmentQueryFilter(String tag, Date start_time,
Date end_time) {
this.tag = tag;
this.start_time = start_time;
this.end_time = end_time;
}
}
/**
* For use with special RTA servers only - not generally supported.
*/
public static class ClBinAppendSegmentWriteOptions extends ClBin {
public ClAppendSegmentWriteOptions write_opts;
public ClBinAppendSegmentWriteOptions(String name, Object value,
ClAppendSegmentWriteOptions write_opts) {
this.name = name;
this.value = value;
this.write_opts = write_opts;
}
}
/**
* For use with special RTA servers only - not generally supported.
*/
public static class ClBinAppendSegmentQueryOptions extends ClBin {
public Collection <ClAppendSegmentQueryFilter> query_filters;
public int max_results;
public ClBinAppendSegmentQueryOptions(String name, Object value,
Collection<ClAppendSegmentQueryFilter> query_filters,
int max_results) {
this.name = name;
this.value = value;
this.query_filters = query_filters;
this.max_results = max_results;
}
}
//=================================================================
// Package-Private Functions
//
//-------------------------------------------------------
// Logging Helpers
//-------------------------------------------------------
static void error(String msg) {
ClLog(ClLogLevel.ERROR, msg);
}
static void warn(String msg) {
ClLog(ClLogLevel.WARN, msg);
}
static void info(String msg) {
ClLog(ClLogLevel.INFO, msg);
}
static void debug(String msg) {
ClLog(ClLogLevel.DEBUG, msg);
}
static void verbose(String msg) {
ClLog(ClLogLevel.VERBOSE, msg);
}
//=================================================================
// Package-Private Nested Interfaces & Classes
//
//-------------------------------------------------------
// Exceptions
//-------------------------------------------------------
static class SerializeException extends Exception {
// Generated by random.org:
static final long serialVersionUID = 0x7DF875330CD56169L;
}
//=================================================================
// Private Functions
//
//-------------------------------------------------------
// Argument Conversion Helpers
//-------------------------------------------------------
private static ArrayList<ClBin> binAsCollection(String name, Object value) {
ArrayList<ClBin> bins = new ArrayList<ClBin>(1);
bins.add(new ClBin(name, value));
return bins;
}
private static HashMap<String, Object> binAsMap(String name, Object value) {
HashMap<String, Object> bins = new HashMap<String, Object>();
bins.put(name, value);
return bins;
}
private static ArrayList<String> binNameAsCollection(String name) {
ArrayList<String> binNames = new ArrayList<String>(1);
binNames.add(name);
return binNames;
}
//-------------------------------------------------------
// Argument Checking Helpers
//-------------------------------------------------------
private static boolean isValidArgs(String tag, Object arg1, Object arg2,
Object arg3, Object arg4, Object arg5) {
if (! isValidArg(arg1)) {
warn(tag + "(): unexpected null argument [1]");
return false;
}
if (! isValidArg(arg2)) {
warn(tag + "(): unexpected null argument [2]");
return false;
}
if (! isValidArg(arg3)) {
warn(tag + "(): unexpected null argument [3]");
return false;
}
if (! isValidArg(arg4)) {
warn(tag + "(): unexpected null argument [4]");
return false;
}
if (! isValidArg(arg5)) {
warn(tag + "(): unexpected null argument [5]");
return false;
}
return true;
}
private static boolean isValidArgs(String tag, Object arg1, Object arg2,
Object arg3, Object arg4) {
if (! isValidArg(arg1)) {
warn(tag + "(): unexpected null argument [1]");
return false;
}
if (! isValidArg(arg2)) {
warn(tag + "(): unexpected null argument [2]");
return false;
}
if (! isValidArg(arg3)) {
warn(tag + "(): unexpected null argument [3]");
return false;
}
if (! isValidArg(arg4)) {
warn(tag + "(): unexpected null argument [4]");
return false;
}
return true;
}
private static boolean isValidArgs(String tag, Object arg1, Object arg2,
Object arg3) {
if (! isValidArg(arg1)) {
warn(tag + "(): unexpected null argument [1]");
return false;
}
if (! isValidArg(arg2)) {
warn(tag + "(): unexpected null argument [2]");
return false;
}
if (! isValidArg(arg3)) {
warn(tag + "(): unexpected null argument [3]");
return false;
}
return true;
}
private static boolean isValidArgs(String tag, Object arg1, Object arg2) {
if (! isValidArg(arg1)) {
warn(tag + "(): unexpected null argument [1]");
return false;
}
if (! isValidArg(arg2)) {
warn(tag + "(): unexpected null argument [2]");
return false;
}
return true;
}
private static boolean isValidArg(Object arg) {
if (arg == null) {
return false;
}
if (arg instanceof ClBin) {
if (! (isValidArg(((ClBin)arg).name) &&
isValidArg(((ClBin)arg).value))) {
return false;
}
}
else if (arg instanceof Collection) {
for (Object obj : (Collection<?>)arg) {
if (! isValidArg(obj)) {
return false;
}
}
}
else if (arg instanceof Map) {
Map<?, ?> map = (Map<?, ?>)arg;
for (Object key : map.keySet()) {
if (! (isValidArg(key) && isValidArg(map.get(key)))) {
return false;
}
}
}
return true;
}
//-------------------------------------------------------
// Append & Prepend Operation Helpers
//-------------------------------------------------------
private ClResultCode pend(PendType pt, Object key, Object value) {
return pend(pt, key, value, null, null);
}
private ClResultCode pend(PendType pt, Object key, Object value,
ClOptions opts, ClWriteOptions wOpts) {
return pend(pt, mDefaultNamespace, "", key, binAsCollection("", value),
opts, wOpts);
}
private ClResultCode pend(PendType pt, String namespace, String set,
Object key, String binName, Object value, ClOptions opts,
ClWriteOptions wOpts) {
return pend(pt, namespace, set, key, binAsCollection(binName, value),
opts, wOpts);
}
private ClResultCode pend(PendType pt, String namespace, String set,
Object key, Collection<ClBin> bins, ClOptions opts,
ClWriteOptions wOpts) {
if (! isValidArgs((pt == PendType.APPEND ? "ap" : "pre") + "pend",
namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
switch (pt) {
case PREPEND:
asb.addPrepend(bins);
break;
case APPEND:
asb.addAppend(bins);
break;
}
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
private ClResultCode pend(PendType pt, String namespace, String set,
Object key, Map<String, Object> bins, ClOptions opts,
ClWriteOptions wOpts) {
if (! isValidArgs((pt == PendType.APPEND ? "ap" : "pre") + "pend",
namespace, set, key, bins)) {
return ClResultCode.PARAMETER_ERROR;
}
CLBuffer asb = CLBuffer.create();
asb.set_required(namespace, set, key, wOpts);
switch (pt) {
case PREPEND:
asb.addPrepend(bins);
break;
case APPEND:
asb.addAppend(bins);
break;
}
ClResult r = doRequest(asb, opts);
asb.destroy();
return r.resultCode;
}
//-------------------------------------------------------
// Batch Get Operation Helper
//-------------------------------------------------------
// The getAll argument will override the bins argument - if getAll is true,
// all the bins will be fetched.
//
// If successful, this function will return an array of ClResult objects
// whose size is equal to the number of keys passed in. The ClResult array
// is positionally matched with the keys. If there is no record for a
// requested key in the database, its corresponding result will be null. In
// some error cases, this function will return a single-element array with a
// suggestive error code.
private ClResult[] batchGetGeneric(String namespace, String set,
Collection<Object> keys, Collection<String> bins, ClOptions opts,
boolean getAll, boolean getData) {
if (! isValidArgs("batchGet", namespace, set, keys)) {
ClResult[] results = new ClResult[1];
results[0] = new ClResult(ClResultCode.PARAMETER_ERROR);
return results;
}
int numKeys = keys.size();
if (numKeys == 0) {
return null;
}
if (mConnMgr == null) {
ClResult[] results = new ClResult[1];
results[0] = new ClResult(ClResultCode.CLIENT_ERROR);
return results;
}
ClResult[] results = new ClResult[numKeys];
// Hack - 255 node-IDs should be enough. (Note: node-ID is not same as
// number of nodes.) We are using an indexable array because we need
// quick access to the node when adding keys to its batch payload.
int numNodes = 255;
// I don't know how to declare this without the warning. [AKG]
@SuppressWarnings("unchecked")
ArrayList<BatchElement>[] batchLists = new ArrayList[numNodes];
String[] nodeNames = new String[numNodes];
for (int n = 0; n < numNodes; n++) {
batchLists[n] = new ArrayList<BatchElement>();
}
IMessageDigest md = HashFactory.getInstance("RIPEMD-160");
CLBuffer clBuffer = CLBuffer.create();
byte[] buf = new byte[512]; // for holding set and key
int setOffset = 0;
int setLen = CLBuffer.stringToUtf8(set, buf, setOffset);
int keyOffset = setLen + 1;
// Create the digests for all the keys passed in.
Iterator<Object> keysIterator = keys.iterator();
for (int i = 0; keysIterator.hasNext(); i++) {
Object key = keysIterator.next();
int keyLen = clBuffer.keyToBytes(key, buf, keyOffset);
md.reset();
md.update(buf, setOffset, setLen);
md.update(buf, keyOffset, keyLen);
byte[] digest = md.digest();
long partitionId = CLBuffer.get_ntohl_intel(digest, 0);
// Can't use mod directly - mod will give negative numbers. First
// bitwise-and determines positive/negative correctly, then mod.
partitionId = (partitionId & 0xFFFF) %
CLConnectionManager.n_server_partitions;
CLNode node = mConnMgr.partitionWriteHashMap.get(
String.format("%s:%d", namespace, partitionId));
if (node == null) {
batchLists[0].add(new BatchElement(i, key, digest));
nodeNames[0] = null;
}
else {
batchLists[node.id].add(new BatchElement(i, key, digest));
nodeNames[node.id] = node.name;
}
}
clBuffer.destroy();
// Dispatch the work to each node on a different thread.
ArrayList<Thread> allThreads = new ArrayList<Thread>(batchLists.length);
for (int i = 0; i < batchLists.length; i++) {
if (batchLists[i].size() > 0) {
NodeBatchExecutor nbe = new NodeBatchExecutor(results,
batchLists[i], namespace, set, bins, opts, getAll,
getData, nodeNames[i]);
Thread t = new Thread(nbe, "node" + i);
allThreads.add(t);
t.start();
}
}
// Wait for all the threads to finish their work and return results.
Iterator<Thread> threadsIterator = allThreads.iterator();
while (threadsIterator.hasNext()) {
Thread t = threadsIterator.next();
try {
t.join();
}
catch (Exception e) {
info("thread " + t.getId() + " faced some issue: " + e);
}
}
return results;
}
//-------------------------------------------------------
// Transaction Helpers
//-------------------------------------------------------
private ClResult doRequestUsingNode(CLBuffer asb, CLNode node,
ClOptions opts) {
if (mConnMgr == null) {
return new ClResult(ClResultCode.CLIENT_ERROR);
}
int timeout = opts != null ? opts.mTimeout : DEFAULT_TIMEOUT;
int sleepMillis = timeout / RETRY_COUNT;
if (sleepMillis < MIN_TIMEOUT_MS_WAIT) {
sleepMillis = MIN_TIMEOUT_MS_WAIT;
}
if (sleepMillis > MAX_TIMEOUT_MS_WAIT) {
sleepMillis = MAX_TIMEOUT_MS_WAIT;
}
int remainingMillis = timeout;
int tryCount = 0;
ClResult r = null;
long stop = System.currentTimeMillis() + timeout;
do {
CLConnection conn = null;
try {
conn = mConnMgr.getConnection(asb.ns, node, remainingMillis);
int newRemainingMillis =
(int)(stop - System.currentTimeMillis());
if (conn != null && newRemainingMillis <= 0) {
info("connected to " + conn.address.getHostName() +
" within " + remainingMillis +
"ms but timed out on try " + tryCount +
" (timeout = " + timeout + ")");
mConnMgr.releaseConnection(conn, true);
break;
}
remainingMillis = newRemainingMillis;
if (conn != null) {
asb.setTimeout(remainingMillis);
conn.setTimeout(remainingMillis);
conn.send(asb);
r = conn.retrieve(asb);
conn.reportNetworkSuccess();
}
else {
info("could not get connection");
}
}
// Serialize errors aren't node's fault - don't dunn, don't retry.
catch (SerializeException se) {
info("doRequestUsingNode(): serialize exception");
// conn won't be null here.
mConnMgr.releaseConnection(conn, true);
return new ClResult(ClResultCode.SERIALIZE_ERROR);
}
// IO exceptions are the fault of the node - decrease node health.
catch (IOException ioe) {
info("doRequestUsingNode(): IO exception '" + ioe.toString() +
"' when connecting to " +
conn.address.getHostName() + " with " +
remainingMillis + "ms remaining");
// conn won't be null here.
conn.reportNetworkFailure();
mConnMgr.releaseConnection(conn, false);
conn = null;
}
catch (Exception e) {
info("doRequestUsingNode(): exception '" + e.toString() + "'");
//e.printStackTrace();
}
// Always return to pool if no error.
if (conn != null) {
mConnMgr.releaseConnection(conn, true);
conn = null;
}
if (r == null && ClOptions.wantsRetry(opts)) {
// Break if we exceeded caller-specified timeout.
remainingMillis = (int)(stop - System.currentTimeMillis());
if (remainingMillis <= sleepMillis) {
break;
}
debug("doRequestUsingNode(): sleeping " + sleepMillis + "ms");
try {
Thread.sleep((long)sleepMillis);
}
catch (InterruptedException e) {
break;
}
tryCount++;
}
} while (r == null && ClOptions.wantsRetry(opts));
if (r == null) {
r = new ClResult(ClResultCode.TIMEOUT);
}
return r;
}
// Prepare the buffer, then do the transaction.
private ClResult doRequest(CLBuffer asb, ClOptions opts) {
try {
asb.prepare();
}
catch (SerializeException e) {
return new ClResult(ClResultCode.SERIALIZE_ERROR);
}
return doRequestPrepared(asb, opts);
}
// Once we have a buffer, loop getting connections to nodes and making the
// transactions happen.
private ClResult doRequestPrepared(CLBuffer asb, ClOptions opts) {
if (mConnMgr == null) {
return new ClResult(ClResultCode.CLIENT_ERROR);
}
int timeout = opts != null ? opts.mTimeout : DEFAULT_TIMEOUT;
long waitNanos = ((long)timeout * 1000000L) / 6;
// Less than a half a millisecond will usually hurt a server.
if (waitNanos < 500000) {
waitNanos = 500000;
}
int tries = 0;
ClResult r;
long start = System.currentTimeMillis();
do {
if (tries > 50) {
info("transaction with LARGE (" + tries + ") retries, " +
(System.currentTimeMillis() - start) + "ms");
}
else if (tries > 3) {
info("transaction with " + tries + " retries, " +
(System.currentTimeMillis() - start) + "ms");
}
tries++;
r = null;
CLConnection conn = null;
try {
conn = mConnMgr.getConnection(asb.ns, (int)asb.partition_id,
asb.partition_type, timeout);
int deltaMillis = (int)(System.currentTimeMillis() - start);
if (deltaMillis < 0) {
deltaMillis = 0;
}
if (conn != null) {
if (deltaMillis >= timeout) {
info("connected to " + conn.address.getHostName() +
" in " + deltaMillis +
"ms but timed out (timeout = " + timeout + ")");
mConnMgr.releaseConnection(conn, true);
break;
}
asb.setTimeout(timeout - deltaMillis);
conn.setTimeout(timeout - deltaMillis);
conn.send(asb);
r = conn.retrieve(asb);
conn.reportNetworkSuccess();
}
else {
info("could not get connection");
}
}
// Serialize errors aren't node's fault - don't dunn, don't retry.
catch (SerializeException se) {
info("doRequest(): serialize exception");
// conn won't be null here.
mConnMgr.releaseConnection(conn, true);
return new ClResult(ClResultCode.SERIALIZE_ERROR);
}
// IO exceptions are the fault of the node - decrease node health.
catch (IOException ioe) {
int deltaMillis = (int)(System.currentTimeMillis() - start);
if (conn != null) {
info("doRequest(): IO exception '" + ioe.toString() +
"' when connecting to " +
conn.address.getHostName() + " after " +
deltaMillis + "ms");
}
else {
info("doRequest(): IO exception '" + ioe.toString() +
"' after " + deltaMillis + "ms");
}
//ioe.printStackTrace();
if (conn != null) {
conn.reportNetworkFailure();
mConnMgr.releaseConnection(conn, false);
conn = null;
}
}
catch (Exception e) {
info("doRequest(): exception '" + e.toString() + "'");
//e.printStackTrace();
}
// Always return to pool if no error.
if (conn != null) {
mConnMgr.releaseConnection(conn, true);
conn = null;
}
// A strange pathological case has been observed where a thread
// grabs connections, is able to send to the server, but gets
// immediate "connection closed" read errors. For this reason, use a
// form of exponential retry - something that at least gives you
// some quick retry, but backs off if there are consistant errors of
// this type. The error we're seeing now (Jan 2011) seems more like
// a JVM oddity, but we must protect the service from wayward
// clients. Thus, back off on any kind of exception. [BB]
//
// Also this is informative (perhaps dated by the time you read it):
// http://www.sagui.org/~gustavo/blog/code
if (r == null && ClOptions.wantsRetry(opts)) {
// Break if we exceeded caller-specified timeout.
int deltaMillis = (int)(System.currentTimeMillis() - start);
if (deltaMillis < 0) {
deltaMillis = 0;
}
if (deltaMillis + (int)(waitNanos / 1000000) >= timeout) {
break;
}
info("doRequest() waiting " + waitNanos + "ns");
long nanoEnd = System.nanoTime() + waitNanos;
do {
LockSupport.parkNanos(waitNanos);
} while (System.nanoTime() < nanoEnd);
waitNanos *= 2.2;
}
} while (r == null && ClOptions.wantsRetry(opts));
if (r == null) {
r = new ClResult(ClResultCode.TIMEOUT);
}
return r;
}
//-------------------------------------------------------
// Logging Helpers
//-------------------------------------------------------
private static void ClLogInit() {
if (gSdf == null) {
gSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
}
}
public static void ClLog(ClLogLevel logLevel, String msg) {
if (logLevel.ordinal() > gLogLevel.ordinal()) {
return;
}
if (gLogCallback != null) {
gLogCallback.logCallback(logLevel, msg);
return;
}
String date = gSdf != null ? gSdf.format(new Date()) : "";
System.err.println(date + " CITRUSLEAF [" +
Thread.currentThread().getId() + "] " + msg);
}
//=================================================================
// Private Nested Interfaces & Classes
//
private static class BatchElement {
public int posInGlobalBatch;
public Object key;
public byte[] digest;
public BatchElement(int index, Object key, byte[] digest) {
posInGlobalBatch = index;
this.key = key;
this.digest = digest;
}
}
private class NodeBatchExecutor implements Runnable {
private ClResult[] mAllResults;
private ArrayList<BatchElement> mBatchList;
private String mNamespace;
private String mSet;
private boolean mGetAll;
private boolean mGetData;
private ClOptions mOpts;
private Collection<String> mBins;
private String mNodeName;
public NodeBatchExecutor(ClResult[] allResults,
ArrayList<BatchElement> batchList, String namespace, String set,
Collection<String> bins, ClOptions opts, boolean getAll,
boolean getData, String nodeName) {
mAllResults = allResults;
mBatchList = batchList;
mNamespace = namespace;
mSet = set;
mBins = bins;
mGetAll = getAll;
mGetData = getData;
mOpts = opts;
mNodeName = nodeName;
}
public void run() {
CLBuffer asb = CLBuffer.create();
ArrayList<Object> nodeKeys = new ArrayList<Object>();
ArrayList<byte[]> nodeDigests = new ArrayList<byte[]>();
Iterator<BatchElement> listIterator = mBatchList.iterator();
while (listIterator.hasNext()) {
BatchElement elem = listIterator.next();
nodeKeys.add(elem.key);
nodeDigests.add(elem.digest);
}
// Request a batch operation.
asb.set_required(mNamespace, mSet, null, null);
asb.setBatchRead(nodeKeys, nodeDigests);
if (mGetAll) {
asb.readAll();
}
else if (mGetData) {
asb.addRead(mBins);
}
else {
asb.readExists();
asb.readNoBindata();
}
// Prepare the command message to be sent to the server node.
try {
asb.batchPrepare();
}
catch (SerializeException e) {
asb.destroy();
fillError(ClResultCode.SERIALIZE_ERROR);
return;
}
// Send the message to the server node and get back the result.
CLNode node = mNodeName != null ?
mConnMgr.getNodeFromNodeName(mNodeName) : null;
ClResult r = node != null ?
doRequestUsingNode(asb, node, mOpts) :
doRequestPrepared(asb, mOpts);
asb.destroy();
if (r.resultArray == null)
{
fillError(ClResultCode.CLIENT_ERROR);
return;
}
if (r.resultCode != ClResultCode.OK) {
fillError(r.resultCode);
return;
}
Iterator<BatchElement> resultIterator = mBatchList.iterator();
for (int i = 0; resultIterator.hasNext(); i++) {
BatchElement elem = resultIterator.next();
mAllResults[elem.posInGlobalBatch] = r.resultArray[i];
}
}
private void fillError(ClResultCode code) {
Iterator<BatchElement> resultIterator = mBatchList.iterator();
while (resultIterator.hasNext()) {
BatchElement elem = resultIterator.next();
mAllResults[elem.posInGlobalBatch] = new ClResult(code);
}
}
}
}