CLOVER🍀

That was when it all began.

QuarkusのInfinispan Client(Hot Rod) Extensionを試す

これは、なにをしたくて書いたもの?

QuarkusにInfinispan向けのExtensionがあるというので、ちょっと試してみようかと。

https://quarkus.io/guides/infinispan-client-guide

Infinispan Client Extension

Infinispan Client Extensionということで、Embedded Modeではなく、いわゆるClient/Server Mode(Hot Rod)になります。

なので、RemoteCacheを使ったものが基本になります。

今回は、Quarkus 0.14.0を使います。対応するInfinispanは、なんと10.0.0.Beta2です。思っていたよりも、だいぶ前のめりですね。

ざっと見た感じ、通常のKey/Valueでのアクセス、CDI、Remote Query、Counter、Near Cache、Encryption、Authenticationが
使えるようです。

https://quarkus.io/guides/infinispan-client-guide

quarkus/infinispan-client-guide.adoc at 0.14.0 · quarkusio/quarkus · GitHub

今回は、簡単にKey/Valueでのアクセスで試してみましょう。

環境

今回の環境は、こちら。

$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b03-0ubuntu1.18.04.1-b03)
OpenJDK 64-Bit Server VM (build 25.212-b03, mixed mode)


$ mvn -version
Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-05T04:00:29+09:00)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 1.8.0_212, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-48-generic", arch: "amd64", family: "unix"


$ $GRAALVM_HOME/bin/native-image --version
GraalVM Version 1.0.0-rc16 CE

Infinispan Serverは、10.0.0.Beta2のものを起動させておきます。IPアドレスは、172.17.0.2とします。

サンプルプログラムの作成

では、サンプルプログラムを作ってみましょう。

まずはプロジェクトの作成。Infinispan Client Extensionと、JSONも使うのでRESTEasy JSONBをExtensionとして追加します。

$ mvn io.quarkus:quarkus-maven-plugin:0.14.0:create \
    -DprojectGroupId=org.littlewings \
    -DprojectArtifactId=infinispan-client-basic \
    -DclassName="org.littlewings.quarkus.infinispan.InfinispanResource" \
    -Dpath="/infinispan" \
    -Dextensions="resteasy-jsonb,infinispan-client"

今回は、書籍データをお題にJSONデータを扱うJAX-RSリソースクラスを作ってみましょう。

何気なく、Infinispan Clientを使うノリでこんなクラスを用意。
※なお、このコードと次のJAX-RSリソースクラスの組み合わせでは失敗します src/main/java/org/littlewings/quarkus/infinispan/Book.java

package org.littlewings.quarkus.infinispan;

import java.io.Serializable;

public class Book implements Serializable {
    private static final long serialVersionUID = 1L;

    private String isbn;
    private String title;
    private int price;

    public Book() {
    }

    // getter/setterは省略
}

こんな感じの、JAX-RSリソースクラスを用意します。
src/main/java/org/littlewings/quarkus/infinispan/InfinispanResource.java

package org.littlewings.quarkus.infinispan;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.infinispan.client.runtime.Remote;
import org.infinispan.client.hotrod.RemoteCache;

@Path("/infinispan")
public class InfinispanResource {
    @Inject
    @Remote
    RemoteCache<String, Book> defaultCache;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String register(Book book) {
        defaultCache.put(book.getIsbn(), book);

        return "ok!!";
    }

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Book findByIsbn(@PathParam("isbn") String isbn) {
        return defaultCache.get(isbn);
    }
}

RemoteCacheは、こんな感じでCDIによりインジェクションできます。

    @Inject
    @Remote
    RemoteCache<String, Book> defaultCache;

@Remoteアノテーションになにも指定しないとデフォルトのRemoteCacheを使い、名前を指定するとその名前に応じた
RemoteCacheを利用するようになります。

Infinispan Serverは、別のサーバーで動いているので、接続先を指定する必要があります。

その設定は、application.propertiesに書きます。

Configuration

接続先は、「quarkus.infinispan-client.server-list」で定義します。Infinispanのhotrod-client.propertiesとは、ちょっと違う名前ですね。
src/main/resources/application.properties

# Configuration file
# key = value

quarkus.infinispan-client.server-list=172.17.0.2:11222

この他に設定できるのは、「quarkus.infinispan-client.near-cache-max-entries」のようです。

では、それ以外の項目はどうするかというと、hotrod-client.propertiesで指定します。

