diff --git a/docs/src/main/asciidoc/hibernate-search-orm-elasticsearch.adoc b/docs/src/main/asciidoc/hibernate-search-orm-elasticsearch.adoc index e0b09a227b57b..5b399f02e4801 100644 --- a/docs/src/main/asciidoc/hibernate-search-orm-elasticsearch.adoc +++ b/docs/src/main/asciidoc/hibernate-search-orm-elasticsearch.adoc @@ -707,7 +707,7 @@ Entities are attached to a persistence unit by link:hibernate-orm#multiple-persistence-units-attaching-model-classes[configuring the Hibernate ORM extension]. [[multiple-persistence-units-attaching-cdi]] -=== CDI integration +== CDI integration You can inject Hibernate Search's main entry points, `SearchSession` and `SearchMapping`, using CDI: @@ -762,6 +762,35 @@ We also inject some data and execute the mass indexer. In a real life application, it is obviously something you won't do at startup. ==== +[[offline-startup]] +== Offline startup + +By default, Hibernate Search sends a few requests to the Elasticsearch cluster on startup. +If the Elasticsearch cluster is not necessarily up and running when Hibernate Search starts, +this could cause a startup failure. + +To address this, you can configure Hibernate Search to not send any request on startup: + +* Disable Elasticsearch version checks on startup by setting the configuration property + link:#quarkus-hibernate-search-orm-elasticsearch_quarkus.hibernate-search-orm.elasticsearch.version-check.enabled[`quarkus.hibernate-search-orm.elasticsearch.version-check.enabled`] + to `false`. +* Disable schema management on startup by setting the configuration property + link:#quarkus-hibernate-search-orm-elasticsearch_quarkus.hibernate-search-orm.schema-management.strategy[`quarkus.hibernate-search-orm.schema-management.strategy`] + to `none`. + +Of course, even with this configuration, Hibernate Search still won't be able to index anything or run search queries +until the Elasticsearch cluster becomes accessible. + +[IMPORTANT] +==== +If you disable automatic schema creation by setting `quarkus.hibernate-search-orm.schema-management.strategy` to `none`, +you will have to create the schema manually at some point before your application starts persisting/updating entities +and executing search requests. + +See link:{hibernate-search-doc-prefix}#mapper-orm-schema-management-manager[this section of the reference documentation] +for more information. +==== + == Further reading If you are interested in learning more about Hibernate Search 6, diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/offline/IndexedEntity.java b/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/offline/IndexedEntity.java new file mode 100644 index 0000000000000..d084682cbec67 --- /dev/null +++ b/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/offline/IndexedEntity.java @@ -0,0 +1,17 @@ +package io.quarkus.hibernate.search.elasticsearch.test.offline; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; + +@Entity +@Indexed +public class IndexedEntity { + + @Id + @GeneratedValue + public Long id; + +} diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/offline/StartOfflineTest.java b/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/offline/StartOfflineTest.java new file mode 100644 index 0000000000000..a0e2f693ee84c --- /dev/null +++ b/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/offline/StartOfflineTest.java @@ -0,0 +1,63 @@ +package io.quarkus.hibernate.search.elasticsearch.test.offline; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import javax.inject.Inject; +import javax.transaction.Transactional; + +import org.hibernate.search.mapper.orm.entity.SearchIndexedEntity; +import org.hibernate.search.mapper.orm.mapping.SearchMapping; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.util.common.SearchException; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Test that an application can be configured to start successfully + * even if the Elasticsearch cluster is offline when the application starts. + */ +public class StartOfflineTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(IndexedEntity.class) + .addAsResource("application-start-offline.properties", "application.properties")); + + @Inject + SearchMapping searchMapping; + + @Inject + SearchSession searchSession; + + @Test + public void testHibernateSearchStarted() { + assertThat(searchMapping.allIndexedEntities()) + .hasSize(1) + .element(0) + .returns(IndexedEntity.class, SearchIndexedEntity::javaClass); + } + + @Test + @Transactional + public void testSchemaManagementAvailableButFailsSinceElasticsearchNotStarted() { + assertThatThrownBy(() -> searchSession.schemaManager(IndexedEntity.class).createIfMissing()) + .isInstanceOf(SearchException.class) + .hasMessageContaining("Elasticsearch request failed: Connection refused"); + } + + @Test + @Transactional + public void testSearchAvailableButFailsSinceElasticsearchNotStarted() { + assertThatThrownBy(() -> searchSession.search(IndexedEntity.class) + .where(f -> f.matchAll()).fetchHits(20)) + .isInstanceOf(SearchException.class) + .hasMessageContaining("Elasticsearch request failed: Connection refused"); + } + +} diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/resources/application-start-offline.properties b/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/resources/application-start-offline.properties new file mode 100644 index 0000000000000..426b14c22c391 --- /dev/null +++ b/extensions/hibernate-search-orm-elasticsearch/deployment/src/test/resources/application-start-offline.properties @@ -0,0 +1,11 @@ +quarkus.datasource.db-kind=h2 +quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1 + +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=drop-and-create + +quarkus.hibernate-search-orm.elasticsearch.version=7.10 +# Simulate an offline Elasticsearch instance by pointing to a non-existing cluster +quarkus.hibernate-search-orm.elasticsearch.hosts=localhost:14800 +quarkus.hibernate-search-orm.schema-management.strategy=none +quarkus.hibernate-search-orm.elasticsearch.version-check.enabled=false 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 11f847315c442..599c4d1f99854 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 @@ -67,6 +67,15 @@ public static class ElasticsearchBackendBuildTimeConfig { @ConfigItem public Optional version; + // TODO This should be a runtime property, but we need https://hibernate.atlassian.net/browse/HSEARCH-4214 fixed + /** + * Whether Hibernate Search should check the version of the Elasticsearch cluster on startup. + *

