CLOVER🍀

That was when it all began.

Infinispan Hot Rodの未サポートメソッド

ドキュメントに、Hot Rod Clientではいくつか未サポートのメソッドがあるよと書いてあったので、ちょっと試してみることにしました。
https://docs.jboss.org/author/display/ISPN/Java+Hot+Rod+client#JavaHotRodclient-Unsupportedmethods

対象は、

boolean remove(Object key, Object value);
boolean replace(Object key, Object value);
boolean replace(Object key, Object oldValue, Object value);

ということらしいです。使うとUnsupportedOperationExceptionが飛んでくるんだとか。

では、ちょっと試してみましょう。まずはEmbedded Modeから。
src/main/scala/EmbeddedClient.scala

import org.infinispan.manager.DefaultCacheManager

object EmbeddedClient {
  def main(args: Array[String]): Unit = {
    val manager = new DefaultCacheManager
    val cache = manager.getCache[String, Integer]

    cache.put("key1", 1)
    println("put-1 => " + cache.get("key1"))

    cache.put("key1", 2)
    println("put-2 => " + cache.get("key1"))

    cache.remove("key1")
    println("remove => " + cache.get("key1"))

    cache.put("key1", 2)
    println("put-2-2 => " + cache.get("key1"))
    cache.remove("key1", 2)
    println("remove value => " + cache.get("key1"))

    cache.put("key1", 3)
    println("put-3 => " + cache.get("key1"))

    cache.remove("key1", 1)
    println("remove value, ng pattern => " + cache.get("key1"))

    cache.replace("key1", 3, 4)
    println("replace old, new => " + cache.get("key1"))

    cache.replace("key1", 3, 5)
    println("replace old, new, ng pattern => " + cache.get("key1"))

    cache.replace("key1", 6)
    println("replace => " + cache.get("key1"))
  }
}

実行。

[info] put-1 => 1
[info] put-2 => 2
[info] remove => null
[info] put-2-2 => 2
[info] remove value => null
[info] put-3 => 3
[info] remove value, ng pattern => 3
[info] replace old, new => 4
[info] replace old, new, ng pattern => 4
[info] replace => 6

まあ、普通に動きますね。

では、これをHot Rodを使うように修正して試してみます。
src/main/scala/HotRodClient.scala

import scala.util.Try

import org.infinispan.client.hotrod.{Flag, RemoteCacheManager, VersionedValue}

object HotRodClient {
  def main(args: Array[String]): Unit = {
    val manager = new RemoteCacheManager("localhost")
    val cache = manager.getCache[String, Integer]()

    cache.put("key1", 1)
    println("put-1 => " + cache.get("key1"))

    cache.put("key1", 2)
    println("put-2 => " + cache.get("key1"))

    cache.remove("key1")
    println("remove => " + cache.get("key1"))

    cache.put("key1", 2)
    println("put-2-2 => " + cache.get("key1"))
    println(Try { cache.remove("key1", 2) })
    println("remove value => " + cache.get("key1"))

    cache.put("key1", 3)
    println("put-3 => " + cache.get("key1"))

    println(Try { cache.remove("key1", 1) })
    println("remove value, ng pattern => " + cache.get("key1"))

    println(Try { cache.replace("key1", 3, 4) })
    println("replace old, new => " + cache.get("key1"))

    println(Try { cache.replace("key1", 3, 5) })
    println("replace old, new, ng pattern => " + cache.get("key1"))

    cache.replace("key1", 6)
    println("replace => " + cache.get("key1"))
  }
}

未サポートというメソッドは、Tryで囲っています。では、Infinispanを起動して

$ $ISPN_HOME/bin/startServer.sh -r hotrod

実行。

[info] put-1 => 1
[info] put-2 => 2
[info] remove => null
[info] put-2-2 => 2
[info] Failure(java.lang.UnsupportedOperationException)
[info] remove value => 2
[info] put-3 => 3
[info] Failure(java.lang.UnsupportedOperationException)
[info] remove value, ng pattern => 3
[info] Failure(java.lang.UnsupportedOperationException)
[info] replace old, new => 3
[info] Failure(java.lang.UnsupportedOperationException)
[info] replace old, new, ng pattern => 3
[info] replace => 6

