diff --git a/docs/src/main/asciidoc/images/dev-ui-infinispan.png b/docs/src/main/asciidoc/images/dev-ui-infinispan.png new file mode 100644 index 00000000000000..378347e23c317b Binary files /dev/null and b/docs/src/main/asciidoc/images/dev-ui-infinispan.png differ diff --git a/docs/src/main/asciidoc/images/infinispan-console-client-guide.png b/docs/src/main/asciidoc/images/infinispan-console-client-guide.png new file mode 100644 index 00000000000000..b4ed55833898b0 Binary files /dev/null and b/docs/src/main/asciidoc/images/infinispan-console-client-guide.png differ diff --git a/docs/src/main/asciidoc/infinispan-client-reference.adoc b/docs/src/main/asciidoc/infinispan-client-reference.adoc new file mode 100644 index 00000000000000..285b9041d55ea2 --- /dev/null +++ b/docs/src/main/asciidoc/infinispan-client-reference.adoc @@ -0,0 +1,755 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Infinispan Client Extension Reference Guide +include::_attributes.adoc[] +:categories: data +:summary: Infinispan is an in memory distributed data store and cache server that offers flexible deployment options and robust capabilities for storing, managing, and processing data. + +Infinispan is a distributed, in-memory key/value store that provides Quarkus applications with a highly configurable +and independently scalable data layer. +This extension gives you client functionality that connects applications running on Quarkus with remote Infinispan clusters. + +To find out more about Infinispan, visit the https://infinispan.org/documentation[Infinispan documentation]. + +== Solution + +We recommend that you complete each step in the following sections to create the application. +However, you can proceed directly to the completed solution as follows: + +Clone the Git repository: `git clone {quickstarts-clone-url}` or download an {quickstarts-archive-url}[archive]. +Locate the solution in the `infinispan-client-quickstart` {quickstarts-tree-url}/infinispan-client-quickstart[directory]. + +== Adding the Infinispan client extension + +Run the following command in the base directory of your Quarkus project to add the `infinispan-client` extension: + +:add-extension-extensions: infinispan-client +include::{includes}/devtools/extension-add.adoc[] + +This command adds the following dependency to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-infinispan-client + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation 'io.quarkus:quarkus-infinispan-client' +annotationProcessor 'org.infinispan.protostream:protostream-processor:4.6.1.Final' <1> +---- +<1> Mandatory in the Gradle build to enable the generation of the files in the annotation based serialization + +== Configuring the Infinispan client + +Open the `application.properties` file in the `src/main/resources` directory with any text editor. + +Note that Infinispan documentation refers to a `hotrod-client.properties` file. +You can configure the Infinispan client with either properties file but `application.properties` always takes +priority over `hotrod-client.properties`. + +Additionally, you cannot update configuration properties at runtime. +If you modify `application.properties` or `hotrod-client.properties`, you must rebuild the application before those changes take effect. + +== Connecting to Infinispan clusters + +If you are running an Infinispan Server add the following properties to connect: + +[source,properties] +---- +quarkus.infinispan-client.hosts=localhost:11222 <1> + +quarkus.infinispan-client.username=admin <2> +quarkus.infinispan-client.password=password <3> + +quarkus.infinispan-client.client-intelligence=BASIC <4> +---- +<1> Sets Infinispan Server address list, separated with commas +<2> Sets the authentication username +<3> Sets the authentication password +<4> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac. + +Alternatively, you can use uri connection by providing a single connection property +[source,properties] +---- +quarkus.infinispan-client.uri=hotrod://admin:password@localhost:11222 <1> +quarkus.infinispan-client.client-intelligence=BASIC <2> +---- +<1> Sets Infinispan URI connection. The following properties will be ignored: hosts, username and password. +<2> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac + +[TIP] +==== +Use Infinispan Dev Services to run a server and connect without configuration. +==== + +.Running Infinispan Server + +To use the Infinispan client extension, you need at least one running instance of Infinispan Server. + +Check out our 5-minute https://infinispan.org/get-started/[Getting stated with Infinispan] tutorial to run Infinispan Server locally. + +Infinispan Server also enables authentication and security authorization by default, so you need to create a user with permissions. + +* If you run the Infinispan Server image, pass the `USER="admin"` and `PASS="password"` parameters. +* If you run the bare metal distribution, use the Command Line Interface (CLI) as follows: ++ +[source,bash] +---- +$ ./bin/cli.sh user create admin -p password +---- + +=== Infinispan Health Check +If you are using the quarkus-smallrye-health extension, the Infinispan client extensions will automatically add a readiness health check to validate the connection. + +When you access the `/q/health/ready` endpoint of your application you will have information about the server connection and available caches. + +This behavior can be disabled via the property `quarkus.infinispan-client.health.enabled`. + +=== Tracing with OpenTelemetry +Infinispan supports instrumentation of the server via OpenTelemetry. Having the `quarkus-opentelemetry` extension will propagate +the traces from the Infinispan Client to the Server. +This behavior can be disabled via the property `quarkus.infinispan-client.tracing.propagation.enabled` + +=== Creating caches from the client + +When a cache is accessed from the client, if the cache does not exist in the Infinispan Server and you want +to create it on first access, use one of the following properties: + +[source,properties] +---- +quarkus.infinispan-client.cache.books.configuration-uri=cacheConfig.json <1> +quarkus.infinispan-client.cache.magazine.configuration= <2> +---- +<1> The file name located under the `resources` folder that contains the configuration of the 'books' cache +<2> The configuration of the 'magazine' cache as a plain text property + +If both `configuration-uri` and `configuration` are configured for the same cache with the same Quarkus profile, +`configuration-uri` gets preference over `configuration`. + +[TIP] +==== +Cache configuration can be provided in XML, JSON or YAML. Use the Infinispan Console and the cache configuration Wizard +to learn more about Infinispan Caches and create guided configurations. +==== + +If nothing is configured for a particular cache, it will be created with the following basic configuration: + +.XML +[source,xml,options="nowrap",subs=attributes+,role="primary"] +---- + + + +---- + +.JSON +[source,json,options="nowrap",subs=attributes+,role="secondary"] +---- +{ + "distributed-cache": { + "encoding": { + "media-type": "application/x-protostream" + } + } +} +---- + +.YAML +[source,yaml,options="nowrap",subs=attributes+,role="secondary"] +---- +distributedCache: + encoding: + mediaType: "application/x-protostream" +---- + +=== Authentication mechanisms + +You can use the following authentication mechanisms with the Infinispan client: + +* DIGEST-MD5 +* PLAIN (recommended only in combination with TLS encryption) +* EXTERNAL + +Other authentication mechanisms, such as SCRAM and GSSAPI, are not yet verified with the Infinispan client. + +You can find more information on configuring authentication in https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#hotrod_endpoint_auth-hotrod-client-configuration[Hot Rod Endpoint Authentication Mechanisms]. + +NOTE: You must configure authentication in the `hotrod-client.properties` file if you use Dependency Injection. + +== Serialization (Key Value types support) + +By default, the client will support keys and values of the following types: byte[], +primitive wrappers (e.g. Integer, Long, Double), String, Date and Instant. User types require +some additional steps that are detailed here. Let's say we have the following user classes: + +.Author.java +[source,java] +---- +public class Author { + private final String name; + private final String surname; + + public Author(String name, String surname) { + this.name = Objects.requireNonNull(name); + this.surname = Objects.requireNonNull(surname); + } + // Getter/Setter/equals/hashCode/toString omitted +} +---- + +.Book.java +[source,java] +---- +public class Book { + private final String title; + private final String description; + private final int publicationYear; + private final Set authors; + private final BigDecimal price; + + public Book(String title, String description, int publicationYear, Set authors, BigDecimal price) { + this.title = Objects.requireNonNull(title); + this.description = Objects.requireNonNull(description); + this.publicationYear = publicationYear; + this.authors = Objects.requireNonNull(authors); + this.price = price; + } + // Getter/Setter/equals/hashCode/toString omitted +} +---- + +Serialization of user types uses a library based on protobuf, +called https://github.com/infinispan/protostream[Protostream]. + +[TIP] +==== +Infinispan caches can store keys and values in different encodings, but recommend using https://developers.google.com/protocol-buffers[Protocol Buffers (Protobuf)]. + +For more information see our https://infinispan.org/docs/stable/titles/encoding/encoding.html[Cache Encoding and Marshalling] guide. +==== + + +=== Annotation based Serialization + +This can be done automatically by adding protostream annotations to your user classes. +In addition, a single Initializer annotated interface is required which controls how +the supporting classes are generated. + +Here is an example of how the preceding classes should be changed: + +.Author.java +[source,java] +---- + @ProtoFactory + public Author(String name, String surname) { + this.name = Objects.requireNonNull(name); + this.surname = Objects.requireNonNull(surname); + } + + @ProtoField(number = 1) + public String getName() { + return name; + } + + @ProtoField(number = 2) + public String getSurname() { + return surname; + } +---- + +.Book.java +[source,java] +---- + @ProtoFactory + public Book(String title, String description, int publicationYear, Set authors) { + this.title = Objects.requireNonNull(title); + this.description = Objects.requireNonNull(description); + this.publicationYear = publicationYear; + this.authors = Objects.requireNonNull(authors); + } + + @ProtoField(number = 1) + public String getTitle() { + return title; + } + + @ProtoField(number = 2) + public String getDescription() { + return description; + } + + @ProtoField(number = 3, defaultValue = "-1") + public int getPublicationYear() { + return publicationYear; + } + + @ProtoField(number = 4) + public Set getAuthors() { + return authors; + } +---- + +If your classes have only mutable fields, then the `ProtoFactory` annotation +is not required, assuming your class has a no arg constructor. + +Then all that is required is a very simple `GeneratedSchema` interface with an annotation +on it to specify configuration settings + +.BooksSchema.java +[source,java] +---- +import org.infinispan.protostream.GeneratedSchema; +import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; +import org.infinispan.protostream.types.java.math.BigDecimalAdapter; + +@AutoProtoSchemaBuilder(includeClasses = { Book.class, Author.class, BigDecimalAdapter.class }, schemaPackageName = "book_sample") +interface BookStoreSchema extends GeneratedSchema { +} +---- + +[TIP] +==== +Protostream provides default Protobuf mappers for commonly used types as `BigDecimal`, included in the `org.infinispan.protostream.types` package. +==== + +So in this case we will automatically generate the marshaller and schemas for the included classes and +place them in the schema package automatically. The package does not have to be provided, but if you use Infinispan search capabilities, you must know the generated package. + +NOTE: In Quarkus the `schemaFileName` and `schemaFilePath` attributes should NOT be set on the `AutoProtoSchemaBuilder` annotation. Setting either attributes causes native runtime errors. + +=== Custom serialization + +The previous method is suggested for any case when the user can annotate their classes. +Unfortunately the user may not be able to annotate all classes they will put in the +cache. In this case you must define your schema and create your own Marshaller(s) +yourself. + +Protobuf schema:: You can supply a protobuf schema through either one of two ways. +. Proto File + + +You can put the `.proto` file in the `META-INF` directory of the project. These files will +automatically be picked up at initialization time. ++ +.library.proto +---- +package book_sample; + +message Book { + required string title = 1; + required string description = 2; + required int32 publicationYear = 3; // no native Date type available in Protobuf + repeated Author authors = 4; + requited double price = 5; // no native BigDecimal type available in Protobuf +} + +message Author { + required string name = 1; + required string surname = 2; +} +---- +. In Code + + +Or you can define the proto schema directly in user code by defining a produced bean of type +`org.infinispan.protostream.FileDescriptorSource`. ++ +[source,java] +---- + @Produces + FileDescriptorSource bookProtoDefinition() { + return FileDescriptorSource.fromString("library.proto", "package book_sample;\n" + + "\n" + + "message Book {\n" + + " required string title = 1;\n" + + " required string description = 2;\n" + + " required int32 publicationYear = 3; // no native Date type available in Protobuf\n" + + "\n" + + " repeated Author authors = 4;\n" + + "\n" + + " required double price = 5; // no native BigDecimal type available in Protobuf\n" + + "}\n" + + "\n" + + "message Author {\n" + + " required string name = 1;\n" + + " required string surname = 2;\n" + + "}"); + } +---- +User Marshaller:: +The last thing to do is to provide a `org.infinispan.protostream.MessageMarshaller` implementation +for each user class defined in the proto schema. This class is then provided via `@Produces` in a similar +fashion to the code based proto schema definition above. ++ +Here is the Marshaller class for our Author & Book classes. ++ +NOTE: The type name must match the `.` exactly! ++ +.AuthorMarshaller.java +[source,java] +---- +public class AuthorMarshaller implements MessageMarshaller { + + @Override + public String getTypeName() { + return "book_sample.Author"; + } + + @Override + public Class getJavaClass() { + return Author.class; + } + + @Override + public void writeTo(ProtoStreamWriter writer, Author author) throws IOException { + writer.writeString("name", author.getName()); + writer.writeString("surname", author.getSurname()); + } + + @Override + public Author readFrom(ProtoStreamReader reader) throws IOException { + String name = reader.readString("name"); + String surname = reader.readString("surname"); + return new Author(name, surname); + } +} +---- ++ +.BookMarshaller.java +[source,java] +---- +public class BookMarshaller implements MessageMarshaller { + + @Override + public String getTypeName() { + return "book_sample.Book"; + } + + @Override + public Class getJavaClass() { + return Book.class; + } + + @Override + public void writeTo(ProtoStreamWriter writer, Book book) throws IOException { + writer.writeString("title", book.getTitle()); + writer.writeString("description", book.getDescription()); + writer.writeInt("publicationYear", book.getPublicationYear()); + writer.writeCollection("authors", book.getAuthors(), Author.class); + writer.writeDouble("price", book.getPrice().doubleValue()); + } + + @Override + public Book readFrom(ProtoStreamReader reader) throws IOException { + String title = reader.readString("title"); + String description = reader.readString("description"); + int publicationYear = reader.readInt("publicationYear"); + Set authors = reader.readCollection("authors", new HashSet<>(), Author.class); + BigDecimal price = BigDecimal.valueOf(reader.readDouble("price")); + return new Book(title, description, publicationYear, authors, price); + } +} +---- ++ +And you pass the marshaller by defining the following: ++ +[source,java] +---- + @Produces + MessageMarshaller authorMarshaller() { + return new AuthorMarshaller(); + } + + @Produces + MessageMarshaller bookMarshaller() { + return new BookMarshaller(); + } +---- +NOTE: The above produced Marshaller method MUST return `MessageMarshaller` without types or else it will not be found. + +== Dependency Injection + +As you saw above we support the user injecting Marshaller configuration. You can do the inverse with +the Infinispan client extension providing injection for `RemoteCacheManager` and `RemoteCache` objects. +There is one global `RemoteCacheManager` that takes all the configuration +parameters setup in the above sections. + +It is very simple to inject these components. All you need to do is to add the `@Inject` annotation to +the field, constructor or method. In the below code we utilize field and constructor injection. + +.SomeClass.java +[source,java] +---- + @Inject SomeClass(RemoteCacheManager remoteCacheManager) { + this.remoteCacheManager = remoteCacheManager; + } + + @Inject + @Remote("myCache") + RemoteCache cache; + + RemoteCacheManager remoteCacheManager; +---- + +If you notice the `RemoteCache` declaration has an additional optional annotation named `Remote`. +This is a qualifier annotation allowing you to specify which named cache that will be injected. This +annotation is not required and if it is not supplied, the default cache will be injected. + +NOTE: Other types may be supported for injection, please see other sections for more information + +=== Registering Protobuf Schemas with Infinispan Server +You need to register the generated Protobuf schemas with Infinispan Server to perform queries or convert from +`Protobuf` to other media types such as `JSON`. + +[TIP] +==== +You can check the schemas that exist under the `Schemas` tab by logging into +Infinispan Console at `http://SERVER_HOST:SERVER_PORT` (for example `http://localhost:11222`). + +Check the xref:infinispan-dev-services.adoc[Infinispan Dev Services Guide] to connect to the Infinispan +Dev Services server. +==== + +By default, Protobuf schemas generated this way will be registered by this extension when the client first connects. +However, it might be required to handle the registration manually as a schema may evolve over time when used in +production, so you can disable this from occurring by configuring the +`quarkus.infinispan-client.use-schema-registration` to `false`. + +To configure the schema manually +please use https://infinispan.org/docs/infinispan-operator/main/operator.html[Infinispan Operator] +for Kubernetes deployments, Infinispan Console, +https://infinispan.org/docs/stable/titles/rest/rest.html#rest_v2_protobuf_schemas[REST API] or the +https://infinispan.org/docs/stable/titles/encoding/encoding.html#registering-sci-remote-caches_marshalling[Hot Rod Java Client]. + +[#infinispan-annotations-api] +== Caching using annotations + +The Infinispan Client extension offers a set of annotations that can be used in a CDI managed bean to enable caching abilities with Infinispan. + +[WARNING] +==== +Caching annotations are not allowed on private methods. +They will work fine with any other access modifier including package-private (no explicit modifier). +==== + +=== @CacheResult + +Loads a method result from the cache without executing the method body whenever possible. + +When a method annotated with `@CacheResult` is invoked, Quarkus will use the method argument as the cache key and check in the cache whether the method has been already invoked. +Methods with multiple parameters are not allowed. For composite keys, define a Protobuf schema that will hold multiple values. +If a value is found in the cache, it is returned and the annotated method is never actually executed. +If no value is found, the annotated method is invoked and the returned value is stored in the cache using the computed key. +This annotation cannot be used on a method returning `void`. + +NOTE: Infinispan Client extension is not able yet to cache `null` values unlike the Quarkus-Cache extension. + +=== @CacheInvalidate + +Removes an entry from the cache. + +When a method annotated with `@CacheInvalidate` is invoked, Infinispan will use the method argument as a cache key to try to remove an existing entry from the cache. +If the key does not identify any cache entry, nothing will happen. + +=== @CacheInvalidateAll + +When a method annotated with `@CacheInvalidateAll` is invoked, Infinispan will remove all entries from the cache. + + +== Querying + +The Infinispan client supports both indexed and non-indexed search as long as the +`ProtoStreamMarshaller` is configured above. This allows the user to query based on the +properties of the proto schema. *Indexed queries are preferred for performance reasons*. + +.XML +[source,xml,options="nowrap",subs=attributes+,role="primary"] +---- + + + + + book_sample.Book + + + +---- + +.JSON +[source,json,options="nowrap",subs=attributes+,role="secondary"] +---- +{ + "books": { + "distributed-cache": { + ... + "indexing": { + "enabled": true, + "storage": "filesystem", + "startupMode": "PURGE", + "indexed-entities": [ + "book_sample.Book" + ] + } + } + } +} +---- + +.YAML +[source,yaml,options="nowrap",subs=attributes+,role="secondary"] +---- +distributedCache: + # other configuration + indexing: + enabled: "true" + storage: "filesystem" + startupMode: "PURGE" + indexedEntities: + - "book_sample.Book" +---- + +Query builds upon the proto definitions you can configure when setting up the `ProtoStreamMarshaller`. +Either method of Serialization above will automatically register the schema with the server at +startup, meaning that you will automatically gain the ability to query objects stored in the +remote Infinispan Server. + +.Book.java +[source,java] +---- +@Indexed <1> +public class Book { + + @ProtoFactory + public Book(String title, String description, int publicationYear, Set authors) { + ... + } + + @ProtoField(number = 1) + @Text <2> + public String getTitle() { + return title; + } + + @ProtoField(number = 2) + @Keyword(projectable = true, sortable = true, normalizer = "lowercase", indexNullAs = "unnamed", norms = false) <3> + public String getDescription() { + return description; + } + ... +---- +<1> `@Indexed` annotation makes the POJO indexable +<2> `@Basic` annotation is used for indexed fields without any special transformation +<3> `@Keyword` annotation is used to apply a normalizer to a text field + +You can use either the Query DSL or the Ickle Query language with the Quarkus Infinispan client +extension. + +NOTE: You can read more about https://infinispan.org/docs/stable/titles/query/query.html[querying] in the Infinispan documentation. + + +== Counters + +Infinispan also has a notion of counters and the Quarkus Infinispan client supports them out of +the box. + +The Quarkus Infinispan client extension allows for Dependency Injection +of the `CounterManager` directly. All you need to do is annotate your field, constructor or method, +and you get it with no fuss. You can then use counters as you would normally. + +[source,java] +---- +@Inject +CounterManager counterManager; +---- + +You can read more about https://infinispan.org/docs/stable/titles/developing/developing.html#clustered_counters[clustered counters] in the Infinispan documentation. + +== Near Caching + +Near caching is disabled by default, but you can enable it on a per cache basic by configuring the following properties: + +[source,properties] +---- +quarkus.infinispan-client.cache.books.near-cache-mode=INVALIDATED <1> +quarkus.infinispan-client.cache.books.near-cache-max-entries=200 <2> +quarkus.infinispan-client.cache.books.near-cache-use-bloom-filter=true <3> +---- + +<1> Enables near caching for the 'books' cache by setting the mode to `INVALIDATED` +<2> Sets the maximum number of entries that the near cache of the 'books' cache can hold before eviction occurs +<3> Enables bloom filter for the 'books' cache + +=== Bounded near caching + +You should always use bounded near caches by specifying the maximum number of entries they can contain. + +=== Bloom filters + +If you need to optimize the performance for write operations by reducing the total number of invalidation messages, +enable bloom filter. Bloom filters reside on Infinispan Server and keep track of the entries that the client has requested. +They cannot be used with unbounded near cache: maximum number of entries must be defined when enabling bloom filters. + +== Encryption + +Encryption at this point requires additional steps to get working. + +The first step is to configure the `hotrod-client.properties` file to point to your truststore +and/or keystore. This is further detailed https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#hotrod_encryption[here]. + +The Infinispan Client extension enables SSL/TLS by default. You can read more about this +at xref:native-and-ssl.adoc[Using SSL With Native Executables]. + +== Additional Features + +The Infinispan Client has additional features that were not mentioned here. This means this +feature was not tested in a Quarkus environment, and they may or may not work. Please let us +know if you need these added! + +[[dev-services]] +== Dev Services for Infinispan + +When you use the infinispan-client extension in dev mode or in test, Quarkus automatically starts an Infinispan server and configure your application. + +=== Enabling / Disabling Dev Services for Infinispan + +NOTE: Learn more in the xref:infinispan-dev-services.adoc[Infinispan Dev Services guide]. + +== Shared server + +Quarkus will share the Infinispan broker if you have multiple applications running in dev mode. +Dev Services for Infinispan implements a _service discovery_ mechanism for your multiple Quarkus applications running in _dev_ mode to share a single broker. + +NOTE: Dev Services for Infinispan starts the container with the `quarkus-dev-service-infinispan` label which is used to identify the container. + +If you need multiple (shared) Infinispan server, you can configure the `quarkus.infinispan-client.devservices.service-name` attribute and indicate the server name. +It looks for a container with the same value, or starts a new one if none can be found. +The default service name is `infinispan`. + +Sharing is enabled by default in dev mode, but disabled in test mode. +You can disable the sharing with `quarkus.infinispan-client.devservices.shared=false`. + +== Setting the port + +By default, Dev Services for Infinispan picks a random port and configures the application. +You can set the port by configuring the `quarkus.infinispan-client.devservices.port` property. + +== Testing helpers + +To start an Infinispan Server for your unit tests, Quarkus provides one `QuarkusTestResourceLifecycleManager` that relies on link:https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#junit-testing[Infinispan Server Test Container]. + +- `io.quarkus.test.infinispan.client.InfinispanTestResource` will start a single instance on port 11222 with user 'admin' and password 'password'. + +To use them, you need to add the `io.quarkus:quarkus-test-infinispan-client` dependency to your pom.xml. + +For more information about the usage of a `QuarkusTestResourceLifecycleManager` please read xref:getting-started-testing.adoc#quarkus-test-resource[Quarkus test resource]. + +== Configuration Reference + +include::{generated-dir}/config/quarkus-infinispan-client.adoc[opts=optional, leveloffset=+1] diff --git a/docs/src/main/asciidoc/infinispan-client.adoc b/docs/src/main/asciidoc/infinispan-client.adoc index 4e68b44e224fcf..160196ff297f06 100644 --- a/docs/src/main/asciidoc/infinispan-client.adoc +++ b/docs/src/main/asciidoc/infinispan-client.adoc @@ -3,33 +3,50 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// -= Infinispan Client += Using the Infinispan Client include::_attributes.adoc[] :categories: data -:summary: Infinispan is an in memory distributed data store and cache server that offers flexible deployment options and robust capabilities for storing, managing, and processing data. +:summary: This guide covers how to use Infinispan with Quarkus. -Infinispan is a distributed, in-memory key/value store that provides Quarkus applications with a highly configurable -and independently scalable data layer. -This extension gives you client functionality that connects applications running on Quarkus with remote Infinispan clusters. +This guide demonstrates how your Quarkus application can connect to an Infinispan server using the Infinispan Client extension. -To find out more about Infinispan, visit the https://infinispan.org/documentation[Infinispan documentation]. +== Prerequisites + +include::{includes}/prerequisites.adoc[] +* A working Docker environment + +== Architecture +In this guide, we are going to expose a Greeting Rest API to create and display greeting messages by using +the https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#remotecache_api[Infinispan RemoteCache API] +and `getAsync` and `putAsync` operations. + +We'll be using the Quarkus Infinispan Client extension to connect to interact with Infinispan. == Solution +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `infinispan-client-quickstart` {quickstarts-tree-url}/infinispan-client-quickstart[directory]. + +== Creating the Maven Project -We recommend that you complete each step in the following sections to create the application. -However, you can proceed directly to the completed solution as follows: +First, we need a new project. Create a new project with the following command: -Clone the Git repository: `git clone {quickstarts-clone-url}` or download an {quickstarts-archive-url}[archive]. -Locate the solution in the `infinispan-client-quickstart` {quickstarts-tree-url}/infinispan-client-quickstart[directory]. +:create-app-artifact-id: infinispan-client-quickstart +:create-app-extensions: infinispan-client,resteasy-reactive +include::{includes}/devtools/create-app.adoc[] -== Adding the Infinispan client extension +This command generates a new project, importing the Infinispan Client extension. -Run the following command in the base directory of your Quarkus project to add the `infinispan-client` extension: +If you already have your Quarkus project configured, you can add the `infinispan-client` extension +to your project by running the following command in your project base directory: :add-extension-extensions: infinispan-client include::{includes}/devtools/extension-add.adoc[] -This command adds the following dependency to your build file: +This will add the following to your build file: [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml @@ -43,713 +60,294 @@ This command adds the following dependency to your build file: [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -implementation 'io.quarkus:quarkus-infinispan-client' -annotationProcessor 'org.infinispan.protostream:protostream-processor:4.5.1.Final' <1> +implementation("io.quarkus:quarkus-infinispan-client") +annotationProcessor 'org.infinispan.protostream:protostream-processor:4.6.1.Final' <1> ---- <1> Mandatory in the Gradle build to enable the generation of the files in the annotation based serialization -== Configuring the Infinispan client - -Open the `application.properties` file in the `src/main/resources` directory with any text editor. - -Note that Infinispan documentation refers to a `hotrod-client.properties` file. -You can configure the Infinispan client with either properties file but `application.properties` always takes -priority over `hotrod-client.properties`. - -Additionally, you cannot update configuration properties at runtime. -If you modify `application.properties` or `hotrod-client.properties`, you must rebuild the application before those changes take effect. - -== Connecting to Infinispan clusters - -If you are running an Infinispan Server add the following properties to connect: +== Creating the Greeting POJO +We are going to model our increments using the `Greeting` POJO. +Create the `src/main/java/org/acme/infinispan/client/Greeting.java` file, with the following content: -[source,properties] +[source, java] ---- -quarkus.infinispan-client.hosts=localhost:11222 <1> +package org.acme.infinispan.client; -quarkus.infinispan-client.username=admin <2> -quarkus.infinispan-client.password=password <3> +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; -quarkus.infinispan-client.client-intelligence=BASIC <4> ----- -<1> Sets Infinispan Server address list, separated with commas -<2> Sets the authentication username -<3> Sets the authentication password -<4> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac. +public class Greeting { // <1> + @ProtoField(number = 1) // <2> + public String name; -Alternatively, you can use uri connection by providing a single connection property -[source,properties] ----- -quarkus.infinispan-client.uri=hotrod://admin:password@localhost:11222 <1> -quarkus.infinispan-client.client-intelligence=BASIC <2> ----- -<1> Sets Infinispan URI connection. The following properties will be ignored: hosts, username and password. -<2> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac - -[TIP] -==== -Use Infinispan Dev Services to run a server and connect without configuration. -==== - -.Running Infinispan Server - -To use the Infinispan client extension, you need at least one running instance of Infinispan Server. - -Check out our 5-minute https://infinispan.org/get-started/[Getting stated with Infinispan] tutorial to run Infinispan Server locally. - -Infinispan Server also enables authentication and security authorization by default, so you need to create a user with permissions. - -* If you run the Infinispan Server image, pass the `USER="admin"` and `PASS="password"` parameters. -* If you run the bare metal distribution, use the Command Line Interface (CLI) as follows: -+ -[source,bash] ----- -$ ./bin/cli.sh user create admin -p password + @ProtoField(number = 2) // <3> + public String message; +} ---- +<1> If your classes have only mutable fields, then the `ProtoFactory` annotation is not required +is not required, assuming your class has a no arg constructor +<2> `@ProtoField` annotation to add the name fieldto as string in the generated Protobuf schema +<3> `@ProtoField` annotation to add the message field to as string in the generated Protobuf schema -=== Infinispan Health Check -If you are using the quarkus-smallrye-health extension, the Infinispan client extensions will automatically add a readiness health check to validate the connection. - -When you access the `/q/health/ready` endpoint of your application you will have information about the server connection and available caches. - -This behavior can be disabled via the property `quarkus.infinispan-client.health.enabled`. - -=== Tracing with OpenTelemetry -Infinispan supports instrumentation of the server via OpenTelemetry. Having the `quarkus-opentelemetry` extension will propagate -the traces from the Infinispan Client to the Server. -This behavior can be disabled via the property `quarkus.infinispan-client.tracing.propagation.enabled` +Note that we are not going to use Java serialization. https://github.com/infinispan/protostream[Protostream] is +a serialization library based on Protobuf data format part of Infinispan. Using an annotation based API, we +will store our data in Protobuf format. -=== Creating caches from the client +== Creating the Greeting Schema +We are going to create our serialization schema using the `GreetingSchema` interface. +Create the `src/main/java/org/acme/infinispan/client/GreetingSchema.java` file, with the following content: -When a cache is accessed from the client, if the cache does not exist in the Infinispan Server and you want -to create it on first access, use one of the following properties: - -[source,properties] ----- -quarkus.infinispan-client.cache.books.configuration-uri=cacheConfig.json <1> -quarkus.infinispan-client.cache.magazine.configuration= <2> +[source, java] ---- -<1> The file name located under the `resources` folder that contains the configuration of the 'books' cache -<2> The configuration of the 'magazine' cache as a plain text property - -If both `configuration-uri` and `configuration` are configured for the same cache with the same Quarkus profile, -`configuration-uri` gets preference over `configuration`. - -[TIP] -==== -Cache configuration can be provided in XML, JSON or YAML. Use the Infinispan Console and the cache configuration Wizard -to learn more about Infinispan Caches and create guided configurations. -==== - -If nothing is configured for a particular cache, it will be created with the following basic configuration: +package org.acme.infinispan.client; -.XML -[source,xml,options="nowrap",subs=attributes+,role="primary"] ----- - - - ----- +import org.infinispan.protostream.GeneratedSchema; +import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -.JSON -[source,json,options="nowrap",subs=attributes+,role="secondary"] ----- -{ - "distributed-cache": { - "encoding": { - "media-type": "application/x-protostream" - } - } +@AutoProtoSchemaBuilder(includeClasses = Greeting.class) // <1> +public interface GreetingSchema extends GeneratedSchema { // <2> } ---- +<1> Includes the `Greeting` pojo with the `@AutoProtoSchemaBuilder` annotation +<2> Extends `GeneratedSchema` Protostream API interface -.YAML -[source,yaml,options="nowrap",subs=attributes+,role="secondary"] ----- -distributedCache: - encoding: - mediaType: "application/x-protostream" ----- - -=== Authentication mechanisms - -You can use the following authentication mechanisms with the Infinispan client: - -* DIGEST-MD5 -* PLAIN (recommended only in combination with TLS encryption) -* EXTERNAL - -Other authentication mechanisms, such as SCRAM and GSSAPI, are not yet verified with the Infinispan client. +The Protobuf Schema that will be generated and used both on client and Infinispan Server side, will have +the following content: -You can find more information on configuring authentication in https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#hotrod_endpoint_auth-hotrod-client-configuration[Hot Rod Endpoint Authentication Mechanisms]. - -NOTE: You must configure authentication in the `hotrod-client.properties` file if you use Dependency Injection. - -== Serialization (Key Value types support) - -By default, the client will support keys and values of the following types: byte[], -primitive wrappers (e.g. Integer, Long, Double), String, Date and Instant. User types require -some additional steps that are detailed here. Let's say we have the following user classes: - -.Author.java -[source,java] ----- -public class Author { - private final String name; - private final String surname; - - public Author(String name, String surname) { - this.name = Objects.requireNonNull(name); - this.surname = Objects.requireNonNull(surname); - } - // Getter/Setter/equals/hashCode/toString omitted -} +[source, protobuf] ---- +// File name: GreetingSchema.proto +// Generated from : org.acme.infinispan.client.GreetingSchema -.Book.java -[source,java] ----- -public class Book { - private final String title; - private final String description; - private final int publicationYear; - private final Set authors; - private final BigDecimal price; - - public Book(String title, String description, int publicationYear, Set authors, BigDecimal price) { - this.title = Objects.requireNonNull(title); - this.description = Objects.requireNonNull(description); - this.publicationYear = publicationYear; - this.authors = Objects.requireNonNull(authors); - this.price = price; - } - // Getter/Setter/equals/hashCode/toString omitted -} ----- +syntax = "proto2"; -Serialization of user types uses a library based on protobuf, -called https://github.com/infinispan/protostream[Protostream]. +message Greeting { -[TIP] -==== -Infinispan caches can store keys and values in different encodings, but recommend using https://developers.google.com/protocol-buffers[Protocol Buffers (Protobuf)]. + optional string name = 1; -For more information see our https://infinispan.org/docs/stable/titles/encoding/encoding.html[Cache Encoding and Marshalling] guide. -==== - - -=== Annotation based Serialization - -This can be done automatically by adding protostream annotations to your user classes. -In addition, a single Initializer annotated interface is required which controls how -the supporting classes are generated. - -Here is an example of how the preceding classes should be changed: - -.Author.java -[source,java] + optional string message = 2; +} ---- - @ProtoFactory - public Author(String name, String surname) { - this.name = Objects.requireNonNull(name); - this.surname = Objects.requireNonNull(surname); - } - @ProtoField(number = 1) - public String getName() { - return name; - } +== Creating the Infinispan Greeting Resource +Create the `src/main/java/org/acme/infinispan/client/InfinispanGreetingResource.java` file, with the following content: - @ProtoField(number = 2) - public String getSurname() { - return surname; - } +[source, java] ---- +package org.acme.infinispan.client; -.Book.java -[source,java] ----- - @ProtoFactory - public Book(String title, String description, int publicationYear, Set authors) { - this.title = Objects.requireNonNull(title); - this.description = Objects.requireNonNull(description); - this.publicationYear = publicationYear; - this.authors = Objects.requireNonNull(authors); - } +import io.quarkus.infinispan.client.Remote; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import org.infinispan.client.hotrod.RemoteCache; - @ProtoField(number = 1) - public String getTitle() { - return title; - } +import java.util.concurrent.CompletionStage; - @ProtoField(number = 2) - public String getDescription() { - return description; - } +@Path("/greeting") +public class InfinispanGreetingResource { - @ProtoField(number = 3, defaultValue = "-1") - public int getPublicationYear() { - return publicationYear; + @Inject + @Remote("mycache") // <1> + RemoteCache cache; //<2> + + @POST + @Path("/{id}") + public CompletionStage postGreeting(String id, Greeting greeting) { + return cache.putAsync(id, greeting) // <3> + .thenApply(g -> "Greeting done!") + .exceptionally(ex -> ex.getMessage()); } - @ProtoField(number = 4) - public Set getAuthors() { - return authors; + @GET + @Path("/{id}") + public CompletionStage getGreeting(String id) { + return cache.getAsync(id); // <4> } +} ---- +<1> Use the `@Remote` annotation to use a cache. If the cache does not exist, will be created with a +default configuration *on first access*. +<2> Inject the `RemoteCache` +<3> Put the greeting id as a key and the Greeting pojo as a value +<4> Get the greeting by id as the key -If your classes have only mutable fields, then the `ProtoFactory` annotation -is not required, assuming your class has a no arg constructor. +== Creating the test class -Then all that is required is a very simple `GeneratedSchema` interface with an annotation -on it to specify configuration settings +Edit the `pom.xml` file to add the following dependency: -.BooksSchema.java -[source,java] +[source, xml] ---- -import org.infinispan.protostream.GeneratedSchema; -import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; -import org.infinispan.protostream.types.java.math.BigDecimalAdapter; - -@AutoProtoSchemaBuilder(includeClasses = { Book.class, Author.class, BigDecimalAdapter.class }, schemaPackageName = "book_sample") -interface BookStoreSchema extends GeneratedSchema { -} + + io.rest-assured + rest-assured + test + ---- -[TIP] -==== -Protostream provides default Protobuf mappers for commonly used types as `BigDecimal`, included in the `org.infinispan.protostream.types` package. -==== - -So in this case we will automatically generate the marshaller and schemas for the included classes and -place them in the schema package automatically. The package does not have to be provided, but if you use Infinispan search capabilities, you must know the generated package. - -NOTE: In Quarkus the `schemaFileName` and `schemaFilePath` attributes should NOT be set on the `AutoProtoSchemaBuilder` annotation. Setting either attributes causes native runtime errors. +Create the `src/test/java/org/acme/infinispan/client/InfinispanGreetingResourceTest.java` file with the following content: -=== Custom serialization -The previous method is suggested for any case when the user can annotate their classes. -Unfortunately the user may not be able to annotate all classes they will put in the -cache. In this case you must define your schema and create your own Marshaller(s) -yourself. - -Protobuf schema:: You can supply a protobuf schema through either one of two ways. -. Proto File - + -You can put the `.proto` file in the `META-INF` directory of the project. These files will -automatically be picked up at initialization time. -+ -.library.proto +[source, java] ---- -package book_sample; - -message Book { - required string title = 1; - required string description = 2; - required int32 publicationYear = 3; // no native Date type available in Protobuf - repeated Author authors = 4; - requited double price = 5; // no native BigDecimal type available in Protobuf -} +package org.acme.infinispan.client; -message Author { - required string name = 1; - required string surname = 2; -} ----- -. In Code - + -Or you can define the proto schema directly in user code by defining a produced bean of type -`org.infinispan.protostream.FileDescriptorSource`. -+ -[source,java] ----- - @Produces - FileDescriptorSource bookProtoDefinition() { - return FileDescriptorSource.fromString("library.proto", "package book_sample;\n" + - "\n" + - "message Book {\n" + - " required string title = 1;\n" + - " required string description = 2;\n" + - " required int32 publicationYear = 3; // no native Date type available in Protobuf\n" + - "\n" + - " repeated Author authors = 4;\n" + - "\n" + - " required double price = 5; // no native BigDecimal type available in Protobuf\n" + - "}\n" + - "\n" + - "message Author {\n" + - " required string name = 1;\n" + - " required string surname = 2;\n" + - "}"); - } ----- -User Marshaller:: -The last thing to do is to provide a `org.infinispan.protostream.MessageMarshaller` implementation -for each user class defined in the proto schema. This class is then provided via `@Produces` in a similar -fashion to the code based proto schema definition above. -+ -Here is the Marshaller class for our Author & Book classes. -+ -NOTE: The type name must match the `.` exactly! -+ -.AuthorMarshaller.java -[source,java] ----- -public class AuthorMarshaller implements MessageMarshaller { - - @Override - public String getTypeName() { - return "book_sample.Author"; - } - - @Override - public Class getJavaClass() { - return Author.class; - } - - @Override - public void writeTo(ProtoStreamWriter writer, Author author) throws IOException { - writer.writeString("name", author.getName()); - writer.writeString("surname", author.getSurname()); - } - - @Override - public Author readFrom(ProtoStreamReader reader) throws IOException { - String name = reader.readString("name"); - String surname = reader.readString("surname"); - return new Author(name, surname); - } -} ----- -+ -.BookMarshaller.java -[source,java] ----- -public class BookMarshaller implements MessageMarshaller { - - @Override - public String getTypeName() { - return "book_sample.Book"; - } - - @Override - public Class getJavaClass() { - return Book.class; - } - - @Override - public void writeTo(ProtoStreamWriter writer, Book book) throws IOException { - writer.writeString("title", book.getTitle()); - writer.writeString("description", book.getDescription()); - writer.writeInt("publicationYear", book.getPublicationYear()); - writer.writeCollection("authors", book.getAuthors(), Author.class); - writer.writeDouble("price", book.getPrice().doubleValue()); - } - - @Override - public Book readFrom(ProtoStreamReader reader) throws IOException { - String title = reader.readString("title"); - String description = reader.readString("description"); - int publicationYear = reader.readInt("publicationYear"); - Set authors = reader.readCollection("authors", new HashSet<>(), Author.class); - BigDecimal price = BigDecimal.valueOf(reader.readDouble("price")); - return new Book(title, description, publicationYear, authors, price); - } -} ----- -+ -And you pass the marshaller by defining the following: -+ -[source,java] ----- - @Produces - MessageMarshaller authorMarshaller() { - return new AuthorMarshaller(); - } - - @Produces - MessageMarshaller bookMarshaller() { - return new BookMarshaller(); - } ----- -NOTE: The above produced Marshaller method MUST return `MessageMarshaller` without types or else it will not be found. +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.Test; -== Dependency Injection +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; -As you saw above we support the user injecting Marshaller configuration. You can do the inverse with -the Infinispan client extension providing injection for `RemoteCacheManager` and `RemoteCache` objects. -There is one global `RemoteCacheManager` that takes all the configuration -parameters setup in the above sections. +@QuarkusTest +class InfinispanGreetingResourceTest { -It is very simple to inject these components. All you need to do is to add the `@Inject` annotation to -the field, constructor or method. In the below code we utilize field and constructor injection. + @Test + public void testHelloEndpoint() { + given() + .contentType(ContentType.JSON) + .body("{\"name\":\"Infinispan Client\",\"message\":\"Hello World, Infinispan is up!\"}") + .when() + .post("/greeting/quarkus") + .then() + .statusCode(200); -.SomeClass.java -[source,java] ----- - @Inject SomeClass(RemoteCacheManager remoteCacheManager) { - this.remoteCacheManager = remoteCacheManager; + given() + .when().get("/greeting/quarkus") + .then() + .statusCode(200) + .body(is("{\"name\":\"Infinispan Client\",\"message\":\"Hello World, Infinispan is up!\"}")); } - - @Inject - @Remote("myCache") - RemoteCache cache; - - RemoteCacheManager remoteCacheManager; +} ---- -If you notice the `RemoteCache` declaration has an additional optional annotation named `Remote`. -This is a qualifier annotation allowing you to specify which named cache that will be injected. This -annotation is not required and if it is not supplied, the default cache will be injected. +== Get it running -NOTE: Other types may be supported for injection, please see other sections for more information +We just need to run the application using: -=== Registering Protobuf Schemas with Infinispan Server -You need to register the generated Protobuf schemas with Infinispan Server to perform queries or convert from -`Protobuf` to other media types such as `JSON`. +include::{includes}/devtools/dev.adoc[] -[TIP] -==== -You can check the schemas that exist under the `Schemas` tab by logging into -Infinispan Console at `http://SERVER_HOST:SERVER_PORT` (for example `http://localhost:11222`). +We should have the Infinispan server running thanks to the Dev Services. +We can access the Dev Services UI through `http://localhost:8080/q/dev/`. +The Dev UI should display the Infinispan UI Panel. -Check the xref:infinispan-dev-services.adoc[Infinispan Dev Services Guide] to connect to the Infinispan -Dev Services server. -==== +image::dev-ui-infinispan.png[alt=Dev UI Infinispan,align=center,width=40%] -By default, Protobuf schemas generated this way will be registered by this extension when the client first connects. -However, it might be required to handle the registration manually as a schema may evolve over time when used in -production, so you can disable this from occurring by configuring the -`quarkus.infinispan-client.use-schema-registration` to `false`. - -To configure the schema manually -please use https://infinispan.org/docs/infinispan-operator/main/operator.html[Infinispan Operator] -for Kubernetes deployments, Infinispan Console, -https://infinispan.org/docs/stable/titles/rest/rest.html#rest_v2_protobuf_schemas[REST API] or the -https://infinispan.org/docs/stable/titles/encoding/encoding.html#registering-sci-remote-caches_marshalling[Hot Rod Java Client]. - -[#infinispan-annotations-api] -== Caching using annotations - -The Infinispan Client extension offers a set of annotations that can be used in a CDI managed bean to enable caching abilities with Infinispan. - -[WARNING] +[TIP] ==== -Caching annotations are not allowed on private methods. -They will work fine with any other access modifier including package-private (no explicit modifier). +Click on the Web Console link and log using `admin` and `password` default credentials. +Quarkus has uploaded into the Schemas Tab the Protobuf Schema that is needed to marshall on the server the +Greeting POJO with Protobuf. +Check the xref:infinispan-dev-services.adoc[Infinispan Dev Services Guide] to learn more. ==== -=== @CacheResult - -Loads a method result from the cache without executing the method body whenever possible. - -When a method annotated with `@CacheResult` is invoked, Quarkus will use the method argument as the cache key and check in the cache whether the method has been already invoked. -Methods with multiple parameters are not allowed. For composite keys, define a Protobuf schema that will hold multiple values. -If a value is found in the cache, it is returned and the annotated method is never actually executed. -If no value is found, the annotated method is invoked and the returned value is stored in the cache using the computed key. -This annotation cannot be used on a method returning `void`. - -NOTE: Infinispan Client extension is not able yet to cache `null` values unlike the Quarkus-Cache extension. - -=== @CacheInvalidate - -Removes an entry from the cache. - -When a method annotated with `@CacheInvalidate` is invoked, Infinispan will use the method argument as a cache key to try to remove an existing entry from the cache. -If the key does not identify any cache entry, nothing will happen. - -=== @CacheInvalidateAll - -When a method annotated with `@CacheInvalidateAll` is invoked, Infinispan will remove all entries from the cache. - - -== Querying +== Interacting with the Greeting Service +As we have seen above, the Greeting API exposes two Rest endpoints. +In this section we are going to see how to create and display a greeting message. -The Infinispan client supports both indexed and non-indexed search as long as the -`ProtoStreamMarshaller` is configured above. This allows the user to query based on the -properties of the proto schema. *Indexed queries are preferred for performance reasons*. +=== Creating a Greeting Message +With the following command, we will create a greeting message with the id `quarkus`. -.XML -[source,xml,options="nowrap",subs=attributes+,role="primary"] +[source,bash] ---- - - - - - book_sample.Book - - - +curl -X POST http://localhost:8080/greeting/quarkus -H "Content-Type: application/json" -d '{"name" : "Infinispan Client", "message":"Hello World, Infinispan is up!"}' ---- -.JSON -[source,json,options="nowrap",subs=attributes+,role="secondary"] ----- -{ - "books": { - "distributed-cache": { - ... - "indexing": { - "enabled": true, - "storage": "filesystem", - "startupMode": "PURGE", - "indexed-entities": [ - "book_sample.Book" - ] - } - } - } -} ----- +The service should respond with a `Greeting added!` message. -.YAML -[source,yaml,options="nowrap",subs=attributes+,role="secondary"] +=== Displaying a Greeting Message +With the following command, we will display the greeting message with the id `quarkus`. +[source,bash] ---- -distributedCache: - # other configuration - indexing: - enabled: "true" - storage: "filesystem" - startupMode: "PURGE" - indexedEntities: - - "book_sample.Book" +curl http://localhost:8080/greeting/quarkus ---- -Query builds upon the proto definitions you can configure when setting up the `ProtoStreamMarshaller`. -Either method of Serialization above will automatically register the schema with the server at -startup, meaning that you will automatically gain the ability to query objects stored in the -remote Infinispan Server. +The service should respond with the following json content. -.Book.java -[source,java] +[source, json] ---- -@Indexed <1> -public class Book { - - @ProtoFactory - public Book(String title, String description, int publicationYear, Set authors) { - ... - } - - @ProtoField(number = 1) - @Text <2> - public String getTitle() { - return title; - } - - @ProtoField(number = 2) - @Keyword(projectable = true, sortable = true, normalizer = "lowercase", indexNullAs = "unnamed", norms = false) <3> - public String getDescription() { - return description; - } - ... +{ + "name" : "Infinispan Client", + "message" : "Hello World, Infinispan is up!" +} ---- -<1> `@Indexed` annotation makes the POJO indexable -<2> `@Basic` annotation is used for indexed fields without any special transformation -<3> `@Keyword` annotation is used to apply a normalizer to a text field -You can use either the Query DSL or the Ickle Query language with the Quarkus Infinispan client -extension. +=== Display the cache and content with the Infinispan Server Console -NOTE: You can read more about https://infinispan.org/docs/stable/titles/query/query.html[querying] in the Infinispan documentation. +If a requested cache does not exist, Quarkus creates a cache with a Default configuration on first access. +We should be able to reaload the Infinispan Server Console and display the content of the Cache. +The Infinispan Server Console uses the https://infinispan.org/docs/stable/titles/rest/rest.html[Infinispan Server REST API]. +The content can be displayed in JSON thanks to the Protobuf Encoding that converts to JSON format. +image::infinispan-console-client-guide.png[alt=Infinispan Console,align=center,width=90%] -== Counters -Infinispan also has a notion of counters and the Quarkus Infinispan client supports them out of -the box. +== Configuring for production -The Quarkus Infinispan client extension allows for Dependency Injection -of the `CounterManager` directly. All you need to do is annotate your field, constructor or method, -and you get it with no fuss. You can then use counters as you would normally. +At this point, Quarkus uses the Infinispan Dev Service to run an Infinispan server and configure the application. +However, in production, you will run your own Infinispan (or Red Hat Data Grid). -[source,java] ----- -@Inject -CounterManager counterManager; ----- +Let's start an Infinispan server on the port 11222 using: -You can read more about https://infinispan.org/docs/stable/titles/developing/developing.html#clustered_counters[clustered counters] in the Infinispan documentation. - -== Near Caching - -Near caching is disabled by default, but you can enable it on a per cache basic by configuring the following properties: - -[source,properties] +[source, shell] ---- -quarkus.infinispan-client.cache.books.near-cache-mode=INVALIDATED <1> -quarkus.infinispan-client.cache.books.near-cache-max-entries=200 <2> -quarkus.infinispan-client.cache.books.near-cache-use-bloom-filter=true <3> +docker run -it -p 11222:11222 -e USER="admin" -e PASS="password" quay.io/infinispan/server:latest ---- -<1> Enables near caching for the 'books' cache by setting the mode to `INVALIDATED` -<2> Sets the maximum number of entries that the near cache of the 'books' cache can hold before eviction occurs -<3> Enables bloom filter for the 'books' cache - -=== Bounded near caching - -You should always use bounded near caches by specifying the maximum number of entries they can contain. +Then, open the `src/main/resources/application.properties` file and add: -=== Bloom filters - -If you need to optimize the performance for write operations by reducing the total number of invalidation messages, -enable bloom filter. Bloom filters reside on Infinispan Server and keep track of the entries that the client has requested. -They cannot be used with unbounded near cache: maximum number of entries must be defined when enabling bloom filters. - -== Encryption - -Encryption at this point requires additional steps to get working. - -The first step is to configure the `hotrod-client.properties` file to point to your truststore -and/or keystore. This is further detailed https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#hotrod_encryption[here]. - -The Infinispan Client extension enables SSL/TLS by default. You can read more about this -at xref:native-and-ssl.adoc[Using SSL With Native Executables]. - -== Additional Features - -The Infinispan Client has additional features that were not mentioned here. This means this -feature was not tested in a Quarkus environment, and they may or may not work. Please let us -know if you need these added! - -[[dev-services]] -== Dev Services for Infinispan - -When you use the infinispan-client extension in dev mode or in test, Quarkus automatically starts an Infinispan server and configure your application. +[source, properties] +---- +%prod.quarkus.infinispan-client.hosts=localhost:11222 # <1> +%prod.quarkus.infinispan-client.username=admin <2> +%prod.quarkus.infinispan-client.password=password <3> -=== Enabling / Disabling Dev Services for Infinispan +## Docker 4 Mac workaround +%prod.quarkus.infinispan-client.client-intelligence=BASIC <4> +---- +<1> Sets Infinispan Server address list, separated with commas +<2> Sets the authentication username +<3> Sets the authentication password +<4> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac. -NOTE: Learn more in the xref:infinispan-dev-services.adoc[Infinispan Dev Services guide]. +== Packaging and running in JVM mode -== Shared server +You can run the application as a conventional jar file. -Quarkus will share the Infinispan broker if you have multiple applications running in dev mode. -Dev Services for Infinispan implements a _service discovery_ mechanism for your multiple Quarkus applications running in _dev_ mode to share a single broker. +First, we will need to package it: -NOTE: Dev Services for Infinispan starts the container with the `quarkus-dev-service-infinispan` label which is used to identify the container. +include::{includes}/devtools/build.adoc[] -If you need multiple (shared) Infinispan server, you can configure the `quarkus.infinispan-client.devservices.service-name` attribute and indicate the server name. -It looks for a container with the same value, or starts a new one if none can be found. -The default service name is `infinispan`. +NOTE: This command will start an Infinispan instance to execute the tests. -Sharing is enabled by default in dev mode, but disabled in test mode. -You can disable the sharing with `quarkus.infinispan-client.devservices.shared=false`. +Then run it: -== Setting the port +[source,bash] +---- +java -jar target/quarkus-app/quarkus-run.jar +---- -By default, Dev Services for Infinispan picks a random port and configures the application. -You can set the port by configuring the `quarkus.infinispan-client.devservices.port` property. +== Running Native -== Testing helpers +You can also create a native executable from this application without making any +source code changes. A native executable removes the dependency on the JVM: +everything needed to run the application on the target platform is included in +the executable, allowing the application to run with minimal resource overhead. -To start an Infinispan Server for your unit tests, Quarkus provides one `QuarkusTestResourceLifecycleManager` that relies on link:https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#junit-testing[Infinispan Server Test Container]. +Compiling a native executable takes a bit longer, as GraalVM performs additional +steps to remove unnecessary codepaths. Use the `native` profile to compile a +native executable: -- `io.quarkus.test.infinispan.client.InfinispanTestResource` will start a single instance on port 11222 with user 'admin' and password 'password'. +include::{includes}/devtools/build-native.adoc[] -To use them, you need to add the `io.quarkus:quarkus-test-infinispan-client` dependency to your pom.xml. +Once the build is finished, you can run the executable with: -For more information about the usage of a `QuarkusTestResourceLifecycleManager` please read xref:getting-started-testing.adoc#quarkus-test-resource[Quarkus test resource]. +[source,bash] +---- +./target/infinispan-client-quickstart-1.0.0-SNAPSHOT-runner +---- -== Configuration Reference +== Going further -include::{generated-dir}/config/quarkus-infinispan-client.adoc[opts=optional, leveloffset=+1] +To learn more about the Quarkus Infinispan extension, check xref:infinispan-client-reference.adoc[the Infinispan Client extension reference guide].