You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2022/03/25 07:07:53 UTC

[camel-quarkus] branch main updated: Improve Infinispan extension test coverage

This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git


The following commit(s) were added to refs/heads/main by this push:
     new 62c14ec  Improve Infinispan extension test coverage
62c14ec is described below

commit 62c14ec4760a327b606b876f5780dc518692c5d1
Author: James Netherton <ja...@gmail.com>
AuthorDate: Thu Mar 24 15:20:44 2022 +0000

    Improve Infinispan extension test coverage
    
    Fixes #3658
---
 .../pages/reference/extensions/infinispan.adoc     |  13 +
 .../runtime/src/main/doc/configuration.adoc        |   2 +
 .../runtime/src/main/doc/limitations.adoc          |   3 +
 integration-tests/infinispan/pom.xml               |  33 ++
 .../component/infinispan/InfinispanResources.java  | 303 ++++++++++++-
 .../component/infinispan/InfinispanRoutes.java     | 275 +++++++++++-
 .../quarkus/component/infinispan/model/Person.java |  44 ++
 .../component/infinispan/model/PersonSchema.java   |  24 ++
 .../infinispan/InfinispanServerTestResource.java   |   4 +-
 .../component/infinispan/InfinispanTest.java       | 479 ++++++++++++++++++++-
 .../infinispan/src/test/resources/infinispan.xml   |  82 ++++
 pom.xml                                            |   1 +
 poms/bom/pom.xml                                   |   9 +
 13 files changed, 1241 insertions(+), 31 deletions(-)

diff --git a/docs/modules/ROOT/pages/reference/extensions/infinispan.adoc b/docs/modules/ROOT/pages/reference/extensions/infinispan.adoc
index fec768d..951cf43 100644
--- a/docs/modules/ROOT/pages/reference/extensions/infinispan.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/infinispan.adoc
@@ -38,3 +38,16 @@ Or add the coordinates to your existing project:
 ----
 
 Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
+
+== Camel Quarkus limitations
+
+=== InfinispanRemoteAggregationRepository in native mode
+
+At present the `InfinispanRemoteAggregationRepository` is not supported in native mode.
+
+
+== Additional Camel Quarkus configuration
+
+You can either configure the Infinispan client via the relevant Camel Infinispan component & endpoint options, or you
+may use the https://quarkus.io/guides/infinispan-client#configuration-reference[Quarkus Infinispan extension configuration properties].
+
diff --git a/extensions/infinispan/runtime/src/main/doc/configuration.adoc b/extensions/infinispan/runtime/src/main/doc/configuration.adoc
new file mode 100644
index 0000000..000b2d0
--- /dev/null
+++ b/extensions/infinispan/runtime/src/main/doc/configuration.adoc
@@ -0,0 +1,2 @@
+You can either configure the Infinispan client via the relevant Camel Infinispan component & endpoint options, or you
+may use the https://quarkus.io/guides/infinispan-client#configuration-reference[Quarkus Infinispan extension configuration properties].
diff --git a/extensions/infinispan/runtime/src/main/doc/limitations.adoc b/extensions/infinispan/runtime/src/main/doc/limitations.adoc
new file mode 100644
index 0000000..007d7b3
--- /dev/null
+++ b/extensions/infinispan/runtime/src/main/doc/limitations.adoc
@@ -0,0 +1,3 @@
+=== InfinispanRemoteAggregationRepository in native mode
+
+At present the `InfinispanRemoteAggregationRepository` is not supported in native mode.
diff --git a/integration-tests/infinispan/pom.xml b/integration-tests/infinispan/pom.xml
index e5df665..eaaa533 100644
--- a/integration-tests/infinispan/pom.xml
+++ b/integration-tests/infinispan/pom.xml
@@ -44,6 +44,10 @@
             <artifactId>camel-quarkus-direct</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-mock</artifactId>
+        </dependency>
+        <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-resteasy</artifactId>
         </dependency>
@@ -147,6 +151,19 @@
                         </exclusion>
                     </exclusions>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.camel.quarkus</groupId>
+                    <artifactId>camel-quarkus-mock-deployment</artifactId>
+                    <version>${project.version}</version>
+                    <type>pom</type>
+                    <scope>test</scope>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>*</groupId>
+                            <artifactId>*</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
             </dependencies>
         </profile>
         <profile>
@@ -160,6 +177,22 @@
                 <skipTests>true</skipTests>
             </properties>
         </profile>
+        <profile>
+            <id>jdk17-build</id>
+            <activation>
+                <jdk>[17,)</jdk>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <argLine>--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED</argLine>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
     </profiles>
 
 </project>
diff --git a/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanResources.java b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanResources.java
index 9bdd07f..d852091 100644
--- a/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanResources.java
+++ b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanResources.java
@@ -17,30 +17,48 @@
 package org.apache.camel.quarkus.component.infinispan;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
-import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import javax.json.Json;
 import javax.json.JsonObject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.infinispan.InfinispanConstants;
+import org.apache.camel.component.infinispan.InfinispanQueryBuilder;
 import org.apache.camel.component.infinispan.remote.InfinispanRemoteComponent;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.quarkus.component.infinispan.model.Person;
