hotDeploymentWatchedFiles) {
+ if (!classpathFileOptional.isPresent()) {
+ return;
+ }
+ String classpathFile = classpathFileOptional.get();
+
+ Path existingPath = applicationArchivesBuildItem.getRootArchive().getChildPath(classpathFile);
+
+ if (existingPath == null || Files.isDirectory(existingPath)) {
+ //raise exception if explicit file is not present (i.e. not the default)
+ throw new ConfigurationException(
+ "Unable to find file referenced in '"
+ + mapperContext.backendPropertyKey(backendName, indexName, propertyKeyRadical) + "="
+ + classpathFile
+ + "'. Remove property or add file to your path.");
+ }
+ nativeImageResources.produce(new NativeImageResourceBuildItem(classpathFile));
+ hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(classpathFile));
+ }
+
+}
diff --git a/extensions/hibernate-search-backend-elasticsearch/pom.xml b/extensions/hibernate-search-backend-elasticsearch/pom.xml
new file mode 100644
index 00000000000000..4a9b715f7d188e
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-extensions-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-hibernate-search-backend-elasticsearch-parent
+ Quarkus - Hibernate Search - Elasticsearch
+ pom
+
+ deployment
+ runtime
+
+
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/pom.xml b/extensions/hibernate-search-backend-elasticsearch/runtime/pom.xml
new file mode 100644
index 00000000000000..62453cd2403368
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ quarkus-hibernate-search-backend-elasticsearch-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-hibernate-search-backend-elasticsearch
+ Quarkus - Hibernate Search - Elasticsearch - Runtime
+ Elasticsearch/OpenSearch backend for use in other Hibernate Search extensions
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-elasticsearch-rest-client-common
+
+
+ org.hibernate.search
+ hibernate-search-backend-elasticsearch
+
+
+ org.graalvm.sdk
+ graal-sdk
+ provided
+
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/ElasticsearchVersionSubstitution.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/ElasticsearchVersionSubstitution.java
similarity index 87%
rename from extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/ElasticsearchVersionSubstitution.java
rename to extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/ElasticsearchVersionSubstitution.java
index 1bd9dfd6e900da..dc26810940dd4e 100644
--- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/ElasticsearchVersionSubstitution.java
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/ElasticsearchVersionSubstitution.java
@@ -1,4 +1,4 @@
-package io.quarkus.hibernate.search.orm.elasticsearch.runtime;
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion;
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchBuildTimeConfig.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchBuildTimeConfig.java
new file mode 100644
index 00000000000000..9a82a9498736ab
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchBuildTimeConfig.java
@@ -0,0 +1,163 @@
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion;
+
+import io.quarkus.runtime.annotations.ConfigDocMapKey;
+import io.quarkus.runtime.annotations.ConfigDocSection;
+import io.quarkus.runtime.annotations.ConfigGroup;
+import io.smallrye.config.WithParentName;
+
+@ConfigGroup
+public interface HibernateSearchBackendElasticsearchBuildTimeConfig {
+ /**
+ * The version of Elasticsearch used in the cluster.
+ *
+ * As the schema is generated without a connection to the server, this item is mandatory.
+ *
+ * It doesn't have to be the exact version (it can be `7` or `7.1` for instance) but it has to be sufficiently precise
+ * to choose a model dialect (the one used to generate the schema) compatible with the protocol dialect (the one used
+ * to communicate with Elasticsearch).
+ *
+ * There's no rule of thumb here as it depends on the schema incompatibilities introduced by Elasticsearch versions. In
+ * any case, if there is a problem, you will have an error when Hibernate Search tries to connect to the cluster.
+ *
+ * @asciidoclet
+ */
+ Optional version();
+
+ /**
+ * Configuration for the index layout.
+ */
+ LayoutConfig layout();
+
+ /**
+ * The default configuration for the Elasticsearch indexes.
+ */
+ @WithParentName
+ IndexConfig indexDefaults();
+
+ /**
+ * Per-index configuration overrides.
+ */
+ @ConfigDocSection
+ @ConfigDocMapKey("index-name")
+ Map indexes();
+
+ @ConfigGroup
+ interface IndexConfig {
+ /**
+ * Configuration for automatic creation and validation of the Elasticsearch schema:
+ * indexes, their mapping, their settings.
+ */
+ SchemaManagementConfig schemaManagement();
+
+ /**
+ * Configuration for full-text analysis.
+ */
+ AnalysisConfig analysis();
+ }
+
+ @ConfigGroup
+ interface SchemaManagementConfig {
+
+ // @formatter:off
+ /**
+ * Path to a file in the classpath holding custom index settings to be included in the index definition
+ * when creating an Elasticsearch index.
+ *
+ * The provided settings will be merged with those generated by Hibernate Search, including analyzer definitions.
+ * When analysis is configured both through an analysis configurer and these custom settings, the behavior is undefined;
+ * it should not be relied upon.
+ *
+ * See link:{hibernate-search-docs-url}#backend-elasticsearch-configuration-index-settings[this section of the reference documentation]
+ * for more information.
+ *
+ * @asciidoclet
+ */
+ // @formatter:on
+ Optional settingsFile();
+
+ // @formatter:off
+ /**
+ * Path to a file in the classpath holding a custom index mapping to be included in the index definition
+ * when creating an Elasticsearch index.
+ *
+ * The file does not need to (and generally shouldn't) contain the full mapping:
+ * Hibernate Search will automatically inject missing properties (index fields) in the given mapping.
+ *
+ * See link:{hibernate-search-docs-url}#backend-elasticsearch-mapping-custom[this section of the reference documentation]
+ * for more information.
+ *
+ * @asciidoclet
+ */
+ // @formatter:on
+ Optional mappingFile();
+
+ }
+ @ConfigGroup
+ interface AnalysisConfig {
+ /**
+ * One or more xref:hibernate-search-orm-elasticsearch.adoc#bean-reference-note-anchor[bean references]
+ * to the component(s) used to configure full text analysis (e.g. analyzers, normalizers).
+ *
+ * The referenced beans must implement `ElasticsearchAnalysisConfigurer`.
+ *
+ * See xref:#analysis-configurer[Setting up the analyzers] for more information.
+ *
+ * [NOTE]
+ * ====
+ * Instead of setting this configuration property,
+ * you can simply annotate your custom `ElasticsearchAnalysisConfigurer` implementations with `@SearchExtension`
+ * and leave the configuration property unset: Hibernate Search will use the annotated implementation automatically.
+ * See xref:#plugging-in-custom-components[this section] for more information.
+ *
+ * If this configuration property is set, it takes precedence over any `@SearchExtension` annotation.
+ * ====
+ *
+ * @asciidoclet
+ */
+ Optional> configurer();
+ }
+
+ @ConfigGroup
+ interface LayoutConfig {
+ /**
+ * A xref:hibernate-search-orm-elasticsearch.adoc#bean-reference-note-anchor[bean reference] to the component
+ * used to configure the Elasticsearch layout: index names, index aliases, ...
+ *
+ * The referenced bean must implement `IndexLayoutStrategy`.
+ *
+ * Available built-in implementations:
+ *
+ * `simple`::
+ * The default, future-proof strategy: if the index name in Hibernate Search is `myIndex`,
+ * this strategy will create an index named `myindex-000001`, an alias for write operations named `myindex-write`,
+ * and an alias for read operations named `myindex-read`.
+ * `no-alias`::
+ * A strategy without index aliases, mostly useful on legacy clusters:
+ * if the index name in Hibernate Search is `myIndex`,
+ * this strategy will create an index named `myindex`, and will not use any alias.
+ *
+ * See
+ * link:{hibernate-search-docs-url}#backend-elasticsearch-indexlayout[this section of the reference documentation]
+ * for more information.
+ *
+ * [NOTE]
+ * ====
+ * Instead of setting this configuration property,
+ * you can simply annotate your custom `IndexLayoutStrategy` implementation with `@SearchExtension`
+ * and leave the configuration property unset: Hibernate Search will use the annotated implementation automatically.
+ * See xref:#plugging-in-custom-components[this section] for more information.
+ *
+ * If this configuration property is set, it takes precedence over any `@SearchExtension` annotation.
+ * ====
+ *
+ * @asciidoclet
+ */
+ Optional strategy();
+ }
+}
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchConfigHandler.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchConfigHandler.java
new file mode 100644
index 00000000000000..ad14ba7464b63a
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchConfigHandler.java
@@ -0,0 +1,195 @@
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
+
+import static io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendConfig;
+import static io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendIndexConfig;
+import static io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchConfigUtil.mergeInto;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer;
+import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings;
+import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings;
+import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
+import org.hibernate.search.engine.cfg.BackendSettings;
+
+import io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchBackendElasticsearchBuildTimeConfig.IndexConfig;
+
+public final class HibernateSearchBackendElasticsearchConfigHandler {
+
+ public static void contributeBackendBuildTimeProperties(BiConsumer propertyCollector,
+ MapperContext mapperContext,
+ Map backendConfigs) {
+ // We need this weird collecting of names from both @SearchExtension and the configuration properties
+ // because a backend/index could potentially be configured exclusively through configuration properties,
+ // or exclusively through @SearchExtension.
+ // (Well maybe not for backends, but... let's keep it simple.)
+ Map> backendAndIndexNames = new LinkedHashMap<>();
+ mergeInto(backendAndIndexNames, mapperContext.backendAndIndexNamesForSearchExtensions());
+ for (Entry entry : backendConfigs.entrySet()) {
+ mergeInto(backendAndIndexNames, entry.getKey(), entry.getValue().indexes().keySet());
+ }
+
+ for (Entry> entry : backendAndIndexNames.entrySet()) {
+ String backendName = entry.getKey();
+ Set indexNames = entry.getValue();
+ contributeBackendBuildTimeProperties(propertyCollector, mapperContext, backendName, indexNames,
+ backendConfigs.get(backendName));
+ }
+ }
+
+ private static void contributeBackendBuildTimeProperties(BiConsumer propertyCollector,
+ MapperContext mapperContext,
+ String backendName, Set indexNames,
+ HibernateSearchBackendElasticsearchBuildTimeConfig elasticsearchBackendConfig) {
+ addBackendConfig(propertyCollector, backendName, BackendSettings.TYPE,
+ ElasticsearchBackendSettings.TYPE_NAME);
+ if (elasticsearchBackendConfig != null) {
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION,
+ elasticsearchBackendConfig.version());
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ addBackendConfig(propertyCollector, backendName,
+ ElasticsearchBackendSettings.LAYOUT_STRATEGY,
+ mapperContext.singleExtensionBeanReferenceFor(
+ elasticsearchBackendConfig == null ? Optional.empty()
+ : elasticsearchBackendConfig.layout().strategy(),
+ IndexLayoutStrategy.class, backendName, null));
+
+ // Index defaults at the backend level
+ contributeBackendIndexBuildTimeProperties(propertyCollector, mapperContext, backendName, null,
+ elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults());
+
+ // Per-index properties
+ for (String indexName : indexNames) {
+ IndexConfig indexConfig = elasticsearchBackendConfig == null ? null
+ : elasticsearchBackendConfig.indexes().get(indexName);
+ contributeBackendIndexBuildTimeProperties(propertyCollector, mapperContext, backendName, indexName, indexConfig);
+ }
+ }
+
+ private static void contributeBackendIndexBuildTimeProperties(BiConsumer propertyCollector,
+ MapperContext mapperContext,
+ String backendName, String indexName, IndexConfig indexConfig) {
+ if (indexConfig != null) {
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_SETTINGS_FILE,
+ indexConfig.schemaManagement().settingsFile());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MAPPING_FILE,
+ indexConfig.schemaManagement().mappingFile());
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.ANALYSIS_CONFIGURER,
+ mapperContext.multiExtensionBeanReferencesFor(
+ indexConfig == null ? Optional.empty() : indexConfig.analysis().configurer(),
+ ElasticsearchAnalysisConfigurer.class, backendName, indexName));
+ }
+
+ public static void contributeBackendRuntimeProperties(BiConsumer propertyCollector,
+ MapperContext mapperContext,
+ Map backendConfigs) {
+ // We need this weird collecting of names from both @SearchExtension and the configuration properties
+ // because a backend/index could potentially be configured exclusively through configuration properties,
+ // or exclusively through @SearchExtension.
+ // (Well maybe not for backends, but... let's keep it simple.)
+ Map> backendAndIndexNames = new LinkedHashMap<>();
+ mergeInto(backendAndIndexNames, mapperContext.backendAndIndexNamesForSearchExtensions());
+ for (Entry entry : backendConfigs.entrySet()) {
+ mergeInto(backendAndIndexNames, entry.getKey(), entry.getValue().indexes().keySet());
+ }
+
+ for (Entry> entry : backendAndIndexNames.entrySet()) {
+ String backendName = entry.getKey();
+ Set indexNames = entry.getValue();
+ contributeBackendRuntimeProperties(propertyCollector, mapperContext, backendName, indexNames,
+ backendConfigs.get(backendName));
+ }
+ }
+
+ private static void contributeBackendRuntimeProperties(BiConsumer propertyCollector,
+ MapperContext mapperContext,
+ String backendName, Set indexNames,
+ HibernateSearchBackendElasticsearchRuntimeConfig elasticsearchBackendConfig) {
+ if (elasticsearchBackendConfig != null) {
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.HOSTS,
+ elasticsearchBackendConfig.hosts());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PROTOCOL,
+ elasticsearchBackendConfig.protocol().getHibernateSearchString());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.USERNAME,
+ elasticsearchBackendConfig.username());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PASSWORD,
+ elasticsearchBackendConfig.password());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.CONNECTION_TIMEOUT,
+ elasticsearchBackendConfig.connectionTimeout().toMillis());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.READ_TIMEOUT,
+ elasticsearchBackendConfig.readTimeout().toMillis());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.REQUEST_TIMEOUT,
+ elasticsearchBackendConfig.requestTimeout(), Optional::isPresent, d -> d.get().toMillis());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.MAX_CONNECTIONS,
+ elasticsearchBackendConfig.maxConnections());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.MAX_CONNECTIONS_PER_ROUTE,
+ elasticsearchBackendConfig.maxConnectionsPerRoute());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.THREAD_POOL_SIZE,
+ elasticsearchBackendConfig.threadPool().size());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION_CHECK_ENABLED,
+ elasticsearchBackendConfig.versionCheck().enabled());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.QUERY_SHARD_FAILURE_IGNORE,
+ elasticsearchBackendConfig.query().shardFailure().ignore());
+
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_ENABLED,
+ elasticsearchBackendConfig.discovery().enabled());
+ if (elasticsearchBackendConfig.discovery().enabled()) {
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_REFRESH_INTERVAL,
+ elasticsearchBackendConfig.discovery().refreshInterval().getSeconds());
+ }
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ //
+
+ // Index defaults at the backend level
+ contributeBackendIndexRuntimeProperties(propertyCollector, mapperContext, backendName, null,
+ elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults());
+
+ // Per-index properties
+ for (String indexName : indexNames) {
+ HibernateSearchBackendElasticsearchRuntimeConfig.IndexConfig indexConfig = elasticsearchBackendConfig == null ? null
+ : elasticsearchBackendConfig.indexes().get(indexName);
+ contributeBackendIndexRuntimeProperties(propertyCollector, mapperContext, backendName, indexName, indexConfig);
+ }
+ }
+
+ private static void contributeBackendIndexRuntimeProperties(BiConsumer propertyCollector,
+ MapperContext mapperContext,
+ String backendName, String indexName, HibernateSearchBackendElasticsearchRuntimeConfig.IndexConfig indexConfig) {
+ if (indexConfig != null) {
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS,
+ indexConfig.schemaManagement().requiredStatus());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT,
+ indexConfig.schemaManagement().requiredStatusWaitTimeout(), Optional::isPresent,
+ d -> d.get().toMillis());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.INDEXING_QUEUE_COUNT,
+ indexConfig.indexing().queueCount());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.INDEXING_QUEUE_SIZE,
+ indexConfig.indexing().queueSize());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.INDEXING_MAX_BULK_SIZE,
+ indexConfig.indexing().maxBulkSize());
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ //
+ }
+}
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchRecorder.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchRecorder.java
new file mode 100644
index 00000000000000..e41cf9649b7cc3
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchRecorder.java
@@ -0,0 +1,193 @@
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
+
+import static io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendConfig;
+import static io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendIndexConfig;
+import static io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchConfigUtil.mergeInto;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer;
+import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings;
+import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings;
+import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy;
+import org.hibernate.search.engine.cfg.BackendSettings;
+
+import io.quarkus.hibernate.search.backend.elasticsearch.runtime.HibernateSearchBackendElasticsearchBuildTimeConfig.IndexConfig;
+
+public final class HibernateSearchBackendElasticsearchRecorder {
+
+ public void contributeBackendBuildTimeProperties(BiConsumer propertyCollector,
+ MapperContext context,
+ Map backendConfigs) {
+ // We need this weird collecting of names from both @SearchExtension and the configuration properties
+ // because a backend/index could potentially be configured exclusively through configuration properties,
+ // or exclusively through @SearchExtension.
+ // (Well maybe not for backends, but... let's keep it simple.)
+ Map> backendAndIndexNames = new LinkedHashMap<>();
+ mergeInto(backendAndIndexNames, context.backendAndIndexNamesForSearchExtensions());
+ for (Entry entry : backendConfigs.entrySet()) {
+ mergeInto(backendAndIndexNames, entry.getKey(), entry.getValue().indexes().keySet());
+ }
+
+ for (Entry> entry : backendAndIndexNames.entrySet()) {
+ String backendName = entry.getKey();
+ Set indexNames = entry.getValue();
+ contributeBackendBuildTimeProperties(propertyCollector, context, backendName, indexNames,
+ backendConfigs.get(backendName));
+ }
+ }
+
+ private void contributeBackendBuildTimeProperties(BiConsumer propertyCollector,
+ MapperContext context,
+ String backendName, Set indexNames,
+ HibernateSearchBackendElasticsearchBuildTimeConfig elasticsearchBackendConfig) {
+ addBackendConfig(propertyCollector, backendName, BackendSettings.TYPE,
+ ElasticsearchBackendSettings.TYPE_NAME);
+ if (elasticsearchBackendConfig != null) {
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION,
+ elasticsearchBackendConfig.version());
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ addBackendConfig(propertyCollector, backendName,
+ ElasticsearchBackendSettings.LAYOUT_STRATEGY,
+ context.singleExtensionBeanReferenceFor(
+ elasticsearchBackendConfig == null ? Optional.empty()
+ : elasticsearchBackendConfig.layout().strategy(),
+ IndexLayoutStrategy.class, backendName, null));
+
+ // Index defaults at the backend level
+ contributeBackendIndexBuildTimeProperties(propertyCollector, context, backendName, null,
+ elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults());
+
+ // Per-index properties
+ for (String indexName : indexNames) {
+ IndexConfig indexConfig = elasticsearchBackendConfig == null ? null
+ : elasticsearchBackendConfig.indexes().get(indexName);
+ contributeBackendIndexBuildTimeProperties(propertyCollector, context, backendName, indexName, indexConfig);
+ }
+ }
+
+ private void contributeBackendIndexBuildTimeProperties(BiConsumer propertyCollector,
+ MapperContext context,
+ String backendName, String indexName, IndexConfig indexConfig) {
+ if (indexConfig != null) {
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_SETTINGS_FILE,
+ indexConfig.schemaManagement().settingsFile());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MAPPING_FILE,
+ indexConfig.schemaManagement().mappingFile());
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.ANALYSIS_CONFIGURER,
+ context.multiExtensionBeanReferencesFor(
+ indexConfig == null ? Optional.empty() : indexConfig.analysis().configurer(),
+ ElasticsearchAnalysisConfigurer.class, backendName, indexName));
+ }
+
+ public void contributeBackendRuntimeProperties(BiConsumer propertyCollector,
+ MapperContext context,
+ Map backendConfigs) {
+ // We need this weird collecting of names from both @SearchExtension and the configuration properties
+ // because a backend/index could potentially be configured exclusively through configuration properties,
+ // or exclusively through @SearchExtension.
+ // (Well maybe not for backends, but... let's keep it simple.)
+ Map> backendAndIndexNames = new LinkedHashMap<>();
+ mergeInto(backendAndIndexNames, context.backendAndIndexNamesForSearchExtensions());
+ for (Entry entry : backendConfigs.entrySet()) {
+ mergeInto(backendAndIndexNames, entry.getKey(), entry.getValue().indexes().keySet());
+ }
+
+ for (Entry> entry : backendAndIndexNames.entrySet()) {
+ String backendName = entry.getKey();
+ Set indexNames = entry.getValue();
+ contributeBackendRuntimeProperties(propertyCollector, context, backendName, indexNames,
+ backendConfigs.get(backendName));
+ }
+ }
+
+ private void contributeBackendRuntimeProperties(BiConsumer propertyCollector, MapperContext context,
+ String backendName, Set indexNames,
+ HibernateSearchBackendElasticsearchRuntimeConfig elasticsearchBackendConfig) {
+ if (elasticsearchBackendConfig != null) {
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.HOSTS,
+ elasticsearchBackendConfig.hosts());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PROTOCOL,
+ elasticsearchBackendConfig.protocol().getHibernateSearchString());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.USERNAME,
+ elasticsearchBackendConfig.username());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PASSWORD,
+ elasticsearchBackendConfig.password());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.CONNECTION_TIMEOUT,
+ elasticsearchBackendConfig.connectionTimeout().toMillis());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.READ_TIMEOUT,
+ elasticsearchBackendConfig.readTimeout().toMillis());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.REQUEST_TIMEOUT,
+ elasticsearchBackendConfig.requestTimeout(), Optional::isPresent, d -> d.get().toMillis());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.MAX_CONNECTIONS,
+ elasticsearchBackendConfig.maxConnections());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.MAX_CONNECTIONS_PER_ROUTE,
+ elasticsearchBackendConfig.maxConnectionsPerRoute());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.THREAD_POOL_SIZE,
+ elasticsearchBackendConfig.threadPool().size());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION_CHECK_ENABLED,
+ elasticsearchBackendConfig.versionCheck().enabled());
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.QUERY_SHARD_FAILURE_IGNORE,
+ elasticsearchBackendConfig.query().shardFailure().ignore());
+
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_ENABLED,
+ elasticsearchBackendConfig.discovery().enabled());
+ if (elasticsearchBackendConfig.discovery().enabled()) {
+ addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_REFRESH_INTERVAL,
+ elasticsearchBackendConfig.discovery().refreshInterval().getSeconds());
+ }
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ //
+
+ // Index defaults at the backend level
+ contributeBackendIndexRuntimeProperties(propertyCollector, context, backendName, null,
+ elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults());
+
+ // Per-index properties
+ for (String indexName : indexNames) {
+ HibernateSearchBackendElasticsearchRuntimeConfig.IndexConfig indexConfig = elasticsearchBackendConfig == null ? null
+ : elasticsearchBackendConfig.indexes().get(indexName);
+ contributeBackendIndexRuntimeProperties(propertyCollector, context, backendName, indexName, indexConfig);
+ }
+ }
+
+ private void contributeBackendIndexRuntimeProperties(BiConsumer propertyCollector, MapperContext context,
+ String backendName, String indexName, HibernateSearchBackendElasticsearchRuntimeConfig.IndexConfig indexConfig) {
+ if (indexConfig != null) {
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS,
+ indexConfig.schemaManagement().requiredStatus());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT,
+ indexConfig.schemaManagement().requiredStatusWaitTimeout(), Optional::isPresent,
+ d -> d.get().toMillis());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.INDEXING_QUEUE_COUNT,
+ indexConfig.indexing().queueCount());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.INDEXING_QUEUE_SIZE,
+ indexConfig.indexing().queueSize());
+ addBackendIndexConfig(propertyCollector, backendName, indexName,
+ ElasticsearchIndexSettings.INDEXING_MAX_BULK_SIZE,
+ indexConfig.indexing().maxBulkSize());
+ }
+
+ // Settings that may default to a @SearchExtension-annotated-bean
+ //
+ }
+}
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchRuntimeConfig.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchRuntimeConfig.java
new file mode 100644
index 00000000000000..edc4ea1d38c8ea
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchBackendElasticsearchRuntimeConfig.java
@@ -0,0 +1,307 @@
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalInt;
+
+import org.hibernate.search.backend.elasticsearch.index.IndexStatus;
+import org.hibernate.search.engine.cfg.spi.ParseUtils;
+import org.hibernate.search.util.common.SearchException;
+
+import io.quarkus.runtime.annotations.ConfigDocDefault;
+import io.quarkus.runtime.annotations.ConfigDocMapKey;
+import io.quarkus.runtime.annotations.ConfigDocSection;
+import io.quarkus.runtime.annotations.ConfigGroup;
+import io.smallrye.config.WithDefault;
+import io.smallrye.config.WithParentName;
+
+@ConfigGroup
+public interface HibernateSearchBackendElasticsearchRuntimeConfig {
+ /**
+ * The list of hosts of the Elasticsearch servers.
+ */
+ @WithDefault("localhost:9200")
+ List hosts();
+
+ /**
+ * The protocol to use when contacting Elasticsearch servers.
+ * Set to "https" to enable SSL/TLS.
+ */
+ @WithDefault("http")
+ ElasticsearchClientProtocol protocol();
+
+ /**
+ * The username used for authentication.
+ */
+ Optional username();
+
+ /**
+ * The password used for authentication.
+ */
+ Optional password();
+
+ /**
+ * The timeout when establishing a connection to an Elasticsearch server.
+ */
+ @WithDefault("1S")
+ Duration connectionTimeout();
+
+ /**
+ * The timeout when reading responses from an Elasticsearch server.
+ */
+ @WithDefault("30S")
+ Duration readTimeout();
+
+ /**
+ * The timeout when executing a request to an Elasticsearch server.
+ *
+ * This includes the time needed to wait for a connection to be available,
+ * send the request and read the response.
+ *
+ * @asciidoclet
+ */
+ Optional requestTimeout();
+
+ /**
+ * The maximum number of connections to all the Elasticsearch servers.
+ */
+ @WithDefault("20")
+ int maxConnections();
+
+ /**
+ * The maximum number of connections per Elasticsearch server.
+ */
+ @WithDefault("10")
+ int maxConnectionsPerRoute();
+
+ /**
+ * Configuration for the automatic discovery of new Elasticsearch nodes.
+ */
+ DiscoveryConfig discovery();
+
+ /**
+ * Configuration for the thread pool assigned to the backend.
+ */
+ ThreadPoolConfig threadPool();
+
+ /**
+ * Configuration for search queries to this backend.
+ */
+ QueryConfig query();
+
+ /**
+ * Configuration for version checks on this backend.
+ */
+ VersionCheckConfig versionCheck();
+
+ /**
+ * The default configuration for the Elasticsearch indexes.
+ */
+ @WithParentName
+ IndexConfig indexDefaults();
+
+ /**
+ * Per-index configuration overrides.
+ */
+ @ConfigDocSection
+ @ConfigDocMapKey("index-name")
+ Map indexes();
+
+ enum ElasticsearchClientProtocol {
+ /**
+ * Use clear-text HTTP, with SSL/TLS disabled.
+ */
+ HTTP("http"),
+ /**
+ * Use HTTPS, with SSL/TLS enabled.
+ */
+ HTTPS("https");
+
+ public static ElasticsearchClientProtocol of(String value) {
+ return ParseUtils.parseDiscreteValues(
+ values(),
+ ElasticsearchClientProtocol::getHibernateSearchString,
+ (invalidValue, validValues) -> new SearchException(
+ String.format(
+ Locale.ROOT,
+ "Invalid protocol: '%1$s'. Valid protocols are: %2$s.",
+ invalidValue,
+ validValues)),
+ value);
+ }
+
+ private final String hibernateSearchString;
+
+ ElasticsearchClientProtocol(String hibernateSearchString) {
+ this.hibernateSearchString = hibernateSearchString;
+ }
+
+ public String getHibernateSearchString() {
+ return hibernateSearchString;
+ }
+ }
+
+ @ConfigGroup
+ interface VersionCheckConfig {
+ /**
+ * Whether Hibernate Search should check the version of the Elasticsearch cluster on startup.
+ *
+ * Set to `false` if the Elasticsearch cluster may not be available on startup.
+ *
+ * @asciidoclet
+ */
+ @WithDefault("true")
+ boolean enabled();
+ }
+
+ @ConfigGroup
+ interface IndexConfig {
+ /**
+ * Configuration for the schema management of the indexes.
+ */
+ SchemaManagementConfig schemaManagement();
+
+ /**
+ * Configuration for the indexing process that creates, updates and deletes documents.
+ */
+ IndexingConfig indexing();
+ }
+
+ @ConfigGroup
+ interface DiscoveryConfig {
+
+ /**
+ * Defines if automatic discovery is enabled.
+ */
+ @WithDefault("false")
+ Boolean enabled();
+
+ /**
+ * Refresh interval of the node list.
+ */
+ @WithDefault("10S")
+ Duration refreshInterval();
+
+ }
+
+ @ConfigGroup
+ interface ThreadPoolConfig {
+ /**
+ * The size of the thread pool assigned to the backend.
+ *
+ * Note that number is **per backend**, not per index.
+ * Adding more indexes will not add more threads.
+ *
+ * As all operations happening in this thread-pool are non-blocking,
+ * raising its size above the number of processor cores available to the JVM will not bring noticeable performance
+ * benefit.
+ * The only reason to alter this setting would be to reduce the number of threads;
+ * for example, in an application with a single index with a single indexing queue,
+ * running on a machine with 64 processor cores,
+ * you might want to bring down the number of threads.
+ *
+ * Defaults to the number of processor cores available to the JVM on startup.
+ *
+ * @asciidoclet
+ */
+ // We can't set an actual default value here: see comment on this class.
+ OptionalInt size();
+ }
+
+ @ConfigGroup
+ interface QueryConfig {
+ /**
+ * Configuration for the behavior on shard failure.
+ */
+ QueryShardFailureConfig shardFailure();
+ }
+
+ @ConfigGroup
+ interface QueryShardFailureConfig {
+ /**
+ * Whether partial shard failures are ignored (`true`)
+ * or lead to Hibernate Search throwing an exception (`false`).
+ */
+ @WithDefault("false")
+ boolean ignore();
+ }
+
+ // We can't set actual default values in this section,
+ // otherwise "quarkus.hibernate-search-orm.elasticsearch.index-defaults" will be ignored.
+ @ConfigGroup
+ interface SchemaManagementConfig {
+ /**
+ * The minimal https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cluster-health.html[Elasticsearch cluster
+ * status] required on startup.
+ *
+ * @asciidoclet
+ */
+ // We can't set an actual default value here: see comment on this class.
+ @ConfigDocDefault("yellow")
+ Optional requiredStatus();
+
+ /**
+ * How long we should wait for the status before failing the bootstrap.
+ */
+ // We can't set an actual default value here: see comment on this class.
+ @ConfigDocDefault("10S")
+ Optional requiredStatusWaitTimeout();
+ }
+
+ // We can't set actual default values in this section,
+ // otherwise "quarkus.hibernate-search-orm.elasticsearch.index-defaults" will be ignored.
+ @ConfigGroup
+ interface IndexingConfig {
+ /**
+ * The number of indexing queues assigned to each index.
+ *
+ * Higher values will lead to more connections being used in parallel,
+ * which may lead to higher indexing throughput,
+ * but incurs a risk of overloading Elasticsearch,
+ * i.e. of overflowing its HTTP request buffers and tripping
+ * https://www.elastic.co/guide/en/elasticsearch/reference/7.9/circuit-breaker.html[circuit breakers],
+ * leading to Elasticsearch giving up on some request and resulting in indexing failures.
+ *
+ * @asciidoclet
+ */
+ // We can't set an actual default value here: see comment on this class.
+ @ConfigDocDefault("10")
+ OptionalInt queueCount();
+
+ /**
+ * The size of indexing queues.
+ *
+ * Lower values may lead to lower memory usage, especially if there are many queues,
+ * but values that are too low will reduce the likeliness of reaching the max bulk size
+ * and increase the likeliness of application threads blocking because the queue is full,
+ * which may lead to lower indexing throughput.
+ *
+ * @asciidoclet
+ */
+ // We can't set an actual default value here: see comment on this class.
+ @ConfigDocDefault("1000")
+ OptionalInt queueSize();
+
+ /**
+ * The maximum size of bulk requests created when processing indexing queues.
+ *
+ * Higher values will lead to more documents being sent in each HTTP request sent to Elasticsearch,
+ * which may lead to higher indexing throughput,
+ * but incurs a risk of overloading Elasticsearch,
+ * i.e. of overflowing its HTTP request buffers and tripping
+ * https://www.elastic.co/guide/en/elasticsearch/reference/7.9/circuit-breaker.html[circuit breakers],
+ * leading to Elasticsearch giving up on some request and resulting in indexing failures.
+ *
+ * Note that raising this number above the queue size has no effect,
+ * as bulks cannot include more requests than are contained in the queue.
+ *
+ * @asciidoclet
+ */
+ // We can't set an actual default value here: see comment on this class.
+ @ConfigDocDefault("100")
+ OptionalInt maxBulkSize();
+ }
+}
diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchConfigUtil.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchConfigUtil.java
similarity index 83%
rename from extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchConfigUtil.java
rename to extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchConfigUtil.java
index ca3a721bd8e053..069ede0d9f1697 100644
--- a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchConfigUtil.java
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/HibernateSearchConfigUtil.java
@@ -1,7 +1,10 @@
-package io.quarkus.hibernate.search.standalone.elasticsearch.runtime;
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
+import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
@@ -66,4 +69,15 @@ public static void addBackendIndexConfig(BiConsumer property
}
}
}
+
+ public static void mergeInto(Map> target, Map> source) {
+ for (Map.Entry> entry : source.entrySet()) {
+ mergeInto(target, entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static void mergeInto(Map> target, String key, Set values) {
+ target.computeIfAbsent(key, ignored -> new LinkedHashSet<>())
+ .addAll(values);
+ }
}
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/MapperContext.java b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/MapperContext.java
new file mode 100644
index 00000000000000..84713c59e06c7c
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/runtime/MapperContext.java
@@ -0,0 +1,26 @@
+package io.quarkus.hibernate.search.backend.elasticsearch.runtime;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.hibernate.search.engine.environment.bean.BeanReference;
+
+public interface MapperContext {
+
+ String toString();
+
+ Set backendNamesForIndexedEntities();
+
+ Map> backendAndIndexNamesForSearchExtensions();
+
+ String backendPropertyKey(String backendName, String indexName, String propertyKeyRadical);
+
+ Optional> singleExtensionBeanReferenceFor(Optional override, Class beanType,
+ String backendName, String indexName);
+
+ Optional>> multiExtensionBeanReferencesFor(Optional> override,
+ Class beanType,
+ String backendName, String indexName);
+}
diff --git a/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 00000000000000..02c08c6270b4c6
--- /dev/null
+++ b/extensions/hibernate-search-backend-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,18 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "Hibernate Search + Elasticsearch"
+metadata:
+ keywords:
+ - "hibernate-search-elasticsearch"
+ - "search"
+ - "full-text"
+ - "hibernate"
+ - "orm"
+ - "hibernate-orm"
+ - "hibernate-search-orm"
+ - "elasticsearch"
+ - "opensearch"
+ guide: "https://quarkus.io/guides/hibernate-search-orm-elasticsearch"
+ categories:
+ - "data"
+ status: "stable"
diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
index dcd3adfbb183a2..67cc4cb7aab9fb 100644
--- a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
+++ b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
@@ -25,6 +25,10 @@
io.quarkus
quarkus-elasticsearch-rest-client-common-deployment