CLOVER🍀

That was when it all began.

InfinispanのScripting機能(JSR-223)を試す

先日、Infinispan 7.2.0.Finalがリリースされました。

その中で、ちょっと気になる機能として、「Scripting (JSR-223) in server」というものがあります。

Infinispan 7.2 Release Notes
http://infinispan.org/release-notes/

これ、Release Notesには「Allows creation of server-side scripts which can be invoked from HotRod」と書かれているんですけど、どうもEmbedded Modeでも使えそうな感じだったので、まずはこちらから試してみることにしました。
※というか、Hot Rodで使うには自分にはちょっと調べないといけないことがありそうなので…

で、いざ試そうとUser Guideを見てみるのですが、Scriptingについては何も書かれていません…。

Infinispan User Guide
http://infinispan.org/docs/7.2.x/user_guide/user_guide.html

マジですか…。

というわけで、テストコードや実装を見つつ動かしてみました。

そもそも、どんな機能?

動かしたり、実装、テストコードを見ている感じだと、すでにInfinispan上で動作するDistributed Execution Framework(分散Executor)やMap Reduce上で、スクリプトを実行しようという機能みたいです。

この時、スクリプト実行の際にJSR-223の機能が使われます。

準備

ビルドの定義。
build.sbt

name := "embedded-scripting"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.6"

organization := "org.littlewings"

scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature")

updateOptions := updateOptions.value.withCachedResolution(true)

fork in Test := true

libraryDependencies ++= Seq(
  "org.infinispan" % "infinispan-scripting" % "7.2.1.Final",
  "net.jcip" % "jcip-annotations" % "1.0" % "provided",
  "org.scalatest" %% "scalatest" % "2.2.4" % "test",
  "org.codehaus.groovy" % "groovy-all" % "2.4.3" % "test"
)

使うモジュールは、「infinispan-core」ではなく、「infinispan-scripting」になります。

最後にGroovyが入っていますが、これは後で説明します。

Infinispanの設定。
src/test/resources/infinispan.xml

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:7.2 http://www.infinispan.org/schemas/infinispan-config-7.2.xsd"
        xmlns="urn:infinispan:config:7.2">
    <jgroups>
        <stack-file name="udp" path="jgroups.xml"/>
    </jgroups>

    <cache-container name="cacheManager" shutdown-hook="REGISTER">
        <transport cluster="cluster" stack="udp"/>
        <jmx duplicate-domains="true"/>

        <local-cache name="localCache"/>
        <distributed-cache name="distributedCache"/>
    </cache-container>
</infinispan>

Local CacheとDistributed Cacheを用意しました。

