Chapter 16. The Hot Rod Interface

16.1. About Hot Rod

Hot Rod is a binary TCP client-server protocol used in Red Hat JBoss Data Grid. It was created to overcome deficiencies in other client/server protocols, such as Memcached.

Hot Rod will failover on a server cluster that undergoes a topology change. Hot Rod achieves this by providing regular updates to clients about the cluster topology.

Hot Rod enables clients to do smart routing of requests in partitioned or distributed Red Hat JBoss Data Grid server clusters. To do this, Hot Rod allows clients to determine the partition that houses a key and then communicate directly with the server that has the key. This functionality relies on Hot Rod updating the cluster topology with clients, and that the clients use the same consistent hash algorithm as the servers.

Red Hat JBoss Data Grid contains a server module that implements the Hot Rod protocol. The Hot Rod protocol facilitates faster client and server interactions in comparison to other text-based protocols and allows clients to make decisions about load balancing, failover and data location operations.

16.2. Hot Rod Headers

16.2.1. Hot Rod Header Data Types

All keys and values used for Hot Rod in Red Hat JBoss Data Grid are stored as byte arrays. Certain header values, such as those for REST and Memcached, are stored using the following data types instead:

Table 16.1. Header Data Types

Data TypeSizeDetails

vInt

Between 1-5 bytes.

Unsigned variable length integer values.

vLong

Between 1-9 bytes.

Unsigned variable length long values.

string

-

Strings are always represented using UTF-8 encoding.

16.2.2. Request Header

When using Hot Rod to access Red Hat JBoss Data Grid, the contents of the request header consist of the following:

Table 16.2. Request Header Fields

Field NameData Type/SizeDetails

Magic

1 byte

Indicates whether the header is a request header or response header.

Message ID

vLong

Contains the message ID. Responses use this unique ID when responding to a request. This allows Hot Rod clients to implement the protocol in an asynchronous manner.

Version

1 byte

Contains the Hot Rod server version.

Opcode

1 byte

Contains the relevant operation code. In a request header, opcode can only contain the request operation codes.

Cache Name Length

vInt

Stores the length of the cache name. If Cache Name Length is set to 0 and no value is supplied for Cache Name, the operation interacts with the default cache.

Cache Name

string

Stores the name of the target cache for the specified operation. This name must match the name of a predefined cache in the cache configuration file.

Flags

vInt

Contains a numeric value of variable length that represents flags passed to the system. Each bit represents a flag, except the most significant bit, which is used to determine whether more bytes must be read. Using a bit to represent each flag facilitates the representation of flag combinations in a condensed manner.

Client Intelligence

1 byte

Contains a value that indicates the client capabilities to the server.

Topology ID

vInt

Contains the last known view ID in the client. Basic clients supply the value 0 for this field. Clients that support topology or hash information supply the value 0 until the server responds with the current view ID, which is subsequently used until a new view ID is returned by the server to replace the current view ID.

16.2.3. Response Header

When using Hot Rod to access Red Hat JBoss Data Grid, the contents of the response header consist of the following:

Table 16.3. Response Header Fields

Field NameData TypeDetails

Magic

1 byte

Indicates whether the header is a request or response header.

Message ID

vLong

Contains the message ID. This unique ` ID` is used to pair the response with the original request. This allows Hot Rod clients to implement the protocol in an asynchronous manner.

Opcode

1 byte

Contains the relevant operation code. In a response header, opcode can only contain the response operation codes.

Status

1 byte

Contains a code that represents the status of the response.

Topology Change Marker

1 byte

Contains a marker byte that indicates whether the response is included in the topology change information.

16.2.4. Topology Change Headers

16.2.4.1. Topology Change Headers

When using Hot Rod to access Red Hat JBoss Data Grid, response headers respond to changes in the cluster or view formation by looking for clients that can distinguish between different topologies or hash distributions. The Hot Rod server compares the current topology ID and the topology ID sent by the client and, if the two differ, it returns a new topology ID.

16.2.4.2. Topology Change Marker Values

The following is a list of valid values for the Topology Change Marker field in a response header:

Table 16.4. Topology Change Marker Field Values

ValueDetails

0

No topology change information is added.

1

Topology change information is added.

16.2.4.3. Topology Change Headers for Topology-Aware Clients

The response header sent to topology-aware clients when a topology change is returned by the server includes the following elements:

Table 16.5. Topology Change Header Fields

Response Header FieldsData Type/SizeDetails

Response Header with Topology Change Marker

variable

Refer to Response Header.

Topology ID

vInt

Topology ID.

Num Servers in Topology

vInt

Contains the number of Hot Rod servers running in the cluster. This value can be a subset of the entire cluster if only some nodes are running Hot Rod servers.

mX: Host/IP Length

vInt

Contains the length of the hostname or IP address of an individual cluster member. Variable length allows this element to include hostnames, IPv4 and IPv6 addresses.

mX: Host/IP Address

string

Contains the hostname or IP address of an individual cluster member. The Hot Rod client uses this information to access the individual cluster member.

mX: Port

Unsigned Short. 2 bytes

Contains the port used by Hot Rod clients to communicate with the cluster member.

The three entries with the prefix mX, are repeated for each server in the topology. The first server in the topology’s information fields will be prefixed with m1 and the numerical value is incremented by one for each additional server till the value of X equals the number of servers specified in the num servers in topology field.

16.2.4.4. Topology Change Headers for Hash Distribution-Aware Clients

The response header sent to clients when a topology change is returned by the server includes the following elements:

Table 16.6. Topology Change Header Fields

FieldData Type/SizeDetails

Response Header with Topology Change Marker

variable

Refer to Response Header.

Topology ID

vInt

Topology ID.

Number Key Owners

Unsigned short. 2 bytes.

Contains the number of globally configured copies for each distributed key. Contains the value 0 if distribution is not configured on the cache.

Hash Function Version

1 byte

Contains a pointer to the hash function in use. Contains the value 0 if distribution is not configured on the cache.

Hash Space Size

vInt

Contains the modulus used by JBoss Data Grid for all module arithmetic related to hash code generation. Clients use this information to apply the correct hash calculations to the keys. Contains the value 0 if distribution is not configured on the cache.

Number servers in topology

vInt

Contains the number of [path]_ Hot Rod_ servers running in the cluster. This value can be a subset of the entire cluster if only some nodes are running [path]_ Hot Rod_ servers. This value also represents the number of host to port pairings included in the header.

Number Virtual Nodes Owners

vInt

Contains the number of configured virtual nodes. Contains the value 0 if no virtual nodes are configured or if distribution is not configured on the cache.

mX: Host/IP Length

vInt

Contains the length of the hostname or [path]_ IP_ address of an individual cluster member. Variable length allows this element to include hostnames, [path]_ IPv4_ and [path]_ IPv6_ addresses.

mX: Host/IP Address

string

Contains the hostname or [path]_ IP_ address of an individual cluster member. The [path]_ Hot Rod_ client uses this information to access the individual cluster member.

mX: Port

Unsigned short. 2 bytes.

Contains the port used by [path]_ Hot Rod_ clients to communicate with the cluster member.

Hash Function Version

1 byte

0x03

Number of Segments in Topology

vInt

Total number of segments in the topology.

Number of Owners in Segment

1 byte

This can be either 0, 1, or 2 owners.

First Wwner’s Index

vInt

Given the list of all nodes, the position of this owner in this list. This is only present if number of owners for this segment is 1 or 2.

Second Owner’s Index

vInt

Given the list of all nodes, the position of this owner in this list. This is only present if number of owners for this segment is 2.

Note

Even though it is possible to have more than 2 owners per segment, the Hot Rod protocol limits the number of owners to send for efficiency reasons.

The three entries with the prefix mX, are repeated for each server in the topology. The first server in the topology’s information fields will be prefixed with m1 and the numerical value is incremented by one for each additional server till the value of X equals the number of servers specified in the num servers in topology field.

16.3. Hot Rod Operations

16.3.1. Hot Rod Operations

The following are valid operations when using Hot Rod protocol 1.3 to interact with Red Hat JBoss Data Grid:

  • Authenticate
  • AuthMechList
  • BulkGet
  • BulkKeysGet
  • Clear
  • ContainsKey
  • Exec
  • Get
  • GetAll
  • GetWithMetadata
  • GetWithVersion
  • IterationEnd
  • IterationNext
  • IterationStart
  • Ping
  • Put
  • PutAll
  • PutIfAbsent
  • Query
  • Remove
  • RemoveIfUnmodified
  • Replace
  • ReplaceIfUnmodified
  • Stats
  • Size
Important

When using the RemoteCache API to call the Hot Rod client’s Put , PutIfAbsent , Replace , and ReplaceWithVersion operations, if lifespan is set to a value greater than 30 days, the value is treated as UNIX time and represents the number of seconds since the date 1/1/1970.

16.3.2. Hot Rod Authenticate Operation

The purpose of this operation is to authenticate a client against a server using SASL. The authentication process, depending on the chosen mech, might be a multi-step operation. Once complete the connection becomes authenticated.

The Authenticate operation request format includes the following:

Table 16.7. Authenticate Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

Mech

String

String containing the name of the mech chosen by the client for authentication. Empty on the successive invocations.

Response length

vInt

Length of the SASL client response.

Response data

byte array

The SASL client response.

The response header for this operation contains the following:

Table 16.8. Authenticate Operation Response Format

FieldData TypeDetails

Header

variable

Response header.

Completed

byte

0 if further processing is needed, or 1 if authentication is complete.

Challenge length

vInt

Length of the SASL server challenge.

Challenge data

byte array

The SASL server challenge.

16.3.3. Hot Rod AuthMechList Operation

The purpose of this operation is to obtain the list of valid SASL authentication mechs supported by the server. The client will then need to issue an Authenticate request with the preferred mech.

The AuthMechList operation request format includes the following:

Table 16.9. AuthMechList Operation Request Format

FieldData TypeDetails

Header

Variable

Request header

The response header for this operation contains the following:

Table 16.10. AuthMechList Operation Response Format

FieldData TypeDetails

Header

Variable

Response header

Mech count

vInt

The number of mechs.

Mech

String

String containing the name of the SASL mech in its IANA-registered form (e.g. GSSAPI, CRAM-MD5, etc)

The Mech value recurs for each supported mech.

16.3.4. Hot Rod BulkGet Operation

A Hot Rod BulkGet operation uses the following request format:

Table 16.11. BulkGet Operation Request Format

FieldData TypeDetails

Header

variable

Request Header.

Entry Count

vInt

Contains the maximum number of Red Hat JBoss Data Grid entries to be returned by the server. The entry is the key and value pair.

The response header for this operation contains one of the following response statuses:

Table 16.12. BulkGet Operation Response Format

FieldData TypeDetails

Header

variable

Response Header

More

vInt

Represents if more entries must be read from the stream. While More is set to 1, additional entries follow until the value of More is set to 0, which indicates the end of the stream.

Key Length

vInt

Contains the length of the key.

Key

byte array

Contains the key value.

Value Length

vInt

Contains the length of the value.

Value

byte array

Contains the value.

For each entry that was requested, a More, Key Size, Key, Value Size and Value entry is appended to the response.

16.3.5. Hot Rod BulkKeysGet Operation

A Hot Rod BulkKeysGet operation uses the following request format:

Table 16.13. BulkKeysGet Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

Scope

vInt

  • 0 = Default Scope - This scope is used by RemoteCache.keySet() method. If the remote cache is a distributed cache, the server launches a map/reduce operation to retrieve all keys from all of the nodes (A topology-aware Hot Rod Client could be load balancing the request to any one node in the cluster). Otherwise, it will get keys from the cache instance local to the server receiving the request, as the keys must be the same across all nodes in a replicated cache.
  • 1 = Global Scope - This scope behaves the same to Default Scope.
  • 2 = Local Scope - In situations where the remote cache is a distributed cache, the server will not launch a map/reduce operation to retrieve keys from all nodes. Instead, it will only get keys local from the cache instance local to the server receiving the request.

The response header for this operation contains one of the following response statuses:

Table 16.14. BulkKeysGet Operation Response Format

FieldData TypeDetails

Header

variable

Response header.

Response Status

1 byte

0x00 = success, data follows.

More

1 byte

One byte representing whether more keys need to be read from the stream. When set to 1 an entry follows, when set to 0, it is the end of stream and no more entries are left to read.

Key Length

vInt

Length of key

Key

byte array

Retrieved key.

More

1 byte

One byte representing whether more entries need to be read from the stream. So, when it’s set to 1, it means that an entry follows, whereas when it’s set to 0, it’s the end of stream and no more entries are left to read.

The values Key Length and Key recur for each key.

16.3.6. Hot Rod Clear Operation

The clear operation format includes only a header.

Valid response statuses for this operation are as follows:

Table 16.15. Clear Operation Response

Response StatusDetails

0x00

Red Hat JBoss Data Grid was successfully cleared.

16.3.7. Hot Rod ContainsKey Operation

A Hot Rod ContainsKey operation uses the following request format:

Table 16.16. ContainsKey Operation Request Format

FieldData TypeDetails

Header

-

-

Key Length

vInt

Contains the length of the key. The vInt data type is used because of its size (up to 5 bytes), which is larger than the size of Integer.MAX_VALUE. However, Java disallows single array sizes to exceed the size of Integer.MAX_VALUE. As a result, this vInt is also limited to the maximum size of Integer.MAX_VALUE .

Key

Byte array

Contains a key, the corresponding value of which is requested.

The response header for this operation contains one of the following response statuses:

Table 16.17. ContainsKey Operation Response Format

Response StatusDetails

0x00

Successful operation.

0x02

The key does not exist.

The response for this operation is empty.

16.3.8. Hot Rod Exec Operation

The Exec operation request format includes the following:

Table 16.18. Exec Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

Script

String

Name of the script to execute.

Parameter Count

vInt

The number of parameters.

Parameter Name (per parameter)

String

The name of the parameter.

Parameter Length (per parameter)

vInt

The length of the parameter.

Parameter Value (per parameter)

byte array

The value of the parameter.

The response header for this operation contains the following:

Table 16.19. Exec Operation Response Format

FieldData TypeDetails

Header

variable

Response header.

Response status

1 byte

0x00 if the execution completed successfully. 0x85 if the server resulted in an error.

Value Length

vInt

If success, length of return value.

Value

byte array

If success, the result of the execution.

16.3.9. Hot Rod Get Operation

A Hot Rod Get operation uses the following request format:

Table 16.20. Get Operation Request Format

FieldData TypeDetails

Header

Variable

Request Header

Key Length

vInt

Contains the length of the key. The vInt data type is used because of its size (up to 5 bytes), which is larger than the size of Integer.MAX_VALUE. However, Java disallows single array sizes to exceed the size of Integer.MAX_VALUE. As a result, this vInt is also limited to the maximum size of Integer.MAX_VALUE.

Key

Byte array

Contains a key, the corresponding value of which is requested.

The response header for this operation contains one of the following response statuses:

Table 16.21. Get Operation Response Format

Response StatusDetails

0x00

Successful operation.

0x02

The key does not exist.

The format of the get operation’s response when the key is found is as follows:

Table 16.22. Get Operation Response Format

FieldData TypeDetails

Header

Variable

Response Header

Value Length

vInt

Contains the length of the value.

Value