+import org.apache.camel.util.CollectionHelper;
 import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.ServerStatistics;
 
-@Path("/test")
+import static org.apache.camel.quarkus.component.infinispan.InfinispanRoutes.CORRELATOR_HEADER;
+
+@Path("/infinispan")
 @ApplicationScoped
 public class InfinispanResources {
-    public static final String CACHE_NAME = "camel";
+    public static final String CACHE_NAME_CAMEL = "camel";
+    public static final String CACHE_NAME_QUARKUS = "quarkus";
 
     @Inject
     RemoteCacheManager cacheManager;
@@ -51,11 +69,6 @@ public class InfinispanResources {
     @Inject
     CamelContext camelContext;
 
-    @PostConstruct
-    public void setUp() {
-        cacheManager.administration().getOrCreateCache(CACHE_NAME, (String) null);
-    }
-
     @Path("/inspect")
     @GET
     @Produces(MediaType.APPLICATION_JSON)
@@ -68,26 +81,294 @@ public class InfinispanResources {
                 .build();
     }
 
+    @Path("/aggregate")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    public void aggregate(@QueryParam("component") String component, String content) {
+        String uri = component.equals("infinispan") ? "direct:camelAggregation" : "direct:quarkusAggregation";
+        Map<String, Object> headers = getCommonHeaders(component);
+        headers.put(CORRELATOR_HEADER, CORRELATOR_HEADER);
+        template.sendBodyAndHeaders(uri, content, headers);
+    }
+
+    @Path("/clear")
+    @DELETE
+    public void clear(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        template.sendBodyAndHeaders("direct:clear", null, headers);
+    }
+
+    @Path("/clearAsync")
+    @DELETE
+    public void clearAsync(@QueryParam("component") String component)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:clearAsync", null, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/compute")
+    @POST
+    public void compute(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        template.sendBodyAndHeaders("direct:compute", null, headers);
+    }
+
+    @Path("/computeAsync")
+    @POST
+    public void computeAsync(@QueryParam("component") String component)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:computeAsync", null, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/containsKey")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Boolean containsKey(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        return template.requestBodyAndHeaders("direct:containsKey", null, headers, Boolean.class);
+    }
+
+    @Path("/containsValue")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Boolean containsValue(@QueryParam("component") String component, @QueryParam("value") String value) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        return template.requestBodyAndHeaders("direct:containsValue", value, headers, Boolean.class);
+    }
+
     @Path("/get")
     @GET
     @Produces(MediaType.TEXT_PLAIN)
-    public String get(@QueryParam("component") String component) {
+    public String get(@QueryParam("component") String component, @QueryParam("key") String key) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        headers.put(InfinispanConstants.KEY, Objects.requireNonNullElse(key, "the-key"));
+        return template.requestBodyAndHeaders("direct:get", null, headers, String.class);
+    }
+
+    @Path("/getOrDefault")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String getOrDefault(@QueryParam("component") String component) {
         Map<String, Object> headers = getCommonHeaders(component);
-        return template.requestBodyAndHeaders("direct:get", "", headers, String.class);
+        return template.requestBodyAndHeaders("direct:getOrDefault", null, headers, String.class);
     }
 
     @Path("/put")
     @POST
+    @Consumes(MediaType.TEXT_PLAIN)
     @Produces(MediaType.TEXT_PLAIN)
     public String put(@QueryParam("component") String component, String content) {
         Map<String, Object> headers = getCommonHeaders(component);
         return template.requestBodyAndHeaders("direct:put", content, headers, String.class);
     }
 