+ * Set to {@code false} if the Elasticsearch cluster may not be available on startup. + */ + @ConfigItem(name = "version-check.enabled", defaultValue = "true") + public boolean versionCheck; + /** * Configuration for the index layout. */ 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 cd48380e31805..867ac6cf9e203 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 @@ -11,7 +11,6 @@ import javax.enterprise.inject.literal.NamedLiteral; -import org.graalvm.home.Version; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.Metadata; @@ -112,8 +111,6 @@ public void onMetadataInitialized(Metadata metadata, BootstrapContext bootstrapC private static final class HibernateSearchIntegrationStaticInitListener implements HibernateOrmIntegrationStaticInitListener { - private static final Version GRAAL_VM_VERSION_21 = Version.create(21); - private final HibernateSearchElasticsearchBuildTimeConfigPersistenceUnit buildTimeConfig; private HibernateSearchIntegrationStaticInitListener( @@ -151,6 +148,8 @@ private void contributeBackendBuildTimeProperties(BiConsumer pro ElasticsearchBackendSettings.TYPE_NAME); addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION, elasticsearchBackendConfig.version); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION_CHECK_ENABLED, + elasticsearchBackendConfig.versionCheck); addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.LAYOUT_STRATEGY, elasticsearchBackendConfig.layout.strategy); 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 fe3d73e262eda..48c7a4b7a1359 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 @@ -327,10 +327,67 @@ public static class SearchQueryLoadingCacheLookupConfig { @ConfigGroup public static class SchemaManagementConfig { + // @formatter:off /** - * The strategy used for index lifecycle. + * The schema management strategy, controlling how indexes and their schema + * are created, updated, validated or dropped on startup and shutdown. + * + * Available values: + * + * [cols=2] + * !=== + * h!Strategy + * h!Definition + * + * !none + * !Do nothing: assume that indexes already exist and that their schema matches Hibernate Search's expectations. + * + * !validate + * !Validate that indexes exist and that their schema matches Hibernate Search's expectations. + * + * If it does not, throw an exception, but make no attempt to fix the problem. + * + * !create + * !For indexes that do not exist, create them along with their schema. + * + * For indexes that already exist, do nothing: assume that their schema matches Hibernate Search's expectations. + * + * !create-or-validate (**default**) + * !For indexes that do not exist, create them along with their schema. + * + * For indexes that already exist, validate that their schema matches Hibernate Search's expectations. + * + * If it does not, throw an exception, but make no attempt to fix the problem. + * + * !create-or-update + * !For indexes that do not exist, create them along with their schema. + * + * For indexes that already exist, validate that their schema matches Hibernate Search's expectations; + * if it does not match expectations, try to update it. + * + * **This strategy is unfit for production environments**, + * due to several important limitations, + * but can be useful when developing. + * + * !drop-and-create + * !For indexes that do not exist, create them along with their schema. + * + * For indexes that already exist, drop them, then create them along with their schema. + * + * !drop-and-create-and-drop + * !For indexes that do not exist, create them along with their schema. + * + * For indexes that already exist, drop them, then create them along with their schema. + * + * Also, drop indexes and their schema on shutdown. + * !=== + * + * See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#mapper-orm-schema-management-strategy[this section of the reference documentation] + * for more information. + * + * @asciidoclet */ - // We can't set an actual default value here: see comment on this class. + // @formatter:on @ConfigItem(defaultValue = "create-or-validate") SchemaManagementStrategyName strategy;