確かに、UnsupportedOperationExceptionが飛んできてますね。

ところで

boolean replace(Object key, Object value);

は未サポートとドキュメントに書いている割には、動いているような…。ドキュメントが間違ってる??

他にもreplaceAsyncとかの一部が使えませんが、
http://docs.jboss.org/infinispan/5.2/apidocs/org/infinispan/client/hotrod/RemoteCache.html
要は「前の値を指定して削除する、または置き換える」という操作を未サポートだよ、ということみたいですね。

で、この操作は全くできないかというと、古い値を指定することはできないみたいですが、データのバージョンを指定することはできるのでそちらで代替して欲しいみたいです。
https://docs.jboss.org/author/display/ISPN/Java+Hot+Rod+client#JavaHotRodclient-VersionedAPI

では、動いていなかった部分をVersionedValueを使うように書き直してみます。ドキュメントは、

RemoteCache.VersionedValue valueBinary = remoteCache.getVersioned("car");

みたいなRemoteCacheのインナークラスみたいな書きっぷりになっていますが、実際はorg.infinispan.client.hotrodパッケージのトップレベルのインターフェースになっています。

src/main/scala/HotRodClient.scala

import scala.util.Try

import org.infinispan.client.hotrod.{Flag, RemoteCacheManager, VersionedValue}

object HotRodClient {
  def main(args: Array[String]): Unit = {
    val manager = new RemoteCacheManager("localhost")
    val cache = manager.getCache[String, Integer]()

    cache.put("key1", 1)
    println("put-1 => " + cache.get("key1"))

    cache.put("key1", 2)
    println("put-2 => " + cache.get("key1"))

    cache.remove("key1")
    println("remove => " + cache.get("key1"))

    cache.put("key1", 2)
    println("put-2-2 => " + cache.get("key1"))
    var version = cache.getVersioned("key1")
    println(s"current value => ${version.getValue}, current version ${version.getVersion}")
    cache.removeWithVersion("key1", version.getVersion)
    println("removeWithVersion value => " + cache.get("key1"))

    cache.put("key1", 3)
    println("put-3 => " + cache.get("key1"))

    version = cache.getVersioned("key1")
    println(s"current value => ${version.getValue}, current version ${version.getVersion}")
    cache.removeWithVersion("key1", 1)  // 適当なバージョンを指定
    println("removeWithVersion value, ng pattern => " + cache.get("key1"))

    version = cache.getVersioned("key1")
    println(s"current value => ${version.getValue}, current version ${version.getVersion}")
    cache.replaceWithVersion("key1", 4, version.getVersion)
    println("replaceWithVersion old, new => " + cache.get("key1"))

    version = cache.getVersioned("key1")
    println(s"current value => ${version.getValue}, current version ${version.getVersion}")
    cache.replaceWithVersion("key1", 5, 1)  // 適当なバージョンを指定
    println("replaceWithVersion old, new, ng pattern => " + cache.get("key1"))

    cache.replace("key1", 6)
    println("replace => " + cache.get("key1"))
  }
}

RemoteCache#removeWithVersion、RemoteCache#replaceWithVersionを使用するようになりました。

1度Infinispan Serverを再起動して、実行。

[info] put-2 => 2
[info] remove => null
[info] put-2-2 => 2
[info] current value => 2, current version 3
[info] removeWithVersion value => null
[info] put-3 => 3
[info] current value => 3, current version 4
[info] removeWithVersion value, ng pattern => 3
[info] current value => 3, current version 4
[info] replaceWithVersion old, new => 4
[info] current value => 4, current version 5
[info] replaceWithVersion old, new, ng pattern => 4
[info] replace => 6

今度は動きました。

RemoteCache#getではなくて、RemoteCache#getVersionedを使うことで、取得したデータとバージョンを一緒に取れるようです。

で、バージョンを要求するメソッドの引数に一緒に渡し、もしバージョンが一致しなかったらその操作は失敗、というか無視される挙動をします。

    cache.replaceWithVersion("key1", 5, 1)  // 適当なバージョンを指定

その他、普通のMapと違う挙動をするものとしては、

V remove(Object key);
V put(K key, V value);