+    @Path("/putAsync")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    public void putAsync(@QueryParam("component") String component, String content)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:putAsync", content, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/putAll")
+    @POST
+    public void putAll(@QueryParam("component") String component) {
+        Map<String, String> body = CollectionHelper.mapOf("key-1", "value-1", "key-2", "value-2");
+        Map<String, Object> headers = getCommonHeaders(component);
+        template.sendBodyAndHeaders("direct:putAll", body, headers);
+    }
+
+    @Path("/putAllAsync")
+    @POST
+    public void putAllAsync(@QueryParam("component") String component)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, String> body = CollectionHelper.mapOf("key-1", "value-1", "key-2", "value-2");
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:putAllAsync", body, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/putIdempotent")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    public void putIdempotent(
+            @QueryParam("component") String component,
+            @QueryParam("messageId") String messageId,
+            String content) {
+        String uri = component.equals("infinispan") ? "direct:camelIdempotent" : "direct:quarkusIdempotent";
+        Map<String, Object> headers = getCommonHeaders(component);
+        headers.put("MessageId", messageId);
+        template.sendBodyAndHeaders(uri, content, headers);
+    }
+
+    @Path("/putIfAbsent")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.TEXT_PLAIN)
+    public String putIfAbsent(@QueryParam("component") String component, String content) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        return template.requestBodyAndHeaders("direct:putIfAbsent", content, headers, String.class);
+    }
+
+    @Path("/putIfAbsentAsync")
+    @POST
+    @Consumes(MediaType.TEXT_PLAIN)
+    public void putIfAbsentAsync(@QueryParam("component") String component, String content)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:putIfAbsentAsync", content, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/query")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    @SuppressWarnings("unchecked")
+    public Response query(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        String cacheName = (String) headers.get("cacheName");
+
+        cacheManager.getCache(cacheName).put("person", new Person("Test", "Person"));
+
+        String query = "FROM person.Person WHERE firstName = 'Test'";
+        InfinispanQueryBuilder builder = InfinispanQueryBuilder.create(query);
+
+        headers.put(InfinispanConstants.QUERY_BUILDER, builder);
+
+        List<String> result = template.requestBodyAndHeaders("direct:query", null, headers, List.class);
+        if (result.isEmpty()) {
+            return Response.status(404).build();
+        }
+        return Response.ok().entity(result.get(0)).build();
+    }
+
+    @Path("/remove")
+    @DELETE
+    public void remove(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        template.requestBodyAndHeaders("direct:remove", null, headers, String.class);
+    }
+
+    @Path("/removeAsync")
+    @DELETE
+    public void removeAsync(@QueryParam("component") String component)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:removeAsync", null, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/replace")
+    @PATCH
+    @Consumes(MediaType.TEXT_PLAIN)
+    public void replace(@QueryParam("component") String component, String content) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        template.sendBodyAndHeaders("direct:replace", content, headers);
+    }
+
+    @Path("/replaceAsync")
+    @PATCH
+    @Consumes(MediaType.TEXT_PLAIN)
+    public void replaceAsync(@QueryParam("component") String component, String content)
+            throws ExecutionException, InterruptedException, TimeoutException {
+        Map<String, Object> headers = getCommonHeaders(component);
+        CompletableFuture<?> future = template.requestBodyAndHeaders("direct:replaceAsync", content, headers,
+                CompletableFuture.class);
+        future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Path("/size")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Integer size(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        return template.requestBodyAndHeaders("direct:size", null, headers, Integer.class);
+    }
+
+    @Path("/stats")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public Integer stats(@QueryParam("component") String component) {
+        Map<String, Object> headers = getCommonHeaders(component);
+        ServerStatistics statistics = template.requestBodyAndHeaders("direct:stats", null, headers, ServerStatistics.class);
+        return statistics.getIntStatistic(ServerStatistics.CURRENT_NR_OF_ENTRIES);
+    }
+
+    @Path("/mock/aggregation/results")
+    @GET
+    public void assertMockEndpointAggregationResults(@QueryParam("uri") String uri) throws InterruptedException {
+        MockEndpoint mockEndpoint = camelContext.getEndpoint(uri, MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(2);
+        mockEndpoint.expectedBodiesReceived(1 + 3 + 4 + 5, 6 + 7 + 20 + 21);
+
+        try {
+            mockEndpoint.assertIsSatisfied(5000);
+        } finally {
+            mockEndpoint.reset();
+        }
+    }
+
+    @Path("/mock/event/results")
+    @GET
+    public void assertMockEndpointEventResults(@QueryParam("uri") String uri) throws InterruptedException {
+        MockEndpoint mockEndpoint = camelContext.getEndpoint(uri, MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+        mockEndpoint.message(0).header(InfinispanConstants.EVENT_TYPE).isEqualTo("CLIENT_CACHE_ENTRY_CREATED");
+        mockEndpoint.message(0).header(InfinispanConstants.CACHE_NAME).isNotNull();
+        mockEndpoint.message(0).header(InfinispanConstants.KEY).isEqualTo("test-key");
+
+        try {
+            mockEndpoint.assertIsSatisfied(5000);
+        } finally {
+            mockEndpoint.reset();
+        }
+    }
+
+    @Path("/mock/idempotent/results")
+    @GET
+    public void assertMockEndpointIdempotentResults(@QueryParam("uri") String uri) throws InterruptedException {
+        MockEndpoint mockEndpoint = camelContext.getEndpoint(uri, MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+        try {
+            mockEndpoint.assertIsSatisfied(5000);
+        } finally {
+            mockEndpoint.reset();
+        }
+    }
+
+    @POST
+    @Path("consumer/{routeId}/{enabled}")
+    public void manageRoute(
+            @PathParam("routeId") String routeId,
+            @PathParam("enabled") boolean enabled) throws Exception {
+        if (enabled) {
+            camelContext.getRouteController().startRoute(routeId);
+        } else {
+            camelContext.getRouteController().stopRoute(routeId);
+        }
+    }
+
     private Map<String, Object> getCommonHeaders(String componentName) {
         Map<String, Object> headers = new HashMap<>();
         headers.put("component", componentName);
-        headers.put("cacheName", CACHE_NAME);
+
+        if (componentName.equals("infinispan")) {
+            headers.put("cacheName", CACHE_NAME_CAMEL);
+        } else {
+            headers.put("cacheName", CACHE_NAME_QUARKUS);
+        }
+
         return headers;
     }
 }
diff --git a/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanRoutes.java b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanRoutes.java
index c194cee..e9476d7 100644
--- a/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanRoutes.java
+++ b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/InfinispanRoutes.java
@@ -17,36 +17,194 @@
 package org.apache.camel.quarkus.component.infinispan;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.function.BiFunction;
 
 import javax.inject.Named;
 
+import org.apache.camel.AggregationStrategy;
+import org.apache.camel.CamelContext;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.infinispan.InfinispanConstants;
 import org.apache.camel.component.infinispan.InfinispanOperation;
+import org.apache.camel.component.infinispan.remote.InfinispanRemoteAggregationRepository;
 import org.apache.camel.component.infinispan.remote.InfinispanRemoteComponent;
 import org.apache.camel.component.infinispan.remote.InfinispanRemoteComponentConfigurer;
+import org.apache.camel.component.infinispan.remote.InfinispanRemoteConfiguration;
+import org.apache.camel.component.infinispan.remote.InfinispanRemoteCustomListener;
+import org.apache.camel.component.infinispan.remote.InfinispanRemoteIdempotentRepository;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
+import org.infinispan.client.hotrod.annotation.ClientListener;
+import org.infinispan.client.hotrod.configuration.Configuration;
+import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
+import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
+import org.infinispan.commons.marshall.StringMarshaller;
+
+import static org.apache.camel.quarkus.component.infinispan.InfinispanResources.CACHE_NAME_CAMEL;
+import static org.apache.camel.quarkus.component.infinispan.InfinispanResources.CACHE_NAME_QUARKUS;
 
 public class InfinispanRoutes extends RouteBuilder {
+    public static final int COMPLETION_SIZE = 4;
+    public static final String CORRELATOR_HEADER = "CORRELATOR_HEADER";
+
     @Override
     public void configure() {
-        // we do not need to set any information about the target infinispan server
-        // as the RemoteConnectionManager is produced by the infinispan extension
-        // and camel-main automatically bind it to the component
+        from("direct:clear")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.CLEAR)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}");
 
-        from("direct:put")
-                .convertBodyTo(byte[].class)
-                .to("log:cache?showAll=true")
-                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUT)
-                .setHeader(InfinispanConstants.KEY).constant("the-key".getBytes(StandardCharsets.UTF_8))
+        from("direct:clearAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.CLEARASYNC)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:compute")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.COMPUTE)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}?remappingFunction=#customMappingFunction");
+
+        from("direct:computeAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.COMPUTEASYNC)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}?remappingFunction=#customMappingFunction");
+
+        from("direct:containsKey")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.CONTAINSKEY)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:containsValue")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.CONTAINSVALUE)
                 .setHeader(InfinispanConstants.VALUE).body()
