CLOVER🍀

That was when it all began.

Infinispan REST Server & JAX-RS Client

Infinispan 6.0がけっこう大改造してそうな感じなので、またいろいろ変わるんだろうなぁと思いつつも、InfinispanのREST Serverで遊んでみました。

Infinispan REST Server
https://docs.jboss.org/author/display/ISPN/Infinispan+REST+Server

Accessing data in Infinispan via RESTful interface
https://docs.jboss.org/author/display/ISPN/Accessing+data+in+Infinispan+via+RESTful+interface

概ねここに書いてある通りで、

  • HTTP PUT/POSTでデータの登録/更新
  • HTTP GETでデータの取得
  • HTTP DELETEでデータの削除

ができるRESTfulなインタフェースを備えた機能です。

では、試してみましょう…と言いたいところですが、まずはいろいろ準備する必要があります。

とりあえず、Infinispan Serverをダウンロードしてきまして、解凍。

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

今回はクラスタリングをするつもりはないので、スタンドアロンで起動。

$ cd infinispan-server-5.3.0.Final/
$ bin/standalone.sh
=========================================================================

  JBoss Bootstrap Environment

  〜省略〜

21:51:12,222 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: JBoss Infinispan Server 5.3.0.Final (AS 7.2.0.Final) started in 13441ms - Started 92 of 130 services (38 services are passive or on-demand)

この時点で、
http://localhost:8080/
にアクセスすると、RESTに関する説明が書かれたページが表示されます。

が、この後動かしてハマったのですが、よくよく読むとユーザを追加しなくちゃダメだよみたいなことが書いてあります。

とりあえず、Infinispan Serverは落としておきます。

22:02:28,853 INFO  [org.jboss.as] (MSC service thread 1-4) JBAS015950: JBoss Infinispan Server 5.3.0.Final (AS 7.2.0.Final) stopped in 391ms

Ctrl-Cで。

で、ユーザの作り方は、Infinispanのドキュメントを見てみて…あれ?書いてありますっけ??

手順が書いてあったのは、Infinispanのブログでした。

Infinispan Server 5.3.0.Alpha1
http://infinispan.blogspot.jp/2013/04/infinispan-server-530alpha1.html

この手順通りにユーザを追加します。

$ bin/add-user.sh 

What type of user do you wish to add? 
 a) Management User (mgmt-users.properties) 
 b) Application User (application-users.properties)

どんなユーザを追加する?って聞かれているので、ここは

(a): b

で。次の質問は

Enter the details of the new user to add.
Realm (ApplicationRealm) : 

そのままEnter。次は、ユーザ名。ここでは、「username」とします。

Username : username

パスワード。アルファベットと数字と記号が入っていなくてはいけないみたいなので、「password$1」としました。

Password : 
Re-enter Password : 

次に、ロールを聞かれるので「REST」と入力します。

What roles do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]: REST

追加する内容が正しければ、yesと。

About to add user 'username' for realm 'ApplicationRealm'
Is this correct yes/no? yes

ユーザの追加が表示されたパスに対して行われたことが表示されます。

Added user 'username' to file '/xxxxx/infinispan-server-5.3.0.Final/standalone/configuration/application-users.properties'
Added user 'username' with roles REST to file '/xxxxx/infinispan-server-5.3.0.Final/standalone/configuration/application-roles.properties'
Is this new user going to be used for one AS process to connect to another AS process? 
e.g. for a slave host controller connecting to the master or for a Remoting connection for server to server EJB calls.
yes/no? no

最後の質問は、今回は他のサーバはいないので「no」で。

では、再びInfinispanを起動します。

$ bin/standalone.sh

続いてクライアント側ですが、せっかくREST Serverなので、クライアント側もそっち側のものを使うことにしました。

JAX-RSのクライアントを使います。実装は…最近よく聞くJerseyではなく、RESTEasyにしました。

RESTEasy
http://www.jboss.org/resteasy

JBoss系のプロジェクトですしね。

プログラム自体は、Scalaで記述。
build.sbt

name := "infinispan-restclient"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.2"

organization := "littlewings"

resolvers += "jboss" at "http://repository.jboss.org/nexus/content/groups/public/"

libraryDependencies ++= Seq(
  "org.jboss.resteasy" % "resteasy-jaxrs" % "3.0.4.Final",
  "org.jboss.resteasy" % "resteasy-client" % "3.0.4.Final"
)

Resolverを追加してますけど、JAX-RSのクライアントを使うだけなら、Resolverの追加は不要みたい??

JAX-RSクライアントを使った、Infinispanへのアクセスはこちらの資料を参考にしました。正確には、ここからリンクしてあるブログですが。

Infinispan REST API
http://www.slideshare.net/fribeiro1/infinispan-rest-api

こちらはBasic認証の部分はJerseyで書いてあったのですが、RESTEasyを使ったものに書き直しました。

