これは、なにをしたくて書いたもの?
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に書きます。
接続先は、「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」に関する記述は、こちら。
※通常はこちら
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
どうやら、デフォルトで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が検出してくれます。
作成したクラスおよび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を取得する時に、失敗するようになります。
結果、こんな感じで見つけられなくなってしまうので、ご注意を。
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は作れるようですが、今回は試していません。
ここまで準備すると、動作するようになります。
ビルドして、起動。
$ 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」インターフェースを実装したクラスを作成して、
設定してもいいよ、みたいなことが書かれています。
最初に失敗した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
いろいろ遠いですねぇ…。