Package net.citrusleaf

Source Code of net.citrusleaf.CitrusleafClient

/*
* 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 java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.aerospike.client.AerospikeClient;
import com.aerospike.client.AerospikeException;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Log;
import com.aerospike.client.Value;
import com.aerospike.client.Log.Level;
import com.aerospike.client.Operation;
import com.aerospike.client.Record;
import com.aerospike.client.ResultCode;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.RecordExistsAction;
import com.aerospike.client.policy.ScanPolicy;
import com.aerospike.client.policy.WritePolicy;

/**
* Legacy compatibility Layer. This class should only be used for legacy code.
* Please use <code>com.aerospike.client.AerospikeClient</code> for new code.
* <p>
* 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 extends AerospikeClient implements Log.Callback {

  //=================================================================
  // 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 RetryPolicy {
    ONE_SHOT,
    RETRY
  }

  private static final String DEFAULT_NAMESPACE = "ns";


  //=================================================================
  // Globals
  //

  private static SimpleDateFormat gSdf = null;
  private static ClLogLevel gLogLevel = ClLogLevel.INFO;
  private static LogCallback gLogCallback = null;

  //=================================================================
  // Member Data
  //

  private String mDefaultNamespace = DEFAULT_NAMESPACE;
  private final WritePolicy defaultWritePolicy;
  private final Policy defaultPolicy;


  //=================================================================
  // Public Functions
  //

  //-------------------------------------------------------
  // Constructors
  //-------------------------------------------------------

  /**
   * Constructor, creates <code>CitrusleafClient</code> object and initializes
   * logging framework.
   * <p>
   * This constructor does not add hosts.
   */
  public CitrusleafClient() {
    ClLogInit();
    defaultWritePolicy = new WritePolicy();
    defaultWritePolicy.timeout = DEFAULT_TIMEOUT;
    defaultPolicy = new Policy();
    defaultPolicy.timeout = DEFAULT_TIMEOUT;
  }

  /**
   * 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();
    defaultWritePolicy = new WritePolicy();
    defaultWritePolicy.timeout = DEFAULT_TIMEOUT;
    defaultPolicy = new Policy();
    defaultPolicy.timeout = DEFAULT_TIMEOUT;
   
    try {
      super.addServer(hostname, port);
    }
    catch (Exception e) {
    }
  }

  //-------------------------------------------------------
  // 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;
   
    Log.Level ll;
   
    switch (level) {
    case ERROR:
      ll = Log.Level.ERROR;
      break;
    case WARN:
      ll = Log.Level.WARN;
      break;
    case INFO:
    default:
      ll = Log.Level.INFO;
      break;
    case DEBUG:
    case VERBOSE:
      ll = Log.Level.DEBUG;
      break;
    }
    Log.setLevel(ll);
  }

  private static final ClLogLevel[] ClLogLevelLookup = ClLogLevel.values();

  /**
   * Implementation of com.aerospike.client.Log.Callback
   */
  @Override
  public void log(Level level, String message) {
    ClLogLevel cll = ClLogLevelLookup[level.ordinal()];   
    ClLog(cll, message);
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.addServer(hostname, port);
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }
 
  /**
   * Use {@link #isConnected()}.
   */
  @Deprecated
  public boolean connect() {
    return super.isConnected();
  }

  /**
   * Return a list of server nodes in the cluster.
   *
   * @return          list of node names
   */
  public List<String> getNodeNameList() {
    return super.getNodeNames();
  }

  /**
   * 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) {
    try {
      return Key.computeDigest(set, Value.get(key));
    }
    catch (AerospikeException ae) {
      return null;
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.put(defaultWritePolicy, new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), new Bin(binName, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  } 
 
  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, digest), new Bin(binName, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, digest), new Bin(bin.name, bin.value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, digest), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.put(getWritePolicy(opts, wOpts), new Key(namespace, digest), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.append(defaultWritePolicy, new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.append(getWritePolicy(opts, wOpts), new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.append(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), new Bin(binName, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.append(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.append(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.prepend(defaultWritePolicy, new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.prepend(getWritePolicy(opts, wOpts), new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.prepend(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), new Bin(binName, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.prepend(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.prepend(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.add(defaultWritePolicy, new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(mDefaultNamespace, "", Value.get(key)), new Bin(null, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), new Bin(binName, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    Operation[] operations = new Operation[bins.size() + 1];
    int count = 0;
   
    for (ClBin bin : bins) {
      operations[count++] = Operation.add(new Bin(bin.name, bin.value));
    }
    operations[count] = Operation.get();

    try {
      Record record = super.operate(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), operations);
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    Operation[] operations = new Operation[bins.size() + 1];
    int count = 0;
   
    for (Entry<String,Object> entry : bins.entrySet()) {
      operations[count++] = Operation.add(new Bin(entry.getKey(), entry.getValue()));
    }
    operations[count] = Operation.get();

    try {
      Record record = super.operate(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), operations);
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, digest), new Bin(binName, value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, digest), new Bin(bin.name, bin.value));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, digest), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.add(getWritePolicy(opts, wOpts), new Key(namespace, digest), getBins(bins));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      if (super.delete(getWritePolicy(opts, wOpts), new Key(mDefaultNamespace, "", Value.get(key)))) {
        return ClResultCode.OK;       
      }
      else {
        return ClResultCode.KEY_NOT_FOUND_ERROR;       
      }
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      super.delete(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      super.delete(getWritePolicy(opts, wOpts), new Key(namespace, digest));
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      Record record = super.get(defaultPolicy, new Key(mDefaultNamespace, "", Value.get(key)), "");
      ClResult r = getResult(record);
      // Single-bin result is in results Map, also put it in result Object:
      return r.results != null ? r.results.get("") : null;
    }
    catch (AerospikeException ae) {
      return 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) {
    try {
      String name = (binName == null)? "" : binName;
      Record record = super.get(getPolicy(opts), new Key(namespace, set, Value.get(key)), name);
      ClResult r = getResult(record);
      // Result for one bin is in results Map, also put it in result Object:
      if (r.results != null) {
        r.result = r.results.get(name);
      }
      return r;
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    try {
      Record record = super.get(getPolicy(opts), new Key(namespace, set, Value.get(key)), binNames);
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    try {
      Record record = super.get(getPolicy(opts), new Key(namespace, set, Value.get(key)), getBinNames(binNames));
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    try {
      Record record = super.get(getPolicy(opts), new Key(namespace, set, Value.get(key)));
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  } 

  /**
   * 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) {
    ClWriteOptions wOpts = new ClWriteOptions();
    wOpts.expiration = expiration;
   
    Operation[] operations = new Operation[binNames.size() + 1];
    int count = 0;
   
    for (String binName : binNames) {
      operations[count++] = Operation.get(binName);
    }
    operations[count] = Operation.touch();

    try {
      Record record = super.operate(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), operations);
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    try {
      Key[] keyArray = getKeys(namespace, set, keys);
      Record[] records = super.get(getPolicy(opts), keyArray, binName);
      return getRecordResults(records);
    }
    catch (AerospikeException ae) {
      return getResults(ae, keys.size());
    }
  }

  /**
   * 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) {
    try {
      Key[] keyArray = getKeys(namespace, set, keys);
      Record[] records = super.get(getPolicy(opts), keyArray, getBinNames(binNames));
      return getRecordResults(records);
    }
    catch (AerospikeException ae) {
      return getResults(ae, keys.size());
    }
  }

  /**
   * 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) {
    try {
      Key[] keyArray = getKeys(namespace, set, keys);
      Record[] records = super.get(getPolicy(opts), keyArray);
      return getRecordResults(records);
    }
    catch (AerospikeException ae) {
      return getResults(ae, keys.size());
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      Record record = super.get(getPolicy(opts), new Key(namespace, digest), binName);
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    try {
      Record record = super.get(getPolicy(opts), new Key(namespace, digest), getBinNames(binNames));
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {
    try {
      Record record = super.get(getPolicy(opts), new Key(namespace, digest));
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {
      boolean exists = super.exists(defaultPolicy, new Key(mDefaultNamespace, "", Value.get(key)));
      return exists? ClResultCode.OK : ClResultCode.KEY_NOT_FOUND_ERROR;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      boolean exists = super.exists(getPolicy(opts), new Key(namespace, set, Value.get(key)));
      return exists? ClResultCode.OK : ClResultCode.KEY_NOT_FOUND_ERROR;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {
      Key[] keyArray = getKeys(namespace, set, keys);
      boolean[] existsArray = super.exists(getPolicy(opts), keyArray);
      return getKeyResults(existsArray);
    }
    catch (AerospikeException ae) {
      return getResults(ae, keys.size());
    }
  }

  //-------------------------------------------------------
  // 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) {
    try {   
      ScanForwarder sf = new ScanForwarder(scanCb, userData);
      super.scanAll(getScanPolicy(opts, null, noBinData), namespace, set, sf);
      return new ClResult(ClResultCode.OK);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  /**
   * 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) {   
    try {   
      ScanForwarder sf = new ScanForwarder(scanCb, userData);
      super.scanNode(getScanPolicy(opts, scanOpts, noBinData), nodeName, namespace, set, sf);
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    try {   
      ScanForwarder sf = new ScanForwarder(scanCb, userData);
      super.scanAll(getScanPolicy(null, null, false), namespace, set, sf);
      return ClResultCode.OK;
    }
    catch (AerospikeException ae) {
      return getResultCode(ae);
    }
  }

  /**
   * 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) {
    List<String> nodeNames = super.getNodeNames();
   
    try {   
      ScanForwarder sf = new ScanForwarder(scanCb, userData);
      super.scanAll(getScanPolicy(opts, scanOpts, noBinData), namespace, set, sf);
      return getResultCodes(ClResultCode.OK, nodeNames);
    }
    catch (AerospikeException ae) {
      return getResultCodes(ae, nodeNames);
    }
  }

  //-------------------------------------------------------
  // 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) {
    Operation[] operations = new Operation[readBinNames.size() + writeBins.size()];
    int count = 0;
   
    for (ClBin bin : writeBins) {
      operations[count++] = Operation.put(new Bin(bin.name, bin.value));
    }

    for (String binName : readBinNames) {
      operations[count++] = Operation.get(binName);
    }
   
    try {
      Record record = super.operate(getWritePolicy(opts, wOpts), new Key(namespace, set, Value.get(key)), operations);
      return getResult(record);
    }
    catch (AerospikeException ae) {
      return getResult(ae);
    }
  }

  //=================================================================
  // 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;

    /**
     * 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.
     * <p>
     * A timeout value of <code>0</code> means never timeout.
     *
     * @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.
     * <p>
     * A timeout value of <code>0</code> means never 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;
    }
  }

  //=================================================================
  // Private Functions
  //

  //-------------------------------------------------------
  // Logging Helpers
  //-------------------------------------------------------

  private void ClLogInit() {
    if (gSdf == null) {
      gSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
    }
    Log.setLevel(Log.Level.INFO);
    Log.setCallback(this);
  }

  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 ScanPolicy getScanPolicy(ClOptions opts, ClScanningOptions scanOpts, boolean noBinData) {
    ScanPolicy policy = new ScanPolicy();
   
    if (opts != null) {
      policy.timeout = opts.mTimeout;
      policy.maxRetries = 0;
    }
   
    if (scanOpts != null) {
      policy.concurrentNodes = scanOpts.mConcurrentNodes;
      policy.failOnClusterChange = scanOpts.mFailOnClusterChange;
      //policy.threadsPerNode = scanOpts.mThreadsPerNode;
    }
    policy.includeBinData = ! noBinData;
    policy.scanPercent = 100;
    return policy;
  }
 
  private static WritePolicy getWritePolicy(ClOptions opts, ClWriteOptions wOpts) {
    WritePolicy policy = new WritePolicy();
 
    setPolicy(opts, policy);
   
    if (wOpts != null) {
      policy.expiration = wOpts.expiration;     

      if (wOpts.unique) {
        policy.recordExistsAction = RecordExistsAction.FAIL;
      }
      else if (wOpts.mUseGeneration) {
        policy.generation = wOpts.mGeneration;
        policy.recordExistsAction = RecordExistsAction.EXPECT_GEN_EQUAL;
      }
      else if (wOpts.mUseGenerationGt) {
        policy.generation = wOpts.mGeneration;
        policy.recordExistsAction = RecordExistsAction.EXPECT_GEN_GT;       
      }
      else if (wOpts.mUseGenerationDup) {
        policy.generation = wOpts.mGeneration;
        policy.recordExistsAction = RecordExistsAction.DUPLICATE;       
      }
    }
    return policy;
  }
 
  private static Policy getPolicy(ClOptions opts) {
    Policy policy = new Policy();
    setPolicy(opts, policy);
    return policy;
  }

  private static void setPolicy(ClOptions opts, Policy policy) {   
    if (opts != null) {
      policy.timeout = opts.mTimeout;
     
      if (opts.mRetryPolicy == RetryPolicy.ONE_SHOT) {
        policy.maxRetries = 0;     
      }
      else {
        policy.sleepBetweenRetries = policy.timeout / (policy.maxRetries + 1);
       
        if (policy.sleepBetweenRetries > 2000) {
          policy.sleepBetweenRetries = 2000;
        }
      }
    }
    else {
      policy.timeout = DEFAULT_TIMEOUT;
      policy.maxRetries = 2;     
      policy.sleepBetweenRetries = policy.timeout / (policy.maxRetries + 1);
    }
  }

  private static Key[] getKeys(String namespace, String set, Collection<Object> keys) throws AerospikeException {
    Key[] keyArray = new Key[keys.size()];
    int count = 0;
   
    for (Object key : keys) {
      keyArray[count++] = new Key(namespace, set, Value.get(key));
    }
    return keyArray;   
 
 
  private static Bin[] getBins(Collection<ClBin> bins) throws AerospikeException {
    if (bins == null) {
      throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Null bins");
    }
    Bin[] binArray = new Bin[bins.size()];
    int count = 0;
   
    for (ClBin bin : bins) {
      binArray[count++] = new Bin(bin.name, bin.value);
    }
    return binArray;
  }

  private static Bin[] getBins(Map<String, Object> bins) throws AerospikeException {
    if (bins == null) {
      throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Null bins");
    }
    Bin[] binArray = new Bin[bins.size()];
    int count = 0;
   
    for (Entry<String,Object> entry : bins.entrySet()) {
      binArray[count++] = new Bin(entry.getKey(), entry.getValue());
    }
    return binArray;
  }

  private static String[] getBinNames(Collection<String> binNames) throws AerospikeException {
    if (binNames == null) {
      throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Null bin names");
    }
    String[] binArray = new String[binNames.size()];
    int count = 0;
   
    for (String binName : binNames) {
      binArray[count++] = binName;
    }
    return binArray;
  }
 
  private static ClResult[] getRecordResults(Record[] records) {
    ClResult[] results = new ClResult[records.length];
   
    for (int i = 0; i < records.length; i++) {
      results[i] = getResult(records[i]);
    }
    return results;
  }
 
  private static ClResult[] getKeyResults(boolean[] existsArray) {
    ClResult[] results = new ClResult[existsArray.length];
   
    for (int i = 0; i < existsArray.length; i++) {
      ClResultCode rc = (existsArray[i])? ClResultCode.OK : ClResultCode.KEY_NOT_FOUND_ERROR;
      results[i] = new ClResult(rc);
    }
    return results;
  }

  private static ClResult getResult(Record record)  {
    ClResult clr;

    if (record != null) {
      clr = new ClResult(ClResultCode.OK);
      clr.results = record.bins;
      clr.generation = record.generation;
      clr.results_dup = record.duplicates;
    }
    else {
      clr = new ClResult(ClResultCode.KEY_NOT_FOUND_ERROR);
    }
    return clr;
  }

  private static ClResult[] getResults(AerospikeException ae, int size) {
    ClResult[] array = new ClResult[size];
    ClResult result = new ClResult(getResultCode(ae));
   
    for (int i = 0; i < size; i++) {
      array[i] = result;
    }
    return array;
  }

  private static ClResult getResult(AerospikeException ae) {
    return new ClResult(getResultCode(ae));
  }

  private static Map<String,ClResultCode> getResultCodes(AerospikeException ae, List<String> nodeNames) {
    ClResultCode rc = getResultCode(ae);
    return getResultCodes(rc, nodeNames);
  }

  private static Map<String,ClResultCode> getResultCodes(ClResultCode rc, List<String> nodeNames) {
    Map<String,ClResultCode> map = new HashMap<String,ClResultCode>(nodeNames.size());
   
    for (String nodeName : nodeNames) {
      map.put(nodeName, rc);
    }
    return map;
  }

  private static ClResultCode getResultCode(AerospikeException ae) {
    switch (ae.getResultCode()) {
    case ResultCode.INVALID_NODE_ERROR:
    case ResultCode.PARSE_ERROR:
      return ClResultCode.CLIENT_ERROR;
     
    case ResultCode.SERIALIZE_ERROR:
      return ClResultCode.SERIALIZE_ERROR;
     
    case ResultCode.OK:
      return ClResultCode.OK;
     
    case ResultCode.SERVER_ERROR:
    default:
      return ClResultCode.SERVER_ERROR;
     
    case ResultCode.KEY_NOT_FOUND_ERROR:
      return ClResultCode.KEY_NOT_FOUND_ERROR;
     
    case ResultCode.GENERATION_ERROR:
      return ClResultCode.GENERATION_ERROR;
     
    case ResultCode.PARAMETER_ERROR:
      return ClResultCode.PARAMETER_ERROR;
     
    case ResultCode.KEY_EXISTS_ERROR:
      return ClResultCode.KEY_EXISTS_ERROR;
     
    case ResultCode.BIN_EXISTS_ERROR:
      return ClResultCode.BIN_EXISTS_ERROR;
     
    case ResultCode.CLUSTER_KEY_MISMATCH:
      return ClResultCode.CLUSTER_KEY_MISMATCH;
     
    case ResultCode.SERVER_MEM_ERROR:
      return ClResultCode.SERVER_MEM_ERROR;
     
    case ResultCode.TIMEOUT:
      return ClResultCode.TIMEOUT;
     
    case ResultCode.NO_XDS:
      return ClResultCode.NO_XDS;
     
    case ResultCode.SERVER_NOT_AVAILABLE:
      return ClResultCode.SERVER_NOT_AVAILABLE;
     
    case ResultCode.BIN_TYPE_ERROR:
      return ClResultCode.BIN_TYPE_ERROR;
     
    case ResultCode.RECORD_TOO_BIG:
      return ClResultCode.RECORD_TOO_BIG;
     
    case ResultCode.KEY_BUSY:
      return ClResultCode.KEY_BUSY;
    }
  }
}
TOP

Related Classes of net.citrusleaf.CitrusleafClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.