diff --git a/docs/src/main/asciidoc/elasticsearch.adoc b/docs/src/main/asciidoc/elasticsearch.adoc
index 8a907c3819c5d..b39a93d466fe9 100644
--- a/docs/src/main/asciidoc/elasticsearch.adoc
+++ b/docs/src/main/asciidoc/elasticsearch.adoc
@@ -260,6 +260,52 @@ quarkus.elasticsearch.hosts = localhost:9200
If you need a more advanced configuration, you can find the comprehensive list of supported configuration properties at the end of this guide.
+== Programmatically Configuring Elasticsearch
+On top of the parametric configuration, you can also programmatically apply additional configuration to the client by implementing a `RestClientBuilder.HttpClientConfigCallback` and annotating it with `ElasticsearchClientConfig`. You may provide multiple implementations and configuration provided by each implementation will be applied in a randomly ordered cascading manner.
+
+For example, when accessing an Elasticsearch cluster that is set up for TLS on the HTTP layer, the client needs to trust the certificate that Elasticsearch is using. The following is an example of setting up the client to trust the CA that has signed the certificate that Elasticsearch is using, when that CA certificate is available in a PKCS#12 keystore.
+
+[source,java]
+----
+import io.quarkus.elasticsearch.restclient.lowlevel.ElasticsearchClientConfig;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.ssl.SSLContexts;
+import org.elasticsearch.client.RestClientBuilder;
+
+import javax.enterprise.context.Dependent;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+
+@ElasticsearchClientConfig
+@Dependent
+public class SSLContextConfigurator implements RestClientBuilder.HttpClientConfigCallback {
+ @Override
+ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
+ try {
+ String keyStorePass = "password-for-keystore";
+ Path trustStorePath = Paths.get("/path/to/truststore.p12");
+ KeyStore truststore = KeyStore.getInstance("pkcs12");
+ try (InputStream is = Files.newInputStream(trustStorePath)) {
+ truststore.load(is, keyStorePass.toCharArray());
+ }
+ SSLContextBuilder sslBuilder = SSLContexts.custom()
+ .loadTrustMaterial(truststore, null);
+ SSLContext sslContext = sslBuilder.build();
+ httpClientBuilder.setSSLContext(sslContext);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ return httpClientBuilder;
+ }
+}
+----
+See https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/_encrypted_communication.html[Elasticsearch documentation] for more details on this particular example.
== Running an Elasticsearch cluster
diff --git a/extensions/elasticsearch-rest-client/deployment/pom.xml b/extensions/elasticsearch-rest-client/deployment/pom.xml
index 379ab306974f6..314a5c7744d5a 100644
--- a/extensions/elasticsearch-rest-client/deployment/pom.xml
+++ b/extensions/elasticsearch-rest-client/deployment/pom.xml
@@ -34,6 +34,13 @@
io.quarkus
quarkus-smallrye-health-spi
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
diff --git a/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/ElasticsearchClientConfigTest.java b/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/ElasticsearchClientConfigTest.java
new file mode 100644
index 0000000000000..06c308031e99f
--- /dev/null
+++ b/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/ElasticsearchClientConfigTest.java
@@ -0,0 +1,55 @@
+package io.quarkus.elasticsearch.restclient.lowlevel.runtime;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.elasticsearch.client.RestClientBuilder;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.elasticsearch.restclient.lowlevel.ElasticsearchClientConfig;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ElasticsearchClientConfigTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .setArchiveProducer(
+ () -> ShrinkWrap.create(JavaArchive.class).addClass(TestConfigurator.class)
+ .addAsResource(new StringAsset("quarkus.elasticsearch.hosts=elasticsearch:9200"),
+ "application.properties"));
+
+ @Inject
+ ElasticsearchConfig config;
+ @Inject
+ @ElasticsearchClientConfig
+ TestConfigurator testConfigurator;
+
+ @Test
+ public void testRestClientBuilderHelperWithElasticsearchClientConfig() {
+ RestClientBuilderHelper.createRestClientBuilder(config).build();
+ assertTrue(testConfigurator.isInvoked());
+ }
+
+ @ElasticsearchClientConfig
+ @ApplicationScoped
+ public static class TestConfigurator implements RestClientBuilder.HttpClientConfigCallback {
+ private boolean invoked = false;
+
+ @Override
+ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder builder) {
+ invoked = true;
+ return builder;
+ }
+
+ public boolean isInvoked() {
+ return invoked;
+ }
+ }
+
+}
diff --git a/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/ElasticsearchClientConfig.java b/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/ElasticsearchClientConfig.java
new file mode 100644
index 0000000000000..1af174c1e9bc8
--- /dev/null
+++ b/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/ElasticsearchClientConfig.java
@@ -0,0 +1,28 @@
+package io.quarkus.elasticsearch.restclient.lowlevel;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Qualifier;
+
+/**
+ * Annotate implementations of {@code org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback} to provide further
+ * configuration of injected Elasticsearch {@code RestClient} You may provide multiple implementations each annotated with
+ * {@code ElasticsearchClientConfig} and configuration provided by each implementation will be applied in a randomly ordered
+ * cascading manner
+ */
+@Qualifier
+@Target({ FIELD, TYPE, PARAMETER })
+@Retention(RUNTIME)
+public @interface ElasticsearchClientConfig {
+
+ class Literal extends AnnotationLiteral implements ElasticsearchClientConfig {
+
+ }
+}
diff --git a/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/RestClientBuilderHelper.java b/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/RestClientBuilderHelper.java
index 1262b78f75380..7c1f6272d308f 100644
--- a/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/RestClientBuilderHelper.java
+++ b/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/RestClientBuilderHelper.java
@@ -21,6 +21,10 @@
import org.elasticsearch.client.sniff.SnifferBuilder;
import org.jboss.logging.Logger;
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.elasticsearch.restclient.lowlevel.ElasticsearchClientConfig;
+
public final class RestClientBuilderHelper {
private static final Logger LOG = Logger.getLogger(RestClientBuilderHelper.class);
@@ -77,7 +81,16 @@ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpCli
httpClientBuilder.setSSLStrategy(NoopIOSessionStrategy.INSTANCE);
}
- return httpClientBuilder;
+ // Apply configuration from RestClientBuilder.HttpClientConfigCallback implementations annotated with ElasticsearchClientConfig
+ HttpAsyncClientBuilder result = httpClientBuilder;
+ Iterable> handles = Arc.container()
+ .select(RestClientBuilder.HttpClientConfigCallback.class, new ElasticsearchClientConfig.Literal())
+ .handles();
+ for (InstanceHandle handle : handles) {
+ result = handle.get().customizeHttpClient(result);
+ handle.close();
+ }
+ return result;
}
});