Byte array

Contains the requested value.

16.3.10. Hot Rod GetAll Operation

Bulk operation to get all entries that map to a given set of keys.

A Hot Rod GetAll operation uses the following request format:

Table 16.23. GetAll Operation Request Format

FieldData TypeDetails

Header

variable

Request header

Key Count

vInt

How many keys to find entities for.

Key Length

vInt

Length of key.

Key

byte array

Retrieved key.

The Key Length and Key values recur for each key.

The response header for this operation contains the following:

Table 16.24. GetAll Operation Response Format

FieldData TypeDetails

Header

variable

Response header

Entry count

vInt

How many entries are being returned.

Key Length

vInt

Length of key.

Key

byte array

Retrieved key.

Value Length

vInt

Length of value.

Value

byte array

Retrieved value.

The Key Length, Key, Value Length, and Value entries recur per key and value.

16.3.11. Hot Rod GetWithMetadata Operation

A Hot Rod GetWithMetadata operation uses the following request format:

Table 16.25. GetWithMetadata Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

Key Length

vInt

Length of key. Note that the size of a vInt can be up to five bytes, which theoretically can produce bigger numbers than Integer.MAX_VALUE. However, Java cannot create a single array that is bigger than Integer.MAX_VALUE, hence the protocol limits vInt array lengths to Integer.MAX_VALUE.

Key

byte array

Byte array containing the key whose value is being requested.

The response header for this operation contains one of the following response statuses:

Table 16.26. GetWithMetadata Operation Response Format

FieldData TypeDetails

Header

variable

Response header.

Response status

1 byte

0x00 = success, if key retrieved.

0x02 = if key does not exist.

Flag

1 byte

A flag indicating whether the response contains expiration information. The value of the flag is obtained as a bitwise OR operation between INFINITE_LIFESPAN (0x01) and INFINITE_MAXIDLE (0x02).

Created

Long

(optional) a Long representing the timestamp when the entry was created on the server. This value is returned only if the flag’s INFINITE_LIFESPAN bit is not set.

Lifespan

vInt

(optional) a vInt representing the lifespan of the entry in seconds. This value is returned only if the flag’s INFINITE_LIFESPAN bit is not set.

LastUsed

Long

(optional) a Long representing the timestamp when the entry was last accessed on the server. This value is returned only if the flag’s INFINITE_MAXIDLE bit is not set.

MaxIdle

vInt

(optional) a vInt representing the maxIdle of the entry in seconds. This value is returned only if the flag’s INFINITE_MAXIDLE bit is not set.

Entry Version

8 bytes

Unique value of an existing entry modification. The protocol does not mandate that entry_version values are sequential, however they need to be unique per update at the key level.

Value Length

vInt

If success, length of value.

Value

byte array

If success, the requested value.

16.3.12. Hot Rod GetWithVersion Operation

A Hot Rod GetWithVersion operation uses the following request format:

Table 16.27. GetWithVersion Operation Request Format

FieldData TypeDetails

Header

Variable

Request Header

Key Length

vInt

Contains the length of the key. The vInt data type is used because of its size (up to 5 bytes), which is larger than the size of Integer.MAX_VALUE. However, Java disallows single array sizes to exceed the size of Integer.MAX_VALUE. As a result, this vInt is also limited to the maximum size of Integer.MAX_VALUE.

Key

Byte array

Contains a key, the corresponding value of which is requested.

The response header for this operation contains one of the following response statuses:

Table 16.28. GetWithVersion Operation Response Format

Response StatusDetails

0x00

Successful operation.

0x02

The key does not exist.

The format of the GetWithVersion operation’s response when the key is found is as follows:

Table 16.29. GetWithVersion Operation Response Format

FieldData TypeDetails

Header

variable

Response header

Entry Version

8 bytes

Unique value of an existing entry’s modification. The protocol does not mandate that entry_version values are sequential. They just need to be unique per update at the key level.

Value Length

vInt

Contains the length of the value.

Value

Byte array

Contains the requested value.

16.3.13. Hot Rod IterationEnd Operation

The IterationEnd operation request format includes the following:

Table 16.30. IterationEnd Operation Request Format

FieldData TypeDetails

iterationId

String

The unique id of the iteration.

The following are the valid response values returned from this operation:

Table 16.31. IterationEnd Operation Response Format

Response StatusDetails

0x00

Successful operation.

0x05

Error for non existent iterationId.

16.3.14. Hot Rod IterationNext Operation

The IterationNext operation request format includes the following:

Table 16.32. IterationNext Operation Request Format

FieldData TypeDetails

IterationId

String

The unique id of the iteration.

The response header for this operation contains the following:

Table 16.33. IterationNext Operation Response Format

FieldData TypeDetails

Finished segments size

vInt

Size of the bitset representing segments that were finished iterating.

Finished segments

byte array

Bitset encoding of the segments that were finished iterating.

Entry count

vInt

How many entries are being returned.

Number of value projections

vInt

Number of projections for the values.

Metadata

1 byte

If set, entry has metadata associated.

Expiration

1 byte

A flag indicating whether the response contains expiration information. The value of the flag is obtained as a bitwise OR operation between INFINITE_LIFESPAN (0x01) and INFINITE_MAXIDLE (0x02). Only present if the metadata flag above is set.

Created

Long

(optional) a Long representing the timestamp when the entry was created on the server. This value is returned only if the flag’s INFINITE_LIFESPAN bit is not set.

Lifespan

vInt

(optional) a vInt representing the lifespan of the entry in seconds. This value is returned only if the flag’s INFINITE_LIFESPAN bit is not set.

LastUsed

Long

(optional) a Long representing the timestamp when the entry was last accessed on the server. This value is returned only if the flag’s INFINITE_MAXIDLE bit is not set.

MaxIdle

vInt

(optional) a vInt representing the maxIdle of the entry in seconds. This value is returned only if the flag’s INFINITE_MAXIDLE bit is not set.

Entry Version

8 bytes

Unique value of an existing entry’s modification. Only present if Metadata flag is set.

Key Length

vInt

Length of key.

Key

byte array

Retrieved key.

Value Length

vInt

Length of value.

Value

byte array

Retrieved value.

For each entry the Metadata, Expiration, Created, Lifespan, LastUsed, MaxIdle, Entry Version, Key Length, Key, Value Length, and Value fields recur.

16.3.15. Hot Rod IterationStart Operation

The IterationStart operation request format includes the following:

Table 16.34. IterationStart Operation Request Format

FieldData TypeDetails

Segments size

signed vInt

Size of the bitset encoding of the segments ids to iterate on. The size is the maximum segment id rounded to nearest multiple of 8. A value -1 indicates no segment filtering is to be done

Segments

byte array

(Optional) Contains the segments ids bitset encoded, where each bit with value 1 represents a segment in the set. Byte order is little-endian. Example: segments [1,3,12,13] would result in the following encoding:

00001010 00110000
size: 16 bits
first byte: represents segments from 0 to 7, from which 1 and 3 are set
second byte: represents segments from 8 to 15, from which 12 and 13 are set

More details in the java.util.BitSet implementation. Segments will be sent if the previous field is not negative

FilterConverter size

signed vInt

The size of the String representing a KeyValueFilterConverter factory name deployed on the server, or -1 if no filter will be used.

FilterConverter

UTF-8 byte array

(Optional) KeyValueFilterConverter factory name deployed on the server. Present if previous field is not negative.

Parameters size

byte

The number of parameters of the filter. Only present when FilterConverter is provided.

Parameters

byte[][]

An array of parameters. Each parameter is a byte array. Only present if Parameters size is greater than 0.

BatchSize

vInt

Number of entries to transfers from the server at one go.

Metadata

1 byte

1 if metadata is to be returned for each entry, 0 otherwise.

The response header for this operation contains the following:

Table 16.35. IterationEnd Operation Response Format

FieldData TypeDetails

IterationId

String

The unique id of the iteration.

16.3.16. Hot Rod Ping Operation

The ping is an application level request to check for server availability.

Valid response statuses for this operation are as follows:

Table 16.36. Ping Operation Response

Response StatusDetails

0x00

Successful ping without any errors.

16.3.17. Hot Rod Put Operation

The put operation request format includes the following:

.

FieldData TypeDetails

Header

variable

Request header.

Key Length

-

Contains the length of the key.

Key

Byte array

Contains the key value.

TimeUnits

Byte

Time units of lifespan (first 4 bits) and maxIdle (last 4 bits). Special units DEFAULT and INFINITE can be used for default server expiration and no expiration respectively. Possible values:

0x00 = SECONDS
0x01 = MILLISECONDS
0x02 = NANOSECONDS
0x03 = MICROSECONDS
0x04 = MINUTES
0x05 = HOURS
0x06 = DAYS
0x07 = DEFAULT
0x08 = INFINITE

Lifespan

vInt

Duration which the entry is allowed to life. Only sent when time unit is not DEFAULT or INFINITE

Max Idle

vInt

Duration that each entry can be idle before it’s evicted from the cache. Only sent when time unit is not DEFAULT or INFINITE.

Value Length

vInt

Contains the length of the value.

Value

Byte array

The requested value.

The following are the valid response values returned from this operation:

.

Response StatusDetails

0x00

The value was successfully stored.

0x03

The value was successfully stored, and the previous value follows.

An empty response is the default response for this operation. However, if ForceReturnPreviousValue is passed, the previous value and key are returned. If the previous key and value do not exist, the value length would contain the value 0.

16.3.18. Hot Rod PutAll Operation

Bulk operation to put all key value entries into the cache at the same time.

The PutAll operation request format includes the following:

Table 16.37. PutAll Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

TimeUnits

Byte

Time units of lifespan (first 4 bits) and maxIdle (last 4 bits). Special units DEFAULT and INFINITE can be used for default server expiration and no expiration respectively. Possible values:

0x00 = SECONDS
0x01 = MILLISECONDS
0x02 = NANOSECONDS
0x03 = MICROSECONDS
0x04 = MINUTES
0x05 = HOURS
0x06 = DAYS
0x07 = DEFAULT
0x08 = INFINITE

Lifespan

vInt

Duration which the entry is allowed to life. Only sent when time unit is not DEFAULT or INFINITE

Max Idle

vInt

Duration that each entry can be idle before it’s evicted from the cache. Only sent when time unit is not DEFAULT or INFINITE.

Entry count

vInt

How many entries are being inserted.

Key Length

vInt

Length of key.

Key

byte array

Retrieved key.

Value Length

vInt

Length of value.

Value

byte array

Retrieved value.

The Key Length, Key, Value Length, and Value fields repeat for each entry that will be placed.

The response header for this operation contains one of the following response statuses:

Table 16.38. PutAll Operation Response Format

Response StatusDetails

0x00

Successful operation, indicating all keys were successfully put.

16.3.19. Hot Rod PutIfAbsent Operation

The putIfAbsent operation request format includes the following:

Table 16.39. PutIfAbsent Operation Request Fields

FieldData TypeDetails

Header

variable

Request header.

Key Length

vInt

Contains the length of the key.

Key

Byte array

Contains the key value.

TimeUnits

Byte

Time units of lifespan (first 4 bits) and maxIdle (last 4 bits). Special units DEFAULT and INFINITE can be used for default server expiration and no expiration respectively. Possible values:

0x00 = SECONDS
0x01 = MILLISECONDS
0x02 = NANOSECONDS
0x03 = MICROSECONDS
0x04 = MINUTES
0x05 = HOURS
0x06 = DAYS
0x07 = DEFAULT
0x08 = INFINITE

Lifespan

vInt

Duration which the entry is allowed to life. Only sent when time unit is not DEFAULT or INFINITE

Max Idle

vInt

Duration that each entry can be idle before it’s evicted from the cache. Only sent when time unit is not DEFAULT or INFINITE.

Value Length

vInt

Contains the length of the value.

Value

Byte array

Contains the requested value.

The following are the valid response values returned from this operation:

.

Response StatusDetails

0x00

The value was successfully stored.

0x01

The key was present, therefore the value was not stored. The current value of the key is returned.

0x04

The operation failed because the key was present and its value follows in the response.

An empty response is the default response for this operation. However, if ForceReturnPreviousValue is passed, the previous value and key are returned. If the previous key and value do not exist, the value length would contain the value 0.

16.3.20. Hot Rod Query Operation

The Query operation request format includes the following:

Table 16.40. Query Operation Request Fields

FieldData TypeDetails

Header

variable

Request header.

Query Length

vInt

The length of the Protobuf encoded query object.

Query

Byte array

Byte array containing the Protobuf encoded query object, having a length specified by previous field.

The following are the valid response values returned from this operation:

Table 16.41. Query Operation Response

Response StatusDataDetails

Header

variable

Response header.

Response payload Length

vInt

The length of the Protobuf encoded response object.

Response payload

Byte array

Byte array containing the Protobuf encoded response object, having a length specified by previous field.

The Hot Rod Query operation request and response types are defined in the org/infinispan/query/remote/client/query.proto resource filed, found inside infinispan-remote-query-client.jar.

16.3.21. Hot Rod Remove Operation

A Hot RodRemove operation uses the following request format:

Table 16.42. Remove Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

Key Length

vInt

Contains the length of the key. The vInt data type is used because of its size (up to 5 bytes), which is larger than the size of Integer.MAX_VALUE. However, Java disallows single array sizes to exceed the size of Integer.MAX_VALUE. As a result, this vInt is also limited to the maximum size of Integer.MAX_VALUE .

Key

Byte array

Contains a key, the corresponding value of which is requested.

The response header for this operation contains one of the following response statuses:

Table 16.43. Remove Operation Response Format

Response StatusDetails

0x00

Successful operation.

0x02

The key does not exist.

0x03

The key was removed, and the previous or removed value follows in the response.

Normally, the response header for this operation is empty. However, if ForceReturnPreviousValue is passed, the response header contains either:

  • The value and length of the previous key.
  • The value length 0 and the response status 0x02 to indicate that the key does not exist.

The remove operation’s response header contains the previous value and the length of the previous value for the provided key if ForceReturnPreviousValue is passed. If the key does not exist or the previous value was null, the value length is 0.

16.3.22. Hot Rod RemoveIfUnmodified Operation

The RemoveIfUnmodified operation request format includes the following:

Table 16.44. RemoveIfUnmodified Operation Request Fields

FieldData TypeDetails

Header

variable

Request header.

Key Length

vInt

Contains the length of the key.

Key

Byte array

Contains the key value.

Entry Version

8 bytes

The version number for the entry.

The following are the valid response values returned from this operation:

Table 16.45. RemoveIfUnmodified Operation Response

Response StatusDetails

0x00

The entry was replaced or removed.

0x01

The entry replace or remove was unsuccessful because the key was modified.

0x02

The key does not exist.

0x03

The key was removed, and the previous or replaced value follows in the response.

0x04

The entry remove was unsuccessful because the key was modified, and the modified value follows in the response.

An empty response is the default response for this operation. However, if ForceReturnPreviousValue is passed, the previous value and key are returned. If the previous key and value do not exist, the value length would contain the value 0.

16.3.23. Hot Rod Replace Operation

The replace operation request format includes the following:

Table 16.46. Replace Operation Request Fields

FieldData TypeDetails

Header

variable

Request header.

Key Length

vInt

Contains the length of the key.

Key

Byte array