書いたコードはこちら。
src/main/scala/InfinispanRestClient.scala

import javax.ws.rs.client.{ClientBuilder, ClientRequestFilter, Entity}
import javax.ws.rs.core.{MediaType, Response}

import org.jboss.resteasy.client.jaxrs.BasicAuthentication

object InfinispanRestClient {
  def main(args: Array[String]): Unit = {
    val cacheName = "namedCache"
    val range = 1 to 10

    val client = ClientBuilder.newBuilder.build
    client.register(new BasicAuthentication("username", "password$1"), classOf[ClientRequestFilter])

    // データの登録/更新
    range.foreach { i =>
      val (key, entity) = (s"key$i", Entity.text(s"value$i"))
      
      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName/$key")
          .request
          .put(entity)  // postでもOK
      println(s"Put $key Status => ${response.getStatus}")
      response.close()
    }

    // データの取得
    // キーの存在確認だけなら、headを使うみたい
    range.foreach { i =>
      val key = s"key$i"

      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName/$key")
          .request
          .get
      println(s"Get $key Status => ${response.getStatus}")
      println(s"Get Value Key[$key] = ${response.readEntity(classOf[String])}")
      response.close()
    }

    // 存在しないキーに対してアクセス
    {
      val key = "notFoundKey"

      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName/$key")
          .request
          .get

      // 存在しないキーに対しては、404が返る
      require(response.getStatus == Response.Status.NOT_FOUND.getStatusCode)
      response.close()
    }

    // 存在しないCacheに対してアクセス
    {
      val response =
        client
          .target(s"http://localhost:8080/rest/notExistCache")
          .request
          .get

      // 存在しないCacheに対しては、404が返る
      require(response.getStatus == Response.Status.NOT_FOUND.getStatusCode)
      response.close()      
    }

    // キー一覧の取得
    {
      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName")
          // .request(MediaType.TEXT_HTML_TYPE)  // デフォルト
          .request(MediaType.APPLICATION_JSON_TYPE)
          // .request(MediaType.APPLICATION_XML_TYPE)
          // .request(MediaType.TEXT_PLAIN_TYPE)
          // .request(MediaType.WILDCARD_TYPE)  // TEXT_HTML_TYPEと同じ
          .get
      println(s"Get $cacheName Status => ${response.getStatus}")
      println(s"Get $cacheName Value = ${response.readEntity(classOf[String])}")
      response.close()
    }

    // Cacheから特定キーのデータを削除
    range.withFilter(_ % 2 == 0).foreach { i =>
      val key = s"key$i"

      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName/$key")
          .request
          .delete

      println(s"Delete $key Status => ${response.getStatus}")
      response.close()
    }

    // 削除後のデータの確認
    {
      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName")
          .request(MediaType.TEXT_PLAIN_TYPE)
          .get
      require(response.readEntity(classOf[String]).split("""\s+""").size == 5)
      response.close()
    }

    // Cacheの全データを削除
    {
      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName")
          .request
          .delete
      println(s"Delete $cacheName Status => ${response.getStatus}")
      response.close()
    }

    // 削除後のデータの確認
    {
      val response =
        client
          .target(s"http://localhost:8080/rest/$cacheName")
          .request(MediaType.TEXT_PLAIN_TYPE)
          .get
      require(response.readEntity(classOf[String]).isEmpty)
      response.close()
    }

    // Clientのクローズ
    client.close()
  }
}

何気に、初JAX-RS。しかも、クライアントから(笑)。Basic認証の部分は若干てこずったというか、単にオブジェクトを渡すだけだと、Scalaでオーバーロードされたメソッドをうまく解決できませんでした…。

URL自体は

http://[ホスト名]:[ポート]/rest/[Cache名]/[キー]

となっていてわかりやすいですね。

今回は、デフォルトで用意されているCacheを使用しましたが、

standalone/configuration/standalone.xml

追加したければ

        <subsystem xmlns="urn:infinispan:server:core:5.3" default-cache-container="local">
            <cache-container name="local" default-cache="default">
                <local-cache name="default" start="EAGER">
                    <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/>
                    <transaction mode="NONE"/>
                </local-cache>
                <local-cache name="memcachedCache" start="EAGER">
                    <locking isolation="NONE" 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>

の部分に加えてあげればOKです。

例えば、「restCache」を追加したければ

            <cache-container name="local" default-cache="default">
                <local-cache name="default" start="EAGER">
                    <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/>
                    <transaction mode="NONE"/>
                </local-cache>
                <local-cache name="memcachedCache" start="EAGER">
                    <locking isolation="NONE" acquire-timeout="30000" concurrency-level="1000" striping="false"/>
                    <transaction mode="NONE"/>
                </local-cache>
                <local-cache name="namedCache" start="EAGER"/>
                <local-cache name="restCache" start="EAGER"/>
            </cache-container>

みたいにします。