ただ、通常のInfinispanのHot Rod Clientを使う時と異なり、「META-INF/hotrod-client.properties」に置く必要があります。 src/main/resources/META-INF/hotrod-client.properties

infinispan.client.hotrod.server_list=172.17.0.2:11222

「META-INF/hotrod-client.properties」に関する記述は、こちら。

https://github.com/quarkusio/quarkus/blob/0.14.0/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java#L69

※通常はこちら
infinispan/RemoteCacheManager.java at 10.0.0.Beta2 · infinispan/infinispan · GitHub

「META-INF/hotrod-client.properties」を使う場合は、もともとのHot Rod Clientの設定項目名が使えます。設定の一覧は、こちらです。

org.infinispan.client.hotrod.configuration (Infinispan JavaDoc All 10.1.0.Beta1 API)

では、パッケージングして動かしてみましょう。プロジェクト作成時にテストコードもできていますが、今回は修正していないので
テストはスキップします。

$ mvn package -DskipTests=true

起動。

$ java -jar target/infinispan-client-basic-1.0-SNAPSHOT-runner.jar

こんなJSONファイルを作成して、アクセスしてみます。
book1.json

{
  "isbn": "978-4621303252",
  "title": "Effective Java 第3版",
  "price": 4320
}

実行。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/infinispan -d @book1.json
<html><head><title>Error</title></head><body>Internal Server Error</body></html>

コケてしまいました。

2019-05-15 23:41:00,552 ERROR [io.und.request] (executor-thread-1) UT005023: Exception handling request to /infinispan: org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalArgumentException: No marshaller registered for org.littlewings.quarkus.infinispan.Book
    at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
    at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:209)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:496)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:234)
    at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:45)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.quarkus.undertow.runtime.UndertowDeploymentTemplate$7$1$1.call(UndertowDeploymentTemplate.java:437)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:364)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1998)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1525)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1382)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:32)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: java.lang.IllegalArgumentException: No marshaller registered for org.littlewings.quarkus.infinispan.Book
    at org.infinispan.protostream.impl.SerializationContextImpl.getMarshallerDelegate(SerializationContextImpl.java:288)
    at org.infinispan.protostream.WrappedMessage.writeMessage(WrappedMessage.java:240)
    at org.infinispan.protostream.ProtobufUtil.toWrappedByteArray(ProtobufUtil.java:175)
    at org.infinispan.query.remote.client.BaseProtoStreamMarshaller.objectToBuffer(BaseProtoStreamMarshaller.java:57)
    at org.infinispan.commons.marshall.AbstractMarshaller.objectToByteBuffer(AbstractMarshaller.java:70)
    at org.infinispan.client.hotrod.marshall.MarshallerUtil.obj2bytes(MarshallerUtil.java:74)
    at org.infinispan.client.hotrod.DataFormat.valueToBytes(DataFormat.java:98)
    at org.infinispan.client.hotrod.impl.RemoteCacheImpl.valueToBytes(RemoteCacheImpl.java:549)
    at org.infinispan.client.hotrod.impl.RemoteCacheImpl.putAsync(RemoteCacheImpl.java:365)
    at org.infinispan.client.hotrod.impl.RemoteCacheImpl.put(RemoteCacheImpl.java:334)
    at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:79)
    at org.littlewings.quarkus.infinispan.InfinispanResource.register(InfinispanResource.java:25)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:151)
    at org.jboss.resteasy.core.MethodInjectorImpl.lambda$invoke$3(MethodInjectorImpl.java:122)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
    at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:614)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1983)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:110)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:122)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:568)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:442)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:396)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:398)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:367)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invoke$1(ResourceMethodInvoker.java:341)
    at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:981)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2124)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:110)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:341)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    ... 43 more

Marshallerがないよ、と言われています。

Caused by: java.lang.IllegalArgumentException: No marshaller registered for org.littlewings.quarkus.infinispan.Book

https://github.com/quarkusio/quarkus/blob/0.14.0/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java#L96-L97

どうやら、デフォルトでProtoStreamMarshaller(Protocol Buffers)向けのMarshallerを使うようです。

確かに、ガイドにもそれっぽいことが書かれていました。

The default serialization is done using a library based on protobuf. We need to define the proto buf schema and a marshaller for each user type(s).

Serialization (Key Value types support)

ちなみに、Stringやプリミティブのラッパーはbyte配列に変換してくれるようです。

By default the client will support keys and values of the following types: byte[], primitive wrappers (eg. Integer, Long, Double etc.), String, Date and Instant.

対応する範囲は、このあたりのようですね。