-                .toD("${header.component}:${header.cacheName}")
-                .to("log:put?showAll=true");
+                .toD("${header.component}:${header.cacheName}");
 
         from("direct:get")
                 .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.GET)
-                .setHeader(InfinispanConstants.KEY).constant("the-key".getBytes(StandardCharsets.UTF_8))
-                .toD("${header.component}:${header.cacheName}")
-                .to("log:get?showAll=true");
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:getOrDefault")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.GETORDEFAULT)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.DEFAULT_VALUE).constant("default-value")
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:put")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUT)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.VALUE).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:putAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUTASYNC)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.VALUE).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:putAll")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUTALL)
+                .setHeader(InfinispanConstants.MAP).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:putAllAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUTALLASYNC)
+                .setHeader(InfinispanConstants.MAP).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:putIfAbsent")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUTIFABSENT)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.VALUE).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:putIfAbsentAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.PUTIFABSENTASYNC)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.VALUE).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:query")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.QUERY)
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:remove")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.REMOVE)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:removeAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.REMOVEASYNC)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:replace")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.REPLACE)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.VALUE).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:replaceAsync")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.REPLACEASYNC)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .setHeader(InfinispanConstants.VALUE).body()
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:size")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.SIZE)
+                .setHeader(InfinispanConstants.KEY).constant("the-key")
+                .toD("${header.component}:${header.cacheName}");
+
+        from("direct:stats")
+                .setHeader(InfinispanConstants.OPERATION).constant(InfinispanOperation.STATS)
+                .toD("${header.component}:${header.cacheName}");
+
+        from("infinispan:camel?eventTypes=CLIENT_CACHE_ENTRY_CREATED")
+                .id("infinispan-events")
+                .autoStartup(false)
+                .to("mock:resultCreated");
+
+        // Only start aggregation repository routes in JVM mode
+        if (!"executable".equals(System.getProperty("org.graalvm.nativeimage.kind"))) {
+            from("direct:camelAggregation")
+                    .aggregate(header(CORRELATOR_HEADER))
+                    .aggregationRepository(createAggregationRepository("infinispan"))
+                    .aggregationStrategy(createAggregationStrategy())
+                    .completionSize(COMPLETION_SIZE)
+                    .to("mock:aggregationResult");
+
+            from("direct:quarkusAggregation")
+                    .aggregate(header(CORRELATOR_HEADER))
+                    .aggregationRepository(createAggregationRepository("infinispan-quarkus"))
+                    .aggregationStrategy(createAggregationStrategy())
+                    .completionSize(COMPLETION_SIZE)
+                    .to("mock:aggregationResult");
+        }
+
+        from("direct:camelIdempotent")
+                .idempotentConsumer(header("MessageID"), createIdempotentRepository("infinispan"))
+                .to("mock:resultIdempotent");
+
+        from("direct:quarkusIdempotent")
+                .idempotentConsumer(header("MessageID"), createIdempotentRepository("infinispan-quarkus"))
+                .to("mock:resultIdempotent");
+
+        from("infinispan-quarkus:quarkus?eventTypes=CLIENT_CACHE_ENTRY_CREATED")
+                .id("infinispan-quarkus-events")
+                .autoStartup(false)
+                .to("mock:resultCreated");
+
+        from("infinispan:camel?customListener=#customListener")
+                .id("infinispan-custom-listener")
+                .autoStartup(false)
+                .to("mock:resultCustomListener");
+
+        from("infinispan-quarkus:quarkus?customListener=#customListener")
+                .id("infinispan-quarkus-custom-listener")
+                .autoStartup(false)
+                .to("mock:resultCustomListener");
     }
 
     @Named("infinispan-quarkus")
