Chapter 40. Scripting
40.1. Scripting
JBoss Data Grid includes a method of storing scripts on servers, allowing remote clients to execute scripts locally with the JDK’s javax.script.ScriptEngines
. By default the JDK comes with Nashorn, capable of running JavaScript; however, this may be extended to run any JVM language that offers their own ScriptEngine
.
40.2. Accessing the Script Cache
Scripts are stored in a special, protected cache entitled ___script_cache
. As this is a protected cache only loopback requests or connections with authorization enabled will be allowed to access the cache.
The following requirements must be met to connect to the ___script_cache
remotely:
-
A user has been defined with the
___script_manager
role. - The client has a secure connection to the server; this may be attained by following the instructions in Securing Interfaces.
- Authorization has been enabled on the cache-container.
Configuring the Server for Access the Script Cache
The following example covers configuring the server to access the script cache, using the DIGEST-MD5
method of securing the Hot Rod connector.
Add a user to the server as follows:
- Execute the $JDG_HOME/bin/add-user.sh (Linux) or $JDG_HOME\bin\add-user.bat (Windows) script.
Enter
b
at the first prompt to create anApplicationRealm
user.What type of user do you wish to add? a) Management User (mgmt-users.properties) b) Application User (application-users.properties) (a): b
- Follow the prompts to define the desired username and password for the user.
When prompted for the groups enter
___script_manager
for this user:What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[ ]: ___script_manager
Secure the communication between the client and server. As this example is using
DIGEST-MD5
the instructions in Configure Hot Rod Authentication (MD5) will be followed. The following snippet demonstrates the necessary xml configuration:<cache-container name="local" default-cache="default" statistics="true"> <security> <authorization> <identity-role-mapper /> <role name="admin" permissions="ALL" /> <role name="reader" permissions="READ" /> <role name="writer" permissions="WRITE" /> <role name="supervisor" permissions="READ WRITE EXEC" /> </authorization> </security> [...] <cache-container> [...] <hotrod-connector socket-binding="hotrod" cache-container="local"> <authentication security-realm="ApplicationRealm"> <sasl server-name="scriptserver" mechanisms="DIGEST-MD5" qop="auth" /> </authentication> </hotrod-connector>
Create the cache manager using the secured connection, as seen in the following code snippet:
Configuration config = new ConfigurationBuilder() .addServer() .host("localhost") .port(11222) .security() .authentication() .enable() .saslMechanism("DIGEST-MD5") .serverName("scriptserver") .callbackHandler(new MyCallbackHandler("user", "ApplicationRealm", "password".toCharArray())) .build(); cacheManager = new RemoteCacheManager(config);
40.3. Installing Scripts
A script may be added to the ___script_cache
by putting the script into the cache itself with the name of the script as the key, and the content of the script as the value. If the name of the script contains a filename extension, such as sample.js , then the extension will determine the engine used to execute the script. This behavior may be overridden by specifying metadata inside the script itself.
As the contents of the script should be stored in the value of the ___script_cache
this may either be loaded from a pre-existing file, or manually entered. The following examples demonstrate both of these options:
Loading a Script From a File
Assuming the script is stored within a file the following code sample may be used to read the contents of the file and store it into the scripting cache:
private static final String SCRIPT_CACHE = "___script_cache"; private RemoteCache<String, String> scriptingCache; [...] scriptingCache = cacheManager.getCache(SCRIPT_CACHE); [...] private void loadScript(String filename) throws IOException{ StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new FileReader(filename)); for (String line = reader.readLine(); line != null; line = reader.readLine()) { sb.append(line); sb.append("\n"); } System.out.println(sb.toString()); scriptingCache.put(filename,sb.toString()); }
Defining the Contents of the Script
Instead of loading a script from a file the script may be manually defined and placed into the scripting cache:
RemoteCache<String, String> scriptCache = cacheManager.getCache("___script_cache"); scriptCache.put("multiplication.js", "// mode=local,language=javascript\n" + "// parameters=[multiplicand,multiplier]" + "multiplicand * multiplier\n");
40.4. Scripting Metadata
Metadata may be stored in the script to provide additional information to the server on how the script is executed. This metadata is contained in a specially formatted comment on the first lines of the script.
Properties are defined as key=value pairs separated by commas, with the comment styles, such as //
, ;;
, or \#
, depending on the scripting language in use. This information may be split over multiple lines if necessary, and single or double quotes may be used to delimit the values.
The following is an example of a valid metadata comment:
// name=test, language=javascript // mode=local, parameters=[a,b,c]
Metadata Properties
The following metadata properties are available:
mode
: defines the mode of execution of a script. Can be one of the following values:-
local
: the script will be executed only by the node handling the request. The script itself however can invoke clustered operations. -
distributed
: runs the script using the Distributed Executor Service.
-
-
language
: defines the script engine that will be used to execute the script, e.g. Javascript. -
extension
: an alternative method of specifying the script engine that will be used to execute the script, e.g. js. -
role
: a specific role which is required to execute the script. -
parameters
: an array of valid parameter names for this script. Invocations which specify parameter names not included in this list will cause an exception.
As the execution mode is a characteristic of the script there is no additional configuration required on the client to invoke scripts in different modes.
40.5. Script Bindings
The script engine exposes several internal objects as pre-defined bindings when the script is executed. These are:
-
cache
: the cache against which the script is being executed. -
cacheManager
: the cacheManager for the cache. -
marshaller
: the marshaller to use for marshalling/unmarshalling data to the cache. -
scriptingManager
: the instance of the script manager which is being used to run the script. This can be used to run other scripts from a script.
40.6. Script Parameters
In addition to the standard bindings, a script may have a set of named parameters passed in which also appear as bindings. Parameters are passed in as a map of name, value pairs where the name is a string, and the value is any value understood by the marshaller in use.
Consider the following script which takes two parameters, multiplicand
and multiplier
:
// mode=local,language=javascript // parameters=[multiplicand,multiplier] multiplicand * multiplier
As the last operation is an evaluation its result will be returned to the script invoker. Passing in values changes depending on how the script is executed, and will be covered under each execution method.
40.7. Script Execution Using the Hot Rod Java Client
If authorization is disabled on the server then anyone may execute scripts once they have been installed. Otherwise, only users with EXEC
permissions will be allowed to run previously installed scripts.
Scripts may be executed in Hot Rod by calling execute(scriptName, parameters)
on the cache where the script should be executed. In this case the scriptName
corresponds with the name of the script stored in the ___script_cache
, and parameters
is a Map<String,Object>
of named parameters.
The following example demonstrates executing the above multiplication.js
script through Hot Rod:
RemoteCache<String, Integer> cache = cacheManager.getCache(); // Create the parameters for script execution Map<String, Object> params = new HashMap<>(); params.put("multiplicand", 10); params.put("multiplier", 20); // Run the script on the server, passing in the parameters Object result = cache.execute("multiplication.js", params);
40.8. Script Examples
The following examples demonstrate various tasks to assist in the reader’s understanding of the scripting syntax, and to get ideas on what tasks may be suitable for scripts in each environment.
Distributed Execution
The following is a script that runs within a Distributed Executor. Each node will return its address, and all nodes will be collected in a List
to be returned to the client:
// mode:distributed,language=javascript cacheManager.getAddress().toString();
Word Count Stream
The following is a script that runs on the local cache, counting the occurrences of each word in the result set, and then returning the words and their occurrences in a key, value pairing:
// mode=local,language=javascript var Function = Java.type("java.util.function.Function") var Collectors = Java.type("java.util.stream.Collectors") var Arrays = Java.type("org.infinispan.scripting.utils.JSArrays") cache .entrySet().stream() .map(function(e) e.getValue()) .map(function(v) v.toLowerCase()) .map(function(v) v.split(/[\W]+/)) .flatMap(function(f) Arrays.stream(f)) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
40.9. Limitations when Executing Stored Scripts
Java Streams throw an error when used with clusters in DIST mode
It is not possible to use scripts that create a Stream
in JavaScript when the cluster is in DIST
mode. Any attempts to execute these scripts will result in a NotSerializableException
, as the lambdas fail when attempting to be serialized. To workaround this issue it is recommended to manually iterate over data using an Iterator
, or to execute lambdas after the data has been transferred from the script to the originator node.
There are no issues using streams in clusters with other modes.