CLOVER🍀

That was when it all began.

InfinispanのJCache support over Hot Rodを試す

Infinispan 7.2の新機能のうちのひとつで、Hot RodでJCacheが使えるようになったという話。

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

今まではEmbedded ModeでのJCacheサポートはありましたが、こちらが追加されたので試してみました。

ドキュメントはなさそうだったので、実装とテストコードから追っています。

準備

まずは、ビルド定義。
build.sbt

name := "remote-jcache"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.6"

organization := "org.littlewings"

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

updateOptions := updateOptions.value.withCachedResolution(true)

parallelExecution in Test := false

libraryDependencies ++= Seq(
  "javax.cache" % "cache-api" % "1.0.0",
  "org.infinispan" % "infinispan-jcache-remote" % "7.2.1.Final",
  "net.jcip" % "jcip-annotations" % "1.0" % "provided",
  "org.scalatest" %% "scalatest" % "2.2.4" % "test"
)

Hot Rod用のJCacheモジュールは、「infinispan-jcache-remote」らしいです。

また、今回はInfinispan Serverが必要なので、ダウンロードページよりServerモジュールを取得しておきます。

http://infinispan.org/download/

この時点では、Infinispan 7.2.1ですね。ダウンロードしたら、解凍・展開。

$ unzip infinispan-server-7.2.1.Final-bin.zip

サーバーは起動しておきます。

$ infinispan-server-7.2.1.Final/bin/standalone.sh

今回は、スタンドアロンでいきます。

利用するCacheは、事前定義済みで特に設定の入っていない、「namedCache」を使うことにします。
(infinispan-server-7.2.1.Final/standalone/configuration/standalone.xml より)

        <subsystem xmlns="urn:infinispan:server:core:7.2" default-cache-container="local">
            <cache-container name="local" default-cache="default" statistics="true">
                <local-cache name="default" start="EAGER">
                    <locking acquire-timeout="30000" concurrency-level="1000" striping="false"/>
                    <transaction mode="NONE"/>
                </local-cache>
                <local-cache name="memcachedCache" start="EAGER">
                    <locking acquire-timeout="30000" concurrency-level="1000" striping="false"/>
                    <transaction mode="NONE"/>
                </local-cache>
                <local-cache name="namedCache" start="EAGER"/>
            </cache-container>
            <cache-container name="security"/>
        </subsystem>

とりあえず、事前準備はここまで。

JCacheを使ったコードを書く

それでは、JCacheを利用したコードを書いていきます。

動作確認自体は、ScalaTestを使ったテストコードで行うので、雛形を用意。
src/test/scala/org/littlewings/infinispan/jcache/JCacheRemoteSpec.scala

package org.littlewings.infinispan.jcache

import java.util
import java.util.Properties
import java.util.concurrent.TimeUnit
import javax.cache.configuration.{Configuration, MutableConfiguration}
import javax.cache.expiry.{AccessedExpiryPolicy, Duration}
import javax.cache.processor.EntryProcessorResult
import javax.cache.{CacheException, Cache, Caching}

import scala.collection.JavaConverters._

import org.scalatest.FunSpec
import org.scalatest.Matchers._

class JCacheRemoteSpec extends FunSpec {
  describe("Infinispan JCache Remote Spec") {
  // ここに、テストを書く!
  }
}

Hello World的な

まずはJCacheの導入的なコードから。

    it("simple usage") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      val configuration =
        new MutableConfiguration[String, String]()
          .setTypes(classOf[String], classOf[String])

      val cache: Cache[String, String] = manager.createCache("namedCache", configuration)

      cache.put("key1", "value1")
      cache.get("key1") should be("value1")

      cache.remove("key1")

      cache.close()
      manager.close()
      provider.close()
    }

当たり前かもですが、普通にJCacheを使ったコードで動作します。

有効期限を設定する

一定時間アクセスしなかった場合は、エントリが有効期限切れするCacheを作成・利用。

    it("with expiry") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      val configuration =
        new MutableConfiguration()
          .setTypes(classOf[String], classOf[String])
          .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, 5)))

      val cache: Cache[String, String] = manager.createCache("namedCache", configuration)

      cache.put("key1", "value1")
      cache.put("key2", "value2")

      TimeUnit.SECONDS.sleep(3L)

      cache.get("key1")

      TimeUnit.SECONDS.sleep(3L)

      cache.get("key1") should be("value1")
      cache.get("key2") should be(null)

      cache.close()
      manager.close()
      provider.close()
    }

こちらもOK。

CacheManager#createCacheを行わなかった場合は?

個人的に見てきたJCacheの実装では、CacheManagerを構築する際に実装依存の定義ファイルを読めるようにしたりすると、CacheManager#createCacheを呼び出さなくても定義済みのCacheをいきなりgetCacheできたのですが、これをHot Rodで試してみると…

    it("predefined?") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      manager.getCache("namedCache", classOf[String], classOf[String]) should be(null)

      manager.close()
      provider.close()
    }

nullになりました。

というか、Hot Rodを素で使っても、Cacheを定義できるAPIってないですからね。こうなってもおかしくないなぁという感じ。

https://github.com/infinispan/infinispan/blob/7.2.1.Final/jcache/commons/src/main/java/org/infinispan/jcache/AbstractJCacheManager.java#L110

Infinispan Serverに未定義のCacheをcreateCacheしようとした場合

これは、Hot Rodで使う場合はエラーになっていたので、JCache over Hot Rodでもエラーになりました。

    it("not found cache") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      val configuration =
        new MutableConfiguration[String, String]()
          .setTypes(classOf[String], classOf[String])

      a[CacheException] should be thrownBy {
        manager.createCache[String, String, Configuration[String, String]]("testCache", configuration)
      }

      manager.close()
      provider.close()
    }

