Chapter 36. Scripting


36.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.

36.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.

  1. Add a user to the server as follows:

    1. Execute the $JDG_HOME/bin/add-user.sh (Linux) or $JDG_HOME\bin\add-user.bat (Windows) script.
    2. Enter b at the first prompt to create an ApplicationRealm user.

      What type of user do you wish to add?
       a) Management User (mgmt-users.properties)
       b) Application User (application-users.properties)
      (a): b
    3. Follow the prompts to define the desired username and password for the user.
    4. 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
  2. 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 BULK" />
        </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>
  3. 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);

36.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"

36.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.

36.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.

36.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.

36.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);

36.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()));

36.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.

Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat Documentation

We help Red Hat users innovate and achieve their goals with our products and services with content they can trust.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

© 2024 Red Hat, Inc.