@@ -59,4 +217,95 @@ public class InfinispanRoutes extends RouteBuilder {
     public InfinispanRemoteComponentConfigurer quarkusInfinispanConfigurer() {
         return new InfinispanRemoteComponentConfigurer();
     }
+
+    @Named("customMappingFunction")
+    public BiFunction<String, String, String> mappingFunction() {
+        return (k, v) -> v + "-remapped";
+    }
+
+    @Named("customListener")
+    public InfinispanRemoteCustomListener customListener() {
+        return new CustomListener();
+    }
+
+    private InfinispanRemoteIdempotentRepository createIdempotentRepository(String componentName) {
+        String cacheName = componentName.equals("infinispan") ? CACHE_NAME_CAMEL : CACHE_NAME_QUARKUS;
+        InfinispanRemoteConfiguration configuration = getConfiguration(componentName);
+        InfinispanRemoteIdempotentRepository repository = new InfinispanRemoteIdempotentRepository(cacheName);
+        repository.setConfiguration(configuration);
+        return repository;
+    }
+
+    private InfinispanRemoteAggregationRepository createAggregationRepository(String componentName) {
+        String cacheName = componentName.equals("infinispan") ? CACHE_NAME_CAMEL : CACHE_NAME_QUARKUS;
+        InfinispanRemoteAggregationRepository aggregationRepository = new InfinispanRemoteAggregationRepository(cacheName);
+        InfinispanRemoteConfiguration configuration = getConfiguration(componentName);
+        aggregationRepository.setConfiguration(configuration);
+        return aggregationRepository;
+    }
+
+    private InfinispanRemoteConfiguration getConfiguration(String componentName) {
+        CamelContext camelContext = getCamelContext();
+        InfinispanRemoteComponent component = camelContext.getComponent(componentName, InfinispanRemoteComponent.class);
+        InfinispanRemoteConfiguration configuration = component.getConfiguration().clone();
+        configuration.setCacheContainerConfiguration(getConfigurationBuilder());
+        if (componentName.equals("infinispan-quarkus")) {
+            Set<RemoteCacheManager> beans = camelContext.getRegistry().findByType(RemoteCacheManager.class);
+            RemoteCacheManager cacheManager = beans.iterator().next();
+            configuration.setCacheContainer(cacheManager);
+        }
+        return configuration;
+    }
+
+    private Configuration getConfigurationBuilder() {
+        Config config = ConfigProvider.getConfig();
+        ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
+        String[] hostParts = config.getValue("quarkus.infinispan-client.server-list", String.class).split(":");
+
+        clientBuilder.addServer()
+                .host(hostParts[0])
+                .port(Integer.parseInt(hostParts[1]));
+
+        clientBuilder
+                .security()
+                .authentication()
+                .username(config.getValue("quarkus.infinispan-client.auth-username", String.class))
+                .password(config.getValue("quarkus.infinispan-client.auth-password", String.class))
+                .serverName(config.getValue("quarkus.infinispan-client.auth-server-name", String.class))
+                .saslMechanism(config.getValue("quarkus.infinispan-client.sasl-mechanism", String.class))
+                .realm(config.getValue("quarkus.infinispan-client.auth-realm", String.class))
+                .marshaller(new StringMarshaller(StandardCharsets.UTF_8));
+
+        return clientBuilder.build();
+    }
+
+    private AggregationStrategy createAggregationStrategy() {
+        return (oldExchange, newExchange) -> {
+            if (oldExchange == null) {
+                return newExchange;
+            } else {
+                Integer n = newExchange.getIn().getBody(Integer.class);
+                Integer o = oldExchange.getIn().getBody(Integer.class);
+                Integer v = (o == null ? 0 : o) + (n == null ? 0 : n);
+                oldExchange.getIn().setBody(v, Integer.class);
+                return oldExchange;
+            }
+        };
+    }
+
+    @ClientListener
+    static final class CustomListener extends InfinispanRemoteCustomListener {
+
+        @ClientCacheEntryCreated
+        public void entryCreated(ClientCacheEntryCreatedEvent<?> event) {
+            if (isAccepted(event.getType())) {
+                getEventProcessor().processEvent(
+                        event.getType().toString(),
+                        getCacheName(),
+                        event.getKey(),
+                        null,
+                        null);
+            }
+        }
+    }
 }