JGroupsの設定。7.2.0.Finalに含まれているデフォルトのUDPを使って設定を参照しつつ、ちょっとバッファサイズを絞ったものにしました。
src/test/resources/jgroups.xml

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="urn:org:jgroups"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/JGroups-3.6.xsd">
    <UDP
            mcast_addr="${jgroups.udp.mcast_addr:228.6.7.8}"
            mcast_port="${jgroups.udp.mcast_port:46655}"
            ucast_recv_buf_size="150k"
            ucast_send_buf_size="130k"
            mcast_recv_buf_size="150k"
            mcast_send_buf_size="130k"
            ip_ttl="${jgroups.ip_ttl:2}"
            thread_naming_pattern="pl"
            enable_diagnostics="false"

            thread_pool.min_threads="${jgroups.thread_pool.min_threads:2}"
            thread_pool.max_threads="${jgroups.thread_pool.max_threads:30}"
            thread_pool.keep_alive_time="60000"
            thread_pool.queue_enabled="false"

            internal_thread_pool.min_threads="${jgroups.internal_thread_pool.min_threads:5}"
            internal_thread_pool.max_threads="${jgroups.internal_thread_pool.max_threads:20}"
            internal_thread_pool.keep_alive_time="60000"
            internal_thread_pool.queue_enabled="true"
            internal_thread_pool.queue_max_size="500"

            oob_thread_pool.min_threads="${jgroups.oob_thread_pool.min_threads:20}"
            oob_thread_pool.max_threads="${jgroups.oob_thread_pool.max_threads:200}"
            oob_thread_pool.keep_alive_time="60000"
            oob_thread_pool.queue_enabled="false"
            />

    <PING/>
    <MERGE3 min_interval="10000"
            max_interval="30000"
            />
    <FD_SOCK/>
    <FD_ALL timeout="60000"
            interval="15000"
            timeout_check_interval="5000"
            />
    <VERIFY_SUSPECT timeout="5000"
            />

    <pbcast.NAKACK2 xmit_interval="1000"
                    xmit_table_num_rows="50"
                    xmit_table_msgs_per_row="1024"
                    xmit_table_max_compaction_time="30000"
                    max_msg_batch_size="100"
                    resend_last_seqno="true"
            />

    <UNICAST3 xmit_interval="500"
              xmit_table_num_rows="50"
              xmit_table_msgs_per_row="1024"
              xmit_table_max_compaction_time="30000"
              max_msg_batch_size="100"
              conn_expiry_timeout="0"
            />
    <pbcast.STABLE stability_delay="500"
                   desired_avg_gossip="5000"
                   max_bytes="1M"
            />
    <pbcast.GMS print_local_addr="true"
                join_timeout="15000"
            />

    <!-- <tom.TOA/> -->
    <!-- the TOA is only needed for total order transactions-->

    <UFC max_credits="2m"
         min_threshold="0.40"
            />
    <MFC max_credits="2m"
         min_threshold="0.40"
            />
    <FRAG2/>
</config>

Scripting機能の使い方

InfinispanのScripting機能を使うには、SriptingManagerというものに、スクリプトを名前、スクリプトの定義そのもの(両方とも文字列)を登録して、ScriptingManagerのrunScriptメソッドで実行します。

登録したスクリプトの削除も、ScriptingManagerから行えるようです。

また、ScriptingManager自体は、EmbeddedCacheManagerから取得できるGlobalComponentRegistoryから取得します。

          val scriptingManager =
            cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

参考にしたテストコードは、こちらです。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/test/java/org/infinispan/scripting/ScriptingTest.java
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/test/java/org/infinispan/scripting/ClusteredScriptingTest.java

ということで、使っていってみましょう。

簡単なスクリプトの実行

テストコードの雛形。
src/test/scala/org/littlewings/infinispan/scripting/ScriptingSpec.scala

package org.littlewings.infinispan.scripting

import java.util
import javax.script.SimpleBindings

import org.infinispan.scripting.ScriptingManager
import org.scalatest.FunSpec
import org.scalatest.Matchers._

class ScriptingSpec extends FunSpec with InfinispanSpecSupport {
  describe("Infinispan Scripting Spec") {
    describe("with local-cache spec") {
      // ここに、Local Cache向けのテストを書く!
    }

    describe("with distributed-cache spec") {
      // ここに、Distributed Cache向けのテストを書く!
    }
}

これらの中に、それぞれLocal Cache、Distributed Cache向けのテストを書いていきます。

また、このクラスがMix-inしているInfinispanSpecSupportというトレイトは、このような定義です。
src/test/scala/org/littlewings/infinispan/scripting/InfinispanSpecSupport.scala

package org.littlewings.infinispan.scripting

import java.io.{BufferedReader, InputStreamReader}
import java.nio.charset.StandardCharsets

import org.infinispan.Cache
import org.infinispan.manager.DefaultCacheManager
import org.scalatest.Suite

trait InfinispanSpecSupport extends Suite {
  protected def withCache[K, V](cacheName: String, numInstances: Int = 1)(f: Cache[K, V] => Unit): Unit = {
    val managers = (1 to numInstances).map(_ => new DefaultCacheManager("infinispan.xml"))

    try {
      val cache = managers.head.getCache[K, V](cacheName)

      f(cache)

      cache.stop()
    } finally {
      managers.foreach(_.stop())
    }
  }