infinispan/BaseProtoStreamMarshaller.java at 10.0.0.Beta2 · infinispan/infinispan · GitHub

くどくなるので実行結果は載せませんが、以下のようにStringをRemoteCacheに入れるようなコードは、特になにも用意せずとも
動作します。
src/main/java/org/littlewings/quarkus/infinispan/InfinispanStringResource.java

package org.littlewings.quarkus.infinispan;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import io.quarkus.infinispan.client.runtime.Remote;
import org.infinispan.client.hotrod.RemoteCache;

@Path("/infinispan-string")
public class InfinispanStringResource {
    @Inject
    @Remote
    RemoteCache<String, String> defaultCache;

    @GET
    @Path("register")
    @Produces(MediaType.TEXT_PLAIN)
    public String register(@QueryParam("key") String key, @QueryParam("value") String value) {
        defaultCache.put(key, value);

        return "ok!!";
    }

    @GET
    @Path("{key}")
    @Produces(MediaType.TEXT_PLAIN)
    public String find(@PathParam("key") String isbn) {
        return defaultCache.get(isbn);
    }
}

Protocol Buffers用のMarshallerを書く

というわけで、自分で定義したクラスをRemoteCacheに登録するには、Protocol Bufferesを使うことになります。
ちなみに、今のInfinispanでProtocol Buffersを使う場合、Protocol Bufferes 2を使います。

気を取り直して、新しいクラスで定義し直すことにしましょう。 src/main/java/org/littlewings/quarkus/infinispan/ProtoBook.java

package org.littlewings.quarkus.infinispan;

public class ProtoBook {
    private String isbn;
    private String title;
    private int price;

    public static ProtoBook create(String isbn, String title, int price) {
        ProtoBook protoBook = new ProtoBook();
        protoBook.setIsbn(isbn);
        protoBook.setTitle(title);
        protoBook.setPrice(price);
        return protoBook;
    }

    public ProtoBook() {
    }

    // getter、setterは省略
}

このクラスに対応する、Protocol Buffersのスキーマ定義を書きます。
src/main/resources/META-INF/library.proto

package proto_book;

message ProtoBook {
    required string isbn = 1;
    required string title = 2;
    required int32 price = 3;
}

META-INF配下に「.proto」拡張子のファイルを置いておくと、ビルド時にQuarkusが検出してくれます。

https://github.com/quarkusio/quarkus/blob/0.14.0/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java#L122-L125

作成したクラスおよびprotoファイルに対する、Marshallerを作成します。
src/main/java/org/littlewings/quarkus/infinispan/ProtoBookMarshaller.java

package org.littlewings.quarkus.infinispan;

import java.io.IOException;

import org.infinispan.protostream.MessageMarshaller;

public class ProtoBookMarshaller implements MessageMarshaller<ProtoBook> {
    @Override
    public ProtoBook readFrom(ProtoStreamReader reader) throws IOException {
        return ProtoBook.create(
                reader.readString("isbn"),
                reader.readString("title"),
                reader.readInt("price")
        );
    }

    @Override
    public void writeTo(ProtoStreamWriter writer, ProtoBook protoBook) throws IOException {
        writer.writeString("isbn", protoBook.getIsbn());
        writer.writeString("title", protoBook.getTitle());
        writer.writeInt("price", protoBook.getPrice());
    }

    @Override
    public Class<? extends ProtoBook> getJavaClass() {
        return ProtoBook.class;
    }

    @Override
    public String getTypeName() {
        return "proto_book.ProtoBook";
    }
}

そして、このMarshallerに対するProducerを作成します。
src/main/java/org/littlewings/quarkus/infinispan/Bootstrap.java

package org.littlewings.quarkus.infinispan;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

import org.infinispan.protostream.MessageMarshaller;

@ApplicationScoped
public class Bootstrap {
    @Produces
    MessageMarshaller protoBookMessageMarshaller() {
    // MessageMarshaller<ProtoBook> protoBookMessageMarshaller() {  // これはダメ
        return new ProtoBookMarshaller();
    }
}

ここまでやって、やっとMarshallerが利用できるようになります。

なお、コメントに書いていますが、メソッドの戻り値のMessageMarshallerの型に対して、変に型パラメーターを与えてはいけません。

    // MessageMarshaller<ProtoBook> protoBookMessageMarshaller() {  // これはダメ

Quarkus内でMessageMarshaller型のCDI管理Beanを取得する時に、失敗するようになります。

https://github.com/quarkusio/quarkus/blob/0.14.0/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java#L186