これは予想できた挙動です。

Propertiesを渡して設定を行う

Hot Rodを素で使う場合はプロパティファイルで設定したり、ConfigurationBuilderで設定ができたりしたのですが、JCache over Hot RodだとCachingProvider#getCacheManagerを呼び出す際に、PropertiesにHot Rodで利用可能な項目を設定して呼び出すことで行うようです。

    it("with Properties") {
      val properties = new Properties
      properties.setProperty("infinispan.client.hotrod.server_list", "localhost:11222")
      // 以下でも可
      // org.infinispan.client.hotrod.impl.ConfigurationProperties に定義
      //properties.setProperty(ConfigurationProperties.SERVER_LIST, "localhost:11222")
      properties.setProperty("infinispan.client.hotrod.protocol_version", "2.1")


      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager(provider.getDefaultURI, provider.getDefaultClassLoader, properties)

      val configuration =
        new MutableConfiguration[String, String]()
          .setTypes(classOf[String], classOf[String])

      val cache: Cache[String, String] = manager.createCache("namedCache", configuration)

      cache.put("key1", "value1")
      cache.get("key1") should be("value1")

      cache.remove("key1")

      cache.close()
      manager.close()
      provider.close()
    }

なお、自分はあまりHot Rodの設定をしたことがないのですが、設定項目はこのあたりを見るのがよいようです。

http://docs.jboss.org/infinispan/7.2/apidocs/org/infinispan/client/hotrod/RemoteCacheManager.html
https://github.com/infinispan/infinispan/blob/7.2.1.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/ConfigurationProperties.java

自分で定義したクラスをCacheに登録する

今まで、StringをCacheに放り込んでいましたが、自分で定義したクラスも普通に使えますよ、と。

@SerialVersionUID(1L)
class Person(val firstName: String, val lastName: String, val age: Int) extends Serializable

ただし、シリアライズ可能としておくこと。

    it("user defined class") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      val configuration =
        new MutableConfiguration[String, Person]()
          .setTypes(classOf[String], classOf[Person])

      val cache: Cache[String, Person] = manager.createCache("namedCache", configuration)

      cache.put("1", new Person("カツオ", "磯野", 12))
      cache.get("1").firstName should be("カツオ")

      cache.remove("key1")

      cache.close()
      manager.close()
      provider.close()
    }

EntryProcessorを使う

最後に、EntryProcessorを使ってみます。

今までScalaで書いていましたが、EntryProcessorはJavaで書かないとうまく動かせませんでした…。
src/test/java/org/littlewings/infinispan/jcache/DoublingEntryProcessor.java

package org.littlewings.infinispan.jcache;

import java.io.Serializable;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.MutableEntry;

public class DoublingEntryProcessor implements EntryProcessor<String, Integer, Integer> {
    @Override
    public Integer process(MutableEntry<String, Integer> entry, Object... arguments) {
        return entry.getValue() * 2;
    }
}

EntryProcessorは、ここではSerializableにしなくても動きました。
※とはいえ、普通はやった方がいいと思います

EntryProcessor利用コード。

    it("entry processor") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      val configuration =
        new MutableConfiguration[String, Integer]()
          .setTypes(classOf[String], classOf[Integer])

      val cache: Cache[String, Integer] = manager.createCache("namedCache", configuration)

      (1 to 10).foreach(i => cache.put(s"key$i", i))

      val keys = (1 to 10).map(i => s"key$i").toSet.asJava
      val result: util.Map[String, EntryProcessorResult[Integer]] = cache.invokeAll(keys, new DoublingEntryProcessor)

      result.asScala.values.map(_.get).foldLeft(0)(_ + _) should be(110)

      cache.clear()

      cache.close()
      manager.close()
      provider.close()
    }

このEntryProcessor、どこで動いているかですが、どうもクライアント側で動いているようです。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/jcache/commons/src/main/java/org/infinispan/jcache/AbstractJCache.java#L172

なので、Serializableでなくてもよかった、と。上記のAbstractJCacheは、Embedded Cache、Hot Rod共通の親クラスなので、Embedded ModeのEntryProcessorもCache#invokeやinvokeAllを呼び出したNodeで動きそうですね。

そんな感じなので、自前で定義したクラスを対象にしたEntryProcessorを作ってみて
src/test/java/org/littlewings/infinispan/jcache/FirstNameEntryProcessor.java

package org.littlewings.infinispan.jcache;

import javax.cache.processor.EntryProcessor;
import javax.cache.processor.MutableEntry;

public class FirstNameEntryProcessor implements EntryProcessor<String, Person, String> {
    @Override
    public String process(MutableEntry<String, Person> entry, Object... arguments) {
        return entry.getValue().firstName();
    }
}

これを利用するコードを書いてみましたが、こちらもOKでした。

    it("entry processor, with user defined class") {
      val provider = Caching.getCachingProvider
      val manager = provider.getCacheManager

      val configuration =
        new MutableConfiguration[String, Person]()
          .setTypes(classOf[String], classOf[Person])

      val cache: Cache[String, Person] = manager.createCache("namedCache", configuration)

      cache.put("1", new Person("カツオ", "磯野", 12))

      cache.invoke("1", new FirstNameEntryProcessor) should be("カツオ")

      cache.clear()

      cache.close()
      manager.close()
      provider.close()
    }

使い方についてはだいぶ分かった気がするので、OKでしょう。

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