Quarkus - Infinispan Client

Infinispan is an in memory data grid that allows running in a server outside of application processes. This extensionprovides functionality to allow the client that can connect to said server when running in Quarkus.

More information can be found about Infinispan at https://infinispan.org and the client/server athttps://infinispan.org/docs/dev/user_guide/user_guide.html#client_server

Configuration

Once you have your Quarkus project configured you can add the infinispan-client extensionto your project by running the following from the command line in your project base directory.

/mvnw quarkus:add-extension -Dextensions="infinispan-client"

  1. This will add the following to your pom.xml
  1. <dependency>
  2. <groupId>io.quarkus</groupId>
  3. <artifactId>quarkus-infinispan-client</artifactId>
  4. <scope>provided</scope>
  5. </dependency>

The Infinispan client is configurable in the application.properties file that can beprovided in the src/main/resources directory. These are the properties thatcan be configured in this file:

Configuration property fixed at build time - ️ Configuration property overridable at runtime

Configuration propertyTypeDefault
quarkus.infinispan-client.near-cache-max-entriesSets the bounded entry count for near cache. If this value is 0 or less near cache is disabled.int0
quarkus.infinispan-client.server-listSets the host name/port to connect to. Each one is separated by a semicolon (eg. host1:11222;host2:11222).string
quarkus.infinispan-client.client-intelligenceSets client intelligence used by authenticationstring
quarkus.infinispan-client.use-authEnables or disables authenticationstring
quarkus.infinispan-client.auth-usernameSets user name used by authenticationstring
quarkus.infinispan-client.auth-passwordSets password used by authenticationstring
quarkus.infinispan-client.auth-realmSets realm used by authenticationstring
quarkus.infinispan-client.auth-server-nameSets server name used by authenticationstring
quarkus.infinispan-client.auth-client-subjectSets client subject used by authenticationstring
quarkus.infinispan-client.auth-callback-handlerSets callback handler used by authenticationstring
quarkus.infinispan-client.sasl-mechanismSets SASL mechanism used by authenticationstring

It is also possible to configure a hotrod-client.properties as described in the Infinispan user guide. Note thatthe hotrod-client.properties values overwrite any matching property from the other configuration values (eg. near cache).This properties file is build time only and if it is changed, requires a full rebuild.

Serialization (Key Value types support)

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. User types requiresome additional steps that are detailed here. Let’s say we have the following user classes:

Author.java

  1. public class Author {
  2. private final String name;
  3. private final String surname;
  4. public Author(String name, String surname) {
  5. this.name = Objects.requireNonNull(name);
  6. this.surname = Objects.requireNonNull(surname);
  7. }
  8. // Getter/Setter/equals/hashCode/toString omitted
  9. }

Book.java

  1. public class Book {
  2. private final String title;
  3. private final String description;
  4. private final int publicationYear;
  5. private final Set<Author> authors;
  6. public Book(String title, String description, int publicationYear, Set<Author> authors) {
  7. this.title = Objects.requireNonNull(title);
  8. this.description = Objects.requireNonNull(description);
  9. this.publicationYear = publicationYear;
  10. this.authors = Objects.requireNonNull(authors);
  11. }
  12. // Getter/Setter/equals/hashCode/toString omitted
  13. }

Serialization of user types uses a library based on protobuf, called Protostream.

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 howthe supporting classes are generated.

Here is an example of how the preceding classes should be changed:

Author.java

  1. @ProtoFactory
  2. public Author(String name, String surname) {
  3. this.name = Objects.requireNonNull(name);
  4. this.surname = Objects.requireNonNull(surname);
  5. }
  6. @ProtoField(number = 1)
  7. public String getName() {
  8. return name;
  9. }
  10. @ProtoField(number = 2)
  11. public String getSurname() {
  12. return surname;
  13. }