結果、こんな感じで見つけられなくなってしまうので、ご注意を。

23:03:33,082 ERROR [io.qua.dev.DevModeMain] Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
    [error]: Build step io.quarkus.infinispan.client.deployment.InfinispanClientProcessor#setup threw an exception: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.ProtoBookBinaryMarshaller
    at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:137)
    at io.quarkus.dev.DevModeMain.doStart(DevModeMain.java:131)
    at io.quarkus.dev.DevModeMain.main(DevModeMain.java:84)
Caused by: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
    [error]: Build step io.quarkus.infinispan.client.deployment.InfinispanClientProcessor#setup threw an exception: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.ProtoBookBinaryMarshaller
    at io.quarkus.builder.Execution.run(Execution.java:124)
    at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:137)
    at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:108)
    at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:102)
    ... 2 more
Caused by: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.ProtoBookBinaryMarshaller
    at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:516)
    at io.quarkus.builder.BuildContext.run(BuildContext.java:414)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1998)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1525)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1416)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.ProtoBookBinaryMarshaller
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at io.quarkus.infinispan.client.runtime.InfinispanClientProducer.replaceProperties(InfinispanClientProducer.java:93)
    at io.quarkus.infinispan.client.deployment.InfinispanClientProcessor.setup(InfinispanClientProcessor.java:111)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:507)
    ... 7 more

なお、現在のHot Rod Clientは、アノテーションでこのあたりのMarshallerなどの処理は書けるのですが、Quarkusではまだサポート
していないようです。

Annotation based proto stream marshalling is not yet supported in the Quarkus Infinispan client. This will be added soon, allowing you to only annotate your classes, skipping the following steps.

では、このクラスを使うJAX-RSリソースクラスを作成します。

src/main/java/org/littlewings/quarkus/infinispan/InfinispanProtoResource.java

package org.littlewings.quarkus.infinispan;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.infinispan.client.runtime.Remote;
import org.infinispan.client.hotrod.RemoteCache;

@Path("/infinispan-proto")
public class InfinispanProtoResource {
    @Inject
    @Remote("protoBookCache")
    RemoteCache<String, ProtoBook> protoBookCache;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.TEXT_PLAIN)
    public String register(ProtoBook book) {
        protoBookCache.put(book.getIsbn(), book);

        return "ok!!";
    }

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public ProtoBook findByIsbn(@PathParam("isbn") String isbn) {
        return protoBookCache.get(isbn);
    }
}

せっかくなので、新しいRemoteCacheを作成しましょうか。

    @Inject
    @Remote("protoBookCache")
    RemoteCache<String, ProtoBook> protoBookCache;

Infinispan Server側で、「protoBookCache:add」というCacheを作成。

$ bin/ispn-cli.sh -c --command='/subsystem=datagrid-infinispan/cache-container=clustered/configurations=CONFIGURATIONS/distributed-cache-configuration=protoBookCacheConfiguration:add(start=EAGER,mode=SYNC)'
WARN: can't find jboss-cli.xml. Using default configuration values.
{"outcome" => "success"}
$ bin/ispn-cli.sh -c --command='/subsystem=datagrid-infinispan/cache-container=clustered/distributed-cache=protoBookCache:add(configuration=protoBookCacheConfiguration)'
WARN: can't find jboss-cli.xml. Using default configuration values.
{"outcome" => "success"}

なお、Quarkusのサンプルにあるように、APIでもCacheは作れるようですが、今回は試していません。

https://github.com/quarkusio/quarkus-quickstarts/blob/0.14.0/infinispan-client/src/main/java/org/acme/infinispanclient/InfinispanClientApp.java#L24

ここまで準備すると、動作するようになります。

ビルドして、起動。

$ mvn package -DskipTests=true
$ java -jar target/infinispan-client-basic-1.0-SNAPSHOT-runner.jar

今度はOKです。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/infinispan-proto -d @book1.json
ok!!

$ curl localhost:8080/infinispan-proto/978-4621303252
{"isbn":"978-4621303252","price":4320,"title":"Effective Java 第3版"}

ネイティブイメージでも、確認してみましょう。

$ mvn -Pnative package -DskipTests=true
$ ./target/infinispan-client-basic-1.0-SNAPSHOT-runner

同じ結果になります。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/infinispan-proto -d @book1.json
ok!!

$ curl localhost:8080/infinispan-proto/978-4621303252
{"isbn":"978-4621303252","price":4320,"title":"Effective Java 第3版"}