が前の値を返さない、というものがあるそうな。

そうなん?

Embedded Modeのサンプルをちょっと変えてみて

    val manager = new DefaultCacheManager
    val cache = manager.getCache[String, Integer]

    println("put-1 return => " + cache.put("key1", 1))
    println("put-1 => " + cache.get("key1"))

    println("put-2 return => " + cache.put("key1", 2))
    println("put-2 => " + cache.get("key1"))

    println("remove return => " + cache.remove("key1"))
    println("remove => " + cache.get("key1"))

実行。

[info] put-1 return => null
[info] put-1 => 1
[info] put-2 return => 1
[info] put-2 => 2
[info] remove return => 2
[info] remove => null

こっちは、値が返ってきますね。

では、Hot Rodの方だと

    val manager = new RemoteCacheManager("localhost")
    val cache = manager.getCache[String, Integer]()

    println("put-1 return => " + cache.put("key1", 1))
    println("put-1 => " + cache.get("key1"))

    println("put-2 return => " + cache.put("key1", 2))
    println("put-2 => " + cache.get("key1"))

    println("remove return => " + cache.remove("key1"))
    println("remove => " + cache.get("key1"))

実行。

[info] put-1 return => null
[info] put-1 => 1
[info] put-2 return => null
[info] put-2 => 2
[info] remove return => null
[info] remove => null

確かに返ってきません…。

この挙動を変更するには、2つ方法があるようです。
https://docs.jboss.org/author/display/ISPN/Java+Hot+Rod+client#JavaHotRodclient-Returnvalues

まずは、RemoteCache#withFlagsを使用するように変更します。

// Flagは、以下のパッケージ
import org.infinispan.client.hotrod.{Flag, RemoteCacheManager, VersionedValue}

    val manager = new RemoteCacheManager("localhost")
    val cache = manager.getCache[String, Integer]()

    println("put-1 return => " + cache.withFlags(Flag.FORCE_RETURN_VALUE).put("key1", 1))
    println("put-1 => " + cache.get("key1"))

    println("put-2 return => " + cache.withFlags(Flag.FORCE_RETURN_VALUE).put("key1", 2))
    println("put-2 => " + cache.get("key1"))

    println("remove return => " + cache.withFlags(Flag.FORCE_RETURN_VALUE).remove("key1"))
    println("remove => " + cache.get("key1"))

実行。

[info] put-1 return => 6
[info] put-1 => 1
[info] put-2 return => 1
[info] put-2 => 2
[info] remove return => 2
[info] remove => null

RemoteCache#withFlagsはRemoteCacheを返すので、それに対してputやremoveを実行します。ただし、この方法は最初の1回目のメソッド呼び出しにしか効果がありません。

よって、こういう使い方をしても、最初の1回しか効きません。

    val manager = new RemoteCacheManager("localhost")
    val cache = manager.getCache[String, Integer]().withFlags(Flag.FORCE_RETURN_VALUE)

    println("put-1 return => " + cache.put("key1", 1))  // => 戻り値があるのは、ここだけ
    println("put-1 => " + cache.get("key1"))

    println("put-2 return => " + cache.put("key1", 2))
    println("put-2 => " + cache.get("key1"))

    println("remove return => " + cache.remove("key1"))
    println("remove => " + cache.get("key1"))

デフォルトの挙動として、戻り値を返すようにする場合はRemoteCacheManagerに明示的に設定します。
http://docs.jboss.org/infinispan/5.2/apidocs/org/infinispan/client/hotrod/RemoteCacheManager.html

import java.util.Properties

    val properties = new Properties
    properties.put("infinispan.client.hotrod.server_list", "localhost:11222")
    properties.put("infinispan.client.hotrod.force_return_values", "true")

    val manager = new RemoteCacheManager(properties)
    val cache = manager.getCache[String, Integer]()

    println("put-1 return => " + cache.put("key1", 1))
    println("put-1 => " + cache.get("key1"))

    println("put-2 return => " + cache.put("key1", 2))
    println("put-2 => " + cache.get("key1"))

    println("remove return => " + cache.remove("key1"))
    println("remove => " + cache.get("key1"))

これで、どのput/removeも値を返すようになります。