Book.java

  1. @ProtoFactory
  2. public Book(String title, String description, int publicationYear, Set<Author> authors) {
  3. this.title = Objects.requireNonNull(title);
  4. this.description = Objects.requireNonNull(description);
  5. this.publicationYear = publicationYear;
  6. this.authors = Objects.requireNonNull(authors);
  7. }
  8. @ProtoField(number = 1)
  9. public String getTitle() {
  10. return title;
  11. }
  12. @ProtoField(number = 2)
  13. public String getDescription() {
  14. return description;
  15. }
  16. @ProtoField(number = 3, defaultValue = "-1")
  17. public int getPublicationYear() {
  18. return publicationYear;
  19. }
  20. @ProtoField(number = 4)
  21. public Set<Author> getAuthors() {
  22. return authors;
  23. }

If your classes have only mutable fields, then the ProtoFactory annotationis not required, assuming your class has a no arg constructor.

Then all that is required is a very simple SerializationContextInitializer interface with an annotationon it to specify configuration settings

BookContextInitializer.java

  1. @AutoProtoSchemaBuilder(includeClasses = { Book.class, Author.class }, schemaPackageName = "book_sample")
  2. interface BookContextInitializer extends SerializationContextInitializer {
  3. }

So in this case we will automatically generate the marshaller and schemas for the included classes andplace them in the schema package automatically. The package does not have to be provided, but if youutilize querying, you must know the generated package.

In Quarkus the schemaFileName and schemaFilePath attributes should NOT be set on the AutoProtoSchemaBuilder annotation, setting either will cause native runtime to error.

User written 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 thecache. 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 willautomatically be picked up at initialization time.

library.proto

  1. package book_sample;
  2.  
  3. message Book {
  4. required string title = 1;
  5. required string description = 2;
  6. required int32 publicationYear = 3; // no native Date type available in Protobuf
  7.  
  8. repeated Author authors = 4;
  9. }
  10.  
  11. message Author {
  12. required string name = 1;
  13. required string surname = 2;
  14. }
  • In Code Or you can define the proto schema directly in user code by defining a produced bean of typeorg.infinispan.protostream.FileDescriptorSource.
  1. @Produces
  2. FileDescriptorSource bookProtoDefinition() {
  3. return FileDescriptorSource.fromString("library.proto", "package book_sample;\n" +
  4. "\n" +
  5. "message Book {\n" +
  6. " required string title = 1;\n" +
  7. " required string description = 2;\n" +
  8. " required int32 publicationYear = 3; // no native Date type available in Protobuf\n" +
  9. "\n" +
  10. " repeated Author authors = 4;\n" +
  11. "}\n" +
  12. "\n" +
  13. "message Author {\n" +
  14. " required string name = 1;\n" +
  15. " required string surname = 2;\n" +
  16. "}");
  17. }
  • User Marshaller
  • The last thing to do is to provide a org.infinispan.protostream.MessageMarshaller implementationfor each user class defined in the proto schema. This class is then provided via @Produces in a similarfashion to the code based proto schema definition above.

Here is the Marshaller class for our Author & Book classes.

The type name must match the <protobuf package>.<protobuf message> exactly!

AuthorMarshaller.java

  1. public class AuthorMarshaller implements MessageMarshaller<Author> {
  2. @Override
  3. public String getTypeName() {
  4. return "book_sample.Author";
  5. }
  6. @Override
  7. public Class<? extends Author> getJavaClass() {
  8. return Author.class;
  9. }
  10. @Override
  11. public void writeTo(ProtoStreamWriter writer, Author author) throws IOException {
  12. writer.writeString("name", author.getName());
  13. writer.writeString("surname", author.getSurname());
  14. }
  15. @Override
  16. public Author readFrom(ProtoStreamReader reader) throws IOException {
  17. String name = reader.readString("name");
  18. String surname = reader.readString("surname");
  19. return new Author(name, surname);
  20. }
  21. }