Contains the key value.

TimeUnits

Byte

Time units of lifespan (first 4 bits) and maxIdle (last 4 bits). Special units DEFAULT and INFINITE can be used for default server expiration and no expiration respectively. Possible values:

0x00 = SECONDS
0x01 = MILLISECONDS
0x02 = NANOSECONDS
0x03 = MICROSECONDS
0x04 = MINUTES
0x05 = HOURS
0x06 = DAYS
0x07 = DEFAULT
0x08 = INFINITE

Lifespan

vInt

Duration which the entry is allowed to life. Only sent when time unit is not DEFAULT or INFINITE

Max Idle

vInt

Duration that each entry can be idle before it’s evicted from the cache. Only sent when time unit is not DEFAULT or INFINITE.

Value Length

vInt

Contains the length of the value.

Value

Byte array

Contains the requested value.

The following are the valid response values returned from this operation:

Table 16.47. Replace Operation Response

Response StatusDetails

0x00

The value was successfully stored.

0x01

The value was not stored because the key does not exist.

0x03

The value was successfully replaced, and the previous or replaced value follows in the response.

An empty response is the default response for this operation. However, if ForceReturnPreviousValue is passed, the previous value and key are returned. If the previous key and value do not exist, the value length would contain the value 0.

16.3.24. Hot Rod ReplaceIfUnmodified Operation

The ReplaceIfUnmodified operation request format includes the following:

Table 16.48. ReplaceIfUnmodified Operation Request Format

FieldData TypeDetails

Header

variable

Request header.

Key Length

vInt

Length of key. Note that the size of a vint can be up to 5 bytes which in theory can produce bigger numbers than Integer.MAX_VALUE. However, Java cannot create a single array that’s bigger than Integer.MAX_VALUE, hence the protocol is limiting vint array lengths to Integer.MAX_VALUE.

Key

byte array

Byte array containing the key whose value is being requested.

TimeUnits

Byte

Time units of lifespan (first 4 bits) and maxIdle (last 4 bits). Special units DEFAULT and INFINITE can be used for default server expiration and no expiration respectively. Possible values:

0x00 = SECONDS
0x01 = MILLISECONDS
0x02 = NANOSECONDS
0x03 = MICROSECONDS
0x04 = MINUTES
0x05 = HOURS
0x06 = DAYS
0x07 = DEFAULT
0x08 = INFINITE

Lifespan

vInt

Duration which the entry is allowed to life. Only sent when time unit is not DEFAULT or INFINITE

Max Idle

vInt

Duration that each entry can be idle before it’s evicted from the cache. Only sent when time unit is not DEFAULT or INFINITE.

Entry Version

8 bytes

Use the value returned by GetWithVersion operation.

Value Length

vInt

Length of value.

Value

byte array

Value to be stored.

The response header for this operation contains one of the following response statuses:

Table 16.49. ReplaceIfUnmodified Operation Response Status

Response StatusDetails

0x00

The value was successfully stored.

0x01

Replace did not happen because key had been modified.

0x02

Replace did not happen because key does not exist.

0x03

The key was replaced, and the previous or replaced value follows in the response.

0x04

The entry replace was unsuccessful because the key was modified, and the modified value follows in the response.

The following are the valid response values returned from this operation:

Table 16.50. ReplaceIfUnmodified Operation Response Format

FieldData TypeDetails

Header

variable

Response header.

Previous value length

vInt

If force return previous value flag was sent in the request, the length of the previous value will be returned. If the key does not exist, value length would be 0. If no flag was sent, no value length would be present.

Previous value

byte array

If force return previous value flag was sent in the request and the key was replaced, previous value.

16.3.25. Hot Rod ReplaceWithVersion Operation

The ReplaceWithVersion operation request format includes the following:

Note

In the RemoteCache API, the Hot Rod ReplaceWithVersion operation uses the ReplaceIfUnmodified operation. As a result, these two operations are exactly the same in JBoss Data Grid.

Table 16.51. ReplaceWithVersion Operation Request Fields

FieldData TypeDetails

Header

-

-

Key Length

vInt

Contains the length of the key.

Key

Byte array

Contains the key value.

Lifespan

vInt

Contains the number of seconds before the entry expires. If the number of seconds exceeds thirty days, the value is treated as UNIX time (i.e. the number of seconds since the date 1/1/1970) as the entry lifespan. When set to the value 0, the entry will never expire.

Max Idle

vInt

Contains the number of seconds an entry is allowed to remain idle before it is evicted from the cache. If this entry is set to 0, the entry is allowed to remain idle indefinitely without being evicted due to the max idle value.

Entry Version

8 bytes

The version number for the entry.

Value Length

vInt

Contains the length of the value.

Value

Byte array

Contains the requested value.

The following are the valid response values returned from this operation:

Table 16.52. ReplaceWithVersion Operation Response

Response StatusDetails

0x00

Returned status if the entry was replaced or removed.

0x01

Returns status if the entry replace or remove was unsuccessful because the key was modified.

0x02

Returns status if the key does not exist.

An empty response is the default response for this operation. However, if ForceReturnPreviousValue is passed, the previous value and key are returned. If the previous key and value do not exist, the value length would contain the value 0.

16.3.26. Hot Rod Stats Operation

This operation returns a summary of all available statistics. For each returned statistic, a name and value is returned in both string and UTF-8 formats.

The following are supported statistics for this operation:

Table 16.53. Stats Operation Request Fields

NameDetails

timeSinceStart

Contains the number of seconds since Hot Rod started.

currentNumberOfEntries

Contains the number of entries that currently exist in the Hot Rod server.

totalNumberOfEntries

Contains the total number of entries stored in the Hot Rod server.

stores

Contains the number of put operations attempted.

retrievals

Contains the number of get operations attempted.

hits

Contains the number of get hits.

misses

Contains the number of get misses.

removeHits

Contains the number of remove hits.

removeMisses

Contains the number of removal misses.

globalCurrentNumberOfEntries

Number of entries currently across the Hot Rod cluster.

globalStores

Total number of put operations across the Hot Rod cluster.

globalRetrievals

Total number of get operations across the Hot Rod cluster.

globalHits

Total number of get hits across the Hot Rod cluster.

globalMisses

Total number of get misses across the Hot Rod cluster.

globalRemoveHits

Total number of removal hits across the Hot Rod cluster.

globalRemoveMisses

Total number of removal misses across the Hot Rod cluster.

Note

Any of the statistics beginning with global are not available if Hot Rod is running in local mode.

The response header for this operation contains the following:

Table 16.54. Stats Operation Response

NameData TypeDetails

Header

variable

Response Header.

Number of Stats

vInt

Contains the number of individual statistics returned.

Name Length

vInt

Contains the length of the named statistic.

Name

string

Contains the name of the statistic.

Value Length

vInt

Contains the length of the value.

Value

string

Contains the statistic value.

The values Name Length, Name, Value Length and Value recur for each statistic requested.

16.3.27. Hot Rod Size Operation

The Size operation request format includes the following:

Table 16.55. Size Operation Request Format

FieldData TypeDetails

Header

variable

Request header

The response header for this operation contains the following:

Table 16.56. Size Operation Response Format

FieldData TypeDetails

Header

variable

Response header.

Size

vInt

Size of the remote cache, which is calculated globally in the clustered set ups, and if present, takes cache store contents into account as well.

16.4. Hot Rod Operation Values

16.4.1. Hot Rod Operation Values

The following is a list of valid opcode values for a request header and their corresponding response header values:

Table 16.57. Opcode Request and Response Header Values

OperationRequest Operation CodeResponse Operation Code

put

0x01

0x02

get

0x03

0x04

putIfAbsent

0x05

0x06

replace

0x07

0x08

replaceIfUnmodified

0x09

0x0A

remove

0x0B

0x0C

removeIfUnmodified

0x0D

0x0E

containsKey

0x0F

0x10

clear

0x13

0x14

stats

0x15

0x16

ping

0x17

0x18

bulkGet

0x19

0x1A

getWithMetadata

0x1B

0x1C

bulkKeysGet

0x1D

0x1E

query

0x1F

0x20

authMechList

0x21

0x22

auth

0x23

0x24

addClientListener

0x25

0x26

removeClientListener

0x27

0x28

size

0x29

0x2A

exec

0x2B

0x2C

putAll

0x2D

0x2E

getAll

0x2F

0x30

iterationStart

0x31

0x32

iterationNext

0x33

0x34

iterationEnd

0x35

0x36

Additionally, if the response header opcode value is 0x50, it indicates an error response.

16.4.2. Magic Values

The following is a list of valid values for the Magic field in request and response headers:

Table 16.58. Magic Field Values

ValueDetails

0xA0

Cache request marker.

0xA1

Cache response marker.

16.4.3. Status Values

The following is a table that contains all valid values for the Status field in a response header:

Table 16.59. Status Values

ValueDetails

0x00

No error.

0x01

Not put/removed/replaced.

0x02

Key does not exist.

0x06

Success status and compatibility mode is enabled.

0x07

Success status and return previous value, with compatibility mode is enabled.

0x08

Not executed and return previous value, with compatibility mode is enabled.

0x81

Invalid Magic value or Message ID.

0x82

Unknown command.

0x83

Unknown version.

0x84

Request parsing error.

0x85

Server error.

0x86

Command timed out.

16.4.4. Client Intelligence Values

The following is a list of valid values for Client Intelligence in a request header:

Table 16.60. Client Intelligence Field Values

ValueDetails

0x01

Indicates a basic client that does not require any cluster or hash information.

0x02

Indicates a client that is aware of topology and requires cluster information.

0x03

Indicates a client that is aware of hash and distribution and requires both the cluster and hash information.

16.4.5. Flag Values

The following is a list of valid flag values in the request header:

Table 16.61. Flag Field Values

ValueDetails

0x0001

ForceReturnPreviousValue

16.4.6. Hot Rod Error Handling

Table 16.62. Hot Rod Error Handling using Response Header Fields

FieldData TypeDetails

Error Opcode

-

Contains the error operation code.

Error Status Number

-

Contains a status number that corresponds to the error opcode.

Error Message Length

vInt

Contains the length of the error message.

Error Message

string

Contains the actual error message. If an 0x84 error code returns, which indicates that there was an error in parsing the request, this field contains the latest version supported by the [path]_ Hot Rod_ server.

16.5. Hot Rod Remote Events

16.5.1. Hot Rod Remote Events

Clients may register Remote Event Listeners, allowing them to receive updates on events happening in the server. As soon as a client listener has been added events are generated and sent, allowing the client to receive all events that have occurred after adding the listener.

16.5.2. Hot Rod Add Client Listener for Remote Events

Adding client listeners for remote events uses the following request format:

Table 16.63. Add Client Listener Operation Request Format

FieldData TypeDetails

Header

variable

Request Header.

Listener ID

byte array

Listener identifier.

Include state

byte

When this byte is set to 1, cached state is sent back to remote clients when either adding a cache listener for the first time, or when the node where a remote listener is registered changes in a clustered environment. When enabled, state is sent back as cache entry created events to the clients. If set to 0, no state is sent back to the client when adding a listener, nor it gets state when the node where the listener is registered changes.

Key/value filter factory name

String

Optional name of the key/value filter factory to be used with this listener. The factory is used to create key/value filter instances which allow events to be filtered directly in the Hot Rod server, avoiding sending events that the client is not interested in. If no factory is to be used, the length of the string is 0.

Key/value filter factory parameter count

byte

The key/value filter factory, when creating a filter instance, can take an arbitrary number of parameters, enabling the factory to be used to create different filter instances dynamically. This count field indicates how many parameters will be passed to the factory. If no factory name was provided, this field is not present in the request.

Key/value filter factory parameter (per parameter)

byte array

Key/value filter factory parameter.

Converter factory name

String

Optional name of the converter factory to be used with this listener. The factory is used to transform the contents of the events sent to clients. By default, when no converter is in use, events are well defined, according to the type of event generated. However, there might be situations where users want to add extra information to the event, or they want to reduce the size of the events. In these cases, a converter can be used to transform the event contents. The given converter factory name produces converter instances to do this job. If no factory is to be used, the length of the string is 0.

Converter factory parameter count

byte

The converter factory, when creating a converter instance, can take an arbitrary number of parameters, enabling the factory to be used to create different converter instances dynamically. This count field indicates how many parameters will be passed to the factory. If no factory name was provided, this field is not present in the request.

Converter factory parameter (per parameter)

byte array

Converter factory parameter.

Use raw data

byte

If filter/converter parameters should be raw binary, then 1, otherwise 0.

The format of the operation’s response is as follows:

Table 16.64. Add Client Listener Response Format

FieldData TypeDetails

Header

Variable

Response Header.

16.5.3. Hot Rod Remote Client Listener for Remote Events

Removing a previously added client listener uses the following request format:

Table 16.65. Remove Client Listener Operation Request Format

FieldData TypeDetails

Header

variable

Request Header.

Listener ID

byte array

Listener Identifier

The format of the operation’s response is as follows:

Table 16.66. Add Client Listener Response Format

FieldData TypeDetails

Header

Variable

Response Header.

16.5.4. Hot Rod Event Header

Each remote event uses a header that adheres to the following format:

Table 16.67. Remote Event Header

Field NameSizeValue

Magic

1 byte

0xA1 = response

Message ID

vLong

ID of event

Opcode

1 byte

A code responding to the Event type:

0x60 = cache entry created event
0x61 = cache entry modified event
0x62 = cache entry removed event
0x50 = error

Status

1 byte

Status of the response, with the following possible values:

0x00 = No error

Topology Change Marker

1 byte

Since events are not associated with a particular incoming topology ID to be able to decide whether a new topology is required to be sent or not, new topologies will never be sent with events. Hence, this marker will always have 0 value for events.

16.5.5. Hot Rod Cache Entry Created Event

The CacheEntryCreated event includes the following:

Table 16.68. Cache Entry Created Event

Field NameSizeValue

Header

variable

Event header with 0x60 operation code.

Listener ID

byte array

Listener for which this event is directed

Custom Marker

byte

Custom event marker. For created events, this is 0.

Command Retried

byte

Marker for events that are result of retried commands. If command is retried, it returns 1, otherwise 0.

Key

byte array

Created key.

Version

long

Version of the created entry. This version information can be used to make conditional operations on this cache entry.

16.5.6. Hot Rod Cache Entry Modified Event

The CacheEntryModified event includes the following:

Table 16.69. Cache Entry Modified Event

Field NameSizeValue

Header

variable

Event header with 0x61 operation code.

Listener ID

byte array

Listener for which this event is directed

Custom Marker

byte

Custom event marker. For created events, this is 0.

Command Retried

byte

Marker for events that are result of retried commands. If command is retried, it returns 1, otherwise 0.

Key

byte array

Modified key.

Version

long

Version of the modified entry. This version information can be used to make conditional operations on this cache entry.

16.5.7. Hot Rod Cache Entry Removed Event

The CacheEntryRemoved event includes the following:

Table 16.70. Cache Entry Removed Event

Field NameSizeValue

Header

variable

Event header with 0x62 operation code.

Listener ID

byte array

Listener for which this event is directed

Custom Marker

byte

Custom event marker. For created events, this is 0.

Command Retried

byte

Marker for events that are result of retried commands. If command is retried, it returns 1, otherwise 0.