というわけで、簡単な範囲ですが確認できました。

通常のInfinispanのHot Rod Clientとは、少し勝手が違うんですねー。

Marshallerについて補足

ところで、ドキュメントには「org.infinispan.commons.marshaller.Marshaller」インターフェースを実装したクラスを作成して、
設定してもいいよ、みたいなことが書かれています。

Providing your own Marshaller

最初に失敗したBookクラスを、シリアライズできるようにしてみましょう。

Marshallerを作成します。めちゃくちゃその場しのぎ感がすごいですが…。
src/main/java/org/littlewings/quarkus/infinispan/BookMarshaller.java

package org.littlewings.quarkus.infinispan;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;

import org.infinispan.commons.dataconversion.MediaType;
import org.infinispan.commons.io.ByteBuffer;
import org.infinispan.commons.io.ByteBufferImpl;
import org.infinispan.commons.marshall.AbstractMarshaller;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.marshall.StringMarshaller;

public class BookMarshaller extends AbstractMarshaller {
    Marshaller marshaller = new StringMarshaller(StandardCharsets.UTF_8);

    @Override
    protected ByteBuffer objectToBuffer(Object o, int estimatedSize) throws IOException, InterruptedException {
        if (o instanceof String) {
            return marshaller.objectToBuffer(o);
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeUTF(((Book) o).getIsbn());
            oos.writeUTF(((Book) o).getTitle());
            oos.writeInt(((Book) o).getPrice());
        }

        return new ByteBufferImpl(baos.toByteArray());
    }

    @Override
    public Object objectFromByteBuffer(byte[] buf, int offset, int length) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bais = new ByteArrayInputStream(buf);

        Book book = new Book();

        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
            book.setIsbn(ois.readUTF());
            book.setTitle(ois.readUTF());
            book.setPrice(ois.readInt());
        }

        return book;
    }

    @Override
    public boolean isMarshallable(Object o) throws Exception {
        return o instanceof Book;
    }

    @Override
    public MediaType mediaType() {
        return MediaType.APPLICATION_SERIALIZED_OBJECT;
    }
}

ObjectOutputStream.writeObjectなどは(ネイティブイメージにすると)使えないので、今回はこんな感じに…。

途中で、「もうProtocol Buffersでいいや」という気分になったので、ほんっと適当です。

このクラスを、「infinispan.client.hotrod.marshaller」に登録します。
src/main/resources/META-INF/hotrod-client.properties

infinispan.client.hotrod.marshaller=org.littlewings.quarkus.infinispan.BookMarshaller

ビルドして起動。

$ mvn package -DskipTests=true
$ java -jar target/infinispan-client-basic-1.0-SNAPSHOT-runner.jar

すると、最初に失敗したJAX-RSリソースクラスも動作するようになります。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/infinispan -d @book1.json
ok!!


$ curl localhost:8080/infinispan/978-4621303252
{"isbn":"978-4621303252","price":4320,"title":"Effective Java 第3版"}

ネイティブイメージにしても、問題なく動きます。

なのですが、「quarkus:dev」だと

$ mvn compile quarkus:dev

作成したMarshallerが見つけられなくなります…。

00:53:49,730 ERROR [io.qua.dev.DevModeMain] Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
    [error]: Build step io.quarkus.infinispan.client.deployment.InfinispanClientProcessor#setup threw an exception: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.BookMarshaller
    at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:137)
    at io.quarkus.dev.DevModeMain.doStart(DevModeMain.java:131)
    at io.quarkus.dev.DevModeMain.main(DevModeMain.java:84)
Caused by: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
    [error]: Build step io.quarkus.infinispan.client.deployment.InfinispanClientProcessor#setup threw an exception: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.BookMarshaller
    at io.quarkus.builder.Execution.run(Execution.java:124)
    at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:137)
    at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:108)
    at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:102)
    ... 2 more
Caused by: java.lang.IllegalStateException: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.BookMarshaller
    at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:516)
    at io.quarkus.builder.BuildContext.run(BuildContext.java:414)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1998)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1525)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1416)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: java.lang.ClassNotFoundException: org.littlewings.quarkus.infinispan.BookMarshaller
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at io.quarkus.infinispan.client.runtime.InfinispanClientProducer.replaceProperties(InfinispanClientProducer.java:93)
    at io.quarkus.infinispan.client.deployment.InfinispanClientProcessor.setup(InfinispanClientProcessor.java:111)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:507)
    ... 7 more

いろいろ遠いですねぇ…。