BookMarshaller.java

  1. public class BookMarshaller implements MessageMarshaller<Book> {
  2. @Override
  3. public String getTypeName() {
  4. return "book_sample.Book";
  5. }
  6. @Override
  7. public Class<? extends Book> getJavaClass() {
  8. return Book.class;
  9. }
  10. @Override
  11. public void writeTo(ProtoStreamWriter writer, Book book) throws IOException {
  12. writer.writeString("title", book.getTitle());
  13. writer.writeString("description", book.getDescription());
  14. writer.writeInt("publicationYear", book.getPublicationYear());
  15. writer.writeCollection("authors", book.getAuthors(), Author.class);
  16. }
  17. @Override
  18. public Book readFrom(ProtoStreamReader reader) throws IOException {
  19. String title = reader.readString("title");
  20. String description = reader.readString("description");
  21. int publicationYear = reader.readInt("publicationYear");
  22. Set<Author> authors = reader.readCollection("authors", new HashSet<>(), Author.class);
  23. return new Book(title, description, publicationYear, authors);
  24. }
  25. }

And you pass the marshaller by defining the following:

  1. @Produces
  2. MessageMarshaller authorMarshaller() {
  3. return new AuthorMarshaller();
  4. }
  5. @Produces
  6. MessageMarshaller bookMarshaller() {
  7. return new BookMarshaller();
  8. }

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 withthe Infinispan client extension providing injection for RemoteCacheManager and RemoteCache objects.There is one global RemoteCacheManager that takes all of the configurationparameters setup in the above sections.

It is very simple to inject these components. All you need to do is to add the Inject annotation tothe field, constructor or method. In the below code we utilize field and constructor injection.

SomeClass.java

  1. @Inject SomeClass(RemoteCacheManager remoteCacheManager) {
  2. this.remoteCacheManager = remoteCacheManager;
  3. }
  4. @Inject @Remote("myCache")
  5. RemoteCache<String, Book> cache;
  6. 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. Thisannotation is not required and if it is not supplied, the default cache will be injected.

Other types may be supported for injection, please see other sections for more information

Querying

The Infinispan client supports both indexed and non indexed querying as long as theProtoStreamMarshaller is configured above. This allows the user to query based on theproperties of the proto schema.

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 atstartup, meaning that you will automatically gain the ability to query objects stored in theremote Infinispan Server.

You can read more about this at https://infinispan.org/docs/dev/user_guide/user_guide.html#query_dsl.

You can use either the Query DSL or the Ickle Query language with the Quarkus Infinispan clientextension.

Counters

Infinispan also has a notion of counters and the Quarkus Infinispan client supports them out ofthe box.

The Quarkus Infinispan client extension allows for Dependency Injectionof the CounterManager directly. All you need to do is annotate your field, constructor or methodand you get it with no fuss. You can then use counters as you would normally.

  1. @Inject
  2. CounterManager counterManager;

Near Caching

Near caching is disabled by default, but you can enable it by setting the profile config propertyquarkus.infinispan-client.near-cache-max-entries to a value greater than 0. You can also configurea regular expression so that only a subset of caches have near caching applied through thequarkus.infinispan-client.near-cache-name-pattern attribute.

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 truststoreand/or keystore. This is further detailed athttps://infinispan.org/docs/dev/user_guide/user_guide.html#hr_encryption.

The Infinispan Client extension enables SSL by default. You can read more about thisat Using SSL With Native Executables.

Authentication

This chart illustrates what mechanisms have been verified to be working properly withthe Quarkus Infinispan Client extension.

Table 1. Mechanisms
NameVerified
DIGEST-MD5Y
PLAINY
EXTERNALY
GSSAPIN
CustomN

The guide for configuring these can be found at https://infinispan.org/docs/dev/user_guide/user_guide.html#authentication.However you need to configure these through the hotrod-client.properties file if using Dependency Injection.

Additional Features

The Infinispan Client has additional features that were not mentioned here. This means thisfeature was not tested in a Quarkus environment and they may or may not work. Please let usknow if you need these added!