Key

byte array

Removed key.

16.5.8. Hot Rod Custom Event

The Custom event includes the following:

Table 16.71. Custom Event

Field NameSizeValue

Header

variable

Event header with event specific operation code

Listener ID

byte array

Listener for which this event is directed

Custom Marker

byte

Custom event marker. For custom events whose event data needs to be unmarshalled before returning to user the value is 1. For custom events that need to return the event data as-is to the user, the value is 2.

Event Data

byte array

Custom event data. If the custom marker is 1, the bytes represent the marshalled version of the instance returned by the converter. If custom marker is 2, it represents the byte array, as returned by the converter.

16.6. Put Request Example

The following is the coded request from a sample put request using Hot Rod:

Table 16.72. Put Request Example

Byte01234567

8

0xA0

0x09

0x41

0x01

0x07

0x4D ('M')

0x79 ('y')

0x43 ('C')

16

0x61 ('a')

0x63 ('c')

0x68 ('h')

0x65 ('e')

0x00

0x03

0x00

0x00

24

0x00

0x05

0x48 ('H')

0x65 ('e')

0x6C ('l')

0x6C ('l')

0x6F ('o')

0x00

32

0x00

0x05

0x57 ('W')

0x6F ('o')

0x72 ('r')

0x6C ('l')

0x64 ('d')

-

The following table contains all header fields and their values for the example request:

Table 16.73. Example Request Field Names and Values

Field NameByteValue

Magic

0

0xA0

Version

2

0x41

Cache Name Length

4

0x07

Flag

12

0x00

Topology ID

14

0x00

Transaction ID

16

0x00

Key

18-22

'Hello'

Max Idle

24

0x00

Value

26-30

'World'

Message ID

1

0x09

Opcode

3

0x01

Cache Name

5-11

'MyCache'

Client Intelligence

13

0x03

Transaction Type

15

0x00

Key Field Length

17

0x05

Lifespan

23

0x00

Value Field Length

25

0x05

The following is a coded response for the sample put request:

Table 16.74. Coded Response for the Sample Put Request

Byte01234567

8

0xA1

0x09

0x01

0x00

0x00

-

-

-

The following table contains all header fields and their values for the example response:

Table 16.75. Example Response Field Names and Values

Field NameByteValue

Magic

0

0xA1

Opcode

2

0x01

Topology Change Marker

4

0x00

Message ID

1

0x09

Status

3

0x00

16.7. Hot Rod Java Client

16.7.1. Hot Rod Java Client

Hot Rod is a binary, language neutral protocol. A Java client is able to interact with a server via the Hot Rod protocol using the Hot Rod Java Client API.

16.7.2. Hot Rod Java Client Download

Use the following steps to download the JBoss Data Grid Hot Rod Java Client:

Procedure: Download Hot Rod Java Client

  1. Log into the Customer Portal at https://access.redhat.com.
  2. Click the Downloads button near the top of the page.
  3. In the Product Downloads page, click Red Hat JBoss Data Grid.
  4. Select the appropriate JBoss Data Grid version from the Version: drop down menu.
  5. Locate the Red Hat JBoss Data Grid 7.2 Hot Rod Java Client entry and click the corresponding Download link.

16.7.3. Hot Rod Java Client Configuration

The Hot Rod Java client is configured both programmatically and externally using a configuration file or a properties file. The following example illustrate creation of a client instance using the available Java fluent API:

Client Instance Creation

org.infinispan.client.hotrod.configuration.ConfigurationBuilder cb
= new org.infinispan.client.hotrod.configuration.ConfigurationBuilder();
cb.tcpNoDelay(true)
  .connectionPool()
      .numTestsPerEvictionRun(3)
      .testOnBorrow(false)
      .testOnReturn(false)
      .testWhileIdle(true)
  .addServer()
      .host("localhost")
      .port(11222);
RemoteCacheManager rmc = new RemoteCacheManager(cb.build());

Configuring the Hot Rod Java client using a properties file

To configure the Hot Rod Java client, edit the hotrod-client.properties file on the classpath.

The following example shows the possible content of the hotrod-client.properties file.

Configuration

infinispan.client.hotrod.transport_factory = org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory

infinispan.client.hotrod.server_list = 127.0.0.1:11222

infinispan.client.hotrod.marshaller = org.infinispan.commons.marshall.jboss.GenericJBossMarshaller

infinispan.client.hotrod.async_executor_factory = org.infinispan.client.hotrod.impl.async.DefaultAsyncExecutorFactory

infinispan.client.hotrod.default_executor_factory.pool_size = 1

infinispan.client.hotrod.default_executor_factory.queue_size = 10000

infinispan.client.hotrod.hash_function_impl.1 = org.infinispan.client.hotrod.impl.consistenthash.ConsistentHashV1

infinispan.client.hotrod.tcp_no_delay = true

infinispan.client.hotrod.ping_on_startup = true

infinispan.client.hotrod.request_balancing_strategy = org.infinispan.client.hotrod.impl.transport.tcp.RoundRobinBalancingStrategy

infinispan.client.hotrod.key_size_estimate = 64

infinispan.client.hotrod.value_size_estimate = 512

infinispan.client.hotrod.force_return_values = false

infinispan.client.hotrod.tcp_keep_alive = true

## below is connection pooling config

maxActive=-1

maxTotal = -1

maxIdle = -1

whenExhaustedAction = 1

timeBetweenEvictionRunsMillis=120000

minEvictableIdleTimeMillis=300000

testWhileIdle = true

minIdle = 1

Note

