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 Infinispan documentation.
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].
Run the following command in the base directory of your Quarkus project to add the infinispan-client
extension:
./mvnw quarkus:add-extension -Dextensions="infinispan-client"
This command adds the following dependency to your pom.xml
:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client</artifactId>
</dependency>
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.
Add the following properties to connect to Infinispan Server:
# Infinispan Server address
quarkus.infinispan-client.server-list=localhost:11222
# Authentication
quarkus.infinispan-client.auth-username=admin
quarkus.infinispan-client.auth-password=password
# Infinispan client intelligence
# Use BASIC as a Docker for Mac workaround
quarkus.infinispan-client.client-intelligence=BASIC
To use the Infinispan client extension, you need at least one running instance of Infinispan Server.
Check out our 5 minute 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"
andPASS="password"
parameters. -
If you run the bare metal distribution, use the Command Line Interface (CLI) as follows:
$ ./bin/cli.sh user create admin -p password
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 Hot Rod Endpoint Authentication Mechanisms.
Note
|
You must configure authentication in the |
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 require some additional steps that are detailed here. Let’s say we have the following user classes:
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
}
public class Book {
private final String title;
private final String description;
private final int publicationYear;
private final Set<Author> authors;
private final BigDecimal price;
public Book(String title, String description, int publicationYear, Set<Author> 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 Protostream.
Tip
|
Infinispan caches can store keys and values in different encodings, but recommend using Protocol Buffers (Protobuf). For more information see our Cache Encoding and Marshalling guide. |
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:
@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;
}
@ProtoFactory
public Book(String title, String description, int publicationYear, Set<Author> 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<Author> 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
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 query 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.
|
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 theMETA-INF
directory of the project. These files will automatically be picked up at initialization time.library.protopackage 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 typeorg.infinispan.protostream.FileDescriptorSource
.@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.
NoteThe type name must match the <protobuf package>.<protobuf message>
exactly!AuthorMarshaller.javapublic class AuthorMarshaller implements MessageMarshaller<Author> { @Override public String getTypeName() { return "book_sample.Author"; } @Override public Class<? extends Author> 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.javapublic class BookMarshaller implements MessageMarshaller<Book> { @Override public String getTypeName() { return "book_sample.Book"; } @Override public Class<? extends Book> 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<Author> 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:
@Produces MessageMarshaller authorMarshaller() { return new AuthorMarshaller(); } @Produces MessageMarshaller bookMarshaller() { return new BookMarshaller(); }
NoteThe above produced Marshaller method MUST return MessageMarshaller
without types or else it will not be found.
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 of 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.
@Inject SomeClass(RemoteCacheManager remoteCacheManager) {
this.remoteCacheManager = remoteCacheManager;
}
@Inject @Remote("myCache")
RemoteCache<String, Book> 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 |
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://localhost:11222
|
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 Infinispan Operator for Kubernetes deployments, Infinispan Console, REST API or the Hot Rod Java Client.
The Infinispan client supports both indexed and non-indexed querying as long as the
ProtoStreamMarshaller
is configured above. This allows the user to query based on the
properties 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 at
startup, meaning that you will automatically gain the ability to query objects stored in the
remote Infinispan Server.
You can read more about querying in the Infinispan documentation.
You can use either the Query DSL or the Ickle Query language with the Quarkus Infinispan client extension.
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.
@Inject
CounterManager counterManager;
You can read more about clustered counters in the Infinispan documentation.
Near caching is disabled by default, but you can enable it by setting the profile config property
quarkus.infinispan-client.near-cache-max-entries
to a value greater than 0. You can also configure
a regular expression so that only a subset of caches have near caching applied through the
quarkus.infinispan-client.near-cache-name-pattern
attribute.
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 here.
The Infinispan Client extension enables SSL/TLS by default. You can read more about this at Using SSL With Native Executables.
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!
When you use the infinispan-client extension in dev mode or in test, Quarkus automatically starts an Infinispan server and configure your application.
Dev Services for Infinispan is automatically enabled unless:
-
quarkus.infinispan-client.devservices.enabled
is set tofalse
-
the
quarkus.infinispan-client.server-list
is configured
Dev Services for Infinispan relies on Docker to start the broker.
If your environment does not support Docker, you will need to start the broker manually, or connect to an already running broker.
You can configure the broker address using quarkus.infinispan-client.server-list
.
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
.
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.
To start a Infinispan Server for your unit tests, Quarkus provides one QuarkusTestResourceLifecycleManager
that relies on 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 Quarkus test resource.