  protected def loadScript(filePath: String): String = {
    val classLoader = Thread.currentThread.getContextClassLoader match {
      case null => getClass.getClassLoader
      case cl => cl
    }

    val reader = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream(filePath), StandardCharsets.UTF_8))
    try {
      Iterator
        .continually(reader.read())
        .takeWhile(_ != -1)
        .map(_.asInstanceOf[Char])
        .mkString
    } finally {
      reader.close()
    }
  }
}

Infinispanを使ったクラスタの起動・停止を簡単に行うためのメソッドと、クラスパス上からスクリプトを読み込むためのメソッドが定義してあります。

というわけで、今回使用するスクリプトは、クラスパス上に置くものとします。

Local Cache

それでは、まずはLocal Cacheでスクリプトを実行してみましょう。

スクリプトの用意。
src/test/resources/scripts/noCacheBindings.js

var str1 = "Hello";
var str2 = "Scripting";

str1 + " " + str2 + "!!";

これを、ScriptingManagerに登録して実行します。

      it("no Cache binding") {
        withCache[String, Integer]("localCache") { cache =>
          (1 to 5).foreach(i => cache.put(s"key$i", i))

          val scriptingManager =
            cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

          scriptingManager.addScript("noCacheBindings.js", loadScript("scripts/noCacheBindings.js"))

          val future = scriptingManager.runScript[String]("noCacheBindings.js")
          future.get should be("Hello Scripting!!")
        }
      }

Cacheに登録された内容はスクリプト中で全然使っていませんが、スクリプトは一応動かせたようです。

なお、スクリプトはJSR-223の機能で動きます。今回、実行環境にはJava 8を使っているので、このJavaScriptはNashornで動いていることになります。

次の例。スクリプトには、ScriptingManager#runScriptにjavax.script.Bindingsを渡すことで、任意のパラメータを追加することができます。

      it("no Cache binding, with user parameters") {
        withCache[String, Integer]("localCache") { cache =>
          (1 to 5).foreach(i => cache.put(s"key$i", i))

          val scriptingManager =
            cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

          scriptingManager.addScript("noCacheWithBindings.js", loadScript("scripts/noCacheWithBindings.js"))

          val bindings = new SimpleBindings
          bindings.put("cacheName", "localCache")

          val future = scriptingManager.runScript[util.List[_]]("noCacheWithBindings.js", bindings)
          future.get should be(util.Arrays.asList(9, "org.infinispan.scripting.impl.ScriptingManagerImpl"))
        }
      }

ここでは、Cacheの名前を渡しています。

スクリプト
src/test/resources/scripts/noCacheWithBindings.js

var c = cacheManager.getCache(cacheName);

var list = new java.util.ArrayList();
list.add(java.lang.Double.valueOf(c.get("key4") + c.get("key5")).intValue());
list.add(scriptingManager.getClass().getName());

list;

ここでは、Bindingsに設定したCacheの名前から、EmbeddedCacheManagerからCacheを取得してCache内にある値をListに入れて戻しています。

が、いきなり「cacheManager」という変数を使っているように、ここで動かすスクリプトにはいくつか暗黙のBindingsが存在します。

ここでもうひとつ、ScriptingManager#runScriptメソッドにはCacheを渡せる版も存在します。

      it("with Cache binding") {
        withCache[String, Integer]("localCache") { cache =>
          (1 to 5).foreach(i => cache.put(s"key$i", i))

          val scriptingManager =
            cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

          scriptingManager.addScript("withCacheBingings.js", loadScript("scripts/withCacheBingings.js"))

          val future = scriptingManager.runScript[Integer]("withCacheBingings.js", cache)
          future.get should be(9)
        }
      }

この場合、「cache」という名前で渡したCacheのインスタンスが参照できます。
src/test/resources/scripts/withCacheBingings.js

java.lang.Double.valueOf(cache.get("key4") + cache.get("key5")).intValue();