The TCPKEEPALIVE configuration is enabled/disabled on the Hot Rod Java client either through a config property as seen in the example (infinispan.client.hotrod.tcp_keep_alive = true/false or programmatically through the org.infinispan.client.hotrod.ConfigurationBuilder.tcpKeepAlive() method.

Either of the following two constructors must be used in order for the properties file to be consumed by Red Hat JBoss Data Grid:

  1. new RemoteCacheManager(boolean start)
  2. new RemoteCacheManager()

16.7.4. Hot Rod Java Client Basic API

The following code shows how the client API can be used to store or retrieve information from a Hot Rod server using the Hot Rod Java client. This example assumes that a Hot Rod server has been started bound to the default location, localhost:11222.

Basic API

//API entry point, by default it connects to localhost:11222
        BasicCacheContainer cacheContainer = new RemoteCacheManager();
//obtain a handle to the remote default cache
        BasicCache<String, String> cache = cacheContainer.getCache();
//now add something to the cache and ensure it is there
        cache.put("car", "ferrari");
        assert cache.get("car").equals("ferrari");
//remove the data
        cache.remove("car");
        assert !cache.containsKey("car") : "Value must have been removed!";

The RemoteCacheManager corresponds to DefaultCacheManager, and both implement BasicCacheContainer.

This API facilitates migration from local calls to remote calls via Hot Rod. This can be done by switching between DefaultCacheManager and RemoteCacheManager, which is simplified by the common BasicCacheContainer interface.

All keys can be retrieved from the remote cache using the keySet() method. If the remote cache is a distributed cache, the server will start a Map/Reduce job to retrieve all keys from clustered nodes and return all keys to the client.

Use this method with caution if there are a large number of keys.

Set keys = remoteCache.keySet();

16.7.5. Hot Rod Java Client Versioned API

To ensure data consistency, Hot Rod stores a version number that uniquely identifies each modification. Using getVersioned, clients can retrieve the value associated with the key as well as the current version.

When using the Hot Rod Java client, a RemoteCacheManager provides instances of the RemoteCache interface that accesses the named or default cache on the remote cluster. This extends the Cache interface to which it adds new methods, including the versioned API.

Using Versioned Methods

// To use the versioned API, remote classes are specifically needed
RemoteCacheManager remoteCacheManager = new RemoteCacheManager();
RemoteCache<String, String> remoteCache = remoteCacheManager.getCache();
remoteCache.put("car", "ferrari");
VersionedValue valueBinary = remoteCache.getWithMetadata("car");
// removal only takes place only if the version has not been changed
// in between. (a new version is associated with 'car' key on each change)
assert remoteCache.removeWithVersion("car", valueBinary.getVersion());
assert !remoteCache.containsKey("car");

Using Replace

remoteCache.put("car", "ferrari");
VersionedValue valueBinary = remoteCache.getWithMetadata("car");
assert remoteCache.replaceWithVersion("car", "lamborghini", valueBinary.getVersion());

16.7.6. Cluster-Wide Dynamic Cache Creation with Hot Rod Java Client

If a cache needs to be created dynamically from a client, use the createCache() method as follows:

BasicCache<String, String> cache = remoteCacheManager.administration().createCache("newCacheName", "newTemplate");

While a cache created this way will be available on all nodes in the cluster, it will also be ephemeral: shutting down the entire cluster and restarting it will not automatically recreate the caches. To make the caches persistent, use the PERMANENT flag as follows:

BasicCache<String, String> cache = remoteCacheManager.administration().withFlags(AdminFlag.PERMANENT).createCache("newCacheName", "newTemplate");

In order for the above to work, global state must be enabled and a suitable configuration storage selected. The available configuration stores are:

  • VOLATILE: as the name implies, this configuration storage does not support PERMANENT caches.
  • OVERLAY: this stores configurations in the global shared state persistent path in a file named caches.xml.
  • MANAGED: this is only supported in server deployments, and will store PERMANENT caches in the server model.
  • CUSTOM: a custom configuration store.

16.8. Hot Rod C++ Client

16.8.1. Hot Rod C++ Client

The Hot Rod C++ client enables C++ runtime applications to connect and interact with Red Hat JBoss Data Grid remote servers, and to read or write data to remote caches. The Hot Rod C++ client supports all three levels of client intelligence and is supported on the following platforms:

  • Red Hat Enterprise Linux 6, 64-bit
  • Red Hat Enterprise Linux 7, 64-bit

    The Hot Rod C++ client is available as a Technology Preview on 64-bit Windows with Visual Studio 2015.

16.8.2. Hot Rod C++ Client Formats

The Hot Rod C++ client is available in the following two library formats:

  • Static library
  • Shared/Dynamic library

Static Library

The static library is statically linked to an application. This increases the size of the final executable. The application is self-contained and it does not need to ship a separate library.

Shared/Dynamic Library

Shared/Dynamic libraries are dynamically linked to an application at runtime. The library is stored in a separate file and can be upgraded separately from the application, without recompiling the application.

Note

This can only happen if the library’s major version is equal to the one against which the application was linked at compile time, indicating that it is binary compatible.

16.8.3. Hot Rod C++ Client Prerequisites

The following table details requirements needed to use the Hot Rod C++ Client depending on the underlying OS:

Table 16.76. Hot Rod C++ Client Prerequisites by OS

Operating SystemHot Rod C++ Client Prerequisites

RHEL 6, 64-bit

C++ 03 compiler with support for shared_ptr TR1 (GCC 4.0+)

RHEL 7, 64-bit

C++ 11 compiler (GCC 4.8.1)

Windows 7 x64

C 11 compiler (Visual Studio 2015, Microsoft Visual C 2013 Redistributable Package for the x64 platform)

16.8.4. Installing the Hot Rod C++ Client

16.8.4.1. Hot Rod C++ Client Download and Installation

The Hot Rod C++ client is distributed in two file types, based on the Operating System where the client will be used:

  • RHEL servers install via an RPM distribution.
  • Windows servers install via a zip distribution.

16.8.4.2. Hot Rod C++ Client RHEL Download and Installation

To install the client perform the following steps:

  1. Ensure your Red Hat Enterprise Linux (RHEL) system is registered to your account using Red Hat Subscription Manager. For more information, refer to the Red Hat Subscription Management documentation.
  2. Using Red Hat Subscription Manager, enable the appropriate repository based on your version of RHEL:

    Table 16.77. RHSM Repositories

    RHEL VersionRepo Name

    RHEL 6

    jb-datagrid-7.2-for-rhel-6-server-rpms

    RHEL 7

    jb-datagrid-7.2-for-rhel-7-server-rpms

    For instance, to enable the RHEL 7 repo the following command would be used:

    subscription-manager repos --enable=jb-datagrid-7.2-for-rhel-7-server-rpms

    For RHEL 7 you also need to enable the rhel-7-server-optional-rpms repo which provides the required protobuf-devel and protobuf-static RPMs:

    subscription-manager repos --enable=rhel-7-server-optional-rpms
  3. Once the appropriate repos have been added the C++ client RPM may be installed with:

    yum install jdg-cpp-client

16.8.4.3. Hot Rod C++ Client Windows Download and Installation

The Hot Rod C++ Client for Windows is included in a separate zip file jboss-datagrid-<version>-hotrod-cpp-WIN-x86_64.zip under Red Hat JBoss Data Grid binaries on the Red Hat Customer Portal at https://access.redhat.com.

Once downloaded the C++ Client may be installed by extracing the zip file to the desired location on the system.

16.8.5. Utilizing the Protobuf Compiler with the Hot Rod C++ Client

16.8.5.1. Using the Protobuf Compiler in RHEL 7

The C++ Hot Rod client channel in RHEL 7 includes the Protobuf compiler. The following instructions detail using this compiler:

  1. Ensure that the C++ channel has been added to the RHEL system, as outlined in Hot Rod C++ Client RHEL Download and Installation.:
  2. Install the protobuf rpm:

    yum install protobuf
  3. Add the included protobuf libraries to the library path. These libraries are included in /opt/lib64 by default:

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/lib64
  4. Compile the desired protobuf files into C++ header and source files:

    /bin/protoc --cpp_out dllexport_decl=HR_PROTO_EXPORT:/path/to/output/ $FILE
    Note

    HR_PROTO_EXOPRT is a macro defined within the Hot Rod client code, and will be expanded when the files are subsequently compiled.

  5. The resulting header and source files will be generated in the designated output directory, allowing them to be referenced and compiled as normal with the specific application code.

For additional information on Protobuf refer to Protobuf Encoding.

16.8.5.2. Using the Protobuf Compiler in Windows

The C++ Hot Rod client for Windows ships with the precompiled Hot Rod components along with the Protobuf compiler included. For many users the included components may be used without the need for additional compilation; however, should any .proto files require compiling the following instructions document this process:

  1. Extract the jboss-datagrid-<version>-hotrod-cpp-client-WIN-x86_64.zip locally to the filesystem.
  2. Open a command prompt and navigate to the newly extracted directory.
  3. Compile the desired protobuf files into C++ header and source files:

    bin\protoc --cpp_out dllexport_decl=HR_PROTO_EXPORT:path\to\output\ $FILE
    Note

    HR_PROTO_EXOPRT is a macro defined within the Hot Rod client code, and will be expanded when the files are subsequently compiled.

  4. The resulting header and source files will be generated in the designated output directory, allowing them to be referenced and compiled as normal with the specific application code.

For additional information on Protobuf refer to Protobuf Encoding.

16.8.6. Hot Rod C++ Client Configuration

The Hot Rod C++ client interacts with a remote Hot Rod server using the RemoteCache API. To initiate communication with a particular Hot Rod server, configure RemoteCache and choose the specific cache on the Hot Rod server.

Use the ConfigurationBuilder API to configure:

  • The initial set of servers to connect to.
  • Connection pooling attributes.
  • Connection/Socket timeouts and TCP nodelay.
  • Hot Rod protocol version.

Sample C++ main executable file configuration

The following example shows how to use the ConfigurationBuilder to configure a RemoteCacheManager and how to obtain the default remote cache:

SimpleMain.cpp

#include "infinispan/hotrod/ConfigurationBuilder.h"
#include "infinispan/hotrod/RemoteCacheManager.h"
#include "infinispan/hotrod/RemoteCache.h"
#include <stdlib.h>
using namespace infinispan::hotrod;
int main(int argc, char** argv) {
    ConfigurationBuilder b;
    b.addServer().host("127.0.0.1").port(11222);
    RemoteCacheManager cm(builder.build());
    RemoteCache<std::string, std::string> cache = cm.getCache<std::string, std::string>();
    return 0;
}

16.8.7. Hot Rod C++ Client API

The RemoteCacheManager is a starting point to obtain a reference to a RemoteCache. The RemoteCache API can interact with a remote Hot Rod server and the specific cache on that server.

Using the RemoteCache reference obtained in the previous example, it is possible to put, get, replace and remove values in a remote cache. It is also possible to perform bulk operations, such as retrieving all of the keys, and clearing the cache.

When a RemoteCacheManager is stopped, all resources in use are released.

SimpleMain.cpp

RemoteCache<std::string, std::string> rc = cm.getCache<std::string, std::string>();
    std::string k1("key13");
    std::string v1("boron");
    // put
    rc.put(k1, v1);
    std::auto_ptr<std::string> rv(rc.get(k1));
    rc.putIfAbsent(k1, v1);
    std::auto_ptr<std::string> rv2(rc.get(k1));
    std::map<HR_SHARED_PTR<std::string>,HR_SHARED_PTR<std::string> > map = rc.getBulk(0);
    std::cout << "getBulk size" << map.size() << std::endl;
    ..
    .
    cm.stop();

16.8.8. Hot Rod C++ Client Asynchronous API

The Hot Rod C++ client offers asynchronous versions of many of the synchronous methods, allowing non-blocking methods for interacting with remote caches.

These methods follow the same naming convention as the synchronous methods, except that Async is appended to the end of each method. Asynchronous methods return a std::future containing the result of the operation. If a method were to return a std::string, instead it will return a std::future < std::string* >

A list of asynchronous methods are below:

  • clearAsync
  • getAsync
  • putAsync
  • putAllAsync
  • putIfAbsentAsync
  • removeAsync
  • removeWithVersionAsync
  • replaceAsync
  • replaceWithVersionAsync

Hot Rod C++ Asynchronous API Example

The following example demonstrates using these methods:

#include "infinispan/hotrod/ConfigurationBuilder.h"
#include "infinispan/hotrod/RemoteCacheManager.h"
#include "infinispan/hotrod/RemoteCache.h"
#include "infinispan/hotrod/Version.h"

#include "infinispan/hotrod/JBasicMarshaller.h"
#include <iostream>
#include <thread>
#include <future>

using namespace infinispan::hotrod;

int main(int argc, char** argv) {
    ConfigurationBuilder builder;
    builder.addServer().host(argc > 1 ? argv[1] : "127.0.0.1").port(argc > 2 ? atoi(argv[2]) : 11222).protocolVersion(Configuration::PROTOCOL_VERSION_24);
    RemoteCacheManager cacheManager(builder.build(), false);
    auto *km = new BasicMarshaller<std::string>();
    auto *vm = new BasicMarshaller<std::string>();
    auto cache = cacheManager.getCache<std::string, std::string>(km, &Marshaller<std::string>::destroy, vm, &Marshaller<std::string>::destroy );
    cacheManager.start();
    std::string ak1("asyncK1");
    std::string av1("asyncV1");
    std::string ak2("asyncK2");
    std::string av2("asyncV2");
    cache.clear();

    // Put ak1,av1 in async thread
    std::future<std::string*> future_put= cache.putAsync(ak1,av1);
    // Get the value in this thread
    std::string* arv1= cache.get(ak1);

    // Now wait for put completion
    future_put.wait();

    // All is synch now
    std::string* arv11= cache.get(ak1);
    if (!arv11 || arv11->compare(av1))
    {
        std::cout << "fail: expected " << av1 << "got " << (arv11 ? *arv11 : "null") << std::endl;
        return 1;
    }

    // Read ak1 again, but in async way and test that the result is the same
    std::future<std::string*> future_ga= cache.getAsync(ak1);
    std::string* arv2= future_ga.get();
    if (!arv2 || arv2->compare(av1))
    {
        std::cerr << "fail: expected " << av1 << " got " << (arv2 ? *arv2 : "null") << std::endl;
        return 1;
    }

    // Now user pass a simple lambda func that set a flag to true when the put completes
    bool flag=false;
    std::future<std::string*> future_put1= cache.putAsync(ak2,av2,0,0,[&] (std::string *v){flag=true; return v;});
    // The put is not completed here so flag must be false
    if (flag)
    {
        std::cerr << "fail: expected false got true" << std::endl;
        return 1;
    }
    // Now wait for put completion
    future_put1.wait();
    // The user lambda must be executed so flag must be true
    if (!flag)
    {
        std::cerr << "fail: expected true got false" << std::endl;
        return 1;
    }

    // Same test for get
    flag=false;
    // Now user pass a simple lambda func that set a flag to true when the put completes
    std::future<std::string*> future_get1= cache.getAsync(ak2,[&] (std::string *v){flag=true; return v;});
    // The get is not completed here so flag must be false
    if (flag)
    {
        std::cerr << "fail: expected false got true" << std::endl;
        return 1;
    }
    // Now wait for get completion
    future_get1.wait();
    if (!flag)
    {
        std::cerr << "fail: expected true got false" << std::endl;
        return 1;
    }
    std::string* arv3= future_get1.get();
    if (!arv3 || arv3->compare(av2))
    {
        std::cerr << "fail: expected " << av2 << " got " << (arv3 ? *arv3 : "null") << std::endl;
        return 1;
    }
    cacheManager.stop();
}

16.8.9. Hot Rod C++ Client Remote Event Listeners

The Hot Rod C++ client supports remote cache listeners, and these may be added using the add_listener function on the ClientCacheListener.

Important

Remote Event Listeners are a Technology Preview feature of the Hot Rod C++ client in Red Hat JBoss Data Grid 7.2.

This function takes a listener for each event type(create, modify, remove, expire, or custom). For more information on Remote Event Listeners refer to Remote Event Listeners (Hot Rod). An example of this is provided below:

ConfigurationBuilder builder;
    builder.balancingStrategyProducer(nullptr);
builder.addServer().host("127.0.0.1").port(11222);
builder.protocolVersion(Configuration::PROTOCOL_VERSION_24);
RemoteCacheManager cacheManager(builder.build(), false);
cacheManager.start();
JBasicMarshaller<int> *km = new JBasicMarshaller<int>();
JBasicMarshaller<std::string> *vm = new JBasicMarshaller<std::string>();
RemoteCache<int, std::string> cache = cacheManager.getCache<int, std::string>(km,
    &Marshaller<int>::destroy,
    vm,
    &Marshaller<std::string>::destroy);
cache.clear();
std::vector<std::vector<char> > filterFactoryParams;
std::vector<std::vector<char> > converterFactoryParams;
CacheClientListener<int, std::string> cl(cache);
int createdCount=0, modifiedCount=0, removedCount=0, expiredCount=0;

// We're using future and promise to have a basic listeners/main thread synch
int setFutureEventKey=0;
std::promise<void> promise;
std::function<void(ClientCacheEntryCreatedEvent<int>)> listenerCreated = [&createdCount, &setFutureEventKey, &promise](ClientCacheEntryCreatedEvent<int> e) { createdCount++; if (setFutureEventKey==e.getKey()) promise.set_value(); };
std::function<void(ClientCacheEntryModifiedEvent<int>)> listenerModified = [&modifiedCount, &setFutureEventKey, &promise](ClientCacheEntryModifiedEvent <int> e) { modifiedCount++; if (setFutureEventKey==e.getKey()) promise.set_value(); };
std::function<void(ClientCacheEntryRemovedEvent<int>)> listenerRemoved = [&removedCount, &setFutureEventKey, &promise](ClientCacheEntryRemovedEvent <int> e) { removedCount++; if (setFutureEventKey==e.getKey()) promise.set_value(); };
std::function<void(ClientCacheEntryExpiredEvent<int>)> listenerExpired = [&expiredCount, &setFutureEventKey, &promise](ClientCacheEntryExpiredEvent <int> e) { expiredCount++; if (setFutureEventKey==e.getKey()) promise.set_value(); };

cl.add_listener(listenerCreated);
cl.add_listener(listenerModified);
cl.add_listener(listenerRemoved);
cl.add_listener(listenerExpired);

cache.addClientListener(cl, filterFactoryParams, converterFactoryParams);

16.8.10. Hot Rod C++ Client Working with Sites

Multiple Red Hat JBoss Data Grid Server clusters may be deployed so that each cluster belongs to a different site. Such deployments are done to enable data to be backed up from one cluster to another, potentially in a different geographical location. C++ client implementation can failover between nodes within a cluster, along with failing over to a different cluster entirely, should the original cluster become nonresponsive. To be able to failover between clusters all Red Hat JBoss Data Grid Servers must be configured with Cross-Datacenter replication. Instructions for this procedure are found in the Red Hat JBoss Data Grid Administration and Configuration Guide.

Once failed over the client will remain connected to the alternative cluster until this new cluster becomes unavailable, in which case it will throw an exception. If the original cluster becomes operational, the client will not switch over automatically. To switch back to the original cluster use the switchToDefaultCluster() method mentioned below.

Once Cross-Datacenter replication has been configured on the servers, the client has to provide the alternative clusters' configuration with at least one host/port pair details for each of the clusters configured. For example:

#include "infinispan/hotrod/ConfigurationBuilder.h"
#include "infinispan/hotrod/RemoteCacheManager.h"
#include "infinispan/hotrod/RemoteCache.h"
#include <stdlib.h>
using namespace infinispan::hotrod;
int main(int argc, char** argv) {
    ConfigurationBuilder b;
    b.addServer().host("127.0.0.1").port(11222);
    b.addCluster("nyc").addClusterNode("127.0.0.1", 11322);

    RemoteCacheManager cm(builder.build());
    RemoteCache<std::string, std::string> cache = cm.getCache<std::string, std::string>();
    return 0;
}

16.8.10.1. Manual Cluster Switch

In addition to automatic site failover, C++ clients may switch between clusters by calling either of the following methods:

  • switchToCluster(clusterName) - Forces the client to switch to the pre-defined cluster name passed in.
  • switchToDefaultCluster - Forces the client to switch to the initial servers defined in the client configuration.

16.8.11. Performing Remote Queries via the Hot Rod C++ Client

The Hot Rod C++ client allows remote querying, using Google’s Protocol Buffers, once the RemoteCacheManager has been configured with the Protobuf marshaller.

Important

Performing Remote Queries is a Technology Preview feature of the Hot Rod C++ client in Red Hat JBoss Data Grid 7.2.

Enable Remote Querying on the Hot Rod C++ Client

  1. Obtain a connection to the remote Red Hat JBoss Data Grid server:

    #include "addressbook.pb.h"
    #include "bank.pb.h"
    #include <infinispan/hotrod/BasicTypesProtoStreamMarshaller.h>
    #include <infinispan/hotrod/ProtoStreamMarshaller.h>
    #include "infinispan/hotrod/ConfigurationBuilder.h"
    #include "infinispan/hotrod/RemoteCacheManager.h"
    #include "infinispan/hotrod/RemoteCache.h"
    #include "infinispan/hotrod/Version.h"
    #include "infinispan/hotrod/query.pb.h"
    #include "infinispan/hotrod/QueryUtils.h"
    #include <vector>
    #include <tuple>
    
    #define PROTOBUF_METADATA_CACHE_NAME "___protobuf_metadata"
    #define ERRORS_KEY_SUFFIX  ".errors"
    
    using namespace infinispan::hotrod;
    using namespace org::infinispan::query::remote::client;
    
    std::string read(std::string file)
    {
      std::ifstream t(file);
      std::stringstream buffer;
      buffer << t.rdbuf();
      return buffer.str();
    }
    
    int main(int argc, char** argv) {
      std::cout << "Tests for Query" << std::endl;
        ConfigurationBuilder builder;
        builder.addServer().host(argc > 1 ? argv[1] : "127.0.0.1").port(argc > 2 ? atoi(argv[2]) : 11222).protocolVersion(Configuration::PROTOCOL_VERSION_24);
        RemoteCacheManager cacheManager(builder.build(), false);
        cacheManager.start();
  2. Create the Protobuf metadata cache with the Protobuf Marshaller:

        // This example continues the previous codeblock
        // Create the Protobuf Metadata cache peer with a Protobuf marshaller
        auto *km = new BasicTypesProtoStreamMarshaller<std::string>();
        auto *vm = new BasicTypesProtoStreamMarshaller<std::string>();
        auto metadataCache = cacheManager.getCache<std::string, std::string>(
            km, &Marshaller<std::string>::destroy,
            vm, &Marshaller<std::string>::destroy,PROTOBUF_METADATA_CACHE_NAME, false);
  3. Install the data model in the Protobuf metadata cache:

        // This example continues the previous codeblock
        // Install the data model into the Protobuf metadata cache
        metadataCache.put("sample_bank_account/bank.proto", read("proto/bank.proto"));
        if (metadataCache.containsKey(ERRORS_KEY_SUFFIX))
        {
            std::cerr << "fail: error in registering .proto model" << std::endl;
            return -1;
        }
  4. This step adds data to the cache for the purposes of this demonstration, and may be ignored when simply querying a remote cache:

        // This example continues the previous codeblock
        // Fill the cache with the application data: two users Tom and Jerry
        testCache.clear();
        sample_bank_account::User_Address a;
        sample_bank_account::User user1;
        user1.set_id(3);
        user1.set_name("Tom");
        user1.set_surname("Cat");
        user1.set_gender(sample_bank_account::User_Gender_MALE);
        sample_bank_account::User_Address * addr= user1.add_addresses();
        addr->set_street("Via Roma");
        addr->set_number(3);
        addr->set_postcode("202020");
        testCache.put(3, user1);
        user1.set_id(4);
        user1.set_name("Jerry");
        user1.set_surname("Mouse");
        addr->set_street("Via Milano");
        user1.set_gender(sample_bank_account::User_Gender_MALE);
        testCache.put(4, user1);
  5. Query the remote cache:

        // This example continues the previous codeblock
        // Simple query to get User objects
        {
            QueryRequest qr;
            std::cout << "Query: from sample_bank_account.User" << std::endl;
            qr.set_jpqlstring("from sample_bank_account.User");
            QueryResponse resp = testCache.query(qr);
            std::vector<sample_bank_account::User> res;
            unwrapResults(resp, res);
            for (auto i : res) {
                std::cout << "User(id=" << i.id() << ",name=" << i.name()
                << ",surname=" << i.surname() << ")" << std::endl;
            }
        }
        cacheManager.stop();
        return 0;
    }

Additional Query Examples

The following examples are included to demonstrate more complicated queries, and may be used on the same dataset found in the above procedure.

Using a query with a conditional

// Simple query to get User objects with where condition
{
    QueryRequest qr;
    std::cout << "from sample_bank_account.User u where u.addresses.street=\"Via Milano\"" << std::endl;
    qr.set_jpqlstring("from sample_bank_account.User u where u.addresses.street=\"Via Milano\"");
    QueryResponse resp = testCache.query(qr);
    std::vector<sample_bank_account::User> res;
    unwrapResults(resp, res);
    for (auto i : res) {
        std::cout << "User(id=" << i.id() << ",name=" << i.name()
        << ",surname=" << i.surname() << ")" << std::endl;
    }
}

Using a query with a projection

// Simple query to get projection (name, surname)
{
    QueryRequest qr;
    std::cout << "Query: select u.name, u.surname from sample_bank_account.User u" << std::endl;
    qr.set_jpqlstring(
        "select u.name, u.surname from sample_bank_account.User u");
    QueryResponse resp = testCache.query(qr);

    //Typed resultset
    std::vector<std::tuple<std::string, std::string> > prjRes;
    unwrapProjection(resp, prjRes);
    for (auto i : prjRes) {
        std::cout << "Name: " << std::get<0> (i)
        << " Surname: " << std::get<1> (i) << std::endl;
    }
}

16.8.12. Using the Near Cache with the Hot Rod C++ Client

Near caches are optional caches for the Hot Rod C++ client that keep recently accessed data close to the user, providing faster access to data that is accessed frequently. This cache acts as a local Hot Rod client cache that are synchronized with the remote server in the background.

Near caches are enabled programmatically on the ConfigurationBuilder by using the nearCache() method, as seen in the following example:

int main(int argc, char** argv) {
    ConfigurationBuilder confBuilder;
    confBuilder.addServer().host("127.0.0.1").port(11222);
    confBuilder.protocolVersion(Configuration::PROTOCOL_VERSION_24);
    confBuilder.balancingStrategyProducer(nullptr);

    // Enable the near cache support
    confBuilder.nearCache().mode(NearCacheMode::INVALIDATED).maxEntries(4);

The following methods are used to configure the near cache’s behavior:

  • nearCache() - defines a NearCacheConfigurationBuilder which may be modified further.
  • mode(NearCacheMode mode) - requires a NearCacheMode be passed in. Defaults to DISABLED, indicating no near cache is enabled.
  • maxEntries(int maxEntries) - indicates the maximum number of entries for the near cache to contain. Once the near cache is full, the oldest entry will be evicted. Setting this value to 0 defines an unbounded near cache.

Entries in the near cache are kept aligned with the remote cache via events. If a change occurs in the server then an appropriate event is sent to the client, which will update the near cache accordingly.

16.8.13. Script Execution Using the Hot Rod C++ Client

The Hot Rod C++ client allows tasks to be executed directly on JBoss Data Grid servers via Remote Execution. This feature executes logic close to the data, utilizing the resources of all nodes in the cluster. Tasks may be deployed to the server instances, and may then be executed programmatically.

Important

Remote Execution is a Technology Preview feature of the Hot Rod C++ client in Red Hat JBoss Data Grid 7.2.

Installing a Task

Tasks may be installed on the server by being using the put(std::string name, std::string script) method of the ___script_cache. The extension of the script name determines the engine used to execute the script; however, this may be overridden by metadata in the script itself.

The following example demonstrates installing scripts:

Installing a Task with the C++ Client

#include "infinispan/hotrod/ConfigurationBuilder.h"
#include "infinispan/hotrod/RemoteCacheManager.h"
#include "infinispan/hotrod/RemoteCache.h"
#include "infinispan/hotrod/Version.h"
#include "infinispan/hotrod/JBasicMarshaller.h"
using namespace infinispan::hotrod;
int main(int argc, char** argv) {
    // Configure the client
    ConfigurationBuilder builder;
    builder.addServer().host("127.0.0.1").port(11222).protocolVersion(
        Configuration::PROTOCOL_VERSION_24);
    RemoteCacheManager cacheManager(builder.build(), false);
    try {
    // Create the cache with the given marshallers
    auto *km = new JBasicMarshaller<std::string>();
    auto *vm = new JBasicMarshaller<std::string>();
    RemoteCache<std::string, std::string> cache = cacheManager.getCache<
        std::string, std::string>(km, &Marshaller<std::string>::destroy,
        vm, &Marshaller<std::string>::destroy,
        std::string("namedCache"));
    cacheManager.start();

    // Obtain a reference to the ___script_cache
    RemoteCache<std::string, std::string> scriptCache =
        cacheManager.getCache<std::string, std::string>(
        "___script_cache", false);
    // Install on the server the getValue script
    std::string getValueScript(
        "// mode=local,language=javascript\n "
        "var cache = cacheManager.getCache(\"namedCache\");\n "
        "var ct = cache.get(\"accessCounter\");\n "
        "var c = ct==null ? 0 : parseInt(ct);\n "
        "cache.put(\"accessCounter\",(++c).toString());\n "
        "cache.get(\"privateValue\") ");
    std::string getValueScriptName("getValue.js");
    std::string pGetValueScriptName =
        JBasicMarshaller<std::string>::addPreamble(getValueScriptName);
    std::string pGetValueScript =
        JBasicMarshaller<std::string>::addPreamble(getValueScript);
    scriptCache.put(pGetValueScriptName, pGetValueScript);
    // Install on the server the get access counter script
    std::string getAccessScript(
        "// mode=local,language=javascript\n "
        "var cache = cacheManager.getCache(\"namedCache\");\n "
        "cache.get(\"accessCounter\")");
    std::string getAccessScriptName("getAccessCounter.js");
    std::string pGetAccessScriptName =
        JBasicMarshaller<std::string>::addPreamble(getAccessScriptName);
    std::string pGetAccessScript =
        JBasicMarshaller<std::string>::addPreamble(getAccessScript);
    scriptCache.put(pGetAccessScriptName, pGetAccessScript);

Executing a Task

Once installed, a task may be executed by using the execute(std::string name, std::map<std::string, std::string> args) method, passing in the name of the script to execute, along with any arguments that are required for execution.

The following example demonstrates executing a script:

Executing a Script with the C++ Client

    // The following is a continuation of the above example
    cache.put("privateValue", "Counted Access Value");
    std::map<std::string, std::string> s;
    // Execute the getValue script
    std::vector<unsigned char> execValueResult = cache.execute(
        getValueScriptName, s);
    // Execute the getAccess script
    std::vector<unsigned char> execAccessResult = cache.execute(
        getAccessScriptName, s);

    std::string value(
        JBasicMarshallerHelper::unmarshall<std::string>(
            (char*) execValueResult.data()));
    std::string access(
        JBasicMarshallerHelper::unmarshall<std::string>(
            (char*) execAccessResult.data()));

    std::cout << "Returned value is '" << value
        << "' and has been accessed: " << access << " times."
        << std::endl;

  } catch (const Exception& e) {
  std::cout << "is: " << typeid(e).name() << '\n';
  std::cerr << "fail unexpected exception: " << e.what() << std::endl;
  return 1;
  }

	cacheManager.stop();
	return 0;
}

16.9. Hot Rod C# Client

16.9.1. Hot Rod C# Client

The Hot Rod C# client allows .NET runtime applications to connect and interact with Red Hat JBoss Data Grid servers. This client is aware of the cluster topology and hashing scheme, and can access an entry on the server in a single hop similar to the Hot Rod Java and Hot Rod C++ clients.

The Hot Rod C# client is compatible with 64-bit operating systems on which the .NET Framework is supported by Microsoft. Visual Studio 2015 and .NET 4.6.2 are prerequisites for the Hot Rod C# client.

16.9.2. Hot Rod C# Client Download and Installation

The Hot Rod C# client is included in a .msi file jboss-datagrid-<version>-hotrod-dotnet-client.msi packed for download with Red Hat JBoss Data Grid. To install the Hot Rod C# client, execute the following instructions.

Installing the Hot Rod C# Client

  1. As an administrator, navigate to the location where the Hot Rod C# .msi file is downloaded. Run the .msi file to launch the windows installer and then click Next.

    Figure 16.1. Hot Rod C# Client Setup Welcome

    Hot Rod C# Client Setup Welcome
  2. Review the end-user license agreement. Select the I accept the terms in the License Agreement check box and then click Next.

    Figure 16.2. Hot Rod C# Client End-User License Agreement

    Hot Rod C# Client End-User License Agreement
  3. To change the default directory, click Change…​ or click Next to install in the default directory.

    Figure 16.3. Hot Rod C# Client Destination Folder

    Hot Rod C# Client Destination Folder
  4. Click Install to begin the Hot Rod C# client installation.

    Figure 16.4. Hot Rod C# Client Begin Installation

    Hot Rod C# Client Begin Installation
  5. Click Finish to complete the Hot Rod C# client installation.

    Figure 16.5. Hot Rod C# Client Setup Completion

    Hot Rod C# Client Setup Completion

16.9.3. Creating a Hot Rod C# .NET Project

To use the Hot Rod C# client in a .NET project the following steps must be performed:

Configure the Hot Rod C# Project

  1. Add the Path Environment Variables

    The PATH environment variable must have the following folders added:

    C:\path\to\infinispan-hotrod-dotnet 8.5.0.Final\bin
    C:\path\to\infinispan-hotrod-dotnet 8.5.0.Final\lib
  2. Remove Prefer 32 bit

    On the Project properties, under the Build tab, ensure that Prefer 32 bit is unchecked.

  3. Add the Hot Rod C# dlls

    1. On the Solution Explorer view select Project.
    2. Select References.
    3. Right-click on references and select Add Reference.
    4. In the window presented, click Browse and navigate to the C:\path\to\infinispan-hotrod-dotnet 8.5.0.Final\lib\hotrodcs.dll file.
    5. Click OK.

The Hot Rod C# API may now be used in the .NET project.

16.9.4. Hot Rod C# Client Configuration

The Hot Rod C# client is configured programmatically using the ConfigurationBuilder. Configure the host and the port to which the client should connect.

Sample C# file configuration

The following example shows how to use the ConfigurationBuilder to configure a RemoteCacheManager.

C# configuration

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Infinispan.HotRod;
using Infinispan.HotRod.Config;
namespace simpleapp
{
    class Program
    {
        static void Main(string[] args)
        {
            ConfigurationBuilder builder = new ConfigurationBuilder();
            builder.AddServer()
                .Host(args.Length > 1 ? args[0] : "127.0.0.1")
                .Port(args.Length > 2 ? int.Parse(args[1]) : 11222);
            Configuration config = builder.Build();
            RemoteCacheManager cacheManager = new RemoteCacheManager(config);
            [...]
        }
    }
}

16.9.5. Hot Rod C# Client API

The RemoteCacheManager is a starting point to obtain a reference to a RemoteCache.

The following example shows retrieval of a default cache from the server and a few basic operations.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Infinispan.HotRod;
using Infinispan.HotRod.Config;
namespace simpleapp
{
    class Program
    {
        static void Main(string[] args)
        {
            ConfigurationBuilder builder = new ConfigurationBuilder();
            builder.AddServer()
                .Host(args.Length > 1 ? args[0] : "127.0.0.1")
                .Port(args.Length > 2 ? int.Parse(args[1]) : 11222);
            Configuration config = builder.Build();
            RemoteCacheManager cacheManager = new RemoteCacheManager(config);
            cacheManager.Start();
            // Retrieve a reference to the default cache.
            IRemoteCache<String, String> cache = cacheManager.GetCache<String, String>();
            // Add entries.
            cache.Put("key1", "value1");
            cache.PutIfAbsent("key1", "anotherValue1");
            cache.PutIfAbsent("key2", "value2");
            cache.PutIfAbsent("key3", "value3");
            // Retrive entries.
            Console.WriteLine("key1 -> " + cache.Get("key1"));
            // Bulk retrieve key/value pairs.
            int limit = 10;
            IDictionary<String, String> result = cache.GetBulk(limit);
            foreach (KeyValuePair<String, String> kv in result)
            {
                Console.WriteLine(kv.Key + " -> " + kv.Value);
            }
            // Remove entries.
            cache.Remove("key2");
            Console.WriteLine("key2 -> " + cache.Get("key2"));
            cacheManager.Stop();
        }
    }
}

16.9.6. Hot Rod C# Client Asynchronous API

The Hot Rod C# client offers asynchronous versions of many of the synchronous methods, allowing non-blocking methods for interacting with remote caches.

These methods follow the same naming convention as the synchronous methods, except that Async is appended to the end of each method. Asynchronous methods return a Task containing the result of the operation. If a method were to return a String, instead it will return a Task<String>

A list of asynchronous methods are below:

  • ClearAsync
  • GetAsync
  • PutAsync
  • PutAllAsync
  • PutIfAbsentAsync
  • RemoveAsync
  • RemoveWithVersionAsync
  • ReplaceAsync
  • ReplaceWithVersionAsync

Hot Rod C# Asynchronous API Example

The following example demonstrates using these methods:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Infinispan.HotRod;
using Infinispan.HotRod.Config;
namespace simpleapp
{
    class Program
    {
        static void Main(string[] args)
        {
            ConfigurationBuilder builder = new ConfigurationBuilder();
            builder.AddServer()
                .Host(args.Length > 1 ? args[0] : "127.0.0.1")
                .Port(args.Length > 2 ? int.Parse(args[1]) : 11222);
            Configuration config = builder.Build();
            RemoteCacheManager cacheManager = new RemoteCacheManager(config);
            IRemoteCache<String,String> cache = cacheManager.GetCache<String,String>();

            // Add Entries Async
            cache.PutAsync("key1","value1");
            cache.PutAsync("key2","value2");

            // Retrieve Entries Async
            Task<string> futureExec = cache.GetAsync("key1");

            string result = futureExec.Result;
        }
    }
}

16.9.7. Hot Rod C# Client Remote Event Listeners

The Hot Rod C# client supports remote cache listeners, and these may be added using the addListener method on the ClientListener.

Important

Remote Event Listeners is a Technology Preview feature of the Hot Rod C# client in Red Hat JBoss Data Grid 7.2.

This method takes a listener for each event type(create, modify, remove, expire, or custom). For more information on Remote Event Listeners refer to Remote Event Listeners (Hot Rod). An example of a modifiedEvent is provided below:

[...]
private static void modifiedEventAction(Event.ClientCacheEntryModifiedEvent<string> e)
{
    ++modifiedEventCounter;
    modifiedSemaphore.Release();
}
[...]
public void ModifiedEventTest()
{
    IRemoteCache<string, string> cache = remoteManager.GetCache<string, string>();
    cache.Clear();
    Event.ClientListener<string, string> cl = new Event.ClientListener<string, string>();
    cl.filterFactoryName = "";
    cl.converterFactoryName = "";
    cl.addListener(modifiedEventAction);
    cache.addClientListener(cl, new string[] { }, new string[] { }, null);
}

16.9.8. Hot Rod C# Client Working with Sites

Multiple Red Hat JBoss Data Grid Server clusters may be deployed so that each cluster belongs to a different site. Such deployments are done to enable data to be backed up from one cluster to another, potentially in a different geographical location. The C# client implementation can failover between nodes within a cluster, along with failing over to a different cluster entirely, should the original cluster become nonresponsive. To be able to failover between clusters all Red Hat JBoss Data Grid Servers must be configured with Cross-Datacenter replication. Instructions for this procedure are found in the Red Hat JBoss Data Grid Administration and Configuration Guide.

Once failed over the client will remain connected to the alternative cluster until this new cluster becomes unavailable, in which case it will throw an exception. If the original cluster becomes operational, the client will not switch over automatically. To switch back to the original cluster use the SwitchToDefaultCluster() method mentioned below.

Once Cross-Datacenter replication has been configured on the servers, the client has to provide the alternative clusters' configuration with at least one host/port pair details for each of the clusters configured. For example:

ConfigurationBuilder conf1 = new ConfigurationBuilder();
conf1.AddServer().Host("127.0.0.1").Port(11222);
conf1.AddCluster("nyc").AddClusterNode("127.0.0.1", 11322);
RemoteCacheManager manager1 = new RemoteCacheManager(conf1.Build(), true);

ConfigurationBuilder conf2 = new ConfigurationBuilder();
conf2.AddServer().Host("127.0.0.1").Port(11322);
conf2.AddCluster("lon").AddClusterNode("127.0.0.1", 11222);
RemoteCacheManager remoteManager = new RemoteCacheManager(conf2.Build(), true);

16.9.8.1. Manual Cluster Switch

In addition to automatic site failover, C++ clients may switch between clusters by calling either of the following methods:

  • SwitchToCluster(clusterName) - Forces the client to switch to the pre-defined cluster name passed in.
  • SwitchToDefaultCluster() - Forces the client to switch to the initial servers defined in the client configuration.

16.9.9. Performing Remote Queries via the Hot Rod C# Client

The Hot Rod C# client allows remote querying, using Google’s Protocol Buffers, once the RemoteCacheManager has been configured with the Protobuf marshaller.

Important

Performing Remote Queries is a Technology Preview feature of the Hot Rod C# Client in Red Hat JBoss Data Grid 7.2.

Enable Remote Querying on the Hot Rod C# Client

  1. Obtain a connection to the remote JBoss Data Grid server, passing the Protobuf marshaller into the configuration:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Infinispan.HotRod;
    using Infinispan.HotRod.Config;
    using Google.Protobuf;
    using Org.Infinispan.Protostream;
    using Org.Infinispan.Query.Remote.Client;
    using QueryExampleBankAccount;
    using System.IO;
    
    namespace Query
    {
        /// <summary>
        /// This sample code shows how to perform Infinispan queries using the C# client
        /// </summary>
        class Query
        {
            static void Main(string[] args)
            {
                // Cache manager setup
                RemoteCacheManager remoteManager;
                const string ERRORS_KEY_SUFFIX = ".errors";
                const string PROTOBUF_METADATA_CACHE_NAME = "___protobuf_metadata";
                ConfigurationBuilder conf = new ConfigurationBuilder();
                conf.AddServer().Host("127.0.0.1").Port(11222).ConnectionTimeout(90000).SocketTimeout(6000);
                conf.Marshaller(new BasicTypesProtoStreamMarshaller());
                remoteManager = new RemoteCacheManager(conf.Build(), true);
                IRemoteCache<String, String> metadataCache = remoteManager.GetCache<String, String>(PROTOBUF_METADATA_CACHE_NAME);
                IRemoteCache<int, User> testCache = remoteManager.GetCache<int, User>("namedCache");
  2. Install any protobuf entities model:

                // This example continues the previous codeblock
                // Installing the entities model into the Infinispan __protobuf_metadata cache
                metadataCache.Put("sample_bank_account/bank.proto", File.ReadAllText("resources/proto2/bank.proto"));
                if (metadataCache.ContainsKey(ERRORS_KEY_SUFFIX))
                {
                    Console.WriteLine("fail: error in registering .proto model");
                    Environment.Exit(-1);
                }
  3. This step adds data to the cache for the purposes of this demonstration, and may be ignored when simply querying a remote cache:

                // This example continues the previous codeblock
                // The application cache must contain entities only
                testCache.Clear();
                // Fill the application cache
                User user1 = new User();
                user1.Id = 4;
                user1.Name = "Jerry";
                user1.Surname = "Mouse";
                User ret = testCache.Put(4, user1);
  4. Query the remote cache:

                // This example continues the previous codeblock
                // Run a query
                QueryRequest qr = new QueryRequest();
                qr.JpqlString = "from sample_bank_account.User";
                QueryResponse result = testCache.Query(qr);
                List<User> listOfUsers = new List<User>();
                unwrapResults(result, listOfUsers);
    
            }
  5. To process the results convert the protobuf matter into C# objects. The following method demonstrates this conversion:

            // Convert Protobuf matter into C# objects
            private static bool unwrapResults<T>(QueryResponse resp, List<T> res) where T : IMessage<T>
            {
                if (resp.ProjectionSize > 0)
                {  // Query has select
                    return false;
                }
                for (int i = 0; i < resp.NumResults; i++)
                {
                    WrappedMessage wm = resp.Results.ElementAt(i);
    
                    if (wm.WrappedBytes != null)
                    {
                        WrappedMessage wmr = WrappedMessage.Parser.ParseFrom(wm.WrappedBytes);
                        if (wmr.WrappedMessageBytes != null)
                        {
                            System.Reflection.PropertyInfo pi = typeof(T).GetProperty("Parser");
    
                            MessageParser<T> p = (MessageParser<T>)pi.GetValue(null);
                            T u = p.ParseFrom(wmr.WrappedMessageBytes);
                            res.Add(u);
                        }
                    }
                }
                return true;
            }
        }
    }

16.9.10. Using the Near Cache with the Hot Rod C# Client

Near caches are optional caches for the Hot Rod C# client that keep recently accessed data close to the user, providing faster access to data that is accessed frequently. This cache acts as a local Hot Rod client cache that is synchronized with the remote server in the background.

Near caches are enabled programmatically on the ConfigurationBuilder by using the NearCache() method, as seen in the following example:

ConfigurationBuilder conf = new ConfigurationBuilder();
conf.AddServer().Host("127.0.0.1").Port(11222)

// Define a Near Cache that contains up to 10 entries
.NearCache().Mode(NearCacheMode.INVALIDATED).MaxEntries(10);

The following methods are used to configure the near cache’s behavior:

  • NearCache() - defines a NearCacheConfigurationBuilder which may be modified further.
  • Mode(NearCacheMode mode) - requires a NearCacheMode be passed in. Defaults to DISABLED, indicating no near cache is enabled.
  • MaxEntries(int maxEntries) - indicates the maximum number of entries for the near cache to contain. Once the near cache is full, the oldest entry will be evicted. Setting this value to 0 defines an unbounded near cache.

Entries in the near cache are kept aligned with the remote cache via events. If a change occurs in the server then an appropriate event is sent to the client, which will update the near cache accordingly.

16.9.11. Script Execution Using the Hot Rod C# Client

The Hot Rod C# client allows tasks to be executed directly on Red Hat JBoss Data Grid servers via Remote Execution. This feature executes logic close to the data, utilizing the resources of all nodes in the cluster. Tasks may be deployed to the server instances, and may then be executed programmatically.

Installing a Task

Tasks may be installed on the server by being using the Put(string name, string script) method of the ___script_cache. The extension of the script name determines the engine used to execute the script; however, this may be overridden by metadata in the script itself.

The following example demonstrates installing scripts:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Infinispan.HotRod;
using Infinispan.HotRod.Config;

namespace RemoteExec
{
    /// <summary>
    /// This sample code shows how to perform a server remote execution using the C# client
    /// </summary>
    class RemoteExec
    {
        static void Main(string[] args)
        {
            // Cache manager setup
            RemoteCacheManager remoteManager;
            IMarshaller marshaller;
            ConfigurationBuilder conf = new ConfigurationBuilder();
            conf.AddServer().Host("127.0.0.1").Port(11222).ConnectionTimeout(90000).SocketTimeout(6000);
            marshaller = new JBasicMarshaller();
            conf.Marshaller(marshaller);
            remoteManager = new RemoteCacheManager(conf.Build(), true);

            // Install the .js code into the Infinispan __script_cache
            const string SCRIPT_CACHE_NAME = "___script_cache";
            string valueScriptName = "getValue.js";
            string valueScript = "// mode=local,language=javascript\n "
                 + "var cache = cacheManager.getCache(\"namedCache\");\n "
                 + "var ct = cache.get(\"accessCounter\");\n "
                 + "var c = ct==null ? 0 : parseInt(ct);\n "
                 + "cache.put(\"accessCounter\",(++c).toString());\n "
                 + "cache.get(\"privateValue\") ";
            string accessScriptName = "getAccess.js";
            string accessScript = "// mode=local,language=javascript\n "
                + "var cache = cacheManager.getCache(\"namedCache\");\n "
                + "cache.get(\"accessCounter\")";
            IRemoteCache<string, string> scriptCache = remoteManager.GetCache<string, string>(SCRIPT_CACHE_NAME);
            IRemoteCache<string, string> testCache = remoteManager.GetCache<string, string>("namedCache");
            scriptCache.Put(valueScriptName, valueScript);
            scriptCache.Put(accessScriptName, accessScript);

Executing a Task

Once installed, a task may be executed by using the Execute(string name, Dictionary<string, string> scriptArgs) method, passing in the name of the script to execute, along with any arguments that are required for execution.

The following example demonstrates running the scripts:

            // This example continues the previous codeblock
            testCache.Put("privateValue", "Counted Access Value");
            Dictionary<string, string> scriptArgs = new Dictionary<string, string>();
            byte[] ret1 = testCache.Execute(valueScriptName, scriptArgs);
            string value = (string)marshaller.ObjectFromByteBuffer(ret1);
            byte[] ret2 = testCache.Execute(accessScriptName, scriptArgs);
            string accessCount = (string)marshaller.ObjectFromByteBuffer(ret2);
            Console.Write("Return value is '" + value + "' and has been accessed '" + accessCount + "' times.");

        }
    }
}
Important

Script execution using the Hot Rod C# Client is a Technology Preview Feature in JBoss Data Grid 7.2.

16.9.12. String Marshaller for Interoperability

To use the string compatibility marshaller, pass an instance of CompatibilityMarshaller to the Marshaller() method of the ConfigurationBuilder object similar to this:

ConfigurationBuilder builder = new ConfigurationBuilder();
builder.Marshaller(new CompatibilityMarshaller());
RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build(), true);
IRemoteCache<String, String> cache = cacheManager.GetCache<String, String>();
[....]
cache.Put("key", "value");
[...]
cache.Get("key");
[...]
Note

Attempts to store or retrieve non-string key/values will result in a HotRodClientException being thrown.

16.10. Hot Rod Node.js Client

16.10.1. Hot Rod Node.js Client

The Hot Rod Node.js client is an asynchronous event-driven client allowing Node.js users to communicate to Red Hat JBoss Data Grid servers. This client supports many of the features in the Java client, including the ability to execute and store scripts, utilize cache listeners, and receive the full cluster topology.

The asynchronous operation results are represented with Promise instances, allowing the client to easily chain multiple invocations together and centralizing error handling.

16.10.2. Installing the Hot Rod Node.js Client

The Hot Rod Node.js client is included in a standalone distribution that you download separately to Red Hat JBoss Data Grid.

Procedure: Installing the Hot Rod Node.js Client

  1. Download the jboss-datagrid-7.2.x-nodejs-client.zip from the Red Hat Customer Portal.
  2. Extract the downloaded archive.
  3. Use npm to install the provided tarball, as seen in the following command:

    npm install /path/to/jboss-datagrid-7.2.x-nodejs-client/infinispan-7.2.3-Final-redhat-00002.tgz

16.10.3. Hot Rod Node.js Requirements

The Hot Rod Node.js client has the following requirements:

  • Node.js version 0.10 or higher.
  • Red Hat JBoss Data Grid server instance 7.0.0 or higher.

16.10.4. Hot Rod Node.js Basic Functionality

The following example shows how to connect to a Red Hat JBoss Data Grid server and perform basic operations, such as putting and retrieving data. The following example assumes that a Red Hat JBoss Data Grid server is available at the default location of localhost:11222:

var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// As no cache is specified all operations will occur on the 'default' cache
var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {

  // Attempt to put a value in the cache.
  var clientPut = client.put('key', 'value');

  // Retrieve the value just placed
  var clientGet = clientPut.then(
      function() { return client.get('key'); });

  // Print out the value that was retrieved
  var showGet = clientGet.then(
      function(value) { console.log('get(key)=' + value); });

  // Disconnect from the server
  return showGet.finally(
      function() { return client.disconnect(); });
}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

Connecting to a Named Cache

To connect to a specific cache the cacheName attribute may be defined when specifying the location of the Red Hat JBoss Data Grid server instance, as seen in the following example:

var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// and connect to namedCache
var connected = infinispan.client(
  {port: 11222, host: '127.0.0.1'}, {cacheName: 'namedCache'});

connected.then(function (client) {

  // Log the result of the connection
  console.log('Connected to `namedCache`');

  // Disconnect from the server
  return client.disconnect();

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

Using Data Sets

In addition to placing single entries the putAll and getAll methods may be used to place or retrieve a set of data. The following example walks through these operations:

var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// As no cache is specified all operations will occur on the 'default' cache
var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {
  var data = [
    {key: 'multi1', value: 'v1'},
    {key: 'multi2', value: 'v2'},
    {key: 'multi3', value: 'v3'}];

  // Place all of the key/value pairs in the cache
  var clientPutAll = client.putAll(data);

  // Obtain the values for two of the keys
  var clientGetAll = clientPutAll.then(
    function() { return client.getAll(['multi2', 'multi3']); });

  // Print out the values obtained.
  var showGetAll = clientGetAll.then(
    function(entries) {
      console.log('getAll(multi2, multi3)=%s', JSON.stringify(entries));
    }
  );

  // Obtain an iterator for the cache
  var clientIterator = showGetAll.then(
    function() { return client.iterator(1); });

  // Iterate over the entries in the cache, printing the values
  var showIterated = clientIterator.then(
    function(it) {
      function loop(promise, fn) {
        // Simple recursive loop over iterator's next() call
        return promise.then(fn).then(function (entry) {
          return !entry.done ? loop(it.next(), fn) : entry.value;
        });
      }

      return loop(it.next(), function (entry) {
        console.log('iterator.next()=' + JSON.stringify(entry));
        return entry;
      });
    }
  );

  // Clear the cache of all values
  var clientClear = showIterated.then(
    function() { return client.clear(); });

  // Disconnect from the server
  return clientClear.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

16.10.5. Hot Rod Node.js Conditional Operations

The Hot Rod protocol stores metadata in addition to each value associated with the keys.

The getWithMetadata retrieves the value and metadata for the key.

The following example demonstrates utilizing this metadata:

var infinispan = require('infinispan');

// Obtain a connection to the JBoss Data Grid server
// As no cache is specified all operations will occur on the 'default' cache
var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {

  // Attempt to put a value in the cache if it does not exist
  var clientPut = client.putIfAbsent('cond', 'v0');

  // Print out the result of the put operation
  var showPut = clientPut.then(
      function(success) { console.log(':putIfAbsent(cond)=' + success); });

  // Replace the value in the cache
  var clientReplace = showPut.then(
      function() { return client.replace('cond', 'v1'); } );

  // Print out the result of the replace
  var showReplace = clientReplace.then(
      function(success) { console.log('replace(cond)=' + success); });

  // Obtain the value and metadata
  var clientGetMetaForReplace = showReplace.then(
      function() { return client.getWithMetadata('cond'); });

  // Replace the value only if the version matches
  var clientReplaceWithVersion = clientGetMetaForReplace.then(
      function(entry) {
        console.log('getWithMetadata(cond)=' + JSON.stringify(entry));
        return client.replaceWithVersion('cond', 'v2', entry.version);
      }
  );

  // Print out the result of the previous replace
  var showReplaceWithVersion = clientReplaceWithVersion.then(
      function(success) { console.log('replaceWithVersion(cond)=' + success); });

  // Obtain the value and metadata
  var clientGetMetaForRemove = showReplaceWithVersion.then(
      function() { return client.getWithMetadata('cond'); });

  // Remove the value only if the version matches
  var clientRemoveWithVersion = clientGetMetaForRemove.then(
      function(entry) {
        console.log('getWithMetadata(cond)=' + JSON.stringify(entry));
        return client.removeWithVersion('cond', entry.version);
      }
  );

  // Print out the result of the previous remove
  var showRemoveWithVersion = clientRemoveWithVersion.then(
      function(success) { console.log('removeWithVersion(cond)=' + success)});

  // Disconnect from the server
  return showRemoveWithVersion.finally(
      function() { return client.disconnect(); });

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

16.10.6. Hot Rod Node.js Data Sets

The client may specify multiple server addresses when a connection is defined. When multiple servers are defined it will loop through each one until a successful connection to a node is obtained. An example of this configuration is below:

var infinispan = require('infinispan');

// Accepts multiple addresses and fails over if connection not possible
var connected = infinispan.client(
  [{port: 99999, host: '127.0.0.1'}, {port: 11222, host: '127.0.0.1'}]);

connected.then(function (client) {

  // Obtain a list of all members in the cluster
  var members = client.getTopologyInfo().getMembers();

  // Print out the list of members
  console.log('Connected to: ' + JSON.stringify(members));

  // Disconnect from the server
  return client.disconnect();

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

16.10.7. Hot Rod Node.js Remote Events

The Hot Rod Node.js client supports remote cache listeners, and these may be added using the addListener method. This method takes the event type (create, modify, remove, or expiry) and the function callback as parameter. For more information on Remote Event Listeners refer to Remote Event Listeners (Hot Rod). An example of this is shown below:

var infinispan = require('infinispan');
var Promise = require('promise');

var connected = infinispan.client({port: 11222, host: '127.0.0.1'});

connected.then(function (client) {

  var clientAddListenerCreate = client.addListener(
    'create', function(key) { console.log('[Event] Created key: ' + key); });

  var clientAddListeners = clientAddListenerCreate.then(
    function(listenerId) {
      // Multiple callbacks can be associated with a single client-side listener.
      // This is achieved by registering listeners with the same listener id
      // as shown in the example below.
      var clientAddListenerModify = client.addListener(
        'modify', function(key) { console.log('[Event] Modified key: ' + key); },
        {listenerId: listenerId});

      var clientAddListenerRemove = client.addListener(
        'remove', function(key) { console.log('[Event] Removed key: ' + key); },
        {listenerId: listenerId});

      return Promise.all([clientAddListenerModify, clientAddListenerRemove]);
    });

  var clientCreate = clientAddListeners.then(
    function() { return client.putIfAbsent('eventful', 'v0'); });

  var clientModify = clientCreate.then(
    function() { return client.replace('eventful', 'v1'); });

  var clientRemove = clientModify.then(
    function() { return client.remove('eventful'); });

  var clientRemoveListener =
        Promise.all([clientAddListenerCreate, clientRemove]).then(
          function(values) {
            var listenerId = values[0];
            return client.removeListener(listenerId);
          });

  return clientRemoveListener.finally(
    function() { return client.disconnect(); });

}).catch(function(error) {

  console.log("Got error: " + error.message);

});

16.10.8. Hot Rod Node.js Working with Clusters

Red Hat JBoss Data Grid server instances may be clustered together to provide failover and capabilities for scaling up. While working with a cluster is very similar to using a single instance there are a few considerations:

  • The client only needs to know about a single server’s address to receive information about the entire server cluster, regardless of the cluster size.
  • For distributed caches, key-based operations are routed in the cluster using the same consistent hash algorithms used by the server. This means that the client can locate where any particular key resides without the need for extra network hops.
  • For distributed caches, multi-key or key-less operations are routed in round-robin fashion.
  • For replicated and invalidated caches, all operations are routed in round-robin fashion, regardless of whether they are key-based or multi-key/key-less.

All routing and failover is transparent to the client, so operations executed against a cluster look identical to the code examples performed above.

The cluster topology can be obtained using the following example:

var infinispan = require('infinispan');

var connected = infinispan.client({port: 11322, host: '127.0.0.1'});

connected.then(function (client) {

  var members = client.getTopologyInfo().getMembers();

  // Should show all expected cluster members
  console.log('Connected to: ' + JSON.stringify(members));

  // Add your own operations here...

  return client.disconnect();

}).catch(function(error) {

  // Log any errors received
  console.log("Got error: " + error.message);

});

16.10.9. Hot Rod Node.js Working with Sites

Multiple Red Hat JBoss Data Grid Server clusters may be deployed so that each cluster belongs to a different site. Such deployments are done to enable data to be backed up from one cluster to another, potentially in a different geographical location. The Node.js client implementation can failover between nodes within a cluster, along with failing over to a different cluster entirely, should the original cluster become nonresponsive. To be able to failover between clusters all Red Hat JBoss Data Grid Servers must be configured with Cross-Datacenter replication. Instructions for this procedure are found in the Red Hat JBoss Data Grid Administration and Configuration Guide.

Once failed over the client will remain connected to the alternative cluster until this new cluster becomes unavailable, in which case it will try any other clusters defined, including the original server settings.

Once Cross-Datacenter replication has been configured on the servers, the client has to provide the alternative clusters' configuration with at least one host/port pair details for each of the clusters configured. For example:

var connected = infinispan.client({port: 11322, host: '127.0.0.1'},
  {
    clusters: [
      {
        name: 'LON',
        servers: [{port: 1234, host: 'LONA1'}]
      },
      {
        name: 'NYC',
        servers: [{port: 2345, host: 'NYCB1'}, {port: 3456, host: 'NYCB2'}]
      }
    ]
  });

16.10.9.1. Manual Cluster Switch

In addition to automatic site failover, Node.js clients may switch between site clusters manually by calling either of the following methods:

  • switchToCluster(clusterName) - Forces the client to switch to the pre-defined cluster name passed in.
  • switchToDefaultCluster() - Forces the client to switch to the initial servers defined in the client configuration.

For example, to manually switch to the NYC cluster the following could be used:

var connected = infinispan.client({port: 11322, host: '127.0.0.1'},
  {
    clusters: [
      {
        name: 'LON',
        servers: [{port: 1234, host: 'LONA1'}]
      },
      {
        name: 'NYC',
        servers: [{port: 2345, host: 'NYCB1'}, {port: 3456, host: 'NYCB2'}]
      }
    ]
  });

connected.then(function (client) {

  var switchToB = client.getTopologyInfo().switchToCluster('NYC');
  [...]
  });

16.10.10. Memory Profiling

You can profile how much memory Hot Rod Node.js client consumes with the following programs:

  • infinispan_memory_many_get.js profiles memory usage using multiple GET requests.
  • infinispan_memory_one_get.js profiles memory usage using one GET request.

These programs are located in the memory-profiling directory of the client package.

To run the memory profiling programs, do the following:

node --expose-gc memory-profiling/infinispan_memory_many_get.js
Note

You must pass the --expose-gc parameter so that the programs can access the global garbage collector.

Tip: Use Google Chrome Developer Tools to visualize heap dumps. Load heap dumps from the Memory tab. This tab lets you compare multiple snapshots, which is useful for finding objects that have been kept in memory between points in time.

16.10.10.1. Avoiding Memory Issues with Promises

If the Node.js client creates many Promise instances the client can consume too much memory, which degrades performance.

The following program is an example where too many Promise instances are created. In this example, a user stores data and then generates multiple retrievals. The results are printed when all of the retrievals are complete, which results in increased memory consumption.

var _ = require('underscore');
var infinispan = require('infinispan');
var Promise = require('promise');

var heapdump = require('heapdump');

var connected = infinispan.client({port: 11222, host: '127.0.0.1'},{cacheName: 'namedCache'});
console.log("Connected to JDG server");
connected.then(function (client) {
  var sessionA = "Key";
  var clientPut = client.put(sessionA, "test");
  var clientTemp = clientPut;
  return clientTemp.then(function() {

      var initialHeapUsed = process.memoryUsage().heapUsed;
      console.log("process.memoryUsage().heapUsed: " + initialHeapUsed);
      heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot');
      var temp = [];

      var numOps = 10000; // 500000

      _.map(_.range(numOps), function(i) {
        temp.push(client.get(sessionA).then(function(value) {
          console.log("value " + value);
        }));
      });

      var promesas = Promise.all(temp);
      var completed = promesas.then(function() {
        console.log("Promises completed");
      });

      temp = null;
      promesas = null;

      return completed.then(function() {
        global.gc();
        console.log("process.memoryUsage().heapUsed (begin): " + initialHeapUsed);
        console.log("process.memoryUsage().heapUsed: "+process.memoryUsage().heapUsed);

        global.gc();
        console.log("process.memoryUsage().heapUsed: "+process.memoryUsage().heapUsed);

        heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot');

        return client.disconnect();
      });

    });
}).catch(function(err) {
  console.log("connect error", err);
});

The following output shows the increased memory consumption that resulted from having too many Promise instances created for the data retrieval:

node --expose-gc test.js
...
process.memoryUsage().heapUsed (begin): 5620856
process.memoryUsage().heapUsed: 14368456
process.memoryUsage().heapUsed: 14274008

To avoid memory issues with multiple Promise instances, you can either use Promise instances in the platform or generate a new Promise instance, depending on your version of Node.js.

Using Platform Promises

Recent Node.js versions include promise objects so that you do not need to load the promise library with the following line:

var Promise = require('promise')

If you remove that line from the preceding example and then run it with a Node.js version such as 8.11, the memory profiling results are as follows:

$ node --version
v8.11.1
$ node --expose-gc test.js
...
process.memoryUsage().heapUsed (begin): 6379448
process.memoryUsage().heapUsed: 6749056
process.memoryUsage().heapUsed: 6614560

Generating an Extra Promise

Older Node.js versions can generate a new Promise after the collection of promise objects has been handled, as in the following example:

var _ = require('underscore');
var infinispan = require('infinispan');
var Promise = require('promise');

var heapdump = require('heapdump');

var connected = infinispan.client({port: 11222, host: '127.0.0.1'},{cacheName: 'namedCache'});
console.log("Connected to JDG server");
connected.then(function (client) {
  var sessionA = "Key";
  var clientPut=client.put(sessionA, "test");
  var clientTemp = clientPut;
  return clientTemp.then(function() {

    var initialHeapUsed = process.memoryUsage().heapUsed;
    console.log("process.memoryUsage().heapUsed: " + initialHeapUsed);
    heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot');
    var temp = [];

    var numOps = 10000; // 500000

    _.map(_.range(numOps), function(i) {
      temp.push(client.get(sessionA).then(function(value) {
        console.log("value " + value);
      }));
    });

    var promesas = Promise.all(temp);
    var completed = promesas.then(function() {
      console.log("Promises completed");
    });

    temp = null;
    promesas = null;

    var getAfterAll = completed.then(function() {
      return client.get(sessionA);
    });

    var logGet = getAfterAll.then(function(value) {
      console.log("[get after all] value: " + value);
    })

    return logGet.then(function() {
      global.gc();
      console.log("process.memoryUsage().heapUsed (begin): " + initialHeapUsed);
      console.log("process.memoryUsage().heapUsed: "+process.memoryUsage().heapUsed);

      global.gc();
      console.log("process.memoryUsage().heapUsed: "+process.memoryUsage().heapUsed);

      heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot');

      return client.disconnect();
    });
  });
}).catch(function(err) {
  console.log("connect error", err);
});

The preceding example has the following the memory profiling results:

$ node --version
v0.10.48
$ node --expose-gc test.js
...
process.memoryUsage().heapUsed (begin): 5735864
process.memoryUsage().heapUsed: 4054352
process.memoryUsage().heapUsed: 4050064

16.11. Interoperability Between Hot Rod C++ and Hot Rod Java Client

Red Hat JBoss Data Grid provides interoperability between Hot Rod Java and Hot Rod C++ clients to access structured data. This is made possible by structuring and serializing data using Google’s Protobuf format.

For example, using interoperability between languages would allow a Hot Rod C++ client to write the following Person object structured and serialized using Protobuf, and the Hot Rod Java client can read the same Person object structured as Protobuf.

Using Interoperability Between Languages

package sample;
message Person {
    required int32 age = 1;
    required string name = 2;
}

Interoperability between C++ and Hot Rod Java Client is fully supported for primitive data types, strings, and byte arrays, as Protobuf and Protostream are not required for these types of interoperability.

16.12. Compatibility Between Server and Hot Rod Client Versions

Hot Rod clients, such as the Hot Rod Java, Hot Rod C++, and Hot Rod C#, are compatible with different versions of Red Hat JBoss Data Grid server. The server should be of the latest version in order to run with different Hot Rod clients.

Note

It is recommended to use the same version of the Hot Rod client and the Red Hat JBoss Data Grid server, except in a case of migration or upgrade, to prevent any known problems.

Consider the following scenarios.

Scenario 1: Server running on a newer version than the Hot Rod client.

The following will be the impact on the client side:

  • client will not have advantage of the latest protocol improvements.
  • client might run into known issues which are fixed for the server-side version.
  • client can only use the functionalities available in its current version and the previous versions.

Scenario 2: Hot Rod client running on a newer version than the server.

In this case, when a Hot Rod client connects to a Red Hat JBoss Data Grid server, the connection will be rejected with an exception error. The client can be downgraded to a known protocol version by either setting the client side property infinispan.client.hotrod.protocol_version, or by using the ConfigurationBuilder's protocolVersion(String version) method. When downgraded the client version using either of these methods a String containing the desired version should be passed in. In this case the client is able to connect to the server, but will be restricted to the functionality of that version. Any command which is not supported by this protocol version will not work and throw an exception; in addition, the topology information might be inefficient in this case.

Downgrading Client Hot Rod Protocol Version

The following code snippet demonstrates how to downgrade this version using the protocolVersion(String version) method:

Configuration config = new ConfigurationBuilder()
    [...]
    .protocolVersion("2.2")
    .build();
Note

It is not recommended to use this approach without guidance from Red Hat support.

The following table details the compatibility between different Hot Rod client and server versions.

Table 16.78. Hot Rod protocol and server compatibility

Red Hat JBoss Data Grid Server VersionHot Rod Protocol Version

Red Hat JBoss Data Grid 7.2.0

Hot Rod 2.5 and later

Red Hat JBoss Data Grid 7.1.0

Hot Rod 2.5 and later

Red Hat JBoss Data Grid 7.0.0

Hot Rod 2.5 and later