第36章 スクリプト

36.1. スクリプト

データグリッドにはサーバーにスクリプトを格納するメソッドが含まれています。リモートクライアントは、 JDK の javax.script.ScriptEngines を使用してリモートクライアントがローカルでスクリプトを実行できるようにします。デフォルトでは、JDK には JavaScript を実行できる Nashorn が含まれていますが、これを拡張すると独自の ScriptEngine を提供する JVM 言語を実行できます。

36.2. スクリプトキャッシュへのアクセス

スクリプトは ___script_cache という特別な保護されたキャッシュに格納されます。これは保護されたキャッシュであるため、ループバックリクエストや承認が有効になっている接続のみがキャッシュにアクセスできます。

___script_cache へリモートで接続するには、以下の要件を満たす必要があります。

  • ユーザーが ___script_manager ロールで定義されている必要があります。
  • クライアントはサーバーへセキュアに接続できる必要があります。これを実現するには、インターフェースのセキュア化 の手順にしたがってください。
  • キャッシュコンテナーで承認が有効になっている必要があります。

スクリプトキャッシュへのアクセスをサーバーで設定

以下の例は、Hot Rod コネクターをセキュア化する DIGEST-MD5 メソッドを使用して、スクリプトキャッシュへのアクセスをサーバーで設定する方法を示しています。

  1. 以下のように、ユーザーをサーバーに追加します。

    1. $JDG_HOME/bin/add-user.sh スクリプト (Linux の場合) または $JDG_HOME\bin\add-user.bat スクリプト (Windows の場合) を実行します。
    2. 最初のプロンプトで b を作成し、ApplicationRealm ユーザーを作成します。

      What type of user do you wish to add?
       a) Management User (mgmt-users.properties)
       b) Application User (application-users.properties)
      (a): b
    3. プロンプトにしたがって、ユーザーのユーザー名とパスワードを定義します。
    4. グループの入力を要求されたら、このユーザーに対して ___script_manager を入力します。

      What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]: ___script_manager
  2. クライアントサーバー間の通信をセキュア化します。この例では DIGEST-MD5 を使用するため、Hot Rod 認証 (MD5) の設定 の手順にしたがいます。以下のスニペットは必要な xml 設定を示しています。

    <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. 以下のコードスニペットのように、セキュアな接続を使用してキャッシュマネージャーを作成します。

    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. スクリプトのインストール

スクリプトを ___script_cache に追加するには、スクリプトの名前をキーとし、スクリプトの内容を値としてキャッシュ自体をスクリプトに追加します。スクリプトの名前に sample.js などのファイル拡張子が含まれている場合、スクリプトを実行するエンジンは拡張子によって決定されます。この動作をオーバーライドするには、スクリプト内部のメタデータを指定します。

スクリプトの内容は ___script_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());
    }

スクリプトの内容を定義

ファイルからスクリプトをロードする代わりに、手作業でスクリプトを定義し、スクリプティングキャッシュに置くことができます。

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. メタデータのスクリプト

メタデータをスクリプトに格納し、スクリプトの実行方法に関する追加情報をサーバーに提供することができます。このメタデータは、スクリプトの最初の行にある特別書式のコメントに含まれます。

プロパティーは、カンマ区切りのキーバリューペアとして定義され、コメントのスタイルは //;;\# など、使用されるスクリプト言語によって異なります。必要な場合はこの情報を複数の行に分散でき、1 重または 2 重引用符を使用して値が区切られます。

以下は有効なメタデータコメントの例になります。

// name=test, language=javascript
// mode=local, parameters=[a,b,c]

メタデータプロパティー

以下のメタデータプロパティーを使用できます。

  • mode: スクリプト実行モードを定義します。以下の値の 1 つになります。

    • local: スクリプトはリクエストを処理するノードのみによって実行されます。スクリプト自体はクラスター化された操作を呼び出しできます。
    • distributed: 分散エクゼキューターサービスを使用してスクリプトを実行します。
  • language: Javascript など、スクリプトの実行に使用されるスクリプトエンジンを定義します。
  • extension: js などのスクリプトの実行に使用されるスクリプトエンジンを指定する代替メソッド。
  • role: スクリプトの実行に必要な特定のロール。
  • parameters: このスクリプトの有効なパラメーター名のアレイ。パラメーター名を指定する呼び出しがこのリストに含まれていないと、例外が発生します。

実行モードはスクリプトの特徴であるため、異なるモードでスクリプトを呼び出しするためにクライアント側で追加の設定を行う必要はありません。

36.5. スクリプトバインディング

スクリプトエンジンはスクリプトの実行時に複数の内部オブジェクトを事前定義のバインディングとして公開します。これらの内部オブジェクトは次のとおりです。

  • cache: このキャッシュに対してスクリプトが実行されます。
  • cacheManager: キャッシュの cacheManager。
  • marshaller: キャッシュへデータをマーシャル/アンマーシャルするために使用されるマーシャラー。
  • scriptingManager: スクリプトの実行に使用されているスクリプトマネージャーのインスタンス。これを使用してスクリプトから別のスクリプトを実行できます。

36.6. スクリプトパラメーター

スクリプトには、標準のバインディングの他に、バインディングのようにも見える名前付きパラメーターのセットを渡すことができます。パラメーターは、名前と値のペアのマップとして渡され、名前は文字列で、値は使用中のマーシャラーが理解できる値になります。

multiplicandmultiplier の 2 つのパラメーターを取る以下のスクリプトを見てみましょう。

// mode=local,language=javascript
// parameters=[multiplicand,multiplier]
multiplicand * multiplier

最後の操作は評価であるため、その結果はスクリプトインボーカーへ返されます。渡された値はスクリプトの実行方法に応じて変更され、各実行メソッドによって対応されます。

36.7. Hot Rod Java クライアントを使用したスクリプトの実行

サーバー側で承認が無効になっている場合、スクリプトがインストールされると誰でも実行することができます。その他の場合では、EXEC パーミッションを持つユーザーのみがインストール済みのスクリプトを実行することができます。

Hot Rod でスクリプトを実行するにはスクリプトを実行するキャッシュ上で execute(scriptName, parameters) を呼び出します。この場合、scriptName___script_cache に格納されたスクリプトの名前で、parameters は名前付きパラメーターの Map<String,Object> になります。

以下は、Hot Rod 経由で上記の multiplication.js スクリプトを実行する例になります。

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. スクリプトの例

以下の例は、ユーザーが構文のスクリプト化に関する理解を深め、各環境でスクリプトに適切なタスクを考慮するためのさまざまなタスクを示しています。

分散実行

以下は、分散エクゼキューター内で実行されるスクリプトです。各ノードはそのアドレスを返し、すべてのノードはクライアントに返す List に収集されます。

// mode:distributed,language=javascript
cacheManager.getAddress().toString();

単語カウントストリーム

以下は、ローカルキャッシュで実行され、結果セットで各単語の発生回数を数え、単語と発生回数をキーバリューペアで返すスクリプトになります。

// 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. 格納されたスクリプト実行時の制限

Java ストリームを DIST モードのクラスターと使用するとエラーが発生します。

クラスターが DIST モードである場合、JavaScript で Stream を作成するスクリプトを使用することはできません。このようなスクリプトを実行しようとすると、シリアライズの際にラムダが失敗し、NotSerializableException が発生します。この問題を回避するため、Iterator を使用してデータ上で手動で繰り返し処理を行うか、データがスクリプトから発信元ノードへ遷移された後にラムダを実行することが推奨されます。

他のモードのクラスターでストリームを使用した場合は問題はありません。