diff --git a/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/model/Person.java b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/model/Person.java
new file mode 100644
index 0000000..9902d83
--- /dev/null
+++ b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/model/Person.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.infinispan.model;
+
+import java.util.Objects;
+
+import org.infinispan.protostream.annotations.ProtoFactory;
+import org.infinispan.protostream.annotations.ProtoField;
+
+public class Person {
+
+    private final String firstName;
+    private final String lastName;
+
+    @ProtoFactory
+    public Person(String firstName, String lastName) {
+        this.firstName = Objects.requireNonNull(firstName);
+        this.lastName = Objects.requireNonNull(lastName);
+    }
+
+    @ProtoField(number = 1)
+    public String getFirstName() {
+        return firstName;
+    }
+
+    @ProtoField(number = 2)
+    public String getLastName() {
+        return lastName;
+    }
+}
diff --git a/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/model/PersonSchema.java b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/model/PersonSchema.java
new file mode 100644
index 0000000..4569d61
--- /dev/null
+++ b/integration-tests/infinispan/src/main/java/org/apache/camel/quarkus/component/infinispan/model/PersonSchema.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.infinispan.model;
+
+import org.infinispan.protostream.GeneratedSchema;
+import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
+
+@AutoProtoSchemaBuilder(includeClasses = { Person.class }, schemaPackageName = "person")
+interface PersonSchema extends GeneratedSchema {
+}
diff --git a/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanServerTestResource.java b/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanServerTestResource.java
index 812828d..54ebeac 100644
--- a/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanServerTestResource.java
+++ b/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanServerTestResource.java
@@ -23,6 +23,7 @@ import org.apache.camel.util.CollectionHelper;
 import org.apache.commons.lang3.SystemUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.BindMode;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.Wait;
 import org.testcontainers.utility.TestcontainersConfiguration;
@@ -45,6 +46,8 @@ public class InfinispanServerTestResource implements QuarkusTestResourceLifecycl
                     .withExposedPorts(HOTROD_PORT)
                     .withEnv("USER", USER)
                     .withEnv("PASS", PASS)
+                    .withClasspathResourceMapping("infinispan.xml", "/user-config/infinispan.xml", BindMode.READ_ONLY)
+                    .withCommand("-c", "/user-config/infinispan.xml")
                     .waitingFor(Wait.forListeningPort());
 
             container.start();
@@ -58,7 +61,6 @@ public class InfinispanServerTestResource implements QuarkusTestResourceLifecycl
             Map<String, String> result = CollectionHelper.mapOf(
                     // quarkus
                     "quarkus.infinispan-client.server-list", serverList,
-                    "quarkus.infinispan-client.near-cache-max-entries", "3",
                     "quarkus.infinispan-client.auth-username", USER,
                     "quarkus.infinispan-client.auth-password", PASS,
                     "quarkus.infinispan-client.auth-realm", "default",
diff --git a/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanTest.java b/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanTest.java
index abe2592..c39731c 100644
--- a/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanTest.java
+++ b/integration-tests/infinispan/src/test/java/org/apache/camel/quarkus/component/infinispan/InfinispanTest.java
@@ -16,12 +16,20 @@
  */
 package org.apache.camel.quarkus.component.infinispan;
 
+import java.util.UUID;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
 import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.DisabledOnNativeImage;
 import io.quarkus.test.junit.QuarkusTest;
 import io.restassured.RestAssured;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
@@ -30,30 +38,489 @@ import static org.hamcrest.Matchers.notNullValue;
 @QuarkusTestResource(InfinispanServerTestResource.class)
 public class InfinispanTest {
 
+    @AfterEach
+    public void afterEach() {
+        for (String componentName : componentNames()) {
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .delete("/infinispan/clear")
+                    .then()
+                    .statusCode(204);
+
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .get("/infinispan/get")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @DisabledOnNativeImage("https://github.com/apache/camel-quarkus/issues/3657")
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void aggregate(String componentName) {
+        // TODO: https://github.com/apache/camel-quarkus/issues/3657
+        //
+        // Enable testing InfinispanRemoteAggregationRepository with the Quarkus configured client.
+        // Technically it is possible with a custom META-INF/hotrod-client.properties and setting
+        // infinispan.client.hotrod.marshaller=org.infinispan.jboss.marshalling.core.JBossUserMarshaller
+        // However, it potentially impacts some of the Quarkus Infinispan extension functionality that relies on
+        // the default configured ProtoStreamMarshaller, thus we avoid doing it in this test suite
+        Assumptions.assumeTrue(componentName.equals("infinispan"));
+
+        Stream.of(1, 3, 4, 5, 6, 7, 20, 21)
+                .forEach(value -> {
+                    RestAssured.with()
+                            .queryParam("component", componentName)
+                            .body(value)
+                            .post("/infinispan/aggregate")
+                            .then()
+                            .statusCode(204);
+                });
+
+        RestAssured.with()
+                .queryParam("uri", "mock:aggregationResult")
+                .get("/infinispan/mock/aggregation/results")
+                .then()
+                .statusCode(204);
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNamesWithSynchronicity")
+    public void clear(String componentName, boolean isAsync) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("Hello " + componentName));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .delete(computePath("/infinispan/clear", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(204);
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNamesWithSynchronicity")
+    public void compute(String componentName, boolean isAsync) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Initial value")
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .post(computePath("/infinispan/compute", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("Initial value-remapped"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void containsKey(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/containsKey")
+                .then()
+                .statusCode(200)
+                .body(is("false"));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/containsKey")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void containsValue(String componentName) {
+        String value = "test-value";
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .queryParam("value", value)
+                .get("/infinispan/containsValue")
+                .then()
+                .statusCode(200)
+                .body(is("false"));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body(value)
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .queryParam("value", value)
+                .get("/infinispan/containsValue")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void customListener(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .post("/infinispan/consumer/" + componentName + "-custom-listener/true")
+                .then()
+                .statusCode(204);
+
+        try {
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .body("Hello " + componentName)
+                    .post("/infinispan/put")
+                    .then()
+                    .statusCode(204);
+
+            RestAssured.with()
+                    .queryParam("uri", "mock:resultCustomListener")
+                    .get("/infinispan/mock/event/results")
+                    .then()
+                    .statusCode(204);
+        } finally {
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .post("/infinispan/consumer/" + componentName + "-custom-listener/false")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void events(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .post("/infinispan/consumer/" + componentName + "-events/true")
+                .then()
+                .statusCode(204);
+
+        try {
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .body("Hello " + componentName)
+                    .post("/infinispan/put")
+                    .then()
+                    .statusCode(204);
+
+            RestAssured.with()
+                    .queryParam("uri", "mock:resultCreated")
+                    .get("/infinispan/mock/event/results")
+                    .then()
+                    .statusCode(204);
+        } finally {
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .post("/infinispan/consumer/" + componentName + "-events/false")
+                    .then()
+                    .statusCode(204);
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void getOrDefault(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/getOrDefault")
+                .then()
+                .statusCode(200)
+                .body(is("default-value"));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/getOrDefault")
+                .then()
+                .statusCode(200)
+                .body(is("Hello " + componentName));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void idempotent(String componentName) {
+        String messageId = UUID.randomUUID().toString();
+
+        IntStream.of(1, 10).forEach(value -> {
+            RestAssured.with()
+                    .queryParam("component", componentName)
+                    .queryParam("messageId", messageId)
+                    .body("Message " + value)
+                    .post("/infinispan/putIdempotent")
+                    .then()
+                    .statusCode(204);
+        });
+
+        RestAssured.with()
+                .queryParam("uri", "mock:resultIdempotent")
+                .get("/infinispan/mock/idempotent/results")
+                .then()
+                .statusCode(204);
+    }
+
     @Test
     public void inspect() {
         RestAssured.when()
-                .get("/test/inspect")
+                .get("/infinispan/inspect")
                 .then().body(
                         "hosts", is(notNullValue()),
                         "cache-manager", is("none"));
     }
 
     @ParameterizedTest
-    @ValueSource(strings = { "infinispan", "infinispan-quarkus" })
-    public void testInfinispan(String componentName) {
+    @MethodSource("componentNamesWithSynchronicity")
+    public void put(String componentName, boolean isAsync) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post(computePath("/infinispan/put", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("Hello " + componentName));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNamesWithSynchronicity")
+    public void putAll(String componentName, boolean isAsync) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .post(computePath("/infinispan/putAll", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .queryParam("key", "key-1")
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("value-1"));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .queryParam("key", "key-2")
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("value-2"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNamesWithSynchronicity")
+    public void putIfAbsent(String componentName, boolean isAsync) {
         RestAssured.with()
                 .queryParam("component", componentName)
                 .body("Hello " + componentName)
-                .post("/test/put")
+                .post(computePath("/infinispan/putIfAbsent", isAsync))
                 .then()
                 .statusCode(204);
 
         RestAssured.with()
                 .queryParam("component", componentName)
-                .get("/test/get")
+                .get("/infinispan/get")
                 .then()
                 .statusCode(200)
                 .body(is("Hello " + componentName));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("An alternative value")
+                .post(computePath("/infinispan/putIfAbsent", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("Hello " + componentName));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void query(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/query")
+                .then()
+                .statusCode(200);
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNamesWithSynchronicity")
+    public void remove(String componentName, boolean isAsync) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post(computePath("/infinispan/put", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .delete(computePath("/infinispan/remove", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(204);
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNamesWithSynchronicity")
+    public void replace(String componentName, boolean isAsync) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post(computePath("/infinispan/put", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("Hello " + componentName));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("replaced cache value")
+                .patch(computePath("/infinispan/replace", isAsync))
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/get")
+                .then()
+                .statusCode(200)
+                .body(is("replaced cache value"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void size(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/size")
+                .then()
+                .statusCode(200)
+                .body(is("0"));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/size")
+                .then()
+                .statusCode(200)
+                .body(is("1"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("componentNames")
+    public void stats(String componentName) {
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/stats")
+                .then()
+                .statusCode(200)
+                .body(is("0"));
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .body("Hello " + componentName)
+                .post("/infinispan/put")
+                .then()
+                .statusCode(204);
+
+        RestAssured.with()
+                .queryParam("component", componentName)
+                .get("/infinispan/stats")
+                .then()
+                .statusCode(200)
+                .body(is("1"));
+    }
+
+    private String computePath(String path, boolean isAsync) {
+        if (isAsync) {
+            path += "Async";
+        }
+        return path;
+    }
+
+    public static String[] componentNames() {
+        return new String[] {
+                "infinispan",
+                "infinispan-quarkus"
+        };
+    }
+
+    public static Stream<Arguments> componentNamesWithSynchronicity() {
+        return Stream.of(
+                Arguments.of("infinispan", false),
+                Arguments.of("infinispan-quarkus", false),
+                Arguments.of("infinispan", true),
+                Arguments.of("infinispan-quarkus", true));
     }
 }
diff --git a/integration-tests/infinispan/src/test/resources/infinispan.xml b/integration-tests/infinispan/src/test/resources/infinispan.xml
new file mode 100644
index 0000000..60acf47
--- /dev/null
+++ b/integration-tests/infinispan/src/test/resources/infinispan.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="urn:infinispan:config:13.0 https://infinispan.org/schemas/infinispan-config-13.0.xsd
+                            urn:infinispan:server:13.0 https://infinispan.org/schemas/infinispan-server-13.0.xsd"
+            xmlns="urn:infinispan:config:13.0"
+            xmlns:server="urn:infinispan:server:13.0">
+
+    <cache-container name="default" statistics="true">
+        <metrics accurate-size="true"/>
+        <transport cluster="${infinispan.cluster.name:cluster}" stack="${infinispan.cluster.stack:tcp}"
+                   node-name="${infinispan.node.name:}"/>
+        <security>
+            <authorization/>
+        </security>
+
+        <!-- Used by tests where camel-infinispan manages its own cache container -->
+        <local-cache name="camel">
+        </local-cache>
+
+        <!-- Used by tests where camel-infinispan uses the Quarkus managed cache container -->
+        <local-cache name="quarkus">
+        </local-cache>
+    </cache-container>
+
+
+    <server xmlns="urn:infinispan:server:13.0">
+        <interfaces>
+            <interface name="public">
+                <inet-address value="${infinispan.bind.address:127.0.0.1}"/>
+            </interface>
+        </interfaces>
+
+        <socket-bindings default-interface="public" port-offset="${infinispan.socket.binding.port-offset:0}">
+            <socket-binding name="default" port="${infinispan.bind.port:11222}"/>
+            <socket-binding name="memcached" port="11221"/>
+        </socket-bindings>
+
+        <security>
+            <credential-stores>
+                <credential-store name="credentials" path="credentials.pfx">
+                    <clear-text-credential clear-text="secret"/>
+                </credential-store>
+            </credential-stores>
+            <security-realms>
+                <security-realm name="default">
+                    <!-- Uncomment to enable TLS on the realm -->
+                    <!-- server-identities>
+                       <ssl>
+                          <keystore path="application.keystore"
+                                    password="password" alias="server"
+                                    generate-self-signed-certificate-host="localhost"/>
+                       </ssl>
+                    </server-identities-->
+                    <properties-realm groups-attribute="Roles">
+                        <user-properties path="users.properties"/>
+                        <group-properties path="groups.properties"/>
+                    </properties-realm>
+                </security-realm>
+            </security-realms>
+        </security>
+
+        <endpoints socket-binding="default" security-realm="default"/>
+    </server>
+</infinispan>
diff --git a/pom.xml b/pom.xml
index 4f06a59..b10bb94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -98,6 +98,7 @@
         <hapi-fhir.version>${hapi-fhir-version}</hapi-fhir.version>
         <hbase.version>${hbase-version}</hbase.version>
         <htrace.version>4.2.0-incubating</htrace.version><!-- Mess in hbase transitive deps -->
+        <infinispan.version>13.0.6.Final</infinispan.version><!-- @sync io.quarkus:quarkus-bom:${quarkus.version} dep:org.infinispan:infinispan-core -->
         <influxdb.version>${influx-java-driver-version}</influxdb.version>
         <jackson1.version>1.9.13</jackson1.version><!-- Mess in the transitive dependencies of spark and hbase-testing-util -->
         <jackson-asl.version>${jackson1.version}</jackson-asl.version><!-- Can be different from jackson1.version on some occasions -->
diff --git a/poms/bom/pom.xml b/poms/bom/pom.xml
index 982311d..e0e9d02 100644
--- a/poms/bom/pom.xml
+++ b/poms/bom/pom.xml
@@ -1472,6 +1472,10 @@
                         <groupId>org.infinispan</groupId>
                         <artifactId>infinispan-core</artifactId>
                     </exclusion>
+                    <exclusion>
+                        <groupId>org.infinispan</groupId>
+                        <artifactId>infinispan-marshaller-protostuff</artifactId>
+                    </exclusion>
                 </exclusions>
             </dependency>
             <dependency>
@@ -6577,6 +6581,11 @@
                 <version>${graalvm.version}</version>
             </dependency>
             <dependency>
+                <groupId>org.infinispan</groupId>
+                <artifactId>infinispan-jboss-marshalling</artifactId>
+                <version>${infinispan.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.influxdb</groupId>
                 <artifactId>influxdb-java</artifactId>
                 <version>${influxdb.version}</version>