これらの全部の組み合わせを使うと、ScriptingManager#runScriptにCacheとBinginsを渡して実行ということができます。

      it("with Cache binding, with user parameters") {
        withCache[String, Integer]("localCache") { cache =>
          (1 to 5).foreach(i => cache.put(s"key$i", i))

          val scriptingManager =
            cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

          scriptingManager.addScript("withCacheWithParameters.js", loadScript("scripts/withCacheWithParameters.js"))

          val bindings = new SimpleBindings
          bindings.put("prefix", "★")
          bindings.put("suffix", "★")

          val future = scriptingManager.runScript[Integer]("withCacheWithParameters.js", cache, bindings)
          future.get should be("★localCache★")
        }

スクリプト側。
src/test/resources/scripts/withCacheWithParameters.js

prefix + cache.getName() + suffix;
スクリプト中で暗黙的に参照できる変数

ここまでで、スクリプト中で「cacheManager」や「cache」といったものを、Bingingsで設定した以外に暗黙の参照として利用していましたが、スクリプト内で使えるものは以下のようです。

変数名 備考
cacheManager EmbeddedCacheManager
scriptingManager ScriptinManager
cache Cache runScriptの引数にCacheを渡した時のみ利用可能

https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/ScriptingManagerImpl.java#L146
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/SystemBindings.java

また、この後登場するMap Reduceではもう少し増えます。

Distributed Cache

まず、スクリプトの書き方が変わります。
src/test/resources/scripts/distWithCacheBingings.js

// mode=distributed
java.lang.Double.valueOf(cache.get("key4") + cache.get("key5")).intValue();

先頭にコメントで、「mode=distributed」と書く必要があります。書かない場合は、Localモードとして実行されます。

つまり、先ほどのLocal Cacheで動かしていた例だと、スクリプトはこう書いているのが正しかったと。

// mode=local
java.lang.Double.valueOf(cache.get("key4") + cache.get("key5")).intValue();

どうも、スクリプトの先頭がコメント「//」で始まっていると、その中の内容をメタデータとして解釈するようです。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/ScriptingManagerImpl.java#L194

モードには、以下の定義に対応するものがあります。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/ExecutionMode.java

先ほどLocal Cacheで動かしたサンプルは、Distributed Cacheでは多くが動かなくなって、動いたのは以下だけになりました。しかも、なんか不安定…。

      it("with Cache binding") {
        withCache[String, Integer]("distributedCache", 3) { cache =>
          (1 to 5).foreach(i => cache.put(s"key$i", i))

          val scriptingManager =
            cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

          scriptingManager.addScript("distWithCacheBingings.js", loadScript("scripts/distWithCacheBingings.js"))

          val future = scriptingManager.runScript[util.List[Integer]]("distWithCacheBingings.js", cache)
          val list = new util.ArrayList[Integer]
          list.add(9)
          future.get should be(list)
        }
      }

また、スクリプトをdistributed modeで動かす場合は、ScriptingManager#runScriptにCacheを指定することは必須になります。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/DistributedRunner.java#L31

最初の例が動かなくなったのは、このためです。

これらを書くのに参考にしたのは、このあたりです。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/test/java/org/infinispan/scripting/ClusteredScriptingTest.java
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/test/resources/distExec.js

その他、分散実行した時にBindingsに指定したものがうまく渡っていなかったりしました…。

それに、唯一動いたように見れる例も、Distributed Executorを使った時と動きが違うような気が。
※本当は、3つのNodeで動かしているので、「9」が3つListに入って返ってきて欲しい

追記
もう少し追ってみた感じ、ScriptingManagerの内部で持っているスクリプト保持用のCacheが、このコードの書き方でdistributed modeだとひとつのNodeしか初期化されず、distributed modeだとコケてしまうみたいです。
全Nodeに対してaddScriptすると動きそうな感じですが…微妙…。


さらに追記 2015/5/11)

ScriptingManagerの持つCacheが初期化されないパターンがある話、修正してPull Requestしたらマージしてもらえました!
https://github.com/infinispan/infinispan/pull/3450

次回のリリースで、反映されると思います。これがInfinispanへの初Pull Requestなります。

Map Reduceを使う

最後は、Map Reduceを使ってみます。

ここで使うテストコードの雛形は、以下とします。
src/test/scala/org/littlewings/infinispan/scripting/MapReduceScriptingsSpec.scala

package org.littlewings.infinispan.scripting

import java.util

import org.infinispan.scripting.ScriptingManager
import org.scalatest.FunSpec
import org.scalatest.Matchers._

class MapReduceScriptingsSpec extends FunSpec with InfinispanSpecSupport {
  describe("Infinispan Map Reduce Scripting Spec") {
    // ここに、テストを書く!
  }
}

まずは、オーソドックスにJavaScriptでMap Reduceを試します。ここでは、User Guidに載っていたMap Reduceの例と、テストコードのJavaScriptの合わせ技で試します。

用意したスクリプトは、以下の通り。

Mapper。
src/test/resources/scripts/wordCountMapper.js

// mode=mapper,reducer=wordCountReducer.js,collator=wordCountCollator.js,language=javascript
var re = /[\W]+/
var words = value.split(re)
for (var i=0; i < words.length; i++) {
    var word = words[i];
    if (word != null && word.length > 5) {
       collector.emit(words[i].toLowerCase(), 1);
    }
}

メタデータとしてのコメントで、modeがmapperであること、ReducerやCollatorで使用するスクリプトを定義しています。languageはオプションですが、テストコードに書いていたので書いておきました。デフォルト値は「javascript」です。

Reducer。
src/test/resources/scripts/wordCountReducer.js

// mode=reducer,language=javascript
var sum = 0;

while (iter.hasNext()) {
    sum += parseInt(iter.next());
}

sum;

Collator。
src/test/resources/scripts/wordCountCollator.js

// mode=collator,language=javascript
var entrySet = reducedResults.entrySet();
var list = new java.util.ArrayList(entrySet);
java.util.Collections.sort(list, new org.littlewings.infinispan.scripting.EntryComparator())

var results = new java.util.LinkedHashMap();
var limit = list.size() > 20 ? 20 : list.size();

for (var i = 0; i < limit; i++) {
    var entry = list.get(i);
    results.put(entry.getKey(), entry.getValue());
}

results;

ここで、各スクリプトはいくつか暗黙的な変数を参照しています。それぞれ、Mapper、Reducer、Collatorの各インターフェースで、実装すべきメソッドの引数名が
暗黙的にBindingsに設定されています。

インターフェース 使える変数名
Mapper key、value、collator
Reducer(Combiner) reducedKey、iter
Collator reducedResults

実際の定義は、この辺りに。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/MapperScript.java#L26
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/ReducerScript.java#L28
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/CollatorScript.java#L29

そして、これらのスクリプトを使用したテストコードは、こちら。

    it("word count") {
      withCache[String, String]("localCache") { cache =>
        cache.put("1", "Hello world here I am")
        cache.put("2", "Infinispan rules the world")
        cache.put("3", "JUDCon is in Boston")
        cache.put("4", "JBoss World is in Boston as well")
        cache.put("12", "JBoss Application Server")
        cache.put("15", "Hello world")
        cache.put("14", "Infinispan community")
        cache.put("15", "Hello world")

        cache.put("111", "Infinispan open source")
        cache.put("112", "Boston is close to Toronto")
        cache.put("113", "Toronto is a capital of Ontario")
        cache.put("114", "JUDCon is cool")
        cache.put("211", "JBoss World is awesome")
        cache.put("212", "JBoss rules")
        cache.put("213", "JBoss division of RedHat ")
        cache.put("214", "RedHat community")

        val scriptingManager =
          cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

        scriptingManager.addScript("wordCountMapper.js", loadScript("scripts/wordCountMapper.js"))
        scriptingManager.addScript("wordCountReducer.js", loadScript("scripts/wordCountReducer.js"))
        scriptingManager.addScript("wordCountCollator.js", loadScript("scripts/wordCountCollator.js"))

        val future = scriptingManager.runScript[util.Map[String, Double]]("wordCountMapper.js", cache)
        val result = future.get

        result should be(a[util.LinkedHashMap[_, _]])

        result.get("boston") should be(3.0)
        result.get("infinispan") should be(3.0)
        result.get("toronto") should be(2.0)
        result.get("judcon") should be(2.0)
        result.get("community") should be(2.0)
      }
    }

ちょっと理由があって、Local Cacheにしています…。

スクリプトは、それぞれScriptingManager#addScriptで登録しておく必要がありますが、ScriptingManager#runScriptで指定するのは、Mapper用のスクリプトのみです。あとは、コメントとして書かれたメタデータから他のスクリプトが呼び出されていきます。

(オマケ)GroovyでMap Reduceを使う

Infinispanのテストコードで用意されていたJavaScriptを見て、メタデータでlanguageの指定があるくらいなので他の言語でもいけそうだなぁと思いまして。

// mode=reducer,language=javascript

実装を見た感じ、languageにちゃんと指定すればOKそうです。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/scripting/src/main/java/org/infinispan/scripting/impl/ScriptingManagerImpl.java#L245

というわけで、ここでGroovyで試してみました。冒頭のsbtの依存関係にGroovyがあったのは、このためですね。

  "org.codehaus.groovy" % "groovy-all" % "2.4.3" % "test"

では、先ほどのWord Countの各種スクリプトを、Groovyで書き換え。

Mapper。
src/test/resources/scripts/wordCountMapper.groovy

// mode=mapper,reducer=wordCountReducer.groovy,collator=wordCountCollator.groovy,language=groovy
def words = value.split(/[\W]+/)

words
    .grep { it && it.length() > 5 }
    .each { collector.emit(it.toLowerCase(), 1) }

コメントでメタデータとして、「language=groovy」と指定しています。

Reducer。
src/test/resources/scripts/wordCountReducer.groovy

// mode=reducer,language=groovy
iter.sum()

Collator。
src/test/resources/scripts/wordCountCollator.groovy

// mode=collator,language=groovy
def list = reducedResults.entrySet().toList()
def sorted = list.sort(false, new org.littlewings.infinispan.scripting.EntryComparatorInGroovy())

def results = [:]
def limit = sorted.size() > 20 ? 20 : sorted.size()

sorted.take(limit).each { entry ->
    results[entry.key] = entry.value
}

results

うん、JavaScriptよりもGroovyの方が慣れています(笑)。

そして、これらのスクリプトを使ったテストコードはこちら。

    it("word count, using Groovy") {
      withCache[String, String]("localCache") { cache =>
        cache.put("1", "Hello world here I am")
        cache.put("2", "Infinispan rules the world")
        cache.put("3", "JUDCon is in Boston")
        cache.put("4", "JBoss World is in Boston as well")
        cache.put("12", "JBoss Application Server")
        cache.put("15", "Hello world")
        cache.put("14", "Infinispan community")
        cache.put("15", "Hello world")

        cache.put("111", "Infinispan open source")
        cache.put("112", "Boston is close to Toronto")
        cache.put("113", "Toronto is a capital of Ontario")
        cache.put("114", "JUDCon is cool")
        cache.put("211", "JBoss World is awesome")
        cache.put("212", "JBoss rules")
        cache.put("213", "JBoss division of RedHat ")
        cache.put("214", "RedHat community")

        val scriptingManager =
          cache.getCacheManager.getGlobalComponentRegistry.getComponent(classOf[ScriptingManager])

        scriptingManager.addScript("wordCountMapper.groovy", loadScript("scripts/wordCountMapper.groovy"))
        scriptingManager.addScript("wordCountReducer.groovy", loadScript("scripts/wordCountReducer.groovy"))
        scriptingManager.addScript("wordCountCollator.groovy", loadScript("scripts/wordCountCollator.groovy"))

        val future = scriptingManager.runScript[util.Map[String, Double]]("wordCountMapper.groovy", cache)
        val result = future.get

        result should be(a[util.LinkedHashMap[_, _]])

        result.get("boston") should be(3.0)
        result.get("infinispan") should be(3.0)
        result.get("toronto") should be(2.0)
        result.get("judcon") should be(2.0)
        result.get("community") should be(2.0)
      }
    }

とりあえず、OKそうです。

とりあえず、動かすには動かせたのですが、Distributed Cacheの時にちょっとうまく動かせている感じがしません。後でもうちょっと見てみようかなぁ…。

JSR-223と統合できるという機能自体は、面白いなぁと思います。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/embedded-scripting