diff --git a/bom/application/pom.xml b/bom/application/pom.xml index f8c9c70c6804f..630e1ad416c5a 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -1378,6 +1378,16 @@ quarkus-mongodb-panache-common-deployment ${project.version} + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common + ${project.version} + + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common-deployment + ${project.version} + io.quarkus quarkus-hibernate-search-orm-elasticsearch diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index 95fd35896d6c3..d93441834129b 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -954,6 +954,19 @@ + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-hibernate-search-orm-elasticsearch diff --git a/docs/pom.xml b/docs/pom.xml index d5e96e352d096..25280e567b7c7 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -965,6 +965,19 @@ + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-hibernate-search-orm-elasticsearch-deployment diff --git a/extensions/hibernate-search-backend-elasticsearch-common/deployment/pom.xml b/extensions/hibernate-search-backend-elasticsearch-common/deployment/pom.xml new file mode 100644 index 0000000000000..e0633ca755425 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/deployment/pom.xml @@ -0,0 +1,52 @@ + + + + quarkus-hibernate-search-backend-elasticsearch-common-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-hibernate-search-backend-elasticsearch-common-deployment + Quarkus - Hibernate Search - Elasticsearch - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-elasticsearch-rest-client-common-deployment + + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + maven-surefire-plugin + + true + + + + + diff --git a/extensions/hibernate-search-backend-elasticsearch-common/deployment/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/deployment/HibernateSearchBackendElasticsearchEnabledBuildItem.java b/extensions/hibernate-search-backend-elasticsearch-common/deployment/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/deployment/HibernateSearchBackendElasticsearchEnabledBuildItem.java new file mode 100644 index 0000000000000..9a2b8b4d037a6 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/deployment/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/deployment/HibernateSearchBackendElasticsearchEnabledBuildItem.java @@ -0,0 +1,28 @@ +package io.quarkus.hibernate.search.backend.elasticsearch.common.deployment; + +import java.util.Map; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchBuildTimeConfig; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.MapperContext; + +public final class HibernateSearchBackendElasticsearchEnabledBuildItem extends MultiBuildItem { + + private final MapperContext mapperContext; + private final Map buildTimeConfig; + + public HibernateSearchBackendElasticsearchEnabledBuildItem(MapperContext mapperContext, + Map buildTimeConfig) { + this.mapperContext = mapperContext; + this.buildTimeConfig = buildTimeConfig; + } + + public MapperContext getMapperContext() { + return mapperContext; + } + + public Map getBuildTimeConfig() { + return buildTimeConfig; + } + +} diff --git a/extensions/hibernate-search-backend-elasticsearch-common/deployment/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/deployment/HibernateSearchBackendElasticsearchProcessor.java b/extensions/hibernate-search-backend-elasticsearch-common/deployment/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/deployment/HibernateSearchBackendElasticsearchProcessor.java new file mode 100644 index 0000000000000..92cde248d1126 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/deployment/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/deployment/HibernateSearchBackendElasticsearchProcessor.java @@ -0,0 +1,153 @@ +package io.quarkus.hibernate.search.backend.elasticsearch.common.deployment; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; + +import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer; +import org.hibernate.search.backend.elasticsearch.gson.spi.GsonClasses; +import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy; + +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchBuildTimeConfig; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.MapperContext; +import io.quarkus.runtime.configuration.ConfigurationException; + +@BuildSteps +class HibernateSearchBackendElasticsearchProcessor { + + @BuildStep + void registerBeans(List enabled, + BuildProducer unremovableBean) { + if (enabled.isEmpty()) { + return; + } + // Some user-injectable beans are retrieved programmatically and shouldn't be removed + unremovableBean.produce(UnremovableBeanBuildItem.beanTypes(ElasticsearchAnalysisConfigurer.class, + IndexLayoutStrategy.class)); + } + + @BuildStep + void registerReflectionForGson(List enabled, + BuildProducer reflectiveClass) { + if (enabled.isEmpty()) { + return; + } + String[] reflectiveClasses = GsonClasses.typesRequiringReflection().toArray(String[]::new); + reflectiveClass.produce(ReflectiveClassBuildItem.builder(reflectiveClasses) + .reason(getClass().getName()) + .methods().fields().build()); + } + + @BuildStep + void processBuildTimeConfig(List enabled, + ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer nativeImageResources, + BuildProducer hotDeploymentWatchedFiles) { + if (enabled.isEmpty()) { + return; + } + for (HibernateSearchBackendElasticsearchEnabledBuildItem enabledItem : enabled) { + processBuildTimeConfig(enabledItem, applicationArchivesBuildItem, nativeImageResources, + hotDeploymentWatchedFiles); + } + } + + private void processBuildTimeConfig(HibernateSearchBackendElasticsearchEnabledBuildItem enabled, + ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer nativeImageResources, + BuildProducer hotDeploymentWatchedFiles) { + Set propertyKeysWithNoVersion = new LinkedHashSet<>(); + var buildTimeConfig = enabled.getBuildTimeConfig(); + + var mapperContext = enabled.getMapperContext(); + Set allBackendNames = new LinkedHashSet<>(mapperContext.getBackendNamesForIndexedEntities()); + allBackendNames.addAll(buildTimeConfig.keySet()); + // For all backends referenced either through @Indexed(backend = ...) or configuration... + for (String backendName : allBackendNames) { + HibernateSearchBackendElasticsearchBuildTimeConfig backendConfig = buildTimeConfig.get(backendName); + // ... we validate that the backend is configured and the version is present + if (backendConfig == null || backendConfig.version().isEmpty()) { + propertyKeysWithNoVersion.add(mapperContext.backendPropertyKey(backendName, null, "version")); + } + if (backendConfig == null) { + continue; + } + // ... we register files referenced from backends configuration + registerClasspathFilesFromBackendConfig(mapperContext, backendName, backendConfig, + applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); + } + if (!propertyKeysWithNoVersion.isEmpty()) { + throw new ConfigurationException( + "The Elasticsearch version needs to be defined via properties: " + + String.join(", ", propertyKeysWithNoVersion) + ".", + propertyKeysWithNoVersion); + } + } + + private static void registerClasspathFilesFromBackendConfig(MapperContext mapperContext, String backendName, + HibernateSearchBackendElasticsearchBuildTimeConfig backendConfig, + ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer nativeImageResources, + BuildProducer hotDeploymentWatchedFiles) { + registerClasspathFilesFromIndexConfig(mapperContext, backendName, null, backendConfig.indexDefaults(), + applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); + for (Entry entry : backendConfig.indexes() + .entrySet()) { + String indexName = entry.getKey(); + HibernateSearchBackendElasticsearchBuildTimeConfig.IndexConfig indexConfig = entry.getValue(); + registerClasspathFilesFromIndexConfig(mapperContext, backendName, indexName, indexConfig, + applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); + } + } + + private static void registerClasspathFilesFromIndexConfig(MapperContext mapperContext, String backendName, String indexName, + HibernateSearchBackendElasticsearchBuildTimeConfig.IndexConfig indexConfig, + ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer nativeImageResources, + BuildProducer hotDeploymentWatchedFiles) { + registerClasspathFileFromConfig(mapperContext, backendName, indexName, "schema-management.settings-file", + indexConfig.schemaManagement().settingsFile(), + applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); + registerClasspathFileFromConfig(mapperContext, backendName, indexName, "schema-management.mapping-file", + indexConfig.schemaManagement().mappingFile(), + applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); + } + + private static void registerClasspathFileFromConfig(MapperContext mapperContext, String backendName, String indexName, + String propertyKeyRadical, + Optional classpathFileOptional, + ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer nativeImageResources, + BuildProducer 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-common/pom.xml b/extensions/hibernate-search-backend-elasticsearch-common/pom.xml new file mode 100644 index 0000000000000..6b16ca71abe6c --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-hibernate-search-backend-elasticsearch-common-parent + Quarkus - Hibernate Search - Elasticsearch + pom + + deployment + runtime + + diff --git a/extensions/hibernate-search-backend-elasticsearch-common/runtime/pom.xml b/extensions/hibernate-search-backend-elasticsearch-common/runtime/pom.xml new file mode 100644 index 0000000000000..5479128e11b0b --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/runtime/pom.xml @@ -0,0 +1,55 @@ + + + + quarkus-hibernate-search-backend-elasticsearch-common-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-hibernate-search-backend-elasticsearch-common + 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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/ElasticsearchVersionSubstitution.java similarity index 86% 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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/ElasticsearchVersionSubstitution.java index 1bd9dfd6e900d..5f519234d3329 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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/ElasticsearchVersionSubstitution.java @@ -1,4 +1,4 @@ -package io.quarkus.hibernate.search.orm.elasticsearch.runtime; +package io.quarkus.hibernate.search.backend.elasticsearch.common.runtime; import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion; diff --git a/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchBuildTimeConfig.java b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchBuildTimeConfig.java new file mode 100644 index 0000000000000..97310b905c87e --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchBuildTimeConfig.java @@ -0,0 +1,123 @@ +package io.quarkus.hibernate.search.backend.elasticsearch.common.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(); + + /** + * 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:#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(); + } +} diff --git a/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchConfigHandler.java b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchConfigHandler.java new file mode 100644 index 0000000000000..062d61c186ad6 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchConfigHandler.java @@ -0,0 +1,195 @@ +package io.quarkus.hibernate.search.backend.elasticsearch.common.runtime; + +import static io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchConfigUtil.addBackendConfig; +import static io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchConfigUtil.addBackendIndexConfig; +import static io.quarkus.hibernate.search.backend.elasticsearch.common.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.common.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.getBackendAndIndexNamesForSearchExtensions()); + 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 + // + + // 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.getBackendAndIndexNamesForSearchExtensions()); + 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 + 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 + 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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchRuntimeConfig.java b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchRuntimeConfig.java new file mode 100644 index 0000000000000..0125788d74225 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchBackendElasticsearchRuntimeConfig.java @@ -0,0 +1,351 @@ +package io.quarkus.hibernate.search.backend.elasticsearch.common.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(); + + /** + * Configuration for the index layout. + */ + LayoutConfig layout(); + + 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(); + } + + @ConfigGroup + interface LayoutConfig { + /** + * A xref:#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-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchConfigUtil.java b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchConfigUtil.java index ca3a721bd8e05..46518474b082c 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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/HibernateSearchConfigUtil.java @@ -1,7 +1,10 @@ -package io.quarkus.hibernate.search.standalone.elasticsearch.runtime; +package io.quarkus.hibernate.search.backend.elasticsearch.common.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-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/MapperContext.java b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/MapperContext.java new file mode 100644 index 0000000000000..91bf5a46b55d4 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/java/io/quarkus/hibernate/search/backend/elasticsearch/common/runtime/MapperContext.java @@ -0,0 +1,26 @@ +package io.quarkus.hibernate.search.backend.elasticsearch.common.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 getBackendNamesForIndexedEntities(); + + Map> getBackendAndIndexNamesForSearchExtensions(); + + 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-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..f9e341e387569 --- /dev/null +++ b/extensions/hibernate-search-backend-elasticsearch-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,6 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Hibernate Search + Elasticsearch" +metadata: + unlisted: true + status: "stable" \ No newline at end of file diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml index 6862482c6010a..3bc1b3c964d42 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 + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common-deployment + io.quarkus quarkus-hibernate-search-orm-elasticsearch diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem.java b/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem.java index 715865766f37f..6d29fec23da46 100644 --- a/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem.java +++ b/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem.java @@ -1,49 +1,31 @@ package io.quarkus.hibernate.search.orm.elasticsearch.deployment; -import java.util.Map; -import java.util.Set; - import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit; +import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchOrmElasticsearchMapperContext; public final class HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem extends MultiBuildItem { - private final String persistenceUnitName; + public final HibernateSearchOrmElasticsearchMapperContext mapperContext; private final HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig; - private final Set backendNamesForIndexedEntities; - private Map> backendAndIndexNamesForSearchExtensions; - public HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem(String persistenceUnitName, - HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig, - Set backendNamesForIndexedEntities, - Map> backendAndIndexNamesForSearchExtensions) { - if (persistenceUnitName == null) { - throw new IllegalArgumentException("persistenceUnitName cannot be null"); - } - this.persistenceUnitName = persistenceUnitName; + public HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem( + HibernateSearchOrmElasticsearchMapperContext mapperContext, + HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig) { + this.mapperContext = mapperContext; this.buildTimeConfig = buildTimeConfig; - this.backendNamesForIndexedEntities = backendNamesForIndexedEntities; - this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; } public String getPersistenceUnitName() { - return persistenceUnitName; + return mapperContext.persistenceUnitName; } public HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit getBuildTimeConfig() { return buildTimeConfig; } - public Set getBackendNamesForIndexedEntities() { - return backendNamesForIndexedEntities; - } - - public Map> getBackendAndIndexNamesForSearchExtensions() { - return backendAndIndexNamesForSearchExtensions; - } - @Override public String toString() { - return getClass().getSimpleName() + " [" + persistenceUnitName + "]"; + return getClass().getSimpleName() + " [" + getPersistenceUnitName() + "]"; } } diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java b/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java index 417947ff23bea..72d3945b3d6c5 100644 --- a/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java +++ b/extensions/hibernate-search-orm-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/deployment/HibernateSearchElasticsearchProcessor.java @@ -5,12 +5,9 @@ import static io.quarkus.hibernate.search.orm.elasticsearch.deployment.ClassNames.ROOT_MAPPING; import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.backendPropertyKey; import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.defaultBackendPropertyKeys; -import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.elasticsearchVersionPropertyKey; import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.mapperPropertyKey; import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.mapperPropertyKeys; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -18,14 +15,10 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion; -import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer; -import org.hibernate.search.backend.elasticsearch.gson.spi.GsonClasses; -import org.hibernate.search.backend.elasticsearch.index.layout.IndexLayoutStrategy; import org.hibernate.search.engine.reporting.FailureHandler; import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategy; import org.hibernate.search.mapper.pojo.work.IndexingPlanSynchronizationStrategy; @@ -42,12 +35,10 @@ import io.quarkus.deployment.annotations.BuildSteps; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.DevServicesAdditionalConfigBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.JandexUtil; import io.quarkus.elasticsearch.restclient.common.deployment.DevservicesElasticsearchBuildItem; @@ -58,15 +49,14 @@ import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticInitListener; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.ElasticsearchVersionSubstitution; +import io.quarkus.hibernate.search.backend.elasticsearch.common.deployment.HibernateSearchBackendElasticsearchEnabledBuildItem; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.ElasticsearchVersionSubstitution; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfig; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.ElasticsearchBackendBuildTimeConfig; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.ElasticsearchIndexBuildTimeConfig; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRecorder; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig; +import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchOrmElasticsearchMapperContext; import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.vertx.http.deployment.spi.RouteBuildItem; @BuildSteps(onlyIf = HibernateSearchEnabled.class) @@ -82,7 +72,6 @@ public void build(HibernateSearchElasticsearchRecorder recorder, CombinedIndexBuildItem combinedIndexBuildItem, HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig, List persistenceUnitDescriptorBuildItems, - BuildProducer reflectiveClass, BuildProducer configuredPersistenceUnits, BuildProducer staticIntegrations, BuildProducer runtimeIntegrations, @@ -112,8 +101,6 @@ public void build(HibernateSearchElasticsearchRecorder recorder, backendAndIndexNamesForSearchExtensions, configuredPersistenceUnits, staticIntegrations, runtimeIntegrations); } - - reflectiveClass.produce(registerReflectionForGson()); } private static Map>> collectPersistenceUnitAndBackendAndIndexNamesForSearchExtensions( @@ -161,8 +148,20 @@ private void buildForPersistenceUnit(HibernateSearchElasticsearchRecorder record } configuredPersistenceUnits - .produce(new HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem(persistenceUnitName, puConfig, - backendNamesForIndexedEntities, backendAndIndexNamesForSearchExtensions)); + .produce(new HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem( + new HibernateSearchOrmElasticsearchMapperContext(persistenceUnitName, + backendNamesForIndexedEntities, backendAndIndexNamesForSearchExtensions), + puConfig)); + } + + @BuildStep + void enableBackend(List enabledPUs, + BuildProducer elasticsearchEnabled) { + for (HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem enabled : enabledPUs) { + var buildTimeConfig = enabled.getBuildTimeConfig(); + elasticsearchEnabled.produce(new HibernateSearchBackendElasticsearchEnabledBuildItem(enabled.mapperContext, + buildTimeConfig == null ? Collections.emptyMap() : buildTimeConfig.backends())); + } } @BuildStep @@ -174,8 +173,7 @@ void registerBeans(List configuredPersistenceUnits, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - for (HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem configuredPersistenceUnit : configuredPersistenceUnits) { - processPersistenceUnitBuildTimeConfig(configuredPersistenceUnit, applicationArchivesBuildItem, nativeImageResources, - hotDeploymentWatchedFiles); - } - } - - private void processPersistenceUnitBuildTimeConfig( - HibernateSearchElasticsearchPersistenceUnitConfiguredBuildItem configuredPersistenceUnit, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - String persistenceUnitName = configuredPersistenceUnit.getPersistenceUnitName(); - HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig = configuredPersistenceUnit - .getBuildTimeConfig(); - - Set propertyKeysWithNoVersion = new LinkedHashSet<>(); - Map backends = buildTimeConfig != null - ? buildTimeConfig.backends() - : Collections.emptyMap(); - - Set allBackendNames = new LinkedHashSet<>(configuredPersistenceUnit.getBackendNamesForIndexedEntities()); - allBackendNames.addAll(backends.keySet()); - // For all backends referenced either through @Indexed(backend = ...) or configuration... - for (String backendName : allBackendNames) { - ElasticsearchBackendBuildTimeConfig backendConfig = backends.get(backendName); - // ... we validate that the backend is configured and the version is present - if (backendConfig == null || backendConfig.version().isEmpty()) { - propertyKeysWithNoVersion.add(elasticsearchVersionPropertyKey(persistenceUnitName, backendName)); - } - // ... we register files referenced from backends configuration - if (backendConfig != null) { - registerClasspathFileFromBackendConfig(persistenceUnitName, backendName, backendConfig, - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - } + recorder.createRuntimeInitListener(configuredPersistenceUnit.mapperContext, + runtimeConfig, integrationRuntimeInitListeners))); } - if (!propertyKeysWithNoVersion.isEmpty()) { - throw new ConfigurationException( - "The Elasticsearch version needs to be defined via properties: " - + String.join(", ", propertyKeysWithNoVersion) + ".", - propertyKeysWithNoVersion); - } - } - - private static void registerClasspathFileFromBackendConfig(String persistenceUnitName, String backendName, - ElasticsearchBackendBuildTimeConfig backendConfig, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - registerClasspathFileFromIndexConfig(persistenceUnitName, backendName, null, backendConfig.indexDefaults(), - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - for (Entry entry : backendConfig.indexes().entrySet()) { - String indexName = entry.getKey(); - ElasticsearchIndexBuildTimeConfig indexConfig = entry.getValue(); - registerClasspathFileFromIndexConfig(persistenceUnitName, backendName, indexName, indexConfig, - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - } - } - - private static void registerClasspathFileFromIndexConfig(String persistenceUnitName, String backendName, String indexName, - ElasticsearchIndexBuildTimeConfig indexConfig, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - registerClasspathFileFromConfig(persistenceUnitName, backendName, indexName, "schema-management.settings-file", - indexConfig.schemaManagement().settingsFile(), - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - registerClasspathFileFromConfig(persistenceUnitName, backendName, indexName, "schema-management.mapping-file", - indexConfig.schemaManagement().mappingFile(), - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - } - - private static void registerClasspathFileFromConfig(String persistenceUnitName, String backendName, String indexName, - String propertyKeyRadical, - Optional classpathFileOptional, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer 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 '" - + backendPropertyKey(persistenceUnitName, backendName, indexName, propertyKeyRadical) + "=" - + classpathFile - + "'. Remove property or add file to your path."); - } - nativeImageResources.produce(new NativeImageResourceBuildItem(classpathFile)); - hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(classpathFile)); - } - - private ReflectiveClassBuildItem registerReflectionForGson() { - String[] reflectiveClasses = GsonClasses.typesRequiringReflection().toArray(String[]::new); - return ReflectiveClassBuildItem.builder(reflectiveClasses) - .reason(getClass().getName()) - .methods().fields().build(); } @BuildStep(onlyIfNot = IsNormal.class) diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/pom.xml b/extensions/hibernate-search-orm-elasticsearch/runtime/pom.xml index 12b6c1e1dd0b0..4653413c7abb8 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/pom.xml +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/pom.xml @@ -26,8 +26,8 @@ quarkus-elasticsearch-rest-client-common - org.hibernate.search - hibernate-search-backend-elasticsearch + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common org.hibernate.search diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchConfigUtil.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchConfigUtil.java index fbd368e354c38..90ce860706171 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchConfigUtil.java +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchConfigUtil.java @@ -8,6 +8,10 @@ import org.hibernate.search.engine.cfg.BackendSettings; import org.hibernate.search.engine.cfg.IndexSettings; +/** + * @deprecated Use {@link io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchConfigUtil} instead. + */ +@Deprecated public class HibernateSearchConfigUtil { public static void addConfig(BiConsumer propertyCollector, String configPath, T value) { diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.java index 6970ffb3363eb..965cf0bc1bdee 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.java +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.java @@ -4,27 +4,25 @@ import java.util.Map; import java.util.Optional; -import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion; - +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchBuildTimeConfig; 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.WithName; -import io.smallrye.config.WithParentName; import io.smallrye.config.WithUnnamedKey; @ConfigGroup public interface HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit { /** - * Configuration for backends. + * Configuration for Elasticsearch/OpenSearch backends. */ @ConfigDocSection @WithName("elasticsearch") @WithUnnamedKey // The default backend has the null key @ConfigDocMapKey("backend-name") - Map backends(); + Map backends(); /** * A xref:hibernate-search-orm-elasticsearch.adoc#bean-reference-note-anchor[bean reference] to a component @@ -62,117 +60,6 @@ public interface HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit { */ MappingConfig mapping(); - @ConfigGroup - interface ElasticsearchBackendBuildTimeConfig { - /** - * 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(); - - /** - * The default configuration for the Elasticsearch indexes. - */ - @WithParentName - ElasticsearchIndexBuildTimeConfig indexDefaults(); - - /** - * Per-index configuration overrides. - */ - @ConfigDocSection - @ConfigDocMapKey("index-name") - Map indexes(); - } - - @ConfigGroup - interface ElasticsearchIndexBuildTimeConfig { - /** - * 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:hibernate-search-orm-elasticsearch.adoc#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:hibernate-search-orm-elasticsearch.adoc#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 CoordinationConfig { diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java index 62431de35b455..09a44b25e8aec 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java @@ -1,12 +1,9 @@ package io.quarkus.hibernate.search.orm.elasticsearch.runtime; -import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendConfig; -import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendIndexConfig; -import static io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchConfigUtil.addConfig; +import static io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchConfigUtil.addConfig; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -23,11 +20,6 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.boot.spi.BootstrapContext; -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 org.hibernate.search.engine.cfg.EngineSettings; import org.hibernate.search.engine.environment.bean.BeanReference; import org.hibernate.search.engine.reporting.FailureHandler; @@ -46,10 +38,7 @@ import io.quarkus.arc.Arc; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticInitListener; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.ElasticsearchBackendBuildTimeConfig; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit.ElasticsearchIndexBuildTimeConfig; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.ElasticsearchBackendRuntimeConfig; -import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.ElasticsearchIndexRuntimeConfig; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchConfigHandler; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.bean.HibernateSearchBeanUtil; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.management.HibernateSearchManagementHandler; import io.quarkus.hibernate.search.orm.elasticsearch.runtime.mapping.QuarkusHibernateOrmSearchMappingConfigurer; @@ -62,8 +51,8 @@ public class HibernateSearchElasticsearchRecorder { public HibernateOrmIntegrationStaticInitListener createStaticInitListener( - String persistenceUnitName, HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig, - Map> backendAndIndexNamesForSearchExtensions, + HibernateSearchOrmElasticsearchMapperContext mapperContext, + HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig, Set rootAnnotationMappedClassNames, List integrationStaticInitListeners) { Set> rootAnnotationMappedClasses = new LinkedHashSet<>(); @@ -75,9 +64,9 @@ public HibernateOrmIntegrationStaticInitListener createStaticInitListener( throw new IllegalStateException("Could not initialize mapped class " + className, e); } } - return new HibernateSearchIntegrationStaticInitListener(persistenceUnitName, - buildTimeConfig.persistenceUnits().get(persistenceUnitName), - backendAndIndexNamesForSearchExtensions, rootAnnotationMappedClasses, + return new HibernateSearchIntegrationStaticInitListener(mapperContext, + buildTimeConfig.persistenceUnits().get(mapperContext.persistenceUnitName), + rootAnnotationMappedClasses, integrationStaticInitListeners); } @@ -86,13 +75,13 @@ public HibernateOrmIntegrationStaticInitListener createStaticInitInactiveListene } public HibernateOrmIntegrationRuntimeInitListener createRuntimeInitListener( - HibernateSearchElasticsearchRuntimeConfig runtimeConfig, String persistenceUnitName, - Map> backendAndIndexNamesForSearchExtensions, + HibernateSearchOrmElasticsearchMapperContext mapperContext, + HibernateSearchElasticsearchRuntimeConfig runtimeConfig, List integrationRuntimeInitListeners) { HibernateSearchElasticsearchRuntimeConfigPersistenceUnit puConfig = runtimeConfig.persistenceUnits() - .get(persistenceUnitName); - return new HibernateSearchIntegrationRuntimeInitListener(persistenceUnitName, puConfig, - backendAndIndexNamesForSearchExtensions, integrationRuntimeInitListeners); + .get(mapperContext.persistenceUnitName); + return new HibernateSearchIntegrationRuntimeInitListener(mapperContext, puConfig, + integrationRuntimeInitListeners); } public void checkNoExplicitActiveTrue(HibernateSearchElasticsearchRuntimeConfig runtimeConfig) { @@ -191,20 +180,19 @@ public void onMetadataInitialized(Metadata metadata, BootstrapContext bootstrapC private static final class HibernateSearchIntegrationStaticInitListener implements HibernateOrmIntegrationStaticInitListener { + private final HibernateSearchOrmElasticsearchMapperContext mapperContext; private final String persistenceUnitName; private final HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig; - private final Map> backendAndIndexNamesForSearchExtensions; private final Set> rootAnnotationMappedClasses; private final List integrationStaticInitListeners; - private HibernateSearchIntegrationStaticInitListener(String persistenceUnitName, + private HibernateSearchIntegrationStaticInitListener(HibernateSearchOrmElasticsearchMapperContext mapperContext, HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig, - Map> backendAndIndexNamesForSearchExtensions, Set> rootAnnotationMappedClasses, List integrationStaticInitListeners) { - this.persistenceUnitName = persistenceUnitName; + this.mapperContext = mapperContext; + this.persistenceUnitName = mapperContext.persistenceUnitName; this.buildTimeConfig = buildTimeConfig; - this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; this.rootAnnotationMappedClasses = rootAnnotationMappedClasses; this.integrationStaticInitListeners = integrationStaticInitListeners; } @@ -227,25 +215,9 @@ public void contributeBootProperties(BiConsumer propertyCollecto buildTimeConfig == null ? Optional.empty() : buildTimeConfig.coordination().strategy(), CoordinationStrategy.class, persistenceUnitName, null, null)); - // 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 backendConfigs = buildTimeConfig == null - ? Collections.emptyMap() - : buildTimeConfig.backends(); - Map> backendAndIndexNames = new LinkedHashMap<>(); - mergeInto(backendAndIndexNames, 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, backendName, indexNames, - backendConfigs.get(backendName)); - } + HibernateSearchBackendElasticsearchConfigHandler.contributeBackendBuildTimeProperties( + propertyCollector, mapperContext, + buildTimeConfig == null ? Collections.emptyMap() : buildTimeConfig.backends()); for (HibernateOrmIntegrationStaticInitListener listener : integrationStaticInitListeners) { listener.contributeBootProperties(propertyCollector); @@ -283,47 +255,6 @@ public void onMetadataInitialized(Metadata metadata, BootstrapContext bootstrapC listener.onMetadataInitialized(metadata, bootstrapContext, propertyCollector); } } - - private void contributeBackendBuildTimeProperties(BiConsumer propertyCollector, String backendName, - Set indexNames, - ElasticsearchBackendBuildTimeConfig elasticsearchBackendConfig) { - addBackendConfig(propertyCollector, backendName, BackendSettings.TYPE, - ElasticsearchBackendSettings.TYPE_NAME); - if (elasticsearchBackendConfig != null) { - addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION, - elasticsearchBackendConfig.version()); - } - - // Index defaults at the backend level - contributeBackendIndexBuildTimeProperties(propertyCollector, backendName, null, - elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults()); - - // Per-index properties - for (String indexName : indexNames) { - ElasticsearchIndexBuildTimeConfig indexConfig = elasticsearchBackendConfig == null ? null - : elasticsearchBackendConfig.indexes().get(indexName); - contributeBackendIndexBuildTimeProperties(propertyCollector, backendName, indexName, indexConfig); - } - } - - private void contributeBackendIndexBuildTimeProperties(BiConsumer propertyCollector, - String backendName, String indexName, ElasticsearchIndexBuildTimeConfig 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, - HibernateSearchBeanUtil.multiExtensionBeanReferencesFor( - indexConfig == null ? Optional.empty() : indexConfig.analysis().configurer(), - ElasticsearchAnalysisConfigurer.class, persistenceUnitName, backendName, indexName)); - } } private static final class HibernateSearchIntegrationRuntimeInitInactiveListener @@ -351,18 +282,17 @@ public List> contributeServiceInitiators() { private static final class HibernateSearchIntegrationRuntimeInitListener implements HibernateOrmIntegrationRuntimeInitListener { + private final HibernateSearchOrmElasticsearchMapperContext mapperContext; private final String persistenceUnitName; private final HibernateSearchElasticsearchRuntimeConfigPersistenceUnit runtimeConfig; - private final Map> backendAndIndexNamesForSearchExtensions; private final List integrationRuntimeInitListeners; - private HibernateSearchIntegrationRuntimeInitListener(String persistenceUnitName, + private HibernateSearchIntegrationRuntimeInitListener(HibernateSearchOrmElasticsearchMapperContext mapperContext, HibernateSearchElasticsearchRuntimeConfigPersistenceUnit runtimeConfig, - Map> backendAndIndexNamesForSearchExtensions, List integrationRuntimeInitListeners) { - this.persistenceUnitName = persistenceUnitName; + this.mapperContext = mapperContext; + this.persistenceUnitName = mapperContext.persistenceUnitName; this.runtimeConfig = runtimeConfig; - this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; this.integrationRuntimeInitListeners = integrationRuntimeInitListeners; } @@ -405,113 +335,15 @@ public void contributeRuntimeProperties(BiConsumer propertyColle : runtimeConfig.automaticIndexing().synchronization().strategy(), AutomaticIndexingSynchronizationStrategy.class, persistenceUnitName, null, null)); - // 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 backendConfigs = runtimeConfig == null - ? Collections.emptyMap() - : runtimeConfig.backends(); - Map> backendAndIndexNames = new LinkedHashMap<>(); - mergeInto(backendAndIndexNames, 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, backendName, indexNames, - backendConfigs.get(backendName)); - } + HibernateSearchBackendElasticsearchConfigHandler.contributeBackendRuntimeProperties( + propertyCollector, mapperContext, + runtimeConfig == null ? Collections.emptyMap() : runtimeConfig.backends()); for (HibernateOrmIntegrationRuntimeInitListener integrationRuntimeInitListener : integrationRuntimeInitListeners) { integrationRuntimeInitListener.contributeRuntimeProperties(propertyCollector); } } - private void contributeBackendRuntimeProperties(BiConsumer propertyCollector, String backendName, - Set indexNames, ElasticsearchBackendRuntimeConfig 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 - addBackendConfig(propertyCollector, backendName, - ElasticsearchBackendSettings.LAYOUT_STRATEGY, - HibernateSearchBeanUtil.singleExtensionBeanReferenceFor( - elasticsearchBackendConfig.layout().strategy(), - IndexLayoutStrategy.class, persistenceUnitName, backendName, null)); - } - - // Settings that may default to a @SearchExtension-annotated-bean - // - - // Index defaults at the backend level - contributeBackendIndexRuntimeProperties(propertyCollector, backendName, null, - elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults()); - - // Per-index properties - for (String indexName : indexNames) { - ElasticsearchIndexRuntimeConfig indexConfig = elasticsearchBackendConfig == null ? null - : elasticsearchBackendConfig.indexes().get(indexName); - contributeBackendIndexRuntimeProperties(propertyCollector, backendName, indexName, indexConfig); - } - } - - private void contributeBackendIndexRuntimeProperties(BiConsumer propertyCollector, - String backendName, String indexName, ElasticsearchIndexRuntimeConfig 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 - // - } - @Override public List> contributeServiceInitiators() { return List.of( diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java index 4de16e8e61bc0..6817febb71aee 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfigPersistenceUnit.java @@ -1,25 +1,19 @@ package io.quarkus.hibernate.search.orm.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.mapper.orm.schema.management.SchemaManagementStrategyName; import org.hibernate.search.mapper.orm.search.loading.EntityLoadingCacheLookupStrategy; -import org.hibernate.search.util.common.SearchException; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchRuntimeConfig; 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.WithName; -import io.smallrye.config.WithParentName; import io.smallrye.config.WithUnnamedKey; @ConfigGroup @@ -41,13 +35,13 @@ public interface HibernateSearchElasticsearchRuntimeConfigPersistenceUnit { Optional active(); /** - * Configuration for backends. + * Configuration for Elasticsearch/OpenSearch backends. */ @ConfigDocSection @WithName("elasticsearch") @WithUnnamedKey // The default backend has the null key @ConfigDocMapKey("backend-name") - Map backends(); + Map backends(); /** * Configuration for automatic creation and validation of the Elasticsearch schema: @@ -79,181 +73,6 @@ public interface HibernateSearchElasticsearchRuntimeConfigPersistenceUnit { */ MultiTenancyConfig multiTenancy(); - @ConfigGroup - interface ElasticsearchBackendRuntimeConfig { - /** - * 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. - */ - ElasticsearchQueryConfig query(); - - /** - * Configuration for version checks on this backend. - */ - ElasticsearchVersionCheckConfig versionCheck(); - - /** - * The default configuration for the Elasticsearch indexes. - */ - @WithParentName - ElasticsearchIndexRuntimeConfig indexDefaults(); - - /** - * Per-index configuration overrides. - */ - @ConfigDocSection - @ConfigDocMapKey("index-name") - Map indexes(); - - /** - * Configuration for the index layout. - */ - LayoutConfig layout(); - } - - 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 ElasticsearchVersionCheckConfig { - /** - * 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 ElasticsearchIndexRuntimeConfig { - /** - * Configuration for the schema management of the indexes. - */ - ElasticsearchIndexSchemaManagementConfig schemaManagement(); - - /** - * Configuration for the indexing process that creates, updates and deletes documents. - */ - ElasticsearchIndexIndexingConfig 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 IndexingConfig { @@ -501,124 +320,6 @@ interface SchemaManagementConfig { } - @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 ElasticsearchQueryConfig { - /** - * Configuration for the behavior on shard failure. - */ - ElasticsearchQueryShardFailureConfig shardFailure(); - } - - @ConfigGroup - interface ElasticsearchQueryShardFailureConfig { - /** - * 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 ElasticsearchIndexSchemaManagementConfig { - /** - * 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 ElasticsearchIndexIndexingConfig { - /** - * 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(); - } - @ConfigGroup interface MultiTenancyConfig { @@ -633,43 +334,4 @@ interface MultiTenancyConfig { Optional> tenantIds(); } - - @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:hibernate-search-orm-elasticsearch.adoc#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-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchOrmElasticsearchMapperContext.java b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchOrmElasticsearchMapperContext.java new file mode 100644 index 0000000000000..df08b6028f206 --- /dev/null +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/HibernateSearchOrmElasticsearchMapperContext.java @@ -0,0 +1,66 @@ +package io.quarkus.hibernate.search.orm.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; + +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.MapperContext; +import io.quarkus.hibernate.search.orm.elasticsearch.runtime.bean.HibernateSearchBeanUtil; +import io.quarkus.runtime.annotations.RecordableConstructor; + +public final class HibernateSearchOrmElasticsearchMapperContext implements MapperContext { + + public final String persistenceUnitName; + private final Set backendNamesForIndexedEntities; + private final Map> backendAndIndexNamesForSearchExtensions; + + @RecordableConstructor + public HibernateSearchOrmElasticsearchMapperContext(String persistenceUnitName, + Set backendNamesForIndexedEntities, + Map> backendAndIndexNamesForSearchExtensions) { + if (persistenceUnitName == null) { + throw new IllegalArgumentException("persistenceUnitName cannot be null"); + } + this.persistenceUnitName = persistenceUnitName; + this.backendNamesForIndexedEntities = backendNamesForIndexedEntities; + this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + persistenceUnitName + "]"; + } + + @Override + public Set getBackendNamesForIndexedEntities() { + return backendNamesForIndexedEntities; + } + + @Override + public Map> getBackendAndIndexNamesForSearchExtensions() { + return backendAndIndexNamesForSearchExtensions; + } + + @Override + public String backendPropertyKey(String backendName, String indexName, String propertyKeyRadical) { + return HibernateSearchElasticsearchRuntimeConfig.backendPropertyKey(persistenceUnitName, backendName, indexName, + propertyKeyRadical); + } + + @Override + public Optional> singleExtensionBeanReferenceFor(Optional override, Class beanType, + String backendName, String indexName) { + return HibernateSearchBeanUtil.singleExtensionBeanReferenceFor(override, beanType, persistenceUnitName, backendName, + indexName); + } + + @Override + public Optional>> multiExtensionBeanReferencesFor(Optional> override, + Class beanType, String backendName, String indexName) { + return HibernateSearchBeanUtil.multiExtensionBeanReferencesFor(override, beanType, persistenceUnitName, backendName, + indexName); + } +} diff --git a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 6c2d3dedc20f9..b19450a3ac1bd 100644 --- a/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/hibernate-search-orm-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,6 +1,6 @@ --- artifact: ${project.groupId}:${project.artifactId}:${project.version} -name: "Hibernate Search + Elasticsearch" +name: "Hibernate Search ORM + Elasticsearch" metadata: keywords: - "hibernate-search-orm-elasticsearch" @@ -12,6 +12,7 @@ metadata: - "hibernate-orm" - "hibernate-search-orm" - "elasticsearch" + - "opensearch" guide: "https://quarkus.io/guides/hibernate-search-orm-elasticsearch" categories: - "data" diff --git a/extensions/hibernate-search-standalone-elasticsearch/deployment/pom.xml b/extensions/hibernate-search-standalone-elasticsearch/deployment/pom.xml index b4ad5c85ba44b..046916dc99544 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/deployment/pom.xml +++ b/extensions/hibernate-search-standalone-elasticsearch/deployment/pom.xml @@ -21,6 +21,10 @@ io.quarkus quarkus-elasticsearch-rest-client-common-deployment + + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common-deployment + io.quarkus quarkus-hibernate-search-standalone-elasticsearch diff --git a/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneEnabledBuildItem.java b/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneEnabledBuildItem.java index b2656fe55f899..bf86d0de6a4fe 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneEnabledBuildItem.java +++ b/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneEnabledBuildItem.java @@ -1,32 +1,21 @@ package io.quarkus.hibernate.search.standalone.elasticsearch.deployment; -import java.util.Map; import java.util.Set; import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneElasticsearchMapperContext; public final class HibernateSearchStandaloneEnabledBuildItem extends SimpleBuildItem { - private final Set backendNamesForIndexedEntities; - private final Map> backendAndIndexNamesForSearchExtensions; + final HibernateSearchStandaloneElasticsearchMapperContext mapperContext; private final Set rootAnnotationMappedClassNames; - public HibernateSearchStandaloneEnabledBuildItem(Set backendNamesForIndexedEntities, - Map> backendAndIndexNamesForSearchExtensions, + public HibernateSearchStandaloneEnabledBuildItem(HibernateSearchStandaloneElasticsearchMapperContext mapperContext, Set rootAnnotationMappedClassNames) { - this.backendNamesForIndexedEntities = backendNamesForIndexedEntities; - this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; + this.mapperContext = mapperContext; this.rootAnnotationMappedClassNames = rootAnnotationMappedClassNames; } - public Set getBackendNamesForIndexedEntities() { - return backendNamesForIndexedEntities; - } - - public Map> getBackendAndIndexNamesForSearchExtensions() { - return backendAndIndexNamesForSearchExtensions; - } - public Set getRootAnnotationMappedClassNames() { return rootAnnotationMappedClassNames; } diff --git a/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneProcessor.java b/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneProcessor.java index d2f67cc817f25..dc7dc6f88f6d8 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneProcessor.java +++ b/extensions/hibernate-search-standalone-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/deployment/HibernateSearchStandaloneProcessor.java @@ -5,19 +5,14 @@ import static io.quarkus.hibernate.search.standalone.elasticsearch.deployment.HibernateSearchTypes.ROOT_MAPPING; import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.backendPropertyKey; import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.defaultBackendPropertyKeys; -import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.elasticsearchVersionPropertyKey; import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.mapperPropertyKey; import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.mapperPropertyKeys; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; @@ -25,7 +20,6 @@ import jakarta.enterprise.inject.Default; import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion; -import org.hibernate.search.backend.elasticsearch.gson.spi.GsonClasses; import org.hibernate.search.mapper.pojo.standalone.mapping.SearchMapping; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; @@ -46,25 +40,21 @@ import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.DevServicesAdditionalConfigBuildItem; -import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.JandexUtil; import io.quarkus.elasticsearch.restclient.common.deployment.DevservicesElasticsearchBuildItem; import io.quarkus.elasticsearch.restclient.common.deployment.ElasticsearchCommonBuildTimeConfig.ElasticsearchDevServicesBuildTimeConfig.Distribution; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.ElasticsearchVersionSubstitution; +import io.quarkus.hibernate.search.backend.elasticsearch.common.deployment.HibernateSearchBackendElasticsearchEnabledBuildItem; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.ElasticsearchVersionSubstitution; import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneBuildTimeConfig; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneBuildTimeConfig.ElasticsearchBackendBuildTimeConfig; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneBuildTimeConfig.ElasticsearchIndexBuildTimeConfig; +import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneElasticsearchMapperContext; import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRecorder; import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig; import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.vertx.http.deployment.spi.RouteBuildItem; @BuildSteps(onlyIf = HibernateSearchStandaloneEnabled.class) @@ -99,8 +89,6 @@ public void configure(CombinedIndexBuildItem combinedIndexBuildItem, return; } - registerReflectionForGson(reflectiveClass); - Set backendNamesForIndexedEntities = new LinkedHashSet<>(); for (AnnotationInstance indexedAnnotation : indexedAnnotations) { AnnotationValue backendNameValue = indexedAnnotation.value("backend"); @@ -113,8 +101,21 @@ public void configure(CombinedIndexBuildItem combinedIndexBuildItem, Set rootAnnotationMappedClassNames = collectRootAnnotationMappedClassNames(index); - enabled.produce(new HibernateSearchStandaloneEnabledBuildItem(backendNamesForIndexedEntities, - backendAndIndexNamesForSearchExtensions, rootAnnotationMappedClassNames)); + var mapperContext = new HibernateSearchStandaloneElasticsearchMapperContext(backendNamesForIndexedEntities, + backendAndIndexNamesForSearchExtensions); + enabled.produce(new HibernateSearchStandaloneEnabledBuildItem(mapperContext, rootAnnotationMappedClassNames)); + } + + @BuildStep + void enableBackend(Optional enabled, + HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, + BuildProducer elasticsearchEnabled) { + if (!enabled.isPresent()) { + // No boot + return; + } + elasticsearchEnabled.produce(new HibernateSearchBackendElasticsearchEnabledBuildItem(enabled.get().mapperContext, + buildTimeConfig.backends())); } private static Map> collectBackendAndIndexNamesForSearchExtensions( @@ -160,104 +161,6 @@ private static Set collectRootAnnotationMappedClassNames(IndexView index return rootAnnotationMappedClassNames; } - @BuildStep - public void processBuildTimeConfig(Optional enabled, - HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - if (enabled.isEmpty()) { - // Disabled => no config processing necessary - return; - } - Set propertyKeysWithNoVersion = new LinkedHashSet<>(); - Map backends = buildTimeConfig != null - ? buildTimeConfig.backends() - : Collections.emptyMap(); - - Set allBackendNames = new LinkedHashSet<>(enabled.get().getBackendNamesForIndexedEntities()); - allBackendNames.addAll(backends.keySet()); - // For all backends referenced either through @Indexed(backend = ...) or configuration... - for (String backendName : allBackendNames) { - ElasticsearchBackendBuildTimeConfig backendConfig = backends.get(backendName); - // ... we validate that the backend is configured and the version is present - if (backendConfig == null || backendConfig.version().isEmpty()) { - propertyKeysWithNoVersion.add(elasticsearchVersionPropertyKey(backendName)); - } - // ... we register files referenced from backends configuration - if (backendConfig != null) { - registerClasspathFileFromBackendConfig(backendName, backendConfig, - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - } - } - if (!propertyKeysWithNoVersion.isEmpty()) { - throw new ConfigurationException( - "The Elasticsearch version needs to be defined via properties: " - + String.join(", ", propertyKeysWithNoVersion) + ".", - propertyKeysWithNoVersion); - } - } - - private static void registerClasspathFileFromBackendConfig(String backendName, - ElasticsearchBackendBuildTimeConfig backendConfig, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - registerClasspathFileFromIndexConfig(backendName, null, backendConfig.indexDefaults(), - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - for (Entry entry : backendConfig.indexes().entrySet()) { - String indexName = entry.getKey(); - ElasticsearchIndexBuildTimeConfig indexConfig = entry.getValue(); - registerClasspathFileFromIndexConfig(backendName, indexName, indexConfig, - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - } - } - - private static void registerClasspathFileFromIndexConfig(String backendName, String indexName, - ElasticsearchIndexBuildTimeConfig indexConfig, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer hotDeploymentWatchedFiles) { - registerClasspathFileFromConfig(backendName, indexName, "schema-management.settings-file", - indexConfig.schemaManagement().settingsFile(), - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - registerClasspathFileFromConfig(backendName, indexName, "schema-management.mapping-file", - indexConfig.schemaManagement().mappingFile(), - applicationArchivesBuildItem, nativeImageResources, hotDeploymentWatchedFiles); - } - - private static void registerClasspathFileFromConfig(String backendName, String indexName, - String propertyKeyRadical, - Optional classpathFileOptional, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer nativeImageResources, - BuildProducer 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 '" - + backendPropertyKey(backendName, indexName, propertyKeyRadical) + "=" - + classpathFile - + "'. Remove property or add file to your path."); - } - nativeImageResources.produce(new NativeImageResourceBuildItem(classpathFile)); - hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(classpathFile)); - } - - private void registerReflectionForGson(BuildProducer reflectiveClass) { - String[] reflectiveClasses = GsonClasses.typesRequiringReflection().toArray(String[]::new); - reflectiveClass.produce(ReflectiveClassBuildItem.builder(reflectiveClasses) - .reason(getClass().getName()) - .methods().fields().build()); - } - @Record(ExecutionTime.RUNTIME_INIT) @BuildStep void defineSearchMappingBean(Optional enabled, @@ -276,8 +179,7 @@ void defineSearchMappingBean(Optional .unremovable() .addQualifier(Default.class) .setRuntimeInit() - .createWith(recorder.createSearchMappingFunction(runtimeConfig, - enabled.get().getBackendAndIndexNamesForSearchExtensions())) + .createWith(recorder.createSearchMappingFunction(enabled.get().mapperContext, runtimeConfig)) .destroyer(BeanDestroyer.AutoCloseableDestroyer.class) .done()); } @@ -297,7 +199,7 @@ public void preBoot(Optional enabled, // Make it possible to record the settings as bytecode: recorderContext.registerSubstitution(ElasticsearchVersion.class, String.class, ElasticsearchVersionSubstitution.class); - recorder.preBoot(buildTimeConfig, enabled.get().getBackendAndIndexNamesForSearchExtensions(), + recorder.preBoot(enabled.get().mapperContext, buildTimeConfig, enabled.get().getRootAnnotationMappedClassNames()); } diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/pom.xml b/extensions/hibernate-search-standalone-elasticsearch/runtime/pom.xml index d71267a449b66..14d0bf298bdd0 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/runtime/pom.xml +++ b/extensions/hibernate-search-standalone-elasticsearch/runtime/pom.xml @@ -22,8 +22,8 @@ quarkus-elasticsearch-rest-client-common - org.hibernate.search - hibernate-search-backend-elasticsearch + io.quarkus + quarkus-hibernate-search-backend-elasticsearch-common org.hibernate.search diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/ElasticsearchVersionSubstitution.java b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/ElasticsearchVersionSubstitution.java deleted file mode 100644 index fc2bb9f0e70cf..0000000000000 --- a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/ElasticsearchVersionSubstitution.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.hibernate.search.standalone.elasticsearch.runtime; - -import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion; - -import io.quarkus.runtime.ObjectSubstitution; - -public class ElasticsearchVersionSubstitution implements ObjectSubstitution { - @Override - public String serialize(ElasticsearchVersion obj) { - return obj.toString(); - } - - @Override - public ElasticsearchVersion deserialize(String obj) { - return ElasticsearchVersion.of(obj); - } -} diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneBuildTimeConfig.java b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneBuildTimeConfig.java index 14c699821b8c4..616f87521052a 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneBuildTimeConfig.java +++ b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneBuildTimeConfig.java @@ -4,8 +4,7 @@ import java.util.Map; import java.util.Optional; -import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion; - +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchBuildTimeConfig; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigGroup; @@ -14,7 +13,6 @@ import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; -import io.smallrye.config.WithParentName; import io.smallrye.config.WithUnnamedKey; @ConfigMapping(prefix = "quarkus.hibernate-search-standalone") @@ -34,16 +32,16 @@ public interface HibernateSearchStandaloneBuildTimeConfig { boolean enabled(); /** - * Configuration for backends. + * Configuration for Elasticsearch/OpenSearch backends. */ @ConfigDocSection @WithName("elasticsearch") @WithUnnamedKey // The default backend has the null key @ConfigDocMapKey("backend-name") - Map backends(); + Map backends(); /** - * A xref:hibernate-search-stqndqlone-elasticsearch.adoc#bean-reference-note-anchor[bean reference] to a component + * A xref:hibernate-search-standalone-elasticsearch.adoc#bean-reference-note-anchor[bean reference] to a component * that should be notified of any failure occurring in a background process * (mainly index operations). * @@ -58,7 +56,7 @@ public interface HibernateSearchStandaloneBuildTimeConfig { * Instead of setting this configuration property, * you can simply annotate your custom `FailureHandler` implementation with `@SearchExtension` * and leave the configuration property unset: Hibernate Search will use the annotated implementation automatically. - * See xref:hibernate-search-stqndqlone-elasticsearch.adoc#plugging-in-custom-components[this section] + * See xref:hibernate-search-standalone-elasticsearch.adoc#plugging-in-custom-components[this section] * for more information. * * If this configuration property is set, it takes precedence over any `@SearchExtension` annotation. @@ -79,117 +77,6 @@ public interface HibernateSearchStandaloneBuildTimeConfig { */ MappingConfig mapping(); - @ConfigGroup - interface ElasticsearchBackendBuildTimeConfig { - /** - * 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(); - - /** - * The default configuration for the Elasticsearch indexes. - */ - @WithParentName - ElasticsearchIndexBuildTimeConfig indexDefaults(); - - /** - * Per-index configuration overrides. - */ - @ConfigDocSection - @ConfigDocMapKey("index-name") - Map indexes(); - } - - @ConfigGroup - interface ElasticsearchIndexBuildTimeConfig { - /** - * 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-standalone-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:hibernate-search-standalone-elasticsearch.adoc#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:hibernate-search-standalone-elasticsearch.adoc#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 MappingConfig { /** diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneElasticsearchMapperContext.java b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneElasticsearchMapperContext.java new file mode 100644 index 0000000000000..950d4e0860279 --- /dev/null +++ b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneElasticsearchMapperContext.java @@ -0,0 +1,57 @@ +package io.quarkus.hibernate.search.standalone.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; + +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.MapperContext; +import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.bean.HibernateSearchBeanUtil; +import io.quarkus.runtime.annotations.RecordableConstructor; + +public final class HibernateSearchStandaloneElasticsearchMapperContext implements MapperContext { + + private final Set backendNamesForIndexedEntities; + private final Map> backendAndIndexNamesForSearchExtensions; + + @RecordableConstructor + public HibernateSearchStandaloneElasticsearchMapperContext(Set backendNamesForIndexedEntities, + Map> backendAndIndexNamesForSearchExtensions) { + this.backendNamesForIndexedEntities = backendNamesForIndexedEntities; + this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{}"; + } + + @Override + public Set getBackendNamesForIndexedEntities() { + return backendNamesForIndexedEntities; + } + + @Override + public Map> getBackendAndIndexNamesForSearchExtensions() { + return backendAndIndexNamesForSearchExtensions; + } + + @Override + public String backendPropertyKey(String backendName, String indexName, String propertyKeyRadical) { + return HibernateSearchStandaloneRuntimeConfig.backendPropertyKey(backendName, indexName, propertyKeyRadical); + } + + @Override + public Optional> singleExtensionBeanReferenceFor(Optional override, Class beanType, + String backendName, String indexName) { + return HibernateSearchBeanUtil.singleExtensionBeanReferenceFor(override, beanType, backendName, indexName); + } + + @Override + public Optional>> multiExtensionBeanReferencesFor(Optional> override, + Class beanType, String backendName, String indexName) { + return HibernateSearchBeanUtil.multiExtensionBeanReferencesFor(override, beanType, backendName, indexName); + } +} diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRecorder.java b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRecorder.java index 708be069ddb1f..9bccbd6789a21 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRecorder.java +++ b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRecorder.java @@ -1,8 +1,6 @@ package io.quarkus.hibernate.search.standalone.elasticsearch.runtime; -import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendConfig; -import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendIndexConfig; -import static io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchConfigUtil.addConfig; +import static io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchConfigUtil.addConfig; import java.util.ArrayList; import java.util.Collections; @@ -10,17 +8,11 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; -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 org.hibernate.search.engine.cfg.EngineSettings; import org.hibernate.search.engine.environment.bean.BeanReference; import org.hibernate.search.engine.reporting.FailureHandler; @@ -34,10 +26,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.SyntheticCreationalContext; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneBuildTimeConfig.ElasticsearchBackendBuildTimeConfig; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneBuildTimeConfig.ElasticsearchIndexBuildTimeConfig; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.ElasticsearchBackendRuntimeConfig; -import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.HibernateSearchStandaloneRuntimeConfig.ElasticsearchIndexRuntimeConfig; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchConfigHandler; import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.bean.ArcBeanProvider; import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.bean.HibernateSearchBeanUtil; import io.quarkus.hibernate.search.standalone.elasticsearch.runtime.management.HibernateSearchStandaloneManagementHandler; @@ -50,8 +39,8 @@ @Recorder public class HibernateSearchStandaloneRecorder { - public void preBoot(HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, - Map> backendAndIndexNamesForSearchExtensions, + public void preBoot(HibernateSearchStandaloneElasticsearchMapperContext mapperContext, + HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, Set rootAnnotationMappedClassNames) { Set> rootAnnotationMappedClasses = new LinkedHashSet<>(); ClassLoader tccl = Thread.currentThread().getContextClassLoader(); @@ -63,7 +52,7 @@ public void preBoot(HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, } } Map bootProperties = new LinkedHashMap<>(); - new StaticInitListener(buildTimeConfig, backendAndIndexNamesForSearchExtensions, rootAnnotationMappedClasses) + new StaticInitListener(mapperContext, buildTimeConfig, rootAnnotationMappedClasses) .contributeBootProperties(bootProperties::put); StandalonePojoIntegrationBooter booter = StandalonePojoIntegrationBooter.builder() .properties(bootProperties) @@ -98,8 +87,8 @@ public void clearPreBootState() { } public Function, SearchMapping> createSearchMappingFunction( - HibernateSearchStandaloneRuntimeConfig runtimeConfig, - Map> backendAndIndexNamesForSearchExtensions) { + HibernateSearchStandaloneElasticsearchMapperContext mapperContext, + HibernateSearchStandaloneRuntimeConfig runtimeConfig) { return new Function, SearchMapping>() { @Override public SearchMapping apply(SyntheticCreationalContext context) { @@ -108,7 +97,7 @@ public SearchMapping apply(SyntheticCreationalContext context) { "Cannot retrieve the SearchMapping: Hibernate Search Standalone was deactivated through configuration properties"); } Map bootProperties = new LinkedHashMap<>(HibernateSearchStandalonePreBootState.pop()); - new RuntimeInitListener(runtimeConfig, backendAndIndexNamesForSearchExtensions) + new RuntimeInitListener(mapperContext, runtimeConfig) .contributeRuntimeProperties(bootProperties::put); StandalonePojoIntegrationBooter booter = StandalonePojoIntegrationBooter.builder() .properties(bootProperties) @@ -133,15 +122,15 @@ public Handler managementHandler() { } private static final class StaticInitListener { + private final HibernateSearchStandaloneElasticsearchMapperContext mapperContext; private final HibernateSearchStandaloneBuildTimeConfig buildTimeConfig; - private final Map> backendAndIndexNamesForSearchExtensions; private final Set> rootAnnotationMappedClasses; - private StaticInitListener(HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, - Map> backendAndIndexNamesForSearchExtensions, + private StaticInitListener(HibernateSearchStandaloneElasticsearchMapperContext mapperContext, + HibernateSearchStandaloneBuildTimeConfig buildTimeConfig, Set> rootAnnotationMappedClasses) { + this.mapperContext = mapperContext; this.buildTimeConfig = buildTimeConfig; - this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; this.rootAnnotationMappedClasses = rootAnnotationMappedClasses; } @@ -157,25 +146,9 @@ public void contributeBootProperties(BiConsumer propertyCollecto StandalonePojoMapperSettings.MAPPING_CONFIGURER, collectAllStandalonePojoMappingConfigurers()); - // 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 backendConfigs = buildTimeConfig == null - ? Collections.emptyMap() - : buildTimeConfig.backends(); - Map> backendAndIndexNames = new LinkedHashMap<>(); - mergeInto(backendAndIndexNames, 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, backendName, indexNames, - backendConfigs.get(backendName)); - } + HibernateSearchBackendElasticsearchConfigHandler.contributeBackendBuildTimeProperties( + propertyCollector, mapperContext, + buildTimeConfig == null ? Collections.emptyMap() : buildTimeConfig.backends()); } private List> collectAllStandalonePojoMappingConfigurers() { @@ -195,59 +168,16 @@ private List> collectAllStandalon return configurers; } - - private void contributeBackendBuildTimeProperties(BiConsumer propertyCollector, String backendName, - Set indexNames, - ElasticsearchBackendBuildTimeConfig elasticsearchBackendConfig) { - addBackendConfig(propertyCollector, backendName, BackendSettings.TYPE, - ElasticsearchBackendSettings.TYPE_NAME); - if (elasticsearchBackendConfig != null) { - addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION, - elasticsearchBackendConfig.version()); - } - - // Index defaults at the backend level - contributeBackendIndexBuildTimeProperties(propertyCollector, backendName, null, - elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults()); - - // Per-index properties - for (String indexName : indexNames) { - ElasticsearchIndexBuildTimeConfig indexConfig = elasticsearchBackendConfig == null ? null - : elasticsearchBackendConfig.indexes().get(indexName); - contributeBackendIndexBuildTimeProperties(propertyCollector, backendName, indexName, indexConfig); - } - } - - private void contributeBackendIndexBuildTimeProperties(BiConsumer propertyCollector, - String backendName, String indexName, ElasticsearchIndexBuildTimeConfig 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, - HibernateSearchBeanUtil.multiExtensionBeanReferencesFor( - indexConfig == null ? Optional.empty() - : indexConfig.analysis().configurer(), - ElasticsearchAnalysisConfigurer.class, backendName, - indexName)); - } } private static final class RuntimeInitListener { + private final HibernateSearchStandaloneElasticsearchMapperContext mapperContext; private final HibernateSearchStandaloneRuntimeConfig runtimeConfig; - private final Map> backendAndIndexNamesForSearchExtensions; - private RuntimeInitListener(HibernateSearchStandaloneRuntimeConfig runtimeConfig, - Map> backendAndIndexNamesForSearchExtensions) { + private RuntimeInitListener(HibernateSearchStandaloneElasticsearchMapperContext mapperContext, + HibernateSearchStandaloneRuntimeConfig runtimeConfig) { + this.mapperContext = mapperContext; this.runtimeConfig = runtimeConfig; - this.backendAndIndexNamesForSearchExtensions = backendAndIndexNamesForSearchExtensions; } public void contributeRuntimeProperties(BiConsumer propertyCollector) { @@ -264,119 +194,9 @@ public void contributeRuntimeProperties(BiConsumer propertyColle : runtimeConfig.indexing().plan().synchronization().strategy(), IndexingPlanSynchronizationStrategy.class, null, null)); - // 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 backendConfigs = runtimeConfig == null - ? Collections.emptyMap() - : runtimeConfig.backends(); - Map> backendAndIndexNames = new LinkedHashMap<>(); - mergeInto(backendAndIndexNames, 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, backendName, indexNames, - backendConfigs.get(backendName)); - } + HibernateSearchBackendElasticsearchConfigHandler.contributeBackendRuntimeProperties( + propertyCollector, mapperContext, + runtimeConfig == null ? Collections.emptyMap() : runtimeConfig.backends()); } - - private void contributeBackendRuntimeProperties(BiConsumer propertyCollector, String backendName, - Set indexNames, ElasticsearchBackendRuntimeConfig 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 - addBackendConfig(propertyCollector, backendName, - ElasticsearchBackendSettings.LAYOUT_STRATEGY, - HibernateSearchBeanUtil.singleExtensionBeanReferenceFor( - elasticsearchBackendConfig.layout().strategy(), - IndexLayoutStrategy.class, backendName, null)); - } - - // Settings that may default to a @SearchExtension-annotated-bean - // - - // Index defaults at the backend level - contributeBackendIndexRuntimeProperties(propertyCollector, backendName, null, - elasticsearchBackendConfig == null ? null : elasticsearchBackendConfig.indexDefaults()); - - // Per-index properties - for (String indexName : indexNames) { - ElasticsearchIndexRuntimeConfig indexConfig = elasticsearchBackendConfig == null ? null - : elasticsearchBackendConfig.indexes().get(indexName); - contributeBackendIndexRuntimeProperties(propertyCollector, backendName, indexName, indexConfig); - } - } - - private void contributeBackendIndexRuntimeProperties(BiConsumer propertyCollector, - String backendName, String indexName, ElasticsearchIndexRuntimeConfig 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 - // - } - } - - private static void mergeInto(Map> target, Map> source) { - for (Entry> entry : source.entrySet()) { - mergeInto(target, entry.getKey(), entry.getValue()); - } - } - - private static void mergeInto(Map> target, String key, Set values) { - target.computeIfAbsent(key, ignored -> new LinkedHashSet<>()) - .addAll(values); } } diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRuntimeConfig.java b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRuntimeConfig.java index 5243927b51241..a8beac6716e7c 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRuntimeConfig.java +++ b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/standalone/elasticsearch/runtime/HibernateSearchStandaloneRuntimeConfig.java @@ -1,17 +1,12 @@ package io.quarkus.hibernate.search.standalone.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.mapper.pojo.standalone.schema.management.SchemaManagementStrategyName; -import org.hibernate.search.util.common.SearchException; +import io.quarkus.hibernate.search.backend.elasticsearch.common.runtime.HibernateSearchBackendElasticsearchRuntimeConfig; import io.quarkus.runtime.annotations.ConfigDocDefault; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigDocSection; @@ -21,7 +16,6 @@ import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; -import io.smallrye.config.WithParentName; import io.smallrye.config.WithUnnamedKey; @ConfigMapping(prefix = "quarkus.hibernate-search-standalone") @@ -44,13 +38,13 @@ public interface HibernateSearchStandaloneRuntimeConfig { Optional active(); /** - * Configuration for backends. + * Configuration for Elasticsearch/OpenSearch backends. */ @ConfigDocSection @WithName("elasticsearch") @WithUnnamedKey // The default backend has the null key @ConfigDocMapKey("backend-name") - Map backends(); + Map backends(); /** * Configuration for automatic creation and validation of the Elasticsearch schema: @@ -63,181 +57,6 @@ public interface HibernateSearchStandaloneRuntimeConfig { */ IndexingConfig indexing(); - @ConfigGroup - interface ElasticsearchBackendRuntimeConfig { - /** - * 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. - */ - ElasticsearchQueryConfig query(); - - /** - * Configuration for version checks on this backend. - */ - ElasticsearchVersionCheckConfig versionCheck(); - - /** - * The default configuration for the Elasticsearch indexes. - */ - @WithParentName - ElasticsearchIndexRuntimeConfig indexDefaults(); - - /** - * Per-index configuration overrides. - */ - @ConfigDocSection - @ConfigDocMapKey("index-name") - Map indexes(); - - /** - * Configuration for the index layout. - */ - LayoutConfig layout(); - } - - 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 ElasticsearchVersionCheckConfig { - /** - * 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 ElasticsearchIndexRuntimeConfig { - /** - * Configuration for the schema management of the indexes. - */ - ElasticsearchIndexSchemaManagementConfig schemaManagement(); - - /** - * Configuration for the indexing process that creates, updates and deletes documents. - */ - ElasticsearchIndexIndexingConfig 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 IndexingConfig { @@ -408,167 +227,6 @@ interface SchemaManagementConfig { } - @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 ElasticsearchQueryConfig { - /** - * Configuration for the behavior on shard failure. - */ - ElasticsearchQueryShardFailureConfig shardFailure(); - } - - @ConfigGroup - interface ElasticsearchQueryShardFailureConfig { - /** - * 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-standalone.elasticsearch.index-defaults" will be ignored. - @ConfigGroup - interface ElasticsearchIndexSchemaManagementConfig { - /** - * 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-standalone.elasticsearch.index-defaults" will be ignored. - @ConfigGroup - interface ElasticsearchIndexIndexingConfig { - /** - * 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(); - } - - @ConfigGroup - interface LayoutConfig { - /** - * A xref:hibernate-search-standalone-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:hibernate-search-standalone-elasticsearch.adoc#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(); - } - - static String elasticsearchVersionPropertyKey(String backendName) { - return backendPropertyKey(backendName, null, "version"); - } - static String extensionPropertyKey(String radical) { StringBuilder keyBuilder = new StringBuilder("quarkus.hibernate-search-standalone."); keyBuilder.append(radical); diff --git a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 538b2011cea0f..274ea3ff9ee74 100644 --- a/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/hibernate-search-standalone-elasticsearch/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,6 +9,7 @@ metadata: - "full-text" - "hibernate" - "elasticsearch" + - "opensearch" guide: "https://quarkus.io/guides/hibernate-search-standalone-elasticsearch" categories: - "data" diff --git a/extensions/pom.xml b/extensions/pom.xml index 4e511fcc40963..7f86c1174b30f 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -99,6 +99,7 @@ hibernate-reactive hibernate-validator panache + hibernate-search-backend-elasticsearch-common hibernate-search-orm-elasticsearch hibernate-search-orm-outbox-polling hibernate-search-standalone-elasticsearch