From 3e636225ed76dc6dc6cafd477b3e35db28dc49b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 26 Sep 2024 11:34:02 +0200 Subject: [PATCH 01/10] Fail on startup when inactive datasources are injected into user beans By reimplementing Datasource inactive/active handling and eager startup through Arc's native features, which is better integrated and gives us this behavior. --- docs/src/main/asciidoc/datasource.adoc | 44 ++++++----- docs/src/main/asciidoc/hibernate-orm.adoc | 13 +++- .../agroal/deployment/AgroalProcessor.java | 9 ++- ...DefaultDatasourceDynamicInjectionTest.java | 13 ++-- ...eDefaultDatasourceStaticInjectionTest.java | 31 ++++---- ...seNamedDatasourceDynamicInjectionTest.java | 13 ++-- ...lseNamedDatasourceStaticInjectionTest.java | 31 ++++---- .../quarkus/agroal/test/EagerStartupTest.java | 20 ++--- ...ourcesAsAlternativesWithActiveDS1Test.java | 25 +++--- ...ourcesAsAlternativesWithActiveDS2Test.java | 25 +++--- .../agroal/runtime/AgroalDataSourceUtil.java | 36 +++++++++ .../runtime/AgroalDataSourcesInitializer.java | 19 ----- .../agroal/runtime/AgroalRecorder.java | 28 ++++++- .../quarkus/agroal/runtime/DataSources.java | 77 +++++-------------- .../runtime/health/DataSourceHealthCheck.java | 20 ++--- .../metrics/AgroalMetricsRecorder.java | 14 ++-- .../common/runtime/DataSourceUtil.java | 21 +++-- .../runtime/DataSourceRuntimeConfig.java | 5 -- .../datasource/runtime/DataSourceSupport.java | 27 +++++++ .../flyway/deployment/FlywayProcessor.java | 7 +- ...DefaultDatasourceDynamicInjectionTest.java | 4 +- ...eDefaultDatasourceStaticInjectionTest.java | 4 +- ...seNamedDataSourceDynamicInjectionTest.java | 4 +- ...lseNamedDataSourceStaticInjectionTest.java | 4 +- ...efaultDatasourceConfigActiveFalseTest.java | 4 +- ...tNamedDatasourceConfigActiveFalseTest.java | 4 +- .../flyway/runtime/FlywayContainerUtil.java | 4 +- .../flyway/runtime/FlywayRecorder.java | 16 ++-- .../deployment/HibernateOrmCdiProcessor.java | 4 +- ...plicitDatasourceConfigActiveFalseTest.java | 4 +- ...plicitDatasourceConfigActiveFalseTest.java | 4 +- ...plicitDatasourceConfigActiveFalseTest.java | 4 +- ...iplePUAsAlternativesWithActivePU1Test.java | 17 +++- ...iplePUAsAlternativesWithActivePU2Test.java | 17 +++- .../FastBootHibernatePersistenceProvider.java | 7 +- ...plicitDatasourceConfigActiveFalseTest.java | 4 +- ...tHibernateReactivePersistenceProvider.java | 13 ++-- .../deployment/LiquibaseProcessor.java | 7 +- ...DefaultDatasourceDynamicInjectionTest.java | 4 +- ...eDefaultDatasourceStaticInjectionTest.java | 4 +- ...seNamedDatasourceDynamicInjectionTest.java | 4 +- ...lseNamedDatasourceStaticInjectionTest.java | 4 +- ...efaultDatasourceConfigActiveFalseTest.java | 4 +- ...tNamedDatasourceConfigActiveFalseTest.java | 4 +- .../runtime/LiquibaseFactoryUtil.java | 4 +- .../liquibase/runtime/LiquibaseRecorder.java | 12 +-- .../runtime/ReactiveDataSourceUtil.java | 41 ++++++++++ .../ReactiveDB2ClientProcessor.java | 11 +-- .../db2/client/runtime/DB2PoolRecorder.java | 31 ++++++-- .../ReactiveDB2DataSourcesHealthCheck.java | 12 ++- .../ReactiveMSSQLClientProcessor.java | 11 +-- ...DefaultDatasourceDynamicInjectionTest.java | 14 ++-- ...eDefaultDatasourceStaticInjectionTest.java | 34 ++++---- ...seNamedDatasourceDynamicInjectionTest.java | 14 ++-- ...lseNamedDatasourceStaticInjectionTest.java | 34 ++++---- .../client/runtime/MSSQLPoolRecorder.java | 31 +++++--- .../ReactiveMSSQLDataSourcesHealthCheck.java | 10 ++- .../ReactiveMySQLClientProcessor.java | 11 +-- ...DefaultDatasourceDynamicInjectionTest.java | 14 ++-- ...eDefaultDatasourceStaticInjectionTest.java | 34 ++++---- ...seNamedDatasourceDynamicInjectionTest.java | 14 ++-- ...lseNamedDatasourceStaticInjectionTest.java | 34 ++++---- .../client/runtime/MySQLPoolRecorder.java | 30 ++++++-- .../ReactiveMySQLDataSourcesHealthCheck.java | 10 ++- .../ReactiveOracleClientProcessor.java | 11 +-- ...DefaultDatasourceDynamicInjectionTest.java | 14 ++-- ...eDefaultDatasourceStaticInjectionTest.java | 34 ++++---- ...seNamedDatasourceDynamicInjectionTest.java | 14 ++-- ...lseNamedDatasourceStaticInjectionTest.java | 34 ++++---- .../client/runtime/OraclePoolRecorder.java | 30 ++++++-- .../ReactiveOracleDataSourcesHealthCheck.java | 10 ++- .../deployment/ReactivePgClientProcessor.java | 11 +-- ...DefaultDatasourceDynamicInjectionTest.java | 14 ++-- ...eDefaultDatasourceStaticInjectionTest.java | 34 ++++---- ...seNamedDatasourceDynamicInjectionTest.java | 14 ++-- ...lseNamedDatasourceStaticInjectionTest.java | 34 ++++---- .../pg/client/runtime/PgPoolRecorder.java | 31 +++++--- .../ReactivePgDataSourcesHealthCheck.java | 10 ++- 78 files changed, 763 insertions(+), 565 deletions(-) delete mode 100644 extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourcesInitializer.java diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index cc16c346dd980..aa4ab1cc864a3 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -449,18 +449,18 @@ When a datasource is configured at build time, it is active by default at runtim This means that Quarkus will start the corresponding JDBC connection pool or reactive client when the application starts. To deactivate a datasource at runtime, set `quarkus.datasource[.optional name].active` to `false`. -Quarkus will then skip starting the JDBC connection pool or reactive client during application startup. -Any attempt to use the deactivated datasource at runtime results in an exception. +If a datasource is not active: -This feature is especially useful when you need the application to select one datasource from a predefined set at runtime. - -[WARNING] -==== -If another Quarkus extension relies on an inactive datasource, that extension might fail to start. +* The datasource will not attempt to connect to the database during application startup. +* The datasource will not contribute a <>. +* Static CDI injection points involving the datasource (`@Inject DataSource ds` or `@Inject Pool pool`) will cause application startup to fail. +* Dynamic retrieval of the datasource (e.g. through `CDI.getBeanContainer()`/`Arc.instance()`, or by injecting an `Instance`) will cause an exception to be thrown. +* Other Quarkus extensions consuming the datasource may cause application startup to fail. ++ +In such a case, you will also need to deactivate those other extensions. +For an example of this scenario, see xref:hibernate-orm.adoc#persistence-unit-active[this section of the Hibernate ORM guide]. -In such a case, you will need to deactivate that other extension as well. -For an example of this scenario, see the xref:hibernate-orm.adoc#persistence-unit-active[Hibernate ORM] section. -==== +This feature is especially useful when you need the application to select one datasource from a predefined set at runtime. For example, with the following configuration: @@ -504,28 +504,33 @@ It can also be useful to define a xref:cdi.adoc#ok-you-said-that-there-are-sever [source,java,indent=0] ---- public class MyProducer { - @Inject - DataSourceSupport dataSourceSupport; - @Inject @DataSource("pg") - AgroalDataSource pgDataSourceBean; + InjectableInstance pgDataSourceBean; // <1> @Inject @DataSource("oracle") - AgroalDataSource oracleDataSourceBean; + InjectableInstance oracleDataSourceBean; - @Produces + @Produces // <2> @ApplicationScoped public AgroalDataSource dataSource() { - if (dataSourceSupport.getInactiveNames().contains("pg")) { - return oracleDataSourceBean; + if (pgDataSourceBean.getHandle().getBean().isActive()) { // <3> + return pgDataSourceBean.get(); + } else if (oracleDataSourceBean.getHandle().getBean().isActive()) { // <3> + return oracleDataSourceBean.get(); } else { - return pgDataSourceBean; + throw new RuntimeException("No active datasource!"); } } } ---- +<1> Don't inject a `DataSource` or `AgroalDatasource` directly, +because that would lead to a failure on startup (can't inject inactive beans). +Instead, inject `InjectableInstance` or `InjectableInstance` +<2> Declare a CDI producer method that will define the default datasource +as either PostgreSQL or Oracle, depending on what is active. +<3> Check whether beans are active before retrieving them. ==== [[datasource-multiple-single-transaction]] @@ -589,6 +594,7 @@ explaining why. == Datasource integrations +[[datasource-health-check]] === Datasource health check If you use the link:https://quarkus.io/extensions/io.quarkus/quarkus-smallrye-health[`quarkus-smallrye-health`] extension, the `quarkus-agroal` and reactive client extensions automatically add a readiness health check to validate the datasource. diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index e8d9c7c3f5c00..e248b29fdcbc7 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -567,7 +567,12 @@ like this: ---- public class MyProducer { @Inject - DataSourceSupport dataSourceSupport; + @DataSource("pg") + InjectableInstance pgDataSourceBean; + + @Inject + @DataSource("oracle") + InjectableInstance oracleDataSourceBean; @Inject @PersistenceUnit("pg") @@ -580,10 +585,12 @@ public class MyProducer { @Produces @ApplicationScoped public Session session() { - if (dataSourceSupport.getInactiveNames().contains("pg")) { + if (pgDataSourceBean.getHandle().getBean().isActive()) { + return pgSessionBean; + } else if (oracleDataSourceBean.getHandle().getBean().isActive()) { return oracleSessionBean; } else { - return pgSessionBean; + throw new RuntimeException("No active datasource!"); } } } diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index b7bf482799c2c..59fa65b8810c6 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -26,7 +26,6 @@ import io.agroal.api.AgroalPoolInterceptor; import io.quarkus.agroal.DataSource; import io.quarkus.agroal.runtime.AgroalDataSourceSupport; -import io.quarkus.agroal.runtime.AgroalDataSourcesInitializer; import io.quarkus.agroal.runtime.AgroalRecorder; import io.quarkus.agroal.runtime.DataSourceJdbcBuildTimeConfig; import io.quarkus.agroal.runtime.DataSources; @@ -36,6 +35,7 @@ import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.agroal.spi.OpenTelemetryInitBuildItem; +import io.quarkus.arc.BeanDestroyer; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; @@ -243,8 +243,6 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, .setDefaultScope(DotNames.SINGLETON).build()); // add the @DataSource class otherwise it won't be registered as a qualifier additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(DataSource.class).build()); - // make sure datasources are initialized at startup - additionalBeans.produce(new AdditionalBeanBuildItem(AgroalDataSourcesInitializer.class)); // make AgroalPoolInterceptor beans unremovable, users still have to make them beans unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(AgroalPoolInterceptor.class)); @@ -288,9 +286,12 @@ void generateDataSourceBeans(AgroalRecorder recorder, .setRuntimeInit() .unremovable() .addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class))) + .startup() + .checkActive(recorder.agroalDataSourceCheckActiveSupplier(dataSourceName)) // pass the runtime config into the recorder to ensure that the DataSource related beans // are created after runtime configuration has been set up - .createWith(recorder.agroalDataSourceSupplier(dataSourceName, dataSourcesRuntimeConfig)); + .createWith(recorder.agroalDataSourceSupplier(dataSourceName, dataSourcesRuntimeConfig)) + .destroyer(BeanDestroyer.AutoCloseableDestroyer.class); if (!DataSourceUtil.isDefault(dataSourceName)) { // this definitely not ideal, but 'elytron-jdbc-security' uses it (although it could be easily changed) diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index fecba5eb20ae7..fd817cd68c4eb 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -11,8 +11,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; public class ConfigActiveFalseDefaultDatasourceDynamicInjectionTest { @@ -41,16 +41,17 @@ private void doTest(InjectableInstance instance) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, b -> b.isActive()); var ds = instance.get(); assertThat(ds).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> ds.getConnection()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 4b5cd1506cc30..9d19304af3076 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,47 +1,46 @@ package io.quarkus.agroal.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.sql.SQLException; +import static org.assertj.core.api.Assertions.assertThat; import javax.sql.DataSource; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.CreationException; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class ConfigActiveFalseDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#ds")); @Inject MyBean myBean; @Test public void test() { - assertThatThrownBy(() -> myBean.useDatasource()) - .isInstanceOf(CreationException.class) - .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped public static class MyBean { @Inject DataSource ds; - - public void useDatasource() throws SQLException { - ds.getConnection(); - } } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 4bc4ac48d0c01..27adbaa6c5da9 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -11,8 +11,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; public class ConfigActiveFalseNamedDatasourceDynamicInjectionTest { @@ -46,16 +46,17 @@ private void doTest(InjectableInstance instance) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, b -> b.isActive()); var ds = instance.get(); assertThat(ds).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> ds.getConnection()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java index 2f402ee176c9a..b8b7e885b1396 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -1,18 +1,17 @@ package io.quarkus.agroal.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.sql.SQLException; +import static org.assertj.core.api.Assertions.assertThat; import javax.sql.DataSource; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.CreationException; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class ConfigActiveFalseNamedDatasourceStaticInjectionTest { @@ -22,20 +21,24 @@ public class ConfigActiveFalseNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.datasource.users.active", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.users.db-kind", "h2"); + .overrideConfigKey("quarkus.datasource.users.db-kind", "h2") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'users' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + + " to 'true' and configure datasource 'users'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#ds")); @Inject MyBean myBean; @Test public void test() { - assertThatThrownBy(() -> myBean.useDatasource()) - .isInstanceOf(CreationException.class) - .hasMessageContainingAll("Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" - + " to 'true' and configure datasource 'users'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped @@ -43,9 +46,5 @@ public static class MyBean { @Inject @io.quarkus.agroal.DataSource("users") DataSource ds; - - public void useDatasource() throws SQLException { - ds.getConnection(); - } } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java index 838c8ad9a7131..5591497b6e345 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java @@ -2,13 +2,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import jakarta.inject.Singleton; +import jakarta.enterprise.context.ApplicationScoped; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.agroal.runtime.DataSources; +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.test.QuarkusUnitTest; @@ -26,16 +28,14 @@ public class EagerStartupTest { @Test public void shouldStartEagerly() { var container = Arc.container(); - var instanceHandle = container.instance(DataSources.class); - // Check that the following call won't trigger a lazy initialization: - // the DataSources bean must be eagerly initialized. - assertThat(container.getActiveContext(Singleton.class).getState() + var instanceHandle = container.instance(AgroalDataSource.class, + AgroalDataSourceUtil.qualifier(DataSourceUtil.DEFAULT_DATASOURCE_NAME)); + // Check that the datasource has already been eagerly created. + assertThat(container.getActiveContext(ApplicationScoped.class).getState() .getContextualInstances().get(instanceHandle.getBean())) - .as("Eagerly instantiated DataSources bean") + .as("Eagerly instantiated DataSource bean") + .isNotInstanceOf(ClientProxy.class) // Just to be sure I didn't misuse CDI: this should be the actual underlying instance. .isNotNull(); - // Check that the datasource has already been eagerly created. - assertThat(instanceHandle.get().isDataSourceCreated(DataSourceUtil.DEFAULT_DATASOURCE_NAME)) - .isTrue(); } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java index 208241cef1d8c..d37178ddf1d9a 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java @@ -12,7 +12,8 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.DataSource; -import io.quarkus.datasource.runtime.DataSourceSupport; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; /** @@ -45,10 +46,6 @@ public class MultipleDataSourcesAsAlternativesWithActiveDS1Test { @Inject AgroalDataSource customIndirectDatasourceBean; - @Inject - @DataSource("ds-2") - AgroalDataSource inactiveDatasourceBean; - @Test public void testExplicitDatasourceBeanUsable() { doTestDatasource(explicitDatasourceBean); @@ -61,7 +58,8 @@ public void testCustomIndirectDatasourceBeanUsable() { @Test public void testInactiveDatasourceBeanUnusable() { - assertThatThrownBy(() -> inactiveDatasourceBean.getConnection()) + assertThatThrownBy(() -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral("ds-2")).get() + .getConnection()) .hasMessageContaining("Datasource 'ds-2' was deactivated through configuration properties."); } @@ -74,24 +72,23 @@ private static void doTestDatasource(AgroalDataSource dataSource) { } private static class MyProducer { - @Inject - DataSourceSupport dataSourceSupport; - @Inject @DataSource("ds-1") - AgroalDataSource dataSource1Bean; + InjectableInstance dataSource1Bean; @Inject @DataSource("ds-2") - AgroalDataSource dataSource2Bean; + InjectableInstance dataSource2Bean; @Produces @ApplicationScoped public AgroalDataSource dataSource() { - if (dataSourceSupport.getInactiveNames().contains("ds-1")) { - return dataSource2Bean; + if (dataSource1Bean.getHandle().getBean().isActive()) { + return dataSource1Bean.get(); + } else if (dataSource2Bean.getHandle().getBean().isActive()) { + return dataSource2Bean.get(); } else { - return dataSource1Bean; + throw new RuntimeException("No active datasource!"); } } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java index 7159381c15143..497953764b919 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java @@ -12,7 +12,8 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.DataSource; -import io.quarkus.datasource.runtime.DataSourceSupport; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; /** @@ -45,10 +46,6 @@ public class MultipleDataSourcesAsAlternativesWithActiveDS2Test { @Inject AgroalDataSource customIndirectDatasourceBean; - @Inject - @DataSource("ds-1") - AgroalDataSource inactiveDatasourceBean; - @Test public void testExplicitDatasourceBeanUsable() { doTestDatasource(explicitDatasourceBean); @@ -61,7 +58,8 @@ public void testCustomIndirectDatasourceBeanUsable() { @Test public void testInactiveDatasourceBeanUnusable() { - assertThatThrownBy(() -> inactiveDatasourceBean.getConnection()) + assertThatThrownBy(() -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral("ds-1")).get() + .getConnection()) .hasMessageContaining("Datasource 'ds-1' was deactivated through configuration properties."); } @@ -74,24 +72,23 @@ private static void doTestDatasource(AgroalDataSource dataSource) { } private static class MyProducer { - @Inject - DataSourceSupport dataSourceSupport; - @Inject @DataSource("ds-1") - AgroalDataSource dataSource1Bean; + InjectableInstance dataSource1Bean; @Inject @DataSource("ds-2") - AgroalDataSource dataSource2Bean; + InjectableInstance dataSource2Bean; @Produces @ApplicationScoped public AgroalDataSource dataSource() { - if (dataSourceSupport.getInactiveNames().contains("ds-1")) { - return dataSource2Bean; + if (dataSource1Bean.getHandle().getBean().isActive()) { + return dataSource1Bean.get(); + } else if (dataSource2Bean.getHandle().getBean().isActive()) { + return dataSource2Bean.get(); } else { - return dataSource1Bean; + throw new RuntimeException("No active datasource!"); } } } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java index 524a48b0e577e..b480de250abce 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java @@ -1,17 +1,53 @@ package io.quarkus.agroal.runtime; import java.lang.annotation.Annotation; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.spi.Bean; +import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.DataSource; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InjectableInstance; import io.quarkus.datasource.common.runtime.DataSourceUtil; public final class AgroalDataSourceUtil { private AgroalDataSourceUtil() { } + public static InjectableInstance dataSourceInstance(String dataSourceName) { + return Arc.container().select(AgroalDataSource.class, qualifier(dataSourceName)); + } + + public static Optional dataSourceIfActive(String dataSourceName) { + var instance = dataSourceInstance(dataSourceName); + // We want to call get() and throw an exception if the name points to an undefined datasource. + if (!instance.isResolvable() || instance.getHandle().getBean().isActive()) { + // TODO remove ClientProxy.unwrap() once we get rid of UnconfiguredDatasource + return Optional.ofNullable(ClientProxy.unwrap(instance.get())); + } else { + return Optional.empty(); + } + } + + public static Set activeDataSourceNames() { + Set activeNames = new LinkedHashSet<>(); + for (var handle : Arc.container().select(AgroalDataSource.class).handles()) { + var bean = handle.getBean(); + if (bean != null && bean.isActive()) { + String name = dataSourceName(bean); + if (name != null) { // There may be custom beans, these will have a null name and will be ignored. + activeNames.add(name); + } + } + } + return activeNames; + } + public static String dataSourceName(Bean bean) { for (Object qualifier : bean.getQualifiers()) { if (qualifier instanceof DataSource) { diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourcesInitializer.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourcesInitializer.java deleted file mode 100644 index 53fe4ae0c9784..0000000000000 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourcesInitializer.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.agroal.runtime; - -import java.util.List; - -import jakarta.enterprise.event.Observes; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.arc.All; -import io.quarkus.runtime.StartupEvent; - -/** - * Make sure datasources are initialized at startup. - */ -public class AgroalDataSourcesInitializer { - - void init(@Observes StartupEvent event, @All List dataSources) { - // nothing to do here, eager injection will initialize the beans - } -} diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java index 52b7d1a20907d..df686267c71aa 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java @@ -3,14 +3,26 @@ import java.util.function.Function; import java.util.function.Supplier; +import jakarta.inject.Inject; + import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.SyntheticCreationalContext; +import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; @Recorder public class AgroalRecorder { + private final RuntimeValue runtimeConfig; + + @Inject + public AgroalRecorder(RuntimeValue runtimeConfig) { + this.runtimeConfig = runtimeConfig; + } + public Supplier dataSourceSupportSupplier(AgroalDataSourceSupport agroalDataSourceSupport) { return new Supplier() { @Override @@ -20,14 +32,28 @@ public AgroalDataSourceSupport get() { }; } + public Supplier agroalDataSourceCheckActiveSupplier(String dataSourceName) { + return new Supplier<>() { + @Override + public ActiveResult get() { + if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); + } + + return ActiveResult.active(); + } + }; + } + public Function, AgroalDataSource> agroalDataSourceSupplier( String dataSourceName, @SuppressWarnings("unused") DataSourcesRuntimeConfig dataSourcesRuntimeConfig) { return new Function<>() { + @SuppressWarnings("deprecation") @Override public AgroalDataSource apply(SyntheticCreationalContext context) { DataSources dataSources = context.getInjectedReference(DataSources.class); - return dataSources.getDataSource(dataSourceName); + return dataSources.createDataSource(dataSourceName); } }; } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index 5f72e43467730..e48f06ce36d5d 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -9,13 +9,8 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; import java.util.stream.Collectors; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Instance; import jakarta.inject.Singleton; @@ -40,11 +35,11 @@ import io.agroal.narayana.NarayanaTransactionIntegration; import io.quarkus.agroal.runtime.JdbcDriver.JdbcDriverLiteral; import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.narayana.jta.runtime.TransactionManagerConfiguration; @@ -80,13 +75,10 @@ public class DataSources { private final TransactionManager transactionManager; private final XAResourceRecoveryRegistry xaResourceRecoveryRegistry; private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; - private final DataSourceSupport dataSourceSupport; private final AgroalDataSourceSupport agroalDataSourceSupport; private final Instance agroalPoolInterceptors; private final Instance agroalOpenTelemetryWrapper; - private final ConcurrentMap dataSources = new ConcurrentHashMap<>(); - public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesJdbcBuildTimeConfig dataSourcesJdbcBuildTimeConfig, DataSourcesJdbcRuntimeConfig dataSourcesJdbcRuntimeConfig, @@ -94,7 +86,6 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, TransactionManager transactionManager, XAResourceRecoveryRegistry xaResourceRecoveryRegistry, TransactionSynchronizationRegistry transactionSynchronizationRegistry, - DataSourceSupport dataSourceSupport, AgroalDataSourceSupport agroalDataSourceSupport, @Any Instance agroalPoolInterceptors, Instance agroalOpenTelemetryWrapper) { @@ -106,7 +97,6 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, this.transactionManager = transactionManager; this.xaResourceRecoveryRegistry = xaResourceRecoveryRegistry; this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; - this.dataSourceSupport = dataSourceSupport; this.agroalDataSourceSupport = agroalDataSourceSupport; this.agroalPoolInterceptors = agroalPoolInterceptors; this.agroalOpenTelemetryWrapper = agroalOpenTelemetryWrapper; @@ -123,47 +113,41 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, *

* This method is thread-safe * - * @deprecated This method should not be used as it can very easily lead to timing issues during bean creation + * @deprecated Use {@link AgroalDataSourceUtil#dataSourceInstance(String)} instead. + * This method should not be used as it can very easily lead to timing issues during bean creation. */ @Deprecated public static AgroalDataSource fromName(String dataSourceName) { - return Arc.container().instance(DataSources.class).get() - .getDataSource(dataSourceName); + return AgroalDataSourceUtil.dataSourceInstance(dataSourceName).get(); } + /** + * @deprecated This shouldn't be needed. + * Use {@link AgroalDataSourceUtil#dataSourceIfActive(String)} to check if a datasource is active. + */ + @Deprecated public boolean isDataSourceCreated(String dataSourceName) { - return dataSources.containsKey(dataSourceName); + return agroalDataSourceSupport.entries.containsKey(dataSourceName); } + /** + * @deprecated Use {@link AgroalDataSourceUtil#activeDataSourceNames()} instead. + */ + @Deprecated public Set getActiveDataSourceNames() { - // Datasources are created on startup, - // and we only create active datasources. - return dataSources.keySet(); + return AgroalDataSourceUtil.activeDataSourceNames(); } + /** + * @deprecated Use {@link AgroalDataSourceUtil#dataSourceInstance(String)} instead. + */ + @Deprecated public AgroalDataSource getDataSource(String dataSourceName) { - return dataSources.computeIfAbsent(dataSourceName, new Function() { - @Override - public AgroalDataSource apply(String s) { - return doCreateDataSource(s, true); - } - }); - } - - @PostConstruct - public void start() { - for (String dataSourceName : agroalDataSourceSupport.entries.keySet()) { - dataSources.computeIfAbsent(dataSourceName, new Function() { - @Override - public AgroalDataSource apply(String s) { - return doCreateDataSource(s, false); - } - }); - } + return ClientProxy.unwrap(AgroalDataSourceUtil.dataSourceInstance(dataSourceName).get()); } @SuppressWarnings("resource") - public AgroalDataSource doCreateDataSource(String dataSourceName, boolean failIfInactive) { + public AgroalDataSource createDataSource(String dataSourceName) { if (!agroalDataSourceSupport.entries.containsKey(dataSourceName)) { throw new IllegalArgumentException("No datasource named '" + dataSourceName + "' exists"); } @@ -172,16 +156,6 @@ public AgroalDataSource doCreateDataSource(String dataSourceName, boolean failIf .dataSources().get(dataSourceName).jdbc(); DataSourceRuntimeConfig dataSourceRuntimeConfig = dataSourcesRuntimeConfig.dataSources().get(dataSourceName); - if (dataSourceSupport.getInactiveNames().contains(dataSourceName)) { - if (failIfInactive) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } else { - // This only happens on startup, and effectively cancels the creation - // so that we only throw an exception on first actual use. - return null; - } - } - DataSourceJdbcRuntimeConfig dataSourceJdbcRuntimeConfig = dataSourcesJdbcRuntimeConfig .dataSources().get(dataSourceName).jdbc(); if (!dataSourceJdbcRuntimeConfig.url().isPresent()) { @@ -443,13 +417,4 @@ private static void loadDriversInTCCL() { } } } - - @PreDestroy - public void stop() { - for (AgroalDataSource dataSource : dataSources.values()) { - if (dataSource != null) { - dataSource.close(); - } - } - } } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java index 4a6e3db63be68..5fdc1975cabb6 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java @@ -3,6 +3,7 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.sql.DataSource; @@ -19,7 +20,7 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.runtime.AgroalDataSourceSupport; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.arc.Arc; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceSupport; @@ -29,28 +30,27 @@ public class DataSourceHealthCheck implements HealthCheck { @Inject - Instance dataSources; + Instance dataSourceSupport; private final Map checkedDataSources = new HashMap<>(); @PostConstruct protected void init() { - if (!dataSources.isResolvable()) { + if (!dataSourceSupport.isResolvable()) { // No configured Agroal datasource at build time. return; } - DataSourceSupport support = Arc.container().instance(DataSourceSupport.class) - .get(); + DataSourceSupport support = dataSourceSupport.get(); AgroalDataSourceSupport agroalSupport = Arc.container().instance(AgroalDataSourceSupport.class) .get(); - Set excludedNames = support.getInactiveOrHealthCheckExcludedNames(); + Set healthCheckExcludedNames = support.getHealthCheckExcludedNames(); for (String name : agroalSupport.entries.keySet()) { - if (excludedNames.contains(name)) { + if (healthCheckExcludedNames.contains(name)) { continue; } - DataSource ds = dataSources.get().getDataSource(name); - if (ds != null) { - checkedDataSources.put(name, ds); + Optional dataSource = AgroalDataSourceUtil.dataSourceIfActive(name); + if (dataSource.isPresent()) { + checkedDataSources.put(name, dataSource.get()); } } } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/metrics/AgroalMetricsRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/metrics/AgroalMetricsRecorder.java index 726a0c0512e5a..92592130f9ed3 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/metrics/AgroalMetricsRecorder.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/metrics/AgroalMetricsRecorder.java @@ -1,15 +1,16 @@ package io.quarkus.agroal.runtime.metrics; import java.time.Duration; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.jboss.logging.Logger; +import io.agroal.api.AgroalDataSource; import io.agroal.api.AgroalDataSourceMetrics; -import io.quarkus.agroal.runtime.DataSources; -import io.quarkus.arc.Arc; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.metrics.MetricsFactory; @@ -34,15 +35,14 @@ public Consumer registerDataSourceMetrics(String dataSourceName) return new Consumer() { @Override public void accept(MetricsFactory metricsFactory) { - DataSources dataSources = Arc.container().instance(DataSources.class).get(); - if (!dataSources.getActiveDataSourceNames().contains(dataSourceName)) { - log.debug("Not registering metrics for datasource '" + dataSourceName + "'" - + " as the datasource has been deactivated in the configuration"); + Optional dataSource = AgroalDataSourceUtil.dataSourceIfActive(dataSourceName); + if (dataSource.isEmpty()) { + log.debug("Not registering metrics for datasource '" + dataSourceName + "' as the datasource is inactive"); return; } String tagValue = DataSourceUtil.isDefault(dataSourceName) ? "default" : dataSourceName; - AgroalDataSourceMetrics metrics = dataSources.getDataSource(dataSourceName).getMetrics(); + AgroalDataSourceMetrics metrics = dataSource.get().getMetrics(); metricsFactory.builder("agroal.active.count") .description( diff --git a/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java b/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java index fe35e781ae7e5..ca1047119cadf 100644 --- a/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java +++ b/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java @@ -50,14 +50,25 @@ public static ConfigurationException dataSourceNotConfigured(String dataSourceNa dataSourcePropertyKey(dataSourceName, "jdbc.url"))); } + /** + * @deprecated Simply call {@code io.quarkus.arc.ClientProxy#unwrap(Object)} on a datasource bean instance: + * it will throw a similar exception, with more details and an actionable message. + */ + @Deprecated public static ConfigurationException dataSourceInactive(String dataSourceName) { - return new ConfigurationException(String.format(Locale.ROOT, + return new ConfigurationException(dataSourceInactiveReasonDeactivated(dataSourceName), + Set.of(dataSourcePropertyKey(dataSourceName, "db-kind"), + dataSourcePropertyKey(dataSourceName, "username"), + dataSourcePropertyKey(dataSourceName, "password"), + dataSourcePropertyKey(dataSourceName, "jdbc.url"))); + } + + public static String dataSourceInactiveReasonDeactivated(String dataSourceName) { + return String.format(Locale.ROOT, "Datasource '%s' was deactivated through configuration properties." - + " To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...)." - + " Alternatively, activate the datasource by setting configuration property '%s' to 'true' and configure datasource '%s'." + + " To activate the datasource, set configuration property '%s' to 'true' and configure datasource '%s'." + " Refer to https://quarkus.io/guides/datasource for guidance.", - dataSourceName, dataSourcePropertyKey(dataSourceName, "active"), dataSourceName), - Set.of(dataSourcePropertyKey(dataSourceName, "active"))); + dataSourceName, dataSourcePropertyKey(dataSourceName, "active"), dataSourceName); } private DataSourceUtil() { diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java index 58fb761d34767..6f4c1ae7e5d43 100644 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java @@ -15,11 +15,6 @@ public interface DataSourceRuntimeConfig { * * See xref:datasource.adoc#datasource-active[this section of the documentation]. * - * If the datasource is not active, it won't start with the application, - * and accessing the corresponding Datasource CDI bean will fail, - * meaning in particular that consumers of this datasource - * (e.g. Hibernate ORM persistence units) will fail to start unless they are inactive too. - * * @asciidoclet */ @WithDefault("true") diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceSupport.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceSupport.java index 21ea9141a98ce..97835ab57c202 100644 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceSupport.java +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceSupport.java @@ -12,22 +12,49 @@ */ public class DataSourceSupport { + private final Set healthCheckExcludedNames; private final Set inactiveNames; private final Set inactiveOrHealthCheckExcludedNames; public DataSourceSupport(Set healthCheckExcludedNames, Set inactiveNames) { + this.healthCheckExcludedNames = healthCheckExcludedNames; this.inactiveOrHealthCheckExcludedNames = new HashSet<>(); inactiveOrHealthCheckExcludedNames.addAll(inactiveNames); inactiveOrHealthCheckExcludedNames.addAll(healthCheckExcludedNames); this.inactiveNames = inactiveNames; } + /** + * @deprecated This may not account for datasources deactivated automatically (due to missing configuration, ...). + * To check if a datasource bean is active, use + * {@code Arc.container().select(...).getHandle().getBean().isActive()}. + * Alternatively, to check if a datasource is active, use the utils + * {@code AgroalDataSourceUtil#dataSourceIfActive(...)}/{@code AgroalDataSourceUtil#activeDataSourceNames()} + * or + * {@code ReactiveDataSourceUtil#dataSourceIfActive(...)}/{@code ReactiveDataSourceUtil#activeDataSourceNames()}. + */ + @Deprecated public Set getInactiveNames() { return inactiveNames; } + /** + * @deprecated This may not account for datasources deactivated automatically (due to missing configuration, ...). + * To check if a datasource is excluded from health checks, use {@link #getHealthCheckExcludedNames()}. + * To check if a datasource bean is active, use + * {@code Arc.container().select(...).getHandle().getBean().isActive()}. + * Alternatively, to check if a datasource is active, use the utils + * {@code AgroalDataSourceUtil#dataSourceIfActive(...)}/{@code AgroalDataSourceUtil#activeDataSourceNames()} + * or + * {@code ReactiveDataSourceUtil#dataSourceIfActive(...)}/{@code ReactiveDataSourceUtil#activeDataSourceNames()}. + */ + @Deprecated public Set getInactiveOrHealthCheckExcludedNames() { return inactiveOrHealthCheckExcludedNames; } + + public Set getHealthCheckExcludedNames() { + return healthCheckExcludedNames; + } } diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java index cd37ff8462667..ad40723138484 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java @@ -18,6 +18,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.sql.DataSource; + import jakarta.enterprise.inject.Default; import jakarta.inject.Singleton; @@ -32,7 +34,7 @@ import org.jboss.jandex.DotName; import org.jboss.logging.Logger; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.deployment.AgroalDataSourceBuildUtil; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem; import io.quarkus.agroal.spi.JdbcInitialSQLGeneratorBuildItem; @@ -212,7 +214,8 @@ void createBeans(FlywayRecorder recorder, .setRuntimeInit() .unremovable() .addInjectionPoint(ClassType.create(DotName.createSimple(FlywayContainerProducer.class))) - .addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class))) + .addInjectionPoint(ClassType.create(DotName.createSimple(DataSource.class)), + AgroalDataSourceBuildUtil.qualifier(dataSourceName)) .createWith(recorder.flywayContainerFunction(dataSourceName, hasMigrations, createPossible)); AnnotationInstance flywayContainerQualifier; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index 7ef6a8fa83699..4fe8bd294c92f 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -30,8 +30,8 @@ public void testBootSucceedsButFlywayDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource '' for Flyway", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 135149791d7b6..73e06b874e1ba 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -28,8 +28,8 @@ public void testBootSucceedsButFlywayDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource '' for Flyway", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java index 13c55ccdba628..2b2d090e35d7f 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java @@ -41,8 +41,8 @@ public void testBootSucceedsButFlywayDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", "Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java index d552365308213..2901ff1084a9b 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java @@ -38,8 +38,8 @@ public void testBootSucceedsButFlywayDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", "Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java index d6f4dd9c9e6d0..7f703bbe1130c 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java @@ -33,8 +33,8 @@ public void testBootSucceedsButFlywayDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource '' for Flyway", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java index e21e2d4996e97..92ec49a503bf2 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java @@ -44,8 +44,8 @@ public void testBootSucceedsButFlywayDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", "Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerUtil.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerUtil.java index 34e6a4629f295..d041506c7e5cc 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerUtil.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayContainerUtil.java @@ -6,7 +6,7 @@ import jakarta.enterprise.inject.Default; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -23,7 +23,7 @@ public static FlywayContainer getFlywayContainer(String dataSourceName) { public static List getActiveFlywayContainers() { List result = new ArrayList<>(); - for (String datasourceName : Arc.container().instance(DataSources.class).get().getActiveDataSourceNames()) { + for (String datasourceName : AgroalDataSourceUtil.activeDataSourceNames()) { InstanceHandle handle = Arc.container().instance(FlywayContainer.class, getFlywayContainerQualifier(datasourceName)); if (!handle.isAvailable()) { diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 3af211f682891..47a87265ebf12 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -20,9 +20,11 @@ import org.flywaydb.core.internal.schemahistory.SchemaHistory; import org.jboss.logging.Logger; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -64,14 +66,15 @@ public Function, FlywayContainer> fl public FlywayContainer apply(SyntheticCreationalContext context) { DataSource dataSource; try { - dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName); + // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources + dataSource = ClientProxy.unwrap(context.getInjectedReference( + DataSource.class, AgroalDataSourceUtil.qualifier(dataSourceName))); if (dataSource instanceof UnconfiguredDataSource) { throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); } - } catch (ConfigurationException e) { + } catch (ConfigurationException | InactiveBeanException e) { // TODO do we really want to enable retrieval of a FlywayContainer for an unconfigured/inactive datasource? - // Assigning ApplicationScoped to the FlywayContainer - // and throwing UnsatisfiedResolutionException on bean creation (first access) + // Marking the FlywayContainer bean as inactive when the datasource is inactive/unconfigured // would probably make more sense. return new UnconfiguredDataSourceFlywayContainer(dataSourceName, String.format(Locale.ROOT, "Unable to find datasource '%s' for Flyway: %s", @@ -101,8 +104,7 @@ public void doStartActions(String dataSourceName) { if (!flywayDataSourceRuntimeConfig.active // If not specified explicitly, Flyway is active when the datasource itself is active. - .orElseGet(() -> Arc.container().instance(DataSources.class).get().getActiveDataSourceNames() - .contains(dataSourceName))) { + .orElseGet(() -> AgroalDataSourceUtil.dataSourceIfActive(dataSourceName).isPresent())) { return; } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index 7a17d32e38408..fbc3f973e8953 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -27,7 +27,7 @@ import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; -import io.quarkus.agroal.runtime.DataSources; +import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; @@ -155,7 +155,7 @@ void generateJpaConfigBean(HibernateOrmRecorder recorder, AnnotationInstance.builder(Any.class).build()); } else { configurator.addInjectionPoint(ParameterizedType.create(DotName.createSimple(Instance.class), - new Type[] { ClassType.create(DotName.createSimple(DataSources.class)) }, null), + new Type[] { ClassType.create(DotName.createSimple(AgroalDataSource.class)) }, null), AnnotationInstance.builder(Any.class).build()); } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigActiveFalseTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigActiveFalseTest.java index 570fdd3e6368f..aa8bc452a9cf8 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigActiveFalseTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigActiveFalseTest.java @@ -27,8 +27,8 @@ public class EntitiesInDefaultPUWithExplicitDatasourceConfigActiveFalseTest { .hasMessageContainingAll( "Unable to find datasource 'ds-1' for persistence unit ''", "Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + " to 'true' and configure datasource 'ds-1'", "Refer to https://quarkus.io/guides/datasource for guidance.")); diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java index 04e0448723341..a74f6d6a24515 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java @@ -22,8 +22,8 @@ public class EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest { .hasMessageContainingAll( "Unable to find datasource '' for persistence unit ''", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance.")); diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigActiveFalseTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigActiveFalseTest.java index 8f5f8c6904fc5..a7089624b7044 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigActiveFalseTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigActiveFalseTest.java @@ -27,8 +27,8 @@ public class EntitiesInNamedPUWithExplicitDatasourceConfigActiveFalseTest { .hasMessageContainingAll( "Unable to find datasource 'ds-1' for persistence unit 'pu-1'", "Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + " to 'true' and configure datasource 'ds-1'", "Refer to https://quarkus.io/guides/datasource for guidance.")); diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java index 298c13516a675..00e75da7c2723 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java @@ -11,7 +11,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.datasource.runtime.DataSourceSupport; +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.arc.InjectableInstance; import io.quarkus.hibernate.orm.PersistenceUnit; import io.quarkus.hibernate.orm.config.namedpu.MyEntity; import io.quarkus.narayana.jta.QuarkusTransaction; @@ -96,7 +98,12 @@ private static void doTestPersistRetrieve(Session session, long id) { private static class MyProducer { @Inject - DataSourceSupport dataSourceSupport; + @DataSource("ds-1") + InjectableInstance dataSource1Bean; + + @Inject + @DataSource("ds-2") + InjectableInstance dataSource2Bean; @Inject @PersistenceUnit("pu-1") @@ -109,10 +116,12 @@ private static class MyProducer { @Produces @ApplicationScoped public Session session() { - if (dataSourceSupport.getInactiveNames().contains("ds-1")) { + if (dataSource1Bean.getHandle().getBean().isActive()) { + return pu1SessionBean; + } else if (dataSource2Bean.getHandle().getBean().isActive()) { return pu2SessionBean; } else { - return pu1SessionBean; + throw new RuntimeException("No active datasource!"); } } } diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java index c9eb9d247962a..333354627274e 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java @@ -11,7 +11,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.datasource.runtime.DataSourceSupport; +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.arc.InjectableInstance; import io.quarkus.hibernate.orm.PersistenceUnit; import io.quarkus.hibernate.orm.config.namedpu.MyEntity; import io.quarkus.narayana.jta.QuarkusTransaction; @@ -96,7 +98,12 @@ private static void doTestPersistRetrieve(Session session, long id) { private static class MyProducer { @Inject - DataSourceSupport dataSourceSupport; + @DataSource("ds-1") + InjectableInstance dataSource1Bean; + + @Inject + @DataSource("ds-2") + InjectableInstance dataSource2Bean; @Inject @PersistenceUnit("pu-1") @@ -109,10 +116,12 @@ private static class MyProducer { @Produces @ApplicationScoped public Session session() { - if (dataSourceSupport.getInactiveNames().contains("ds-1")) { + if (dataSource1Bean.getHandle().getBean().isActive()) { + return pu1SessionBean; + } else if (dataSource2Bean.getHandle().getBean().isActive()) { return pu2SessionBean; } else { - return pu1SessionBean; + throw new RuntimeException("No active datasource!"); } } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index 4e9b779d84a62..029b2e7385110 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -25,9 +25,10 @@ import org.hibernate.service.internal.ProvidedService; import org.jboss.logging.Logger; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings.Builder; import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder; @@ -387,7 +388,9 @@ private static void injectDataSource(String persistenceUnitName, String dataSour DataSource dataSource; try { - dataSource = Arc.container().instance(DataSources.class).get().getDataSource(dataSourceName); + // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources + // and for the instanceof check below. + dataSource = ClientProxy.unwrap(AgroalDataSourceUtil.dataSourceInstance(dataSourceName).get()); if (dataSource instanceof UnconfiguredDataSource) { throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); } diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java index 80ed019c91ce2..57798fb120921 100644 --- a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest.java @@ -23,8 +23,8 @@ public class EntitiesInDefaultPUWithImplicitDatasourceConfigActiveFalseTest { .hasMessageContainingAll( "Unable to find datasource '' for persistence unit 'default-reactive'", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance.")); diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java index aaf39c0596a95..97608331b591b 100644 --- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java +++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/FastBootHibernateReactivePersistenceProvider.java @@ -28,9 +28,10 @@ import org.jboss.logging.Logger; import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.InstanceHandle; import io.quarkus.datasource.common.runtime.DataSourceUtil; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.hibernate.orm.runtime.BuildTimeSettings; import io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider; import io.quarkus.hibernate.orm.runtime.HibernateOrmRuntimeConfig; @@ -283,14 +284,12 @@ private void registerVertxAndPool(String persistenceUnitName, String datasourceName = DataSourceUtil.DEFAULT_DATASOURCE_NAME; Pool pool; try { - if (Arc.container().instance(DataSourceSupport.class).get().getInactiveNames().contains(datasourceName)) { - throw DataSourceUtil.dataSourceInactive(datasourceName); - } - InstanceHandle poolHandle = Arc.container().instance(Pool.class); - if (!poolHandle.isAvailable()) { + InjectableInstance poolHandle = Arc.container().select(Pool.class); + if (!poolHandle.isResolvable()) { throw new IllegalStateException("No pool has been defined for persistence unit " + persistenceUnitName); } - pool = poolHandle.get(); + // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources + pool = ClientProxy.unwrap(poolHandle.get()); } catch (RuntimeException e) { throw PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, datasourceName, e); } diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index 3d12da5af356d..c7967d1ca4e4d 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -16,6 +16,8 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; +import javax.sql.DataSource; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; @@ -25,7 +27,7 @@ import org.jboss.jandex.DotName; import org.jboss.logging.Logger; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.deployment.AgroalDataSourceBuildUtil; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDataSourceSchemaReadyBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -264,7 +266,8 @@ void createBeans(LiquibaseRecorder recorder, .setRuntimeInit() .unremovable() .addInjectionPoint(ClassType.create(DotName.createSimple(LiquibaseFactoryProducer.class))) - .addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class))) + .addInjectionPoint(ClassType.create(DotName.createSimple(DataSource.class)), + AgroalDataSourceBuildUtil.qualifier(dataSourceName)) .createWith(recorder.liquibaseFunction(dataSourceName)); if (DataSourceUtil.isDefault(dataSourceName)) { diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index f84932288d860..fc13f25e80444 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -30,8 +30,8 @@ public void testBootSucceedsButLiquibaseDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource '' for Liquibase", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 6e1c4760a5c75..5fd45a4839a21 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -28,8 +28,8 @@ public void testBootSucceedsButLiquibaseDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource '' for Liquibase", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 06c35584f3785..bcd8ef6227822 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -41,8 +41,8 @@ public void testBootSucceedsButLiquibaseDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", "Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java index 198665b5afa45..864e3310b9a8d 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -38,8 +38,8 @@ public void testBootSucceedsButLiquibaseDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", "Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java index 94c1e7fcfb445..ed0e26b1edb0c 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java @@ -33,8 +33,8 @@ public void testBootSucceedsButLiquibaseDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource '' for Liquibase", "Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java index 91e939fa9be41..46358a9481f32 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java @@ -44,8 +44,8 @@ public void testBootSucceedsButLiquibaseDeactivated() { .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", "Datasource 'users' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"users\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseFactoryUtil.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseFactoryUtil.java index c3d4698cfe4c6..975600c6aac60 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseFactoryUtil.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseFactoryUtil.java @@ -6,7 +6,7 @@ import jakarta.enterprise.inject.Default; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -24,7 +24,7 @@ public static InstanceHandle getLiquibaseFactory(String dataSo public static List> getActiveLiquibaseFactories() { List> result = new ArrayList<>(); - for (String datasourceName : Arc.container().instance(DataSources.class).get().getActiveDataSourceNames()) { + for (String datasourceName : AgroalDataSourceUtil.activeDataSourceNames()) { InstanceHandle handle = Arc.container().instance(LiquibaseFactory.class, getLiquibaseFactoryQualifier(datasourceName)); if (!handle.isAvailable()) { diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java index 01bde1120a577..58174592136bb 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java @@ -7,9 +7,9 @@ import jakarta.enterprise.inject.UnsatisfiedResolutionException; -import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.agroal.runtime.AgroalDataSourceUtil; import io.quarkus.agroal.runtime.UnconfiguredDataSource; -import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -35,7 +35,9 @@ public Function, LiquibaseFactory> public LiquibaseFactory apply(SyntheticCreationalContext context) { DataSource dataSource; try { - dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName); + // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources + dataSource = ClientProxy.unwrap(context.getInjectedReference(DataSource.class, + AgroalDataSourceUtil.qualifier(dataSourceName))); if (dataSource instanceof UnconfiguredDataSource) { throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); } @@ -55,8 +57,8 @@ public void doStartActions(String dataSourceName) { if (!config.getValue().enabled) { return; } - // Liquibase is active when the datasource itself is active. - if (!Arc.container().instance(DataSources.class).get().getActiveDataSourceNames().contains(dataSourceName)) { + // Liquibase is only active when the datasource itself is active. + if (AgroalDataSourceUtil.dataSourceIfActive(dataSourceName).isEmpty()) { return; } diff --git a/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ReactiveDataSourceUtil.java b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ReactiveDataSourceUtil.java index 19302697524ef..5f65eaf5a5276 100644 --- a/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ReactiveDataSourceUtil.java +++ b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/ReactiveDataSourceUtil.java @@ -1,10 +1,15 @@ package io.quarkus.reactive.datasource.runtime; import java.lang.annotation.Annotation; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.spi.Bean; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.vertx.sqlclient.Pool; @@ -13,6 +18,42 @@ public final class ReactiveDataSourceUtil { private ReactiveDataSourceUtil() { } + public static InjectableInstance dataSourceInstance(String dataSourceName) { + return dataSourceInstance(Pool.class, dataSourceName); + } + + public static InjectableInstance dataSourceInstance(Class type, String dataSourceName) { + return Arc.container().select(type, qualifier(dataSourceName)); + } + + public static Optional dataSourceIfActive(String dataSourceName) { + return dataSourceIfActive(Pool.class, dataSourceName); + } + + public static Optional dataSourceIfActive(Class type, String dataSourceName) { + var instance = dataSourceInstance(type, dataSourceName); + // We want to call get() and throw an exception if the name points to an undefined datasource. + if (!instance.isResolvable() || instance.getHandle().getBean().isActive()) { + return Optional.ofNullable(instance.get()); + } else { + return Optional.empty(); + } + } + + public static Set activeDataSourceNames() { + Set activeNames = new LinkedHashSet<>(); + for (var handle : Arc.container().select(Pool.class).handles()) { + var bean = handle.getBean(); + if (bean != null && bean.isActive()) { + String name = dataSourceName(bean); + if (name != null) { // There may be custom beans, these will have a null name and will be ignored. + activeNames.add(name); + } + } + } + return activeNames; + } + public static String dataSourceName(Bean bean) { for (Object qualifier : bean.getQualifiers()) { if (qualifier instanceof ReactiveDataSource) { diff --git a/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java b/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java index b3b14dfec16a9..7beac2a52a16f 100644 --- a/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java +++ b/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.deployment.Capabilities; @@ -212,10 +211,11 @@ private void createPoolIfDefined(DB2PoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(POOL_CREATOR_INJECTION_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(poolFunction) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(db2PoolBeanConfigurator.done()); @@ -226,10 +226,11 @@ private void createPoolIfDefined(DB2PoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(VERTX_DB2_POOL_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(recorder.mutinyDB2Pool(dataSourceName)) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(mutinyDB2PoolConfigurator.done()); } diff --git a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java index f755605237783..f8060a97f8c75 100644 --- a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java +++ b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java @@ -18,15 +18,16 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.util.TypeLiteral; +import jakarta.inject.Inject; import org.jboss.logging.Logger; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; @@ -50,6 +51,26 @@ public class DB2PoolRecorder { private static final TypeLiteral> POOL_CREATOR_TYPE_LITERAL = new TypeLiteral<>() { }; + private final RuntimeValue runtimeConfig; + + @Inject + public DB2PoolRecorder(RuntimeValue runtimeConfig) { + this.runtimeConfig = runtimeConfig; + } + + public Supplier poolCheckActiveSupplier(String dataSourceName) { + return new Supplier<>() { + @Override + public ActiveResult get() { + if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); + } else { + return ActiveResult.active(); + } + } + }; + } + public Function, DB2Pool> configureDB2Pool(RuntimeValue vertx, Supplier eventLoopCount, String dataSourceName, @@ -77,13 +98,10 @@ public DB2Pool apply(SyntheticCreationalContext context) { public Function, io.vertx.mutiny.db2client.DB2Pool> mutinyDB2Pool( String dataSourceName) { return new Function<>() { + @SuppressWarnings("unchecked") @Override public io.vertx.mutiny.db2client.DB2Pool apply( SyntheticCreationalContext context) { - DataSourceSupport datasourceSupport = (DataSourceSupport) context.getInjectedReference(DataSourceSupport.class); - if (datasourceSupport.getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } DB2Pool db2Pool = context.getInjectedReference(DB2Pool.class, qualifier(dataSourceName)); return io.vertx.mutiny.db2client.DB2Pool.newInstance(db2Pool); } @@ -97,9 +115,6 @@ private DB2Pool initialize(VertxInternal vertx, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactiveDB2Config dataSourceReactiveDB2Config, SyntheticCreationalContext context) { - if (context.getInjectedReference(DataSourceSupport.class).getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveDB2Config); DB2ConnectOptions db2ConnectOptions = toConnectOptions(dataSourceName, dataSourceRuntimeConfig, diff --git a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java index fe4d3a860c1a7..49d661561afc4 100644 --- a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java +++ b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/health/ReactiveDB2DataSourcesHealthCheck.java @@ -38,12 +38,16 @@ class ReactiveDB2DataSourcesHealthCheck implements HealthCheck { protected void init() { ArcContainer container = Arc.container(); DataSourceSupport support = container.instance(DataSourceSupport.class).get(); - Set excludedNames = support.getInactiveOrHealthCheckExcludedNames(); + Set excludedNames = support.getHealthCheckExcludedNames(); for (InstanceHandle handle : container.select(DB2Pool.class, Any.Literal.INSTANCE).handles()) { - String db2PoolName = getDB2PoolName(handle.getBean()); - if (!excludedNames.contains(db2PoolName)) { - db2Pools.put(db2PoolName, handle.get()); + if (!handle.getBean().isActive()) { + continue; } + String poolName = getDB2PoolName(handle.getBean()); + if (excludedNames.contains(poolName)) { + continue; + } + db2Pools.put(poolName, handle.get()); } } diff --git a/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java b/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java index 32eb30d69feb7..10e2ee19af96e 100644 --- a/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java +++ b/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.deployment.Capabilities; @@ -211,10 +210,11 @@ private void createPoolIfDefined(MSSQLPoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(POOL_CREATOR_INJECTION_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(poolFunction) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(msSQLPoolBeanConfigurator.done()); @@ -225,10 +225,11 @@ private void createPoolIfDefined(MSSQLPoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(VERTX_MSSQL_POOL_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(recorder.mutinyMSSQLPool(dataSourceName)) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(mutinyMSSQLPoolConfigurator.done()); } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index 46661dc547901..fca2e0a2c09cf 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -10,8 +10,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.mssqlclient.MSSQLPool; import io.vertx.sqlclient.Pool; @@ -58,16 +59,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index a13418fb0050d..b67dc2ea5871d 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,15 @@ package io.quarkus.reactive.mssql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -18,32 +17,29 @@ public class ConfigActiveFalseDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped public static class MyBean { @Inject Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 4754d0888cae4..3feefb3d1bb88 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -10,9 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.mssqlclient.MSSQLPool; import io.vertx.sqlclient.Pool; @@ -66,16 +67,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + " to 'true' and configure datasource 'ds-1'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java index ff80fe8b84745..718321ad42364 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -1,17 +1,16 @@ package io.quarkus.reactive.mssql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -22,23 +21,24 @@ public class ConfigActiveFalseNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.datasource.ds-1.active", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mssql"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mssql") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + + " to 'true' and configure datasource 'ds-1'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool"));; @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" - + " to 'true' and configure datasource 'ds-1'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped @@ -46,9 +46,5 @@ public static class MyBean { @Inject @ReactiveDataSource("ds-1") Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java index f90fd0326dc4a..64ff89272fc6e 100644 --- a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java +++ b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java @@ -18,15 +18,16 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.util.TypeLiteral; +import jakarta.inject.Inject; import org.jboss.logging.Logger; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; @@ -51,6 +52,26 @@ public class MSSQLPoolRecorder { private static final Logger log = Logger.getLogger(MSSQLPoolRecorder.class); + private final RuntimeValue runtimeConfig; + + @Inject + public MSSQLPoolRecorder(RuntimeValue runtimeConfig) { + this.runtimeConfig = runtimeConfig; + } + + public Supplier poolCheckActiveSupplier(String dataSourceName) { + return new Supplier<>() { + @Override + public ActiveResult get() { + if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); + } else { + return ActiveResult.active(); + } + } + }; + } + public Function, MSSQLPool> configureMSSQLPool(RuntimeValue vertx, Supplier eventLoopCount, String dataSourceName, @@ -62,9 +83,6 @@ public Function, MSSQLPool> configureMSSQL return new Function<>() { @Override public MSSQLPool apply(SyntheticCreationalContext context) { - if (context.getInjectedReference(DataSourceSupport.class).getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } MSSQLPool pool = initialize((VertxInternal) vertx.getValue(), eventLoopCount.get(), dataSourceName, @@ -85,11 +103,6 @@ public Function excludedNames = support.getInactiveOrHealthCheckExcludedNames(); + Set excludedNames = support.getHealthCheckExcludedNames(); for (InstanceHandle handle : container.select(MSSQLPool.class, Any.Literal.INSTANCE).handles()) { + if (!handle.getBean().isActive()) { + continue; + } String poolName = ReactiveDataSourceUtil.dataSourceName(handle.getBean()); - if (!excludedNames.contains(poolName)) { - addPool(poolName, handle.get()); + if (excludedNames.contains(poolName)) { + continue; } + addPool(poolName, handle.get()); } } diff --git a/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java b/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java index ad51699bda129..3a740d9a8fea3 100644 --- a/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java +++ b/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.deployment.Capabilities; @@ -212,10 +211,11 @@ private void createPoolIfDefined(MySQLPoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(POOL_CREATOR_INJECTION_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(poolFunction) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(mySQLPoolBeanConfigurator.done()); @@ -226,10 +226,11 @@ private void createPoolIfDefined(MySQLPoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(VERTX_MYSQL_POOL_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(recorder.mutinyMySQLPool(dataSourceName)) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(mutinyMySQLPoolConfigurator.done()); } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index cd586106c9846..8c6afc221721a 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -10,8 +10,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.mysqlclient.MySQLPool; import io.vertx.sqlclient.Pool; @@ -58,16 +59,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 1aab5a2a7e964..631010a80d17a 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,15 @@ package io.quarkus.reactive.mysql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -18,32 +17,29 @@ public class ConfigActiveFalseDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped public static class MyBean { @Inject Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 5f32384ea0807..f461227a87edc 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -10,9 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.mysqlclient.MySQLPool; import io.vertx.sqlclient.Pool; @@ -66,16 +67,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + " to 'true' and configure datasource 'ds-1'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java index 233909063e4f6..e101c5eaddaf5 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -1,17 +1,16 @@ package io.quarkus.reactive.mysql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -22,23 +21,24 @@ public class ConfigActiveFalseNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.datasource.ds-1.active", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mysql"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mysql") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + + " to 'true' and configure datasource 'ds-1'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool"));; @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" - + " to 'true' and configure datasource 'ds-1'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped @@ -46,9 +46,5 @@ public static class MyBean { @Inject @ReactiveDataSource("ds-1") Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java index 7585d52e36a93..ef352e2640f55 100644 --- a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java +++ b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java @@ -20,13 +20,14 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.util.TypeLiteral; +import jakarta.inject.Inject; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; @@ -50,6 +51,26 @@ public class MySQLPoolRecorder { private static final TypeLiteral> POOL_CREATOR_TYPE_LITERAL = new TypeLiteral<>() { }; + private final RuntimeValue runtimeConfig; + + @Inject + public MySQLPoolRecorder(RuntimeValue runtimeConfig) { + this.runtimeConfig = runtimeConfig; + } + + public Supplier poolCheckActiveSupplier(String dataSourceName) { + return new Supplier<>() { + @Override + public ActiveResult get() { + if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); + } else { + return ActiveResult.active(); + } + } + }; + } + public Function, MySQLPool> configureMySQLPool(RuntimeValue vertx, Supplier eventLoopCount, String dataSourceName, @@ -80,10 +101,6 @@ public Function context) { - if (context.getInjectedReference(DataSourceSupport.class).getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveMySQLConfig); List mySQLConnectOptions = toMySQLConnectOptions(dataSourceName, dataSourceRuntimeConfig, diff --git a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java index 1e4218856b7c9..6f401f574b3be 100644 --- a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java +++ b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/health/ReactiveMySQLDataSourcesHealthCheck.java @@ -28,12 +28,16 @@ public ReactiveMySQLDataSourcesHealthCheck() { protected void init() { ArcContainer container = Arc.container(); DataSourceSupport support = container.instance(DataSourceSupport.class).get(); - Set excludedNames = support.getInactiveOrHealthCheckExcludedNames(); + Set excludedNames = support.getHealthCheckExcludedNames(); for (InstanceHandle handle : container.select(MySQLPool.class, Any.Literal.INSTANCE).handles()) { + if (!handle.getBean().isActive()) { + continue; + } String poolName = ReactiveDataSourceUtil.dataSourceName(handle.getBean()); - if (!excludedNames.contains(poolName)) { - addPool(poolName, handle.get()); + if (excludedNames.contains(poolName)) { + continue; } + addPool(poolName, handle.get()); } } diff --git a/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java b/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java index fb178117dc6a7..a95e2b7de3af9 100644 --- a/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java +++ b/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.deployment.Capabilities; @@ -213,10 +212,11 @@ private void createPoolIfDefined(OraclePoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(POOL_CREATOR_INJECTION_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(poolFunction) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(oraclePoolBeanConfigurator.done()); @@ -227,10 +227,11 @@ private void createPoolIfDefined(OraclePoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(VERTX_ORACLE_POOL_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(recorder.mutinyOraclePool(dataSourceName)) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(mutinyOraclePoolConfigurator.done()); } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index 08164d6c1416b..843543e15f008 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -10,8 +10,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.oracleclient.OraclePool; import io.vertx.sqlclient.Pool; @@ -58,16 +59,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 5d8f5b37e1f3e..9951669bbbf22 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,15 @@ package io.quarkus.reactive.oracle.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -18,32 +17,29 @@ public class ConfigActiveFalseDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped public static class MyBean { @Inject Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 5ad96d83652d9..987eefc7a17c9 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -10,9 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.oracleclient.OraclePool; import io.vertx.sqlclient.Pool; @@ -66,16 +67,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + " to 'true' and configure datasource 'ds-1'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java index 03d07806c6b91..0175c286edfea 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -1,17 +1,16 @@ package io.quarkus.reactive.oracle.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -22,23 +21,24 @@ public class ConfigActiveFalseNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.datasource.ds-1.active", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "oracle"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "oracle") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + + " to 'true' and configure datasource 'ds-1'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool"));; @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" - + " to 'true' and configure datasource 'ds-1'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped @@ -46,9 +46,5 @@ public static class MyBean { @Inject @ReactiveDataSource("ds-1") Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java index 907493aaa1efc..9b3cf15a2dd3e 100644 --- a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java +++ b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java @@ -12,15 +12,16 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.util.TypeLiteral; +import jakarta.inject.Inject; import org.jboss.logging.Logger; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; @@ -44,8 +45,28 @@ public class OraclePoolRecorder { private static final TypeLiteral> POOL_CREATOR_TYPE_LITERAL = new TypeLiteral<>() { }; + private final RuntimeValue runtimeConfig; + + @Inject + public OraclePoolRecorder(RuntimeValue runtimeConfig) { + this.runtimeConfig = runtimeConfig; + } + private static final Logger log = Logger.getLogger(OraclePoolRecorder.class); + public Supplier poolCheckActiveSupplier(String dataSourceName) { + return new Supplier<>() { + @Override + public ActiveResult get() { + if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); + } else { + return ActiveResult.active(); + } + } + }; + } + public Function, OraclePool> configureOraclePool(RuntimeValue vertx, Supplier eventLoopCount, String dataSourceName, @@ -76,10 +97,6 @@ public Function context) { - if (context.getInjectedReference(DataSourceSupport.class).getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactiveOracleConfig); OracleConnectOptions oracleConnectOptions = toOracleConnectOptions(dataSourceName, dataSourceRuntimeConfig, diff --git a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/health/ReactiveOracleDataSourcesHealthCheck.java b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/health/ReactiveOracleDataSourcesHealthCheck.java index 797b703cb1ef8..76a76de2fae08 100644 --- a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/health/ReactiveOracleDataSourcesHealthCheck.java +++ b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/health/ReactiveOracleDataSourcesHealthCheck.java @@ -28,12 +28,16 @@ public ReactiveOracleDataSourcesHealthCheck() { protected void init() { ArcContainer container = Arc.container(); DataSourceSupport support = container.instance(DataSourceSupport.class).get(); - Set excludedNames = support.getInactiveOrHealthCheckExcludedNames(); + Set excludedNames = support.getHealthCheckExcludedNames(); for (InstanceHandle handle : container.select(OraclePool.class, Any.Literal.INSTANCE).handles()) { + if (!handle.getBean().isActive()) { + continue; + } String poolName = ReactiveDataSourceUtil.dataSourceName(handle.getBean()); - if (!excludedNames.contains(poolName)) { - addPool(poolName, handle.get()); + if (excludedNames.contains(poolName)) { + continue; } + addPool(poolName, handle.get()); } } diff --git a/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java index b97f2b082b5e0..05dbd0660787e 100644 --- a/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java +++ b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.deployment.Capabilities; @@ -217,10 +216,11 @@ private void createPoolIfDefined(PgPoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(POOL_CREATOR_INJECTION_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(poolFunction) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(pgPoolBeanConfigurator.done()); @@ -232,10 +232,11 @@ private void createPoolIfDefined(PgPoolRecorder recorder, .scope(ApplicationScoped.class) .qualifiers(qualifiers(dataSourceName)) .addInjectionPoint(VERTX_PG_POOL_TYPE, qualifier(dataSourceName)) - .addInjectionPoint(ClassType.create(DataSourceSupport.class)) + .checkActive(recorder.poolCheckActiveSupplier(dataSourceName)) .createWith(recorder.mutinyPgPool(dataSourceName)) .unremovable() - .setRuntimeInit(); + .setRuntimeInit() + .startup(); syntheticBeans.produce(mutinyPgPoolConfigurator.done()); } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index 02db403659cdd..5c7f2e6378138 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -10,8 +10,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.pgclient.PgPool; import io.vertx.sqlclient.Pool; @@ -58,16 +59,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index ccfaf9aab951f..e5df737c93a3d 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,15 @@ package io.quarkus.reactive.pg.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -18,32 +17,29 @@ public class ConfigActiveFalseDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource '' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped public static class MyBean { @Inject Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 3c3f1aed3969e..a44ff75428fa6 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -10,9 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.pgclient.PgPool; import io.vertx.sqlclient.Pool; @@ -66,16 +67,17 @@ private void doTest(InjectableInstance instance, Consumer action) { // The bean is always available to be injected during static init // since we don't know whether the datasource will be active at runtime. // So the bean proxy cannot be null. + assertThat(instance.getHandle().getBean()) + .isNotNull() + .returns(false, InjectableBean::isActive); var pool = instance.get(); assertThat(pool).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> action.accept(pool)) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) + .isInstanceOf(InactiveBeanException.class) .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + " to 'true' and configure datasource 'ds-1'", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java index 7201f116fa02c..a7992b109a9bf 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -1,17 +1,16 @@ package io.quarkus.reactive.pg.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.concurrent.CompletionStage; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -22,23 +21,24 @@ public class ConfigActiveFalseNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.datasource.ds-1.active", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "postgresql"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "postgresql") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".active'" + + " to 'true' and configure datasource 'ds-1'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool"));; @Inject MyBean myBean; @Test public void test() { - // However, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> myBean.usePool()) - .isInstanceOf(RuntimeException.class) - .cause() - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource 'ds-1' was deactivated through configuration properties.", - "To solve this, avoid accessing this datasource at runtime, for instance by deactivating consumers (persistence units, ...).", - "Alternatively, activate the datasource by setting configuration property 'quarkus.datasource.\"ds-1\".active'" - + " to 'true' and configure datasource 'ds-1'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + Assertions.fail("Startup should have failed"); } @ApplicationScoped @@ -46,9 +46,5 @@ public static class MyBean { @Inject @ReactiveDataSource("ds-1") Pool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } } } diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java index 4c49c78c87e2c..bbbfca406716c 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java @@ -19,13 +19,14 @@ import jakarta.enterprise.inject.Instance; import jakarta.enterprise.util.TypeLiteral; +import jakarta.inject.Inject; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; -import io.quarkus.datasource.runtime.DataSourceSupport; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; import io.quarkus.reactive.datasource.runtime.ConnectOptionsSupplier; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig; @@ -49,6 +50,26 @@ public class PgPoolRecorder { private static final TypeLiteral> PG_POOL_CREATOR_TYPE_LITERAL = new TypeLiteral<>() { }; + private final RuntimeValue runtimeConfig; + + @Inject + public PgPoolRecorder(RuntimeValue runtimeConfig) { + this.runtimeConfig = runtimeConfig; + } + + public Supplier poolCheckActiveSupplier(String dataSourceName) { + return new Supplier<>() { + @Override + public ActiveResult get() { + if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); + } else { + return ActiveResult.active(); + } + } + }; + } + public Function, PgPool> configurePgPool(RuntimeValue vertx, Supplier eventLoopCount, String dataSourceName, @@ -79,11 +100,6 @@ public Function, io. @SuppressWarnings("unchecked") @Override public io.vertx.mutiny.pgclient.PgPool apply(SyntheticCreationalContext context) { - DataSourceSupport datasourceSupport = (DataSourceSupport) context.getInjectedReference(DataSourceSupport.class); - if (datasourceSupport.getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } - return io.vertx.mutiny.pgclient.PgPool.newInstance( (PgPool) context.getInjectedReference(PgPool.class, qualifier(dataSourceName))); } @@ -97,9 +113,6 @@ private PgPool initialize(VertxInternal vertx, DataSourceReactiveRuntimeConfig dataSourceReactiveRuntimeConfig, DataSourceReactivePostgreSQLConfig dataSourceReactivePostgreSQLConfig, SyntheticCreationalContext context) { - if (context.getInjectedReference(DataSourceSupport.class).getInactiveNames().contains(dataSourceName)) { - throw DataSourceUtil.dataSourceInactive(dataSourceName); - } PoolOptions poolOptions = toPoolOptions(eventLoopCount, dataSourceRuntimeConfig, dataSourceReactiveRuntimeConfig, dataSourceReactivePostgreSQLConfig); List pgConnectOptionsList = toPgConnectOptions(dataSourceName, dataSourceRuntimeConfig, diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java index 3adf20310f7d8..35fe771cf0c96 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/health/ReactivePgDataSourcesHealthCheck.java @@ -28,12 +28,16 @@ public ReactivePgDataSourcesHealthCheck() { protected void init() { ArcContainer container = Arc.container(); DataSourceSupport support = container.instance(DataSourceSupport.class).get(); - Set excludedNames = support.getInactiveOrHealthCheckExcludedNames(); + Set excludedNames = support.getHealthCheckExcludedNames(); for (InstanceHandle handle : container.select(PgPool.class, Any.Literal.INSTANCE).handles()) { + if (!handle.getBean().isActive()) { + continue; + } String poolName = ReactiveDataSourceUtil.dataSourceName(handle.getBean()); - if (!excludedNames.contains(poolName)) { - addPool(poolName, handle.get()); + if (excludedNames.contains(poolName)) { + continue; } + addPool(poolName, handle.get()); } } From 66cfe30a8723475c90b0601ac22203ba221931a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 27 Sep 2024 13:56:54 +0200 Subject: [PATCH 02/10] Always trim datasource URLs So that we correctly detect empty URLs. --- .../quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java | 3 +++ .../datasource/runtime/DataSourceReactiveRuntimeConfig.java | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java index 60ab80b51c658..662b370b56049 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java @@ -10,6 +10,8 @@ import io.quarkus.runtime.annotations.ConfigDocDefault; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.configuration.TrimmedStringConverter; +import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; @@ -19,6 +21,7 @@ public interface DataSourceJdbcRuntimeConfig { /** * The datasource URL */ + @WithConverter(TrimmedStringConverter.class) Optional url(); /** diff --git a/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java index 2cecb4f2ac9ab..73ec73f62041b 100644 --- a/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java +++ b/extensions/reactive-datasource/runtime/src/main/java/io/quarkus/reactive/datasource/runtime/DataSourceReactiveRuntimeConfig.java @@ -9,10 +9,12 @@ import io.quarkus.runtime.annotations.ConfigDocDefault; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.configuration.TrimmedStringConverter; import io.quarkus.vertx.core.runtime.config.JksConfiguration; import io.quarkus.vertx.core.runtime.config.PemKeyCertConfiguration; import io.quarkus.vertx.core.runtime.config.PemTrustCertConfiguration; import io.quarkus.vertx.core.runtime.config.PfxConfiguration; +import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; @ConfigGroup @@ -31,7 +33,7 @@ public interface DataSourceReactiveRuntimeConfig { * The pool uses round-robin load balancing for server selection during connection establishment. * Note that certain drivers might not accommodate multiple values in this context. */ - Optional> url(); + Optional> url(); /** * The datasource pool maximum size. From cc886677c0ca7a0d3768a1ce06a924e4511df269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 27 Sep 2024 16:16:45 +0200 Subject: [PATCH 03/10] Remove NoConfigTest for datasources Because it duplicates: * ConfigUrlMissingDefaultDatasourceStaticInjectionTest * ConfigUrlMissingDefaultDatasourceDynamicInjectionTest * ConfigUrlMissingNamedDatasourceStaticInjectionTest * ConfigUrlMissingNamedDatasourceDynamicInjectionTest --- .../io/quarkus/agroal/test/NoConfigTest.java | 94 ------------ .../reactive/mssql/client/NoConfigTest.java | 145 ------------------ .../reactive/mysql/client/NoConfigTest.java | 145 ------------------ .../reactive/oracle/client/NoConfigTest.java | 145 ------------------ .../reactive/pg/client/NoConfigTest.java | 144 ----------------- 5 files changed, 673 deletions(-) delete mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java delete mode 100644 extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java delete mode 100644 extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java delete mode 100644 extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java delete mode 100644 extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java deleted file mode 100644 index 9d8e268078bce..0000000000000 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.quarkus.agroal.test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.sql.SQLException; - -import javax.sql.DataSource; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.arc.Arc; -import io.quarkus.runtime.configuration.ConfigurationException; -import io.quarkus.test.QuarkusUnitTest; - -/** - * We should be able to start the application, even with no configuration at all. - */ -public class NoConfigTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The datasource won't be truly "unconfigured" if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); - - @Inject - MyBean myBean; - - @Test - public void dataSource_default() { - DataSource ds = Arc.container().instance(DataSource.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(ds).isNotNull(); - // However, if unconfigured, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> ds.getConnection()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("quarkus.datasource.jdbc.url has not been defined"); - } - - @Test - public void agroalDataSource_default() { - AgroalDataSource ds = Arc.container().instance(AgroalDataSource.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(ds).isNotNull(); - // However, if unconfigured, any attempt to use it at runtime will fail. - assertThatThrownBy(() -> ds.getConnection()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("quarkus.datasource.jdbc.url has not been defined"); - } - - @Test - public void dataSource_named() { - DataSource ds = Arc.container().instance(DataSource.class, - new io.quarkus.agroal.DataSource.DataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(ds).isNull(); - } - - @Test - public void agroalDataSource_named() { - AgroalDataSource ds = Arc.container().instance(AgroalDataSource.class, - new io.quarkus.agroal.DataSource.DataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(ds).isNull(); - } - - @Test - public void injectedBean_default() { - assertThatThrownBy(() -> myBean.useDataSource()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("quarkus.datasource.jdbc.url has not been defined"); - } - - @ApplicationScoped - public static class MyBean { - @Inject - DataSource ds; - - public void useDataSource() throws SQLException { - ds.getConnection(); - } - } -} diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java deleted file mode 100644 index 16b287f159e63..0000000000000 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package io.quarkus.reactive.mssql.client; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.arc.Arc; -import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.test.QuarkusUnitTest; -import io.vertx.mssqlclient.MSSQLPool; -import io.vertx.sqlclient.Pool; - -/** - * We should be able to start the application, even with no configuration at all. - */ -public class NoConfigTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The datasource won't be truly "unconfigured" if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); - - private static final Duration MAX_WAIT = Duration.ofSeconds(10); - - @Inject - MyBean myBean; - - @Test - public void pool_default() { - Pool pool = Arc.container().instance(Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void mutinyPool_default() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void vendorPool_default() { - MSSQLPool pool = Arc.container().instance(MSSQLPool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void mutinyVendorPool_default() { - io.vertx.mutiny.mssqlclient.MSSQLPool pool = Arc.container().instance(io.vertx.mutiny.mssqlclient.MSSQLPool.class) - .get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void pool_named() { - Pool pool = Arc.container().instance(Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyPool_named() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void vendorPool_named() { - MSSQLPool pool = Arc.container().instance(MSSQLPool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyVendorPool_named() { - io.vertx.mutiny.mssqlclient.MSSQLPool pool = Arc.container().instance(io.vertx.mutiny.mssqlclient.MSSQLPool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void injectedBean_default() { - assertThat(myBean.usePool()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @ApplicationScoped - public static class MyBean { - @Inject - MSSQLPool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } - } -} diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java deleted file mode 100644 index ea98c0acb5e8b..0000000000000 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package io.quarkus.reactive.mysql.client; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.arc.Arc; -import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.test.QuarkusUnitTest; -import io.vertx.mysqlclient.MySQLPool; -import io.vertx.sqlclient.Pool; - -/** - * We should be able to start the application, even with no configuration at all. - */ -public class NoConfigTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The datasource won't be truly "unconfigured" if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); - - private static final Duration MAX_WAIT = Duration.ofSeconds(10); - - @Inject - MyBean myBean; - - @Test - public void pool_default() { - Pool pool = Arc.container().instance(Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void mutinyPool_default() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void vendorPool_default() { - MySQLPool pool = Arc.container().instance(MySQLPool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void mutinyVendorPool_default() { - io.vertx.mutiny.mysqlclient.MySQLPool pool = Arc.container().instance(io.vertx.mutiny.mysqlclient.MySQLPool.class) - .get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void pool_named() { - Pool pool = Arc.container().instance(Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyPool_named() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void vendorPool_named() { - MySQLPool pool = Arc.container().instance(MySQLPool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyVendorPool_named() { - io.vertx.mutiny.mysqlclient.MySQLPool pool = Arc.container().instance(io.vertx.mutiny.mysqlclient.MySQLPool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void injectedBean_default() { - assertThat(myBean.usePool()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @ApplicationScoped - public static class MyBean { - @Inject - MySQLPool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } - } -} diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java deleted file mode 100644 index 7b2899780263f..0000000000000 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package io.quarkus.reactive.oracle.client; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.arc.Arc; -import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.test.QuarkusUnitTest; -import io.vertx.oracleclient.OraclePool; -import io.vertx.sqlclient.Pool; - -/** - * We should be able to start the application, even with no configuration at all. - */ -public class NoConfigTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The datasource won't be truly "unconfigured" if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); - - private static final Duration MAX_WAIT = Duration.ofSeconds(10); - - @Inject - MyBean myBean; - - @Test - public void pool_default() { - Pool pool = Arc.container().instance(Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Cannot connect"); - } - - @Test - public void mutinyPool_default() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Cannot connect"); - } - - @Test - public void vendorPool_default() { - OraclePool pool = Arc.container().instance(OraclePool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Cannot connect"); - } - - @Test - public void mutinyVendorPool_default() { - io.vertx.mutiny.oracleclient.OraclePool pool = Arc.container().instance(io.vertx.mutiny.oracleclient.OraclePool.class) - .get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Cannot connect"); - } - - @Test - public void pool_named() { - Pool pool = Arc.container().instance(Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyPool_named() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void vendorPool_named() { - OraclePool pool = Arc.container().instance(OraclePool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyVendorPool_named() { - io.vertx.mutiny.oracleclient.OraclePool pool = Arc.container().instance(io.vertx.mutiny.oracleclient.OraclePool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void injectedBean_default() { - assertThat(myBean.usePool()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Cannot connect"); - } - - @ApplicationScoped - public static class MyBean { - @Inject - OraclePool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } - } -} diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java deleted file mode 100644 index ceaa86c73563f..0000000000000 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java +++ /dev/null @@ -1,144 +0,0 @@ -package io.quarkus.reactive.pg.client; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.arc.Arc; -import io.quarkus.reactive.datasource.ReactiveDataSource; -import io.quarkus.test.QuarkusUnitTest; -import io.vertx.pgclient.PgPool; -import io.vertx.sqlclient.Pool; - -/** - * We should be able to start the application, even with no configuration at all. - */ -public class NoConfigTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The datasource won't be truly "unconfigured" if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); - - private static final Duration MAX_WAIT = Duration.ofSeconds(10); - - @Inject - MyBean myBean; - - @Test - public void pool_default() { - Pool pool = Arc.container().instance(Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void mutinyPool_default() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void vendorPool_default() { - PgPool pool = Arc.container().instance(PgPool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().toCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void mutinyVendorPool_default() { - io.vertx.mutiny.pgclient.PgPool pool = Arc.container().instance(io.vertx.mutiny.pgclient.PgPool.class).get(); - - // The default datasource is a bit special; - // it's historically always been considered as "present" even if there was no explicit configuration. - // So the bean will never be null. - assertThat(pool).isNotNull(); - // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. - assertThat(pool.getConnection().subscribeAsCompletionStage()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @Test - public void pool_named() { - Pool pool = Arc.container().instance(Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyPool_named() { - io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void vendorPool_named() { - PgPool pool = Arc.container().instance(PgPool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void mutinyVendorPool_named() { - io.vertx.mutiny.pgclient.PgPool pool = Arc.container().instance(io.vertx.mutiny.pgclient.PgPool.class, - new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); - // An unconfigured, named datasource has no corresponding bean. - assertThat(pool).isNull(); - } - - @Test - public void injectedBean_default() { - assertThat(myBean.usePool()) - .failsWithin(MAX_WAIT) - .withThrowableThat() - .withMessageContaining("Connection refused"); - } - - @ApplicationScoped - public static class MyBean { - @Inject - PgPool pool; - - public CompletionStage usePool() { - return pool.getConnection().toCompletionStage(); - } - } -} From 4a517f92e56b8389e5058cf13dfad36b03c1df07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 27 Sep 2024 16:25:20 +0200 Subject: [PATCH 04/10] Fix incorrect testing conditions in "active-false" tests for reactive datasources --- .../client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java | 2 +- .../client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java | 2 +- .../client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java | 2 +- .../client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java index 1f6b4aa776aa3..6442b84bacdad 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java @@ -26,7 +26,7 @@ public void testDataSourceHealthCheckExclusion() { .then() .body("status", CoreMatchers.equalTo("UP")) // If the datasource is inactive, there should not be a health check - .body("checks[0].data.\"users\"", CoreMatchers.nullValue()); + .body("checks[0].data.\"ds-1\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java index a8482821f7a05..1869daa6d7fd8 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java @@ -26,7 +26,7 @@ public void testDataSourceHealthCheckExclusion() { .then() .body("status", CoreMatchers.equalTo("UP")) // If the datasource is inactive, there should not be a health check - .body("checks[0].data.\"users\"", CoreMatchers.nullValue()); + .body("checks[0].data.\"ds-1\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java index 9636be1efdfd0..25cd92fe353de 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java @@ -26,7 +26,7 @@ public void testDataSourceHealthCheckExclusion() { .then() .body("status", CoreMatchers.equalTo("UP")) // If the datasource is inactive, there should not be a health check - .body("checks[0].data.\"users\"", CoreMatchers.nullValue()); + .body("checks[0].data.\"ds-1\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java index 8a176414a55a1..a1352cff9db20 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigActiveFalseNamedDatasourceHealthCheckTest.java @@ -26,7 +26,7 @@ public void testDataSourceHealthCheckExclusion() { .then() .body("status", CoreMatchers.equalTo("UP")) // If the datasource is inactive, there should not be a health check - .body("checks[0].data.\"users\"", CoreMatchers.nullValue()); + .body("checks[0].data.\"ds-1\"", CoreMatchers.nullValue()); } } From a16faaca907ead92b7c824dbd0cbdcb90206c79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 30 Sep 2024 17:30:05 +0200 Subject: [PATCH 05/10] Do not rely on default URL of reactive datasources in tests --- .../io/quarkus/reactive/mssql/client/SamePoolInstanceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/SamePoolInstanceTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/SamePoolInstanceTest.java index 4250b0433a2b5..939096383b33c 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/SamePoolInstanceTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/SamePoolInstanceTest.java @@ -13,6 +13,7 @@ public class SamePoolInstanceTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-default-datasource.properties") .overrideConfigKey("quarkus.devservices.enabled", "false"); @Inject From d0c635f8a951702e93392b5bd7e668028a5eaea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 1 Oct 2024 17:36:19 +0200 Subject: [PATCH 06/10] Deactivate datasources when their URL is unset at runtime Consequently: * Skip the health check for datasources without a URL * Fail on startup if datasources without URL are (statically) injected into user bean * Fail on first access if datasources without URL are retrieved dynamically through CDI --- docs/src/main/asciidoc/datasource.adoc | 8 ++- ...DefaultDatasourceDynamicInjectionTest.java | 3 +- ...seNamedDatasourceDynamicInjectionTest.java | 3 +- ...DefaultDatasourceDynamicInjectionTest.java | 9 ++- ...ssingDefaultDatasourceHealthCheckTest.java | 6 +- ...gDefaultDatasourceStaticInjectionTest.java | 20 +++++-- ...ngNamedDatasourceDynamicInjectionTest.java | 9 ++- ...MissingNamedDatasourceHealthCheckTest.java | 6 +- ...ingNamedDatasourceStaticInjectionTest.java | 20 +++++-- .../agroal/runtime/AgroalDataSourceUtil.java | 4 +- .../agroal/runtime/AgroalRecorder.java | 14 ++++- .../quarkus/agroal/runtime/DataSources.java | 7 +-- .../runtime/UnconfiguredDataSource.java | 4 ++ .../common/runtime/DataSourceUtil.java | 8 +++ .../runtime/DataSourceRecorder.java | 4 +- .../runtime/DataSourceRuntimeConfig.java | 6 +- ...DefaultDatasourceDynamicInjectionTest.java | 5 +- ...gDefaultDatasourceStaticInjectionTest.java | 5 +- ...ngNamedDataSourceDynamicInjectionTest.java | 5 +- ...ingNamedDataSourceStaticInjectionTest.java | 5 +- ...tStartDefaultDatasourceUrlMissingTest.java | 5 +- ...rtNamedDatasourceConfigUrlMissingTest.java | 5 +- .../flyway/runtime/FlywayRecorder.java | 10 +--- ...xplicitDatasourceConfigUrlMissingTest.java | 5 +- ...mplicitDatasourceConfigUrlMissingTest.java | 5 +- ...xplicitDatasourceConfigUrlMissingTest.java | 5 +- .../FastBootHibernatePersistenceProvider.java | 6 -- ...DefaultDatasourceDynamicInjectionTest.java | 39 +++++++++++++ ...gDefaultDatasourceStaticInjectionTest.java | 46 +++++++++++++++ ...ConfigUrlMissingDefaultDatasourceTest.java | 31 ---------- ...ngNamedDataSourceDynamicInjectionTest.java | 49 ++++++++++++++++ ...ingNamedDataSourceStaticInjectionTest.java | 57 +++++++++++++++++++ ...onConfigUrlMissingNamedDatasourceTest.java | 39 ------------- ...DefaultDatasourceConfigUrlMissingTest.java | 32 +++++++---- ...rtNamedDatasourceConfigUrlMissingTest.java | 34 +++++++---- .../liquibase/runtime/LiquibaseRecorder.java | 5 -- .../db2/client/runtime/DB2PoolRecorder.java | 16 ++++-- ...DefaultDatasourceDynamicInjectionTest.java | 12 ++-- ...ssingDefaultDatasourceHealthCheckTest.java | 9 ++- ...gDefaultDatasourceStaticInjectionTest.java | 23 +++++--- ...ngNamedDatasourceDynamicInjectionTest.java | 12 ++-- ...MissingNamedDatasourceHealthCheckTest.java | 9 ++- ...ingNamedDatasourceStaticInjectionTest.java | 23 +++++--- .../DataSourceHealthCheckPayloadTest.java | 4 +- .../client/runtime/MSSQLPoolRecorder.java | 16 ++++-- ...DefaultDatasourceDynamicInjectionTest.java | 12 ++-- ...ssingDefaultDatasourceHealthCheckTest.java | 9 ++- ...gDefaultDatasourceStaticInjectionTest.java | 23 +++++--- ...ngNamedDatasourceDynamicInjectionTest.java | 12 ++-- ...MissingNamedDatasourceHealthCheckTest.java | 9 ++- ...ingNamedDatasourceStaticInjectionTest.java | 23 +++++--- .../DataSourceHealthCheckPayloadTest.java | 4 +- .../client/runtime/MySQLPoolRecorder.java | 16 ++++-- ...DefaultDatasourceDynamicInjectionTest.java | 12 ++-- ...ssingDefaultDatasourceHealthCheckTest.java | 9 ++- ...gDefaultDatasourceStaticInjectionTest.java | 23 +++++--- ...ngNamedDatasourceDynamicInjectionTest.java | 12 ++-- ...MissingNamedDatasourceHealthCheckTest.java | 9 ++- ...ingNamedDatasourceStaticInjectionTest.java | 23 +++++--- .../DataSourceHealthCheckPayloadTest.java | 4 +- .../client/runtime/OraclePoolRecorder.java | 19 +++++-- ...DefaultDatasourceDynamicInjectionTest.java | 12 ++-- ...ssingDefaultDatasourceHealthCheckTest.java | 9 ++- ...gDefaultDatasourceStaticInjectionTest.java | 23 +++++--- ...ngNamedDatasourceDynamicInjectionTest.java | 12 ++-- ...MissingNamedDatasourceHealthCheckTest.java | 9 ++- ...ingNamedDatasourceStaticInjectionTest.java | 23 +++++--- .../DataSourceHealthCheckPayloadTest.java | 4 +- .../pg/client/runtime/PgPoolRecorder.java | 16 ++++-- 69 files changed, 614 insertions(+), 361 deletions(-) create mode 100644 extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java create mode 100644 extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java delete mode 100644 extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceTest.java create mode 100644 extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java create mode 100644 extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java delete mode 100644 extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDatasourceTest.java diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index aa4ab1cc864a3..5616316c330cb 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -445,10 +445,14 @@ AgroalDataSource inventoryDataSource; [[datasource-active]] === Activate or deactivate datasources -When a datasource is configured at build time, it is active by default at runtime. +When a datasource is configured at build time and its URL is set at runtime, it is active by default. This means that Quarkus will start the corresponding JDBC connection pool or reactive client when the application starts. -To deactivate a datasource at runtime, set `quarkus.datasource[.optional name].active` to `false`. +To deactivate a datasource at runtime, either: + +* Do not set `quarkus.datasource[.optional name].jdbc.url`/`quarkus.datasource[.optional name].reactive.url`. +* Or set `quarkus.datasource[.optional name].active` to `false`. + If a datasource is not active: * The datasource will not attempt to connect to the database during application startup. diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index fd817cd68c4eb..e8c49bd4bf779 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -12,6 +12,7 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; @@ -43,7 +44,7 @@ private void doTest(InjectableInstance instance) { // So the bean proxy cannot be null. assertThat(instance.getHandle().getBean()) .isNotNull() - .returns(false, b -> b.isActive()); + .returns(false, InjectableBean::isActive); var ds = instance.get(); assertThat(ds).isNotNull(); // However, any attempt to use it at runtime will fail. diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index 27adbaa6c5da9..d32bd76777992 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -12,6 +12,7 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; @@ -48,7 +49,7 @@ private void doTest(InjectableInstance instance) { // So the bean cannot be null. assertThat(instance.getHandle().getBean()) .isNotNull() - .returns(false, b -> b.isActive()); + .returns(false, InjectableBean::isActive); var ds = instance.get(); assertThat(ds).isNotNull(); // However, any attempt to use it at runtime will fail. diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index a697a3db89796..00cfb338c1e8b 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -11,8 +11,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; public class ConfigUrlMissingDefaultDatasourceDynamicInjectionTest { @@ -46,7 +46,10 @@ private void doTest(InjectableInstance instance) { assertThat(ds).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> ds.getConnection()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("quarkus.datasource.jdbc.url has not been defined"); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java index bfe53bdf9c463..d2ce7c201f6e4 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java @@ -19,10 +19,10 @@ public class ConfigUrlMissingDefaultDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // UnconfiguredDataSource always reports as healthy... - // https://github.com/quarkusio/quarkus/issues/36666 + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. .body("status", CoreMatchers.equalTo("UP")) - .body("checks[0].data.\"\"", CoreMatchers.equalTo("UP")); + .body("checks[0].data.\"\"", CoreMatchers.nullValue()); } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 7413575685c63..af3eafe2c3235 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,6 +1,6 @@ package io.quarkus.agroal.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import java.sql.SQLException; @@ -9,10 +9,11 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @@ -20,16 +21,23 @@ public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#ds")); @Inject MyBean myBean; @Test public void test() { - assertThatThrownBy(() -> myBean.useDatasource()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("quarkus.datasource.jdbc.url has not been defined"); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java index e91b9d797c873..721d33d1e66b5 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java @@ -11,8 +11,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; -import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; public class ConfigUrlMissingNamedDatasourceDynamicInjectionTest { @@ -51,7 +51,10 @@ private void doTest(InjectableInstance instance) { assertThat(ds).isNotNull(); // However, any attempt to use it at runtime will fail. assertThatThrownBy(() -> ds.getConnection()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("quarkus.datasource.\"users\".jdbc.url has not been defined"); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceHealthCheckTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceHealthCheckTest.java index b12f402689ee6..295867fe75137 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceHealthCheckTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceHealthCheckTest.java @@ -22,10 +22,10 @@ public class ConfigUrlMissingNamedDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // UnconfiguredDataSource always reports as healthy... - // https://github.com/quarkusio/quarkus/issues/36666 + // A datasource without a JDBC URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. .body("status", CoreMatchers.equalTo("UP")) - .body("checks[0].data.\"users\"", CoreMatchers.equalTo("UP")); + .body("checks[0].data.\"users\"", CoreMatchers.nullValue()); } } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java index 8ca6ac3626977..598e14fe625a9 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java @@ -1,6 +1,6 @@ package io.quarkus.agroal.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import java.sql.SQLException; @@ -9,10 +9,11 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { @@ -23,16 +24,23 @@ public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.devservices.enabled", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.users.db-kind", "h2"); + .overrideConfigKey("quarkus.datasource.users.db-kind", "h2") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#ds")); @Inject MyBean myBean; @Test public void test() { - assertThatThrownBy(() -> myBean.useDatasource()) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("quarkus.datasource.\"users\".jdbc.url has not been defined"); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java index b480de250abce..f5c0af0522274 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalDataSourceUtil.java @@ -11,7 +11,6 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.agroal.DataSource; import io.quarkus.arc.Arc; -import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InjectableInstance; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -27,8 +26,7 @@ public static Optional dataSourceIfActive(String dataSourceNam var instance = dataSourceInstance(dataSourceName); // We want to call get() and throw an exception if the name points to an undefined datasource. if (!instance.isResolvable() || instance.getHandle().getBean().isActive()) { - // TODO remove ClientProxy.unwrap() once we get rid of UnconfiguredDatasource - return Optional.ofNullable(ClientProxy.unwrap(instance.get())); + return Optional.ofNullable(instance.get()); } else { return Optional.empty(); } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java index df686267c71aa..4a1b5b300b0f0 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java @@ -1,5 +1,6 @@ package io.quarkus.agroal.runtime; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -17,10 +18,13 @@ public class AgroalRecorder { private final RuntimeValue runtimeConfig; + private final RuntimeValue jdbcRuntimeConfig; @Inject - public AgroalRecorder(RuntimeValue runtimeConfig) { + public AgroalRecorder(RuntimeValue runtimeConfig, + RuntimeValue jdbcRuntimeConfig) { this.runtimeConfig = runtimeConfig; + this.jdbcRuntimeConfig = jdbcRuntimeConfig; } public Supplier dataSourceSupportSupplier(AgroalDataSourceSupport agroalDataSourceSupport) { @@ -36,10 +40,14 @@ public Supplier agroalDataSourceCheckActiveSupplier(String dataSou return new Supplier<>() { @Override public ActiveResult get() { - if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + Optional active = runtimeConfig.getValue().dataSources().get(dataSourceName).active(); + if (active.isPresent() && !active.get()) { return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); } - + if (jdbcRuntimeConfig.getValue().dataSources().get(dataSourceName).jdbc().url().isEmpty()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonUrlMissing(dataSourceName, + "jdbc.url")); + } return ActiveResult.active(); } }; diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index e48f06ce36d5d..6dff73764cbc9 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -38,7 +38,6 @@ import io.quarkus.arc.ClientProxy; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; -import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; @@ -159,10 +158,8 @@ public AgroalDataSource createDataSource(String dataSourceName) { DataSourceJdbcRuntimeConfig dataSourceJdbcRuntimeConfig = dataSourcesJdbcRuntimeConfig .dataSources().get(dataSourceName).jdbc(); if (!dataSourceJdbcRuntimeConfig.url().isPresent()) { - //this is not an error situation, because we want to allow the situation where a JDBC extension - //is installed but has not been configured - return new UnconfiguredDataSource( - DataSourceUtil.dataSourcePropertyKey(dataSourceName, "jdbc.url") + " has not been defined"); + throw new IllegalArgumentException( + "Datasource " + dataSourceName + " does not have a JDBC URL and should not be created"); } // we first make sure that all available JDBC drivers are loaded in the current TCCL diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/UnconfiguredDataSource.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/UnconfiguredDataSource.java index 828031702bb0a..2fd1534bca49d 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/UnconfiguredDataSource.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/UnconfiguredDataSource.java @@ -15,6 +15,10 @@ import io.agroal.api.configuration.AgroalDataSourceConfiguration; import io.quarkus.runtime.configuration.ConfigurationException; +/** + * @deprecated This is never instantiated by Quarkus. Do not use. + */ +@Deprecated(forRemoval = true) public class UnconfiguredDataSource implements AgroalDataSource { private final String errorMessage; diff --git a/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java b/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java index ca1047119cadf..fd81d2b11cf55 100644 --- a/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java +++ b/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java @@ -71,6 +71,14 @@ public static String dataSourceInactiveReasonDeactivated(String dataSourceName) dataSourceName, dataSourcePropertyKey(dataSourceName, "active"), dataSourceName); } + public static String dataSourceInactiveReasonUrlMissing(String dataSourceName, String urlPropertyRadical) { + return String.format(Locale.ROOT, + "Datasource '%s' was deactivated automatically because its URL is not set." + + " To activate the datasource, set configuration property '%s'." + + " Refer to https://quarkus.io/guides/datasource for guidance.", + dataSourceName, dataSourcePropertyKey(dataSourceName, urlPropertyRadical)); + } + private DataSourceUtil() { } diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRecorder.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRecorder.java index 3f4b459505df8..66fff8694adc6 100644 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRecorder.java +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRecorder.java @@ -3,6 +3,7 @@ import static java.util.stream.Collectors.toUnmodifiableSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; @@ -25,7 +26,8 @@ public RuntimeValue createDataSourceSupport( Stream.Builder inactive = Stream.builder(); for (Map.Entry entry : runtimeConfig.dataSources().entrySet()) { - if (!entry.getValue().active()) { + Optional active = entry.getValue().active(); + if (active.isPresent() && !active.get()) { inactive.add(entry.getKey()); } } diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java index 6f4c1ae7e5d43..47081ebef0fc6 100644 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceRuntimeConfig.java @@ -2,10 +2,10 @@ import java.util.Optional; +import io.quarkus.runtime.annotations.ConfigDocDefault; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.configuration.TrimmedStringConverter; import io.smallrye.config.WithConverter; -import io.smallrye.config.WithDefault; @ConfigGroup public interface DataSourceRuntimeConfig { @@ -17,8 +17,8 @@ public interface DataSourceRuntimeConfig { * * @asciidoclet */ - @WithDefault("true") - boolean active(); + @ConfigDocDefault("`true` if the URL is set, `false` otherwise") + Optional active(); /** * The datasource username diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index 76864cc578b91..95a9d423c7987 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -30,8 +30,9 @@ public void testBootSucceedsButFlywayDeactivated() { .isInstanceOf(CreationException.class) .cause() .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' is not configured.", - "To solve this, configure datasource ''.", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 2393a0fbffb6a..d92ff666cc6f9 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -28,8 +28,9 @@ public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(() -> myBean.useFlyway()) .cause() .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' is not configured.", - "To solve this, configure datasource ''.", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java index 6f1bc9bd8ed8a..eca65c7123a3a 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java @@ -41,8 +41,9 @@ public void testBootSucceedsButFlywayDeactivated() { .isInstanceOf(CreationException.class) .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' is not configured.", - "To solve this, configure datasource 'users'.", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java index abe1a7bc9bdcb..fcbd0ef72f9e4 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java @@ -38,8 +38,9 @@ public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(() -> myBean.useFlyway()) .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' is not configured.", - "To solve this, configure datasource 'users'.", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java index 4cfaab211de12..7ac6c37b2fdba 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java @@ -33,8 +33,9 @@ public void testBootSucceedsButFlywayDeactivated() { .isInstanceOf(CreationException.class) .cause() .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' is not configured.", - "To solve this, configure datasource ''.", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", "Refer to https://quarkus.io/guides/datasource for guidance."); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java index db55982bde28f..e685f83ad1015 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java @@ -44,8 +44,9 @@ public void testBootSucceedsButFlywayDeactivated() { .isInstanceOf(CreationException.class) .cause() .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' is not configured.", - "To solve this, configure datasource 'users'.", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 47a87265ebf12..1c7a7f740d033 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -21,13 +21,10 @@ import org.jboss.logging.Logger; import io.quarkus.agroal.runtime.AgroalDataSourceUtil; -import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.Arc; -import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.SyntheticCreationalContext; -import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigurationException; @@ -66,12 +63,7 @@ public Function, FlywayContainer> fl public FlywayContainer apply(SyntheticCreationalContext context) { DataSource dataSource; try { - // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources - dataSource = ClientProxy.unwrap(context.getInjectedReference( - DataSource.class, AgroalDataSourceUtil.qualifier(dataSourceName))); - if (dataSource instanceof UnconfiguredDataSource) { - throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); - } + dataSource = context.getInjectedReference(DataSource.class, AgroalDataSourceUtil.qualifier(dataSourceName)); } catch (ConfigurationException | InactiveBeanException e) { // TODO do we really want to enable retrieval of a FlywayContainer for an unconfigured/inactive datasource? // Marking the FlywayContainer bean as inactive when the datasource is inactive/unconfigured diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigUrlMissingTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigUrlMissingTest.java index b0837e60ef069..6f9fd5912f1bf 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigUrlMissingTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitDatasourceConfigUrlMissingTest.java @@ -27,8 +27,9 @@ public class EntitiesInDefaultPUWithExplicitDatasourceConfigUrlMissingTest { .isInstanceOf(ConfigurationException.class) .hasMessageContainingAll( "Unable to find datasource 'ds-1' for persistence unit ''", - "Datasource 'ds-1' is not configured.", - "To solve this, configure datasource 'ds-1'.", + "Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".jdbc.url'", "Refer to https://quarkus.io/guides/datasource for guidance.")); @Test diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigUrlMissingTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigUrlMissingTest.java index 384bf91369390..00b503ab5e63b 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigUrlMissingTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitDatasourceConfigUrlMissingTest.java @@ -22,8 +22,9 @@ public class EntitiesInDefaultPUWithImplicitDatasourceConfigUrlMissingTest { .isInstanceOf(ConfigurationException.class) .hasMessageContainingAll( "Unable to find datasource '' for persistence unit ''", - "Datasource '' is not configured.", - "To solve this, configure datasource ''.", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'", "Refer to https://quarkus.io/guides/datasource for guidance.")); @Test diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigUrlMissingTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigUrlMissingTest.java index 5161bfd2e4cf2..53fbd0ba102c5 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigUrlMissingTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitDatasourceConfigUrlMissingTest.java @@ -27,8 +27,9 @@ public class EntitiesInNamedPUWithExplicitDatasourceConfigUrlMissingTest { .isInstanceOf(ConfigurationException.class) .hasMessageContainingAll( "Unable to find datasource 'ds-1' for persistence unit 'pu-1'", - "Datasource 'ds-1' is not configured.", - "To solve this, configure datasource 'ds-1'.", + "Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".jdbc.url'", "Refer to https://quarkus.io/guides/datasource for guidance.")); @Test diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index 029b2e7385110..60f0f2a3d1aad 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -26,10 +26,8 @@ import org.jboss.logging.Logger; import io.quarkus.agroal.runtime.AgroalDataSourceUtil; -import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.Arc; import io.quarkus.arc.ClientProxy; -import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings.Builder; import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder; import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDescriptor; @@ -389,11 +387,7 @@ private static void injectDataSource(String persistenceUnitName, String dataSour DataSource dataSource; try { // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources - // and for the instanceof check below. dataSource = ClientProxy.unwrap(AgroalDataSourceUtil.dataSourceInstance(dataSourceName).get()); - if (dataSource instanceof UnconfiguredDataSource) { - throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); - } } catch (RuntimeException e) { throw PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName, e); } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java new file mode 100644 index 0000000000000..18b35cd69e111 --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -0,0 +1,39 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.CreationException; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The URL won't be missing if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + Instance liquibase; + + @Test + @DisplayName("If the URL is missing for the default datasource, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> liquibase.get().getConfiguration()) + .isInstanceOf(CreationException.class) + .cause() + .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } + +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java new file mode 100644 index 0000000000000..65fd0cbf0e5d1 --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -0,0 +1,46 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The URL won't be missing if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + MyBean myBean; + + @Test + @DisplayName("If the URL is missing for the default datasource, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> myBean.useLiquibase()) + .cause() + .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } + + @ApplicationScoped + public static class MyBean { + @Inject + LiquibaseFactory liquibase; + + public void useLiquibase() { + liquibase.getConfiguration(); + } + } +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceTest.java deleted file mode 100644 index 705a8dfc64c0e..0000000000000 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.quarkus.liquibase.test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; - -public class LiquibaseExtensionConfigUrlMissingDefaultDatasourceTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false") - .assertException(t -> assertThat(t).cause().cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", - "Datasource '' is not configured.", - "To solve this, configure datasource ''.", - "Refer to https://quarkus.io/guides/datasource for guidance.")); - - @Test - @DisplayName("If the URL is missing for the default datasource, the application should fail to boot") - public void testBootFails() { - // Should not be reached because boot should fail. - assertTrue(false); - } - -} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java new file mode 100644 index 0000000000000..1b2b8d5efcd3a --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java @@ -0,0 +1,49 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.CreationException; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.liquibase.LiquibaseDataSource; +import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The URL won't be missing if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + // We need at least one build-time property for the datasource, + // otherwise it's considered unconfigured at build time... + .overrideConfigKey("quarkus.datasource.users.db-kind", "h2") + // We need this otherwise the *default* datasource may impact this test + .overrideConfigKey("quarkus.datasource.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.username", "sa") + .overrideConfigKey("quarkus.datasource.password", "sa") + .overrideConfigKey("quarkus.datasource.jdbc.url", + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + + @Inject + @LiquibaseDataSource("users") + Instance liquibase; + + @Test + @DisplayName("If the URL is missing for a named datasource, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> liquibase.get().getConfiguration()) + .isInstanceOf(CreationException.class) + .cause() + .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java new file mode 100644 index 0000000000000..b9712fc2a4881 --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java @@ -0,0 +1,57 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.liquibase.LiquibaseDataSource; +import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The URL won't be missing if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + // We need at least one build-time property for the datasource, + // otherwise it's considered unconfigured at build time... + .overrideConfigKey("quarkus.datasource.users.db-kind", "h2") + // We need this otherwise the *default* datasource may impact this test + .overrideConfigKey("quarkus.datasource.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.username", "sa") + .overrideConfigKey("quarkus.datasource.password", "sa") + .overrideConfigKey("quarkus.datasource.jdbc.url", + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + + @Inject + MyBean myBean; + + @Test + @DisplayName("If the URL is missing for a named datasource, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> myBean.useLiquibase()) + .cause() + .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } + + @ApplicationScoped + public static class MyBean { + @Inject + @LiquibaseDataSource("users") + LiquibaseFactory liquibase; + + public void useLiquibase() { + liquibase.getConfiguration(); + } + } +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDatasourceTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDatasourceTest.java deleted file mode 100644 index 8c42d114326b9..0000000000000 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDatasourceTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.quarkus.liquibase.test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; - -public class LiquibaseExtensionConfigUrlMissingNamedDatasourceTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false") - // We need at least one build-time property for the datasource, - // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.users.db-kind", "h2") - // We need this otherwise it's going to be the *default* datasource making everything fail - .overrideConfigKey("quarkus.datasource.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.username", "sa") - .overrideConfigKey("quarkus.datasource.password", "sa") - .overrideConfigKey("quarkus.datasource.jdbc.url", - "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") - .assertException(t -> assertThat(t).cause().cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", - "Datasource 'users' is not configured.", - "To solve this, configure datasource 'users'.", - "Refer to https://quarkus.io/guides/datasource for guidance.")); - - @Test - @DisplayName("If the URL is missing for a named datasource, the application should fail to boot") - public void testBootSucceedsButLiquibaseDeactivated() { - // Should not be reached because boot should fail. - assertTrue(false); - } -} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java index edba6f3726165..2b0209a26664d 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java @@ -1,12 +1,16 @@ package io.quarkus.liquibase.test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.CreationException; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; public class LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest { @@ -17,18 +21,22 @@ public class LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTe .addAsResource("db/changeLog.xml", "db/changeLog.xml")) .overrideConfigKey("quarkus.liquibase.migrate-at-start", "true") // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false") - .assertException(t -> assertThat(t).cause().cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", - "Datasource '' is not configured.", - "To solve this, configure datasource ''.", - "Refer to https://quarkus.io/guides/datasource for guidance.")); + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + Instance liquibase; @Test - @DisplayName("If the URL is missing for the default datasource, and if migrate-at-start is enabled, the application should fail to boot") - public void testBootFails() { - // Should not be reached because boot should fail. - assertTrue(false); + @DisplayName("If the URL is missing for the default datasource, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> liquibase.get().getConfiguration()) + .isInstanceOf(CreationException.class) + .cause() + .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java index 2ebc29ac30650..674ca30e41b0b 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java @@ -1,12 +1,17 @@ package io.quarkus.liquibase.test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.CreationException; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.liquibase.LiquibaseDataSource; +import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; public class LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest { @@ -26,17 +31,22 @@ public class LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest .overrideConfigKey("quarkus.datasource.username", "sa") .overrideConfigKey("quarkus.datasource.password", "sa") .overrideConfigKey("quarkus.datasource.jdbc.url", - "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") - .assertException(t -> assertThat(t).cause().cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", - "Datasource 'users' is not configured.", - "To solve this, configure datasource 'users'.", - "Refer to https://quarkus.io/guides/datasource for guidance.")); + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + + @Inject + @LiquibaseDataSource("users") + Instance liquibase; @Test - @DisplayName("If the URL is missing for a named datasource, and if migrate-at-start is enabled, the application should fail to boot") - public void testBootFails() { - // Should not be reached because boot should fail. - assertTrue(false); + @DisplayName("If the URL is missing for a named datasource, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> liquibase.get().getConfiguration()) + .isInstanceOf(CreationException.class) + .cause() + .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java index 58174592136bb..3df8108e58c61 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java @@ -8,11 +8,9 @@ import jakarta.enterprise.inject.UnsatisfiedResolutionException; import io.quarkus.agroal.runtime.AgroalDataSourceUtil; -import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.SyntheticCreationalContext; -import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.runtime.ResettableSystemProperties; import io.quarkus.runtime.RuntimeValue; @@ -38,9 +36,6 @@ public LiquibaseFactory apply(SyntheticCreationalContext conte // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources dataSource = ClientProxy.unwrap(context.getInjectedReference(DataSource.class, AgroalDataSourceUtil.qualifier(dataSourceName))); - if (dataSource instanceof UnconfiguredDataSource) { - throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); - } } catch (RuntimeException e) { throw new UnsatisfiedResolutionException(String.format(Locale.ROOT, "Unable to find datasource '%s' for Liquibase: %s", diff --git a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java index f8060a97f8c75..25e24c2a14bbb 100644 --- a/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java +++ b/extensions/reactive-db2-client/runtime/src/main/java/io/quarkus/reactive/db2/client/runtime/DB2PoolRecorder.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -52,21 +53,28 @@ public class DB2PoolRecorder { }; private final RuntimeValue runtimeConfig; + private final RuntimeValue reactiveRuntimeConfig; @Inject - public DB2PoolRecorder(RuntimeValue runtimeConfig) { + public DB2PoolRecorder(RuntimeValue runtimeConfig, + RuntimeValue reactiveRuntimeConfig) { this.runtimeConfig = runtimeConfig; + this.reactiveRuntimeConfig = reactiveRuntimeConfig; } public Supplier poolCheckActiveSupplier(String dataSourceName) { return new Supplier<>() { @Override public ActiveResult get() { - if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + Optional active = runtimeConfig.getValue().dataSources().get(dataSourceName).active(); + if (active.isPresent() && !active.get()) { return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); - } else { - return ActiveResult.active(); } + if (reactiveRuntimeConfig.getValue().dataSources().get(dataSourceName).reactive().url().isEmpty()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonUrlMissing(dataSourceName, + "reactive.url")); + } + return ActiveResult.active(); } }; } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index c6f64d2b4ab73..42b426f472f52 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.net.ConnectException; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; import io.vertx.mssqlclient.MSSQLPool; @@ -58,11 +58,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(ConnectException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java index bc8f8cd210979..0545e6736a6bb 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java @@ -19,11 +19,10 @@ public class ConfigUrlMissingDefaultDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.\"\"", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.\"\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 1171055d4838e..6068c0ca962b3 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,17 @@ package io.quarkus.reactive.mssql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; -import java.net.ConnectException; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -19,19 +20,23 @@ public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(ConnectException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java index 3d715cc0148b2..b8d2f45c7ae4c 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.net.ConnectException; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -66,11 +66,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(ConnectException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java index 74fcd4f25744b..cc679928cddf9 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java @@ -22,11 +22,10 @@ public class ConfigUrlMissingNamedDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.ds-1", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.ds-1", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java index f231af8cb0d16..7f7c20ac3bdf7 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java @@ -1,16 +1,17 @@ package io.quarkus.reactive.mssql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; -import java.net.ConnectException; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -23,19 +24,23 @@ public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.devservices.enabled", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mssql"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mssql") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(ConnectException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/DataSourceHealthCheckPayloadTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/DataSourceHealthCheckPayloadTest.java index 11f13aafbb78b..d5fcd997ec75d 100644 --- a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/DataSourceHealthCheckPayloadTest.java +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/DataSourceHealthCheckPayloadTest.java @@ -14,7 +14,9 @@ public class DataSourceHealthCheckPayloadTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication() .overrideConfigKey("quarkus.datasource.health.enabled", "true") - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + // this should make the health check fail + .overrideConfigKey("quarkus.datasource.reactive.url", "vertx-reactive:sqlserver://localhost:1/BROKEN"); @Test public void testDataSourceHealthCheckPayload() { diff --git a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java index 64ff89272fc6e..c99ec6071f7a0 100644 --- a/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java +++ b/extensions/reactive-mssql-client/runtime/src/main/java/io/quarkus/reactive/mssql/client/runtime/MSSQLPoolRecorder.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -53,21 +54,28 @@ public class MSSQLPoolRecorder { private static final Logger log = Logger.getLogger(MSSQLPoolRecorder.class); private final RuntimeValue runtimeConfig; + private final RuntimeValue reactiveRuntimeConfig; @Inject - public MSSQLPoolRecorder(RuntimeValue runtimeConfig) { + public MSSQLPoolRecorder(RuntimeValue runtimeConfig, + RuntimeValue reactiveRuntimeConfig) { this.runtimeConfig = runtimeConfig; + this.reactiveRuntimeConfig = reactiveRuntimeConfig; } public Supplier poolCheckActiveSupplier(String dataSourceName) { return new Supplier<>() { @Override public ActiveResult get() { - if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + Optional active = runtimeConfig.getValue().dataSources().get(dataSourceName).active(); + if (active.isPresent() && !active.get()) { return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); - } else { - return ActiveResult.active(); } + if (reactiveRuntimeConfig.getValue().dataSources().get(dataSourceName).reactive().url().isEmpty()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonUrlMissing(dataSourceName, + "reactive.url")); + } + return ActiveResult.active(); } }; } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index 2f877897ba059..d5927fbd4fa21 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.net.ConnectException; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; import io.vertx.mysqlclient.MySQLPool; @@ -58,11 +58,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(ConnectException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java index 0c68d63a3a3c1..214302f065cae 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java @@ -19,11 +19,10 @@ public class ConfigUrlMissingDefaultDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.\"\"", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.\"\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 206b61928b60a..06512c7913191 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,17 @@ package io.quarkus.reactive.mysql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; -import java.net.ConnectException; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -19,19 +20,23 @@ public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(ConnectException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java index 09e8a178ef59e..3749fa2ad07fb 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.net.ConnectException; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -66,11 +66,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(ConnectException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java index 23b7e93b73cf4..bf1af59b05d15 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java @@ -22,11 +22,10 @@ public class ConfigUrlMissingNamedDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.ds-1", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.ds-1", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java index defb3ea36062f..1ca1b022aab83 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java @@ -1,16 +1,17 @@ package io.quarkus.reactive.mysql.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; -import java.net.ConnectException; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -23,19 +24,23 @@ public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.devservices.enabled", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mysql"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "mysql") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(ConnectException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/DataSourceHealthCheckPayloadTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/DataSourceHealthCheckPayloadTest.java index c219c54b0afbd..ffd6151b1db99 100644 --- a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/DataSourceHealthCheckPayloadTest.java +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/DataSourceHealthCheckPayloadTest.java @@ -14,7 +14,9 @@ public class DataSourceHealthCheckPayloadTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication() .overrideConfigKey("quarkus.datasource.health.enabled", "true") - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + // this should make the health check fail + .overrideConfigKey("quarkus.datasource.reactive.url", "vertx-reactive:mysql://localhost:1/BROKEN"); @Test public void testDataSourceHealthCheckPayload() { diff --git a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java index ef352e2640f55..7768eafb4f752 100644 --- a/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java +++ b/extensions/reactive-mysql-client/runtime/src/main/java/io/quarkus/reactive/mysql/client/runtime/MySQLPoolRecorder.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; @@ -52,21 +53,28 @@ public class MySQLPoolRecorder { }; private final RuntimeValue runtimeConfig; + private final RuntimeValue reactiveRuntimeConfig; @Inject - public MySQLPoolRecorder(RuntimeValue runtimeConfig) { + public MySQLPoolRecorder(RuntimeValue runtimeConfig, + RuntimeValue reactiveRuntimeConfig) { this.runtimeConfig = runtimeConfig; + this.reactiveRuntimeConfig = reactiveRuntimeConfig; } public Supplier poolCheckActiveSupplier(String dataSourceName) { return new Supplier<>() { @Override public ActiveResult get() { - if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + Optional active = runtimeConfig.getValue().dataSources().get(dataSourceName).active(); + if (active.isPresent() && !active.get()) { return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); - } else { - return ActiveResult.active(); } + if (reactiveRuntimeConfig.getValue().dataSources().get(dataSourceName).reactive().url().isEmpty()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonUrlMissing(dataSourceName, + "reactive.url")); + } + return ActiveResult.active(); } }; } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index da9d012753aa6..7c1d249825524 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -10,9 +10,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; -import io.vertx.oracleclient.OracleException; import io.vertx.oracleclient.OraclePool; import io.vertx.sqlclient.Pool; @@ -58,11 +58,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(OracleException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java index d7fb1b8bdef00..2a043eac95a63 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java @@ -19,11 +19,10 @@ public class ConfigUrlMissingDefaultDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.\"\"", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.\"\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 1abbc848c5b9e..a53ccaa3d3865 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,17 +1,18 @@ package io.quarkus.reactive.oracle.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; -import io.vertx.oracleclient.OracleException; import io.vertx.sqlclient.Pool; public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @@ -19,19 +20,23 @@ public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(OracleException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java index b143a597a0657..b92c0b30d6c42 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java @@ -10,10 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; -import io.vertx.oracleclient.OracleException; import io.vertx.oracleclient.OraclePool; import io.vertx.sqlclient.Pool; @@ -66,11 +66,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(OracleException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java index 0857ac9fb261d..6d4cdd4bbdcd4 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java @@ -22,11 +22,10 @@ public class ConfigUrlMissingNamedDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.ds-1", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.ds-1", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java index 754a96aac352e..43f59585ff6a4 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java @@ -1,18 +1,19 @@ package io.quarkus.reactive.oracle.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; -import io.vertx.oracleclient.OracleException; import io.vertx.sqlclient.Pool; public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { @@ -23,19 +24,23 @@ public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.devservices.enabled", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "oracle"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "oracle") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(OracleException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DataSourceHealthCheckPayloadTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DataSourceHealthCheckPayloadTest.java index 22daf2732285b..d927d14bdb8b7 100644 --- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DataSourceHealthCheckPayloadTest.java +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DataSourceHealthCheckPayloadTest.java @@ -14,7 +14,9 @@ public class DataSourceHealthCheckPayloadTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication() .overrideConfigKey("quarkus.datasource.health.enabled", "true") - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + // this should make the health check fail + .overrideConfigKey("quarkus.datasource.reactive.url", "vertx-reactive:oracle:thin:@localhost:1/BROKEN"); @Test public void testDataSourceHealthCheckPayload() { diff --git a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java index 9b3cf15a2dd3e..4cdd94feebd90 100644 --- a/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java +++ b/extensions/reactive-oracle-client/runtime/src/main/java/io/quarkus/reactive/oracle/client/runtime/OraclePoolRecorder.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -42,27 +43,33 @@ @Recorder public class OraclePoolRecorder { + private static final Logger log = Logger.getLogger(OraclePoolRecorder.class); private static final TypeLiteral> POOL_CREATOR_TYPE_LITERAL = new TypeLiteral<>() { }; private final RuntimeValue runtimeConfig; + private final RuntimeValue reactiveRuntimeConfig; @Inject - public OraclePoolRecorder(RuntimeValue runtimeConfig) { + public OraclePoolRecorder(RuntimeValue runtimeConfig, + RuntimeValue reactiveRuntimeConfig) { this.runtimeConfig = runtimeConfig; + this.reactiveRuntimeConfig = reactiveRuntimeConfig; } - private static final Logger log = Logger.getLogger(OraclePoolRecorder.class); - public Supplier poolCheckActiveSupplier(String dataSourceName) { return new Supplier<>() { @Override public ActiveResult get() { - if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + Optional active = runtimeConfig.getValue().dataSources().get(dataSourceName).active(); + if (active.isPresent() && !active.get()) { return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); - } else { - return ActiveResult.active(); } + if (reactiveRuntimeConfig.getValue().dataSources().get(dataSourceName).reactive().url().isEmpty()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonUrlMissing(dataSourceName, + "reactive.url")); + } + return ActiveResult.active(); } }; } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index a6b2517b3c305..99c755edad7f9 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.net.ConnectException; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.test.QuarkusUnitTest; import io.vertx.pgclient.PgPool; @@ -58,11 +58,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(ConnectException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java index dde82b176d34e..3f7c4bfc90531 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceHealthCheckTest.java @@ -19,11 +19,10 @@ public class ConfigUrlMissingDefaultDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.\"\"", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.\"\"", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 56d3c2cda7224..dc6b1b115aa8f 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,16 +1,17 @@ package io.quarkus.reactive.pg.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; -import java.net.ConnectException; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -19,19 +20,23 @@ public class ConfigUrlMissingDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(ConnectException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java index fd28bc7ac09a3..c28981876137f 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceDynamicInjectionTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.net.ConnectException; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.arc.InjectableInstance; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -66,11 +66,11 @@ public void mutinyVendorPool() { private void doTest(InjectableInstance instance, Consumer action) { var pool = instance.get(); assertThat(pool).isNotNull(); - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. assertThatThrownBy(() -> action.accept(pool)) - .cause() - .isInstanceOf(ConnectException.class); + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance."); } } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java index e345e322d925d..865d9e281f673 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceHealthCheckTest.java @@ -22,11 +22,10 @@ public class ConfigUrlMissingNamedDatasourceHealthCheckTest { public void testDataSourceHealthCheckExclusion() { RestAssured.when().get("/q/health/ready") .then() - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a failing health check. - .body("status", CoreMatchers.equalTo("DOWN")) - .body("checks[0].data.ds-1", CoreMatchers.startsWithIgnoringCase("DOWN")); + // A datasource without a URL is inactive, and thus not checked for health. + // Note however we have checks in place to fail on startup if such a datasource is injected statically. + .body("status", CoreMatchers.equalTo("UP")) + .body("checks[0].data.ds-1", CoreMatchers.nullValue()); } } diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java index 9dbc5069db523..6f071595b6f51 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/ConfigUrlMissingNamedDatasourceStaticInjectionTest.java @@ -1,16 +1,17 @@ package io.quarkus.reactive.pg.client; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; -import java.net.ConnectException; import java.util.concurrent.CompletionStage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.test.QuarkusUnitTest; import io.vertx.sqlclient.Pool; @@ -23,19 +24,23 @@ public class ConfigUrlMissingNamedDatasourceStaticInjectionTest { .overrideConfigKey("quarkus.devservices.enabled", "false") // We need at least one build-time property for the datasource, // otherwise it's considered unconfigured at build time... - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "pg"); + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "pg") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll("Datasource 'ds-1' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"ds-1\".reactive.url'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#pool")); @Inject MyBean myBean; @Test public void test() { - // When the URL is missing, the client assumes a default one. - // See https://github.com/quarkusio/quarkus/issues/43517 - // In this case the default won't work, resulting in a connection exception. - assertThatThrownBy(() -> myBean.usePool().toCompletableFuture().join()) - .cause() - .isInstanceOf(ConnectException.class); + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/DataSourceHealthCheckPayloadTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/DataSourceHealthCheckPayloadTest.java index 77e65102d0634..a0aa2e6ae376f 100644 --- a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/DataSourceHealthCheckPayloadTest.java +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/DataSourceHealthCheckPayloadTest.java @@ -14,7 +14,9 @@ public class DataSourceHealthCheckPayloadTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication() .overrideConfigKey("quarkus.datasource.health.enabled", "true") - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + // this should make the health check fail + .overrideConfigKey("quarkus.datasource.reactive.url", "vertx-reactive:postgresql://:1/BROKEN"); @Test public void testDataSourceHealthCheckPayload() { diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java index bbbfca406716c..1c9a57aef2063 100644 --- a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolRecorder.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -51,21 +52,28 @@ public class PgPoolRecorder { }; private final RuntimeValue runtimeConfig; + private final RuntimeValue reactiveRuntimeConfig; @Inject - public PgPoolRecorder(RuntimeValue runtimeConfig) { + public PgPoolRecorder(RuntimeValue runtimeConfig, + RuntimeValue reactiveRuntimeConfig) { this.runtimeConfig = runtimeConfig; + this.reactiveRuntimeConfig = reactiveRuntimeConfig; } public Supplier poolCheckActiveSupplier(String dataSourceName) { return new Supplier<>() { @Override public ActiveResult get() { - if (!runtimeConfig.getValue().dataSources().get(dataSourceName).active()) { + Optional active = runtimeConfig.getValue().dataSources().get(dataSourceName).active(); + if (active.isPresent() && !active.get()) { return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonDeactivated(dataSourceName)); - } else { - return ActiveResult.active(); } + if (reactiveRuntimeConfig.getValue().dataSources().get(dataSourceName).reactive().url().isEmpty()) { + return ActiveResult.inactive(DataSourceUtil.dataSourceInactiveReasonUrlMissing(dataSourceName, + "reactive.url")); + } + return ActiveResult.active(); } }; } From 8650f988fb9180babbe6ca856cd6b6a1ec91c933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 27 Sep 2024 17:27:46 +0200 Subject: [PATCH 07/10] Fail on startup when Flyway/Liquibase for inactive datasources are injected into user beans By reimplementing Flyway's/Liquibase's inactive/active handling and eager startup through Arc's native features, which is better integrated and gives us this behavior. --- .../flyway/deployment/FlywayProcessor.java | 4 + ...DefaultDatasourceDynamicInjectionTest.java | 9 +- ...eDefaultDatasourceStaticInjectionTest.java | 30 ++--- ...seNamedDataSourceDynamicInjectionTest.java | 9 +- ...lseNamedDataSourceStaticInjectionTest.java | 30 ++--- ...DefaultDatasourceDynamicInjectionTest.java | 8 +- ...gDefaultDatasourceStaticInjectionTest.java | 29 +++-- ...ngNamedDataSourceDynamicInjectionTest.java | 8 +- ...ingNamedDataSourceStaticInjectionTest.java | 28 +++-- ...efaultDatasourceConfigActiveFalseTest.java | 9 +- ...tStartDefaultDatasourceUrlMissingTest.java | 8 +- ...tNamedDatasourceConfigActiveFalseTest.java | 9 +- ...rtNamedDatasourceConfigUrlMissingTest.java | 8 +- .../flyway/runtime/FlywayRecorder.java | 64 ++++++----- ...UnconfiguredDataSourceFlywayContainer.java | 4 + .../deployment/LiquibaseProcessor.java | 2 + ...DefaultDatasourceDynamicInjectionTest.java | 8 +- ...eDefaultDatasourceStaticInjectionTest.java | 30 ++--- ...seNamedDatasourceDynamicInjectionTest.java | 8 +- ...lseNamedDatasourceStaticInjectionTest.java | 30 ++--- ...DefaultDatasourceDynamicInjectionTest.java | 8 +- ...gDefaultDatasourceStaticInjectionTest.java | 25 ++++- ...ngNamedDataSourceDynamicInjectionTest.java | 8 +- ...ingNamedDataSourceStaticInjectionTest.java | 29 +++-- ...efaultDatasourceConfigActiveFalseTest.java | 8 +- ...DefaultDatasourceConfigUrlMissingTest.java | 8 +- ...tNamedDatasourceConfigActiveFalseTest.java | 8 +- ...rtNamedDatasourceConfigUrlMissingTest.java | 10 +- .../liquibase/runtime/LiquibaseRecorder.java | 105 +++++++++++------- 29 files changed, 317 insertions(+), 227 deletions(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java index ad40723138484..e55818785a9af 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java @@ -216,6 +216,8 @@ void createBeans(FlywayRecorder recorder, .addInjectionPoint(ClassType.create(DotName.createSimple(FlywayContainerProducer.class))) .addInjectionPoint(ClassType.create(DotName.createSimple(DataSource.class)), AgroalDataSourceBuildUtil.qualifier(dataSourceName)) + .startup() + .checkActive(recorder.flywayCheckActiveSupplier(dataSourceName)) .createWith(recorder.flywayContainerFunction(dataSourceName, hasMigrations, createPossible)); AnnotationInstance flywayContainerQualifier; @@ -249,6 +251,8 @@ void createBeans(FlywayRecorder recorder, .setRuntimeInit() .unremovable() .addInjectionPoint(ClassType.create(DotName.createSimple(FlywayContainer.class)), flywayContainerQualifier) + .startup() + .checkActive(recorder.flywayCheckActiveSupplier(dataSourceName)) .createWith(recorder.flywayFunction(dataSourceName)); if (DataSourceUtil.isDefault(dataSourceName)) { diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index 4fe8bd294c92f..21a28b7a1d5c0 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest { @@ -26,10 +26,9 @@ public class FlywayExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTe @DisplayName("If the default datasource is deactivated, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' was deactivated through configuration properties.", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource '' was deactivated automatically because this datasource was deactivated.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 73e06b874e1ba..b8544b21d69be 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,37 +1,41 @@ package io.quarkus.flyway.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Flyway for datasource '' was deactivated automatically because this datasource was deactivated.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#flyway")); @Inject MyBean myBean; @Test - @DisplayName("If the default datasource is deactivated, the application should boot, but Flyway should be deactivated for that datasource") - public void testBootSucceedsButFlywayDeactivated() { - assertThatThrownBy(myBean::useFlyway) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' was deactivated through configuration properties.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java index 2b2d090e35d7f..e6bccd5f7604f 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -37,10 +37,9 @@ public class FlywayExtensionConfigActiveFalseNamedDataSourceDynamicInjectionTest @DisplayName("If a named datasource is deactivated, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' was deactivated through configuration properties.", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource 'users' was deactivated automatically because this datasource was deactivated.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java index 2901ff1084a9b..e6ac840cf0d02 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest.java @@ -1,15 +1,16 @@ package io.quarkus.flyway.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -26,22 +27,25 @@ public class FlywayExtensionConfigActiveFalseNamedDataSourceStaticInjectionTest .overrideConfigKey("quarkus.datasource.username", "sa") .overrideConfigKey("quarkus.datasource.password", "sa") .overrideConfigKey("quarkus.datasource.jdbc.url", - "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Flyway for datasource 'users' was deactivated automatically because this datasource was deactivated.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + + " to 'true' and configure datasource 'users'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#flyway")); @Inject MyBean myBean; @Test - @DisplayName("If a named datasource is deactivated, the application should boot, but Flyway should be deactivated for that datasource") - public void testBootSucceedsButFlywayDeactivated() { - assertThatThrownBy(myBean::useFlyway) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' was deactivated through configuration properties.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" - + " to 'true' and configure datasource 'users'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index 95a9d423c7987..334e65ebc0a15 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest { @@ -27,9 +27,9 @@ public class FlywayExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTes @DisplayName("If the URL is missing for the default datasource, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Flyway", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index d92ff666cc6f9..10ae68c079efb 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,15 +1,16 @@ package io.quarkus.flyway.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest { @@ -17,21 +18,25 @@ public class FlywayExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Flyway for datasource '' was deactivated automatically because this datasource was deactivated", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#flyway")); @Inject MyBean myBean; @Test - @DisplayName("If the URL is missing for the default datasource, the application should boot, but Flyway should be deactivated for that datasource") - public void testBootSucceedsButFlywayDeactivated() { - assertThatThrownBy(() -> myBean.useFlyway()) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' was deactivated automatically because its URL is not set.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java index eca65c7123a3a..b96594909b617 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -38,9 +38,9 @@ public class FlywayExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest @DisplayName("If the URL is missing for a named datasource, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource 'users' was deactivated automatically because this datasource was deactivated", "Datasource 'users' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java index fcbd0ef72f9e4..103a8d694d195 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java @@ -1,15 +1,16 @@ package io.quarkus.flyway.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -27,21 +28,24 @@ public class FlywayExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest { .overrideConfigKey("quarkus.datasource.username", "sa") .overrideConfigKey("quarkus.datasource.password", "sa") .overrideConfigKey("quarkus.datasource.jdbc.url", - "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Flyway for datasource 'users' was deactivated automatically because this datasource was deactivated.", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", + "This bean is injected into", + MyBean.class.getName() + "#flyway")); @Inject MyBean myBean; @Test - @DisplayName("If the URL is missing for a named datasource, the application should boot, but Flyway should be deactivated for that datasource") - public void testBootSucceedsButFlywayDeactivated() { - assertThatThrownBy(() -> myBean.useFlyway()) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' was deactivated automatically because its URL is not set.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java index 7f703bbe1130c..988b420df5fb8 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest { @@ -29,10 +29,9 @@ public class FlywayExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest @DisplayName("If the default datasource is deactivated, even if migrate-at-start is enabled, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Flyway", - "Datasource '' was deactivated through configuration properties.", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource '' was deactivated automatically because this datasource was deactivated", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.active'" + " to 'true' and configure datasource ''", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java index 7ac6c37b2fdba..b2a14fc227918 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest { @@ -30,9 +30,9 @@ public class FlywayExtensionMigrateAtStartDefaultDatasourceUrlMissingTest { @DisplayName("If there is no config for the default datasource, even if migrate-at-start is enabled, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Flyway", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java index 92ec49a503bf2..21b02ee28e800 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -40,10 +40,9 @@ public class FlywayExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest { @DisplayName("If a named datasource is deactivated, even if migrate-at-start is enabled, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", - "Datasource 'users' was deactivated through configuration properties.", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource 'users' was deactivated automatically because this datasource was deactivated", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + " to 'true' and configure datasource 'users'", diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java index e685f83ad1015..231e0dc1be6f2 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; @@ -41,9 +41,9 @@ public class FlywayExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest { @DisplayName("If there is no config for a named datasource, even if migrate-at-start is enabled, the application should boot, but Flyway should be deactivated for that datasource") public void testBootSucceedsButFlywayDeactivated() { assertThatThrownBy(flyway::get) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Flyway", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Flyway for datasource 'users' was deactivated automatically because this datasource was deactivated", "Datasource 'users' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 1c7a7f740d033..2c2e10a4e4199 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -4,6 +4,7 @@ import java.util.Locale; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; import javax.sql.DataSource; @@ -21,13 +22,12 @@ import org.jboss.logging.Logger; import io.quarkus.agroal.runtime.AgroalDataSourceUtil; +import io.quarkus.arc.ActiveResult; import io.quarkus.arc.Arc; -import io.quarkus.arc.InactiveBeanException; -import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.ConfigurationException; @Recorder public class FlywayRecorder { @@ -55,24 +55,41 @@ public void setApplicationCallbackClasses(Map> call QuarkusPathLocationScanner.setApplicationCallbackClasses(callbackClasses); } + public Supplier flywayCheckActiveSupplier(String dataSourceName) { + return new Supplier() { + @Override + public ActiveResult get() { + // Flyway beans are inactive when the datasource itself is inactive. + var dataSourceBean = AgroalDataSourceUtil.dataSourceInstance(dataSourceName).getHandle().getBean(); + var dataSourceActive = dataSourceBean.checkActive(); + if (!dataSourceActive.value()) { + return ActiveResult.inactive( + String.format(Locale.ROOT, + "Flyway for datasource '%s' was deactivated automatically because this datasource was deactivated.", + dataSourceName), + dataSourceActive); + } + + // Note: When quarkus.flyway.active is set to false, Flyway beans are still available. + // The property only controls automatic execution on startup. + // TODO should we change quarkus.flyway.active (see ^) to align on other extensions? + // See https://github.com/quarkusio/quarkus/issues/42244. + // We'd have something like quarkus.liquibase.startup.enabled controlling startup behavior, + // and *if necessary* quarkus.liquibase.active controlling bean availability + // (though IMO controlling that at the datasource level would be enough). + return ActiveResult.active(); + } + }; + } + public Function, FlywayContainer> flywayContainerFunction(String dataSourceName, boolean hasMigrations, boolean createPossible) { return new Function<>() { @Override public FlywayContainer apply(SyntheticCreationalContext context) { - DataSource dataSource; - try { - dataSource = context.getInjectedReference(DataSource.class, AgroalDataSourceUtil.qualifier(dataSourceName)); - } catch (ConfigurationException | InactiveBeanException e) { - // TODO do we really want to enable retrieval of a FlywayContainer for an unconfigured/inactive datasource? - // Marking the FlywayContainer bean as inactive when the datasource is inactive/unconfigured - // would probably make more sense. - return new UnconfiguredDataSourceFlywayContainer(dataSourceName, String.format(Locale.ROOT, - "Unable to find datasource '%s' for Flyway: %s", - dataSourceName, e.getMessage()), e); - } - + DataSource dataSource = context.getInjectedReference(DataSource.class, + AgroalDataSourceUtil.qualifier(dataSourceName)); FlywayContainerProducer flywayProducer = context.getInjectedReference(FlywayContainerProducer.class); return flywayProducer.createFlyway(dataSource, dataSourceName, hasMigrations, createPossible); } @@ -94,24 +111,19 @@ public void doStartActions(String dataSourceName) { FlywayDataSourceRuntimeConfig flywayDataSourceRuntimeConfig = config.getValue() .getConfigForDataSourceName(dataSourceName); - if (!flywayDataSourceRuntimeConfig.active - // If not specified explicitly, Flyway is active when the datasource itself is active. - .orElseGet(() -> AgroalDataSourceUtil.dataSourceIfActive(dataSourceName).isPresent())) { + if (flywayDataSourceRuntimeConfig.active.isPresent() + && !flywayDataSourceRuntimeConfig.active.get()) { return; } - InstanceHandle flywayContainerInstanceHandle = Arc.container().instance(FlywayContainer.class, + InjectableInstance flywayContainerInstance = Arc.container().select(FlywayContainer.class, FlywayContainerUtil.getFlywayContainerQualifier(dataSourceName)); - - if (!flywayContainerInstanceHandle.isAvailable()) { + if (!flywayContainerInstance.isResolvable() + || !flywayContainerInstance.getHandle().getBean().isActive()) { return; } - FlywayContainer flywayContainer = flywayContainerInstanceHandle.get(); - - if (flywayContainer instanceof UnconfiguredDataSourceFlywayContainer) { - return; - } + FlywayContainer flywayContainer = flywayContainerInstance.get(); if (flywayContainer.isCleanAtStart()) { flywayContainer.getFlyway().clean(); diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java index 5011c9898ce0d..b9d5e4be9cbfc 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java @@ -2,6 +2,10 @@ import org.flywaydb.core.Flyway; +/** + * @deprecated This is never instantiated by Quarkus. Do not use. + */ +@Deprecated(forRemoval = true) public class UnconfiguredDataSourceFlywayContainer extends FlywayContainer { private final String message; diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index c7967d1ca4e4d..b6587fb184fc3 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -268,6 +268,8 @@ void createBeans(LiquibaseRecorder recorder, .addInjectionPoint(ClassType.create(DotName.createSimple(LiquibaseFactoryProducer.class))) .addInjectionPoint(ClassType.create(DotName.createSimple(DataSource.class)), AgroalDataSourceBuildUtil.qualifier(dataSourceName)) + .startup() + .checkActive(recorder.liquibaseCheckActiveSupplier(dataSourceName)) .createWith(recorder.liquibaseFunction(dataSourceName)); if (DataSourceUtil.isDefault(dataSourceName)) { diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java index fc13f25e80444..5b07795a9206c 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -26,9 +26,9 @@ public class LiquibaseExtensionConfigActiveFalseDefaultDatasourceDynamicInjectio @DisplayName("If the default datasource is deactivated, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated through configuration properties.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.active'" diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java index 5fd45a4839a21..6e9501bcd2369 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjectionTest.java @@ -1,14 +1,15 @@ package io.quarkus.liquibase.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.junit.jupiter.api.DisplayName; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -16,22 +17,25 @@ public class LiquibaseExtensionConfigActiveFalseDefaultDatasourceStaticInjection @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .overrideConfigKey("quarkus.datasource.active", "false"); + .overrideConfigKey("quarkus.datasource.active", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.active'" + + " to 'true' and configure datasource ''", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#liquibase")); @Inject MyBean myBean; @Test - @DisplayName("If the default datasource is deactivated, the application should boot, but Liquibase should be deactivated for that datasource") - public void testBootSucceedsButLiquibaseDeactivated() { - assertThatThrownBy(myBean::useLiquibase) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", - "Datasource '' was deactivated through configuration properties.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.active'" - + " to 'true' and configure datasource ''", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java index bcd8ef6227822..356d4d7bed7de 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -37,9 +37,9 @@ public class LiquibaseExtensionConfigActiveFalseNamedDatasourceDynamicInjectionT @DisplayName("If a named datasource is deactivated, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource 'users' was deactivated automatically because this datasource was deactivated", "Datasource 'users' was deactivated through configuration properties.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java index 864e3310b9a8d..9d903a5531fd4 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTest.java @@ -1,14 +1,15 @@ package io.quarkus.liquibase.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.junit.jupiter.api.DisplayName; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -26,22 +27,25 @@ public class LiquibaseExtensionConfigActiveFalseNamedDatasourceStaticInjectionTe .overrideConfigKey("quarkus.datasource.username", "sa") .overrideConfigKey("quarkus.datasource.password", "sa") .overrideConfigKey("quarkus.datasource.jdbc.url", - "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Liquibase for datasource 'users' was deactivated automatically because this datasource was deactivated.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" + + " to 'true' and configure datasource 'users'", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#liquibase")); @Inject MyBean myBean; @Test - @DisplayName("If a named datasource is deactivated, the application should boot, but Liquibase should be deactivated for that datasource") - public void testBootSucceedsButLiquibaseDeactivated() { - assertThatThrownBy(myBean::useLiquibase) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", - "Datasource 'users' was deactivated through configuration properties.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" - + " to 'true' and configure datasource 'users'", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java index 18b35cd69e111..9d05a27968af2 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -27,9 +27,9 @@ public class LiquibaseExtensionConfigUrlMissingDefaultDatasourceDynamicInjection @DisplayName("If the URL is missing for the default datasource, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java index 65fd0cbf0e5d1..7a00f504ddebd 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionTest.java @@ -1,14 +1,17 @@ package io.quarkus.liquibase.test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -17,17 +20,35 @@ public class LiquibaseExtensionConfigUrlMissingDefaultDatasourceStaticInjectionT @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() // The URL won't be missing if dev services are enabled - .overrideConfigKey("quarkus.devservices.enabled", "false"); + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated.", + "Datasource '' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#liquibase")); @Inject MyBean myBean; + @Test + public void test() { + Assertions.fail("Startup should have failed"); + } + @Test @DisplayName("If the URL is missing for the default datasource, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> myBean.useLiquibase()) .cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java index 1b2b8d5efcd3a..2c2d56982d87c 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -38,9 +38,9 @@ public class LiquibaseExtensionConfigUrlMissingNamedDataSourceDynamicInjectionTe @DisplayName("If the URL is missing for a named datasource, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource 'users' was deactivated automatically because this datasource was deactivated", "Datasource 'users' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java index b9712fc2a4881..560aa4ca62eed 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTest.java @@ -1,14 +1,15 @@ package io.quarkus.liquibase.test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.junit.jupiter.api.DisplayName; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -27,21 +28,25 @@ public class LiquibaseExtensionConfigUrlMissingNamedDataSourceStaticInjectionTes .overrideConfigKey("quarkus.datasource.username", "sa") .overrideConfigKey("quarkus.datasource.password", "sa") .overrideConfigKey("quarkus.datasource.jdbc.url", - "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") + .assertException(e -> assertThat(e) + // Can't use isInstanceOf due to weird classloading in tests + .satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName())) + .hasMessageContainingAll( + "Liquibase for datasource 'users' was deactivated automatically because this datasource was deactivated.", + "Datasource 'users' was deactivated automatically because its URL is not set.", + "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints + "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", + "Refer to https://quarkus.io/guides/datasource for guidance.", + "This bean is injected into", + MyBean.class.getName() + "#liquibase")); @Inject MyBean myBean; @Test - @DisplayName("If the URL is missing for a named datasource, the application should boot, but Liquibase should be deactivated for that datasource") - public void testBootSucceedsButLiquibaseDeactivated() { - assertThatThrownBy(() -> myBean.useLiquibase()) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", - "Datasource 'users' was deactivated automatically because its URL is not set.", - "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints - "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", - "Refer to https://quarkus.io/guides/datasource for guidance."); + public void test() { + Assertions.fail("Startup should have failed"); } @ApplicationScoped diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java index ed0e26b1edb0c..fe58146d75124 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -29,9 +29,9 @@ public class LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigActiveFalseT @DisplayName("If the default datasource is deactivated, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated through configuration properties.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.active'" diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java index 2b0209a26664d..9c5824eb63c50 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -30,9 +30,9 @@ public class LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigUrlMissingTe @DisplayName("If the URL is missing for the default datasource, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource '' was deactivated automatically because this datasource was deactivated", "Datasource '' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.jdbc.url'.", diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java index 46358a9481f32..14e1e41746224 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -40,9 +40,9 @@ public class LiquibaseExtensionMigrateAtStartNamedDatasourceConfigActiveFalseTes @DisplayName("If a named datasource is deactivated, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource 'users' was deactivated automatically because this datasource was deactivated", "Datasource 'users' was deactivated through configuration properties.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".active'" diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java index 674ca30e41b0b..725713d185915 100644 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.InactiveBeanException; import io.quarkus.liquibase.LiquibaseDataSource; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.test.QuarkusUnitTest; @@ -38,12 +38,12 @@ public class LiquibaseExtensionMigrateAtStartNamedDatasourceConfigUrlMissingTest Instance liquibase; @Test - @DisplayName("If the URL is missing for a named datasource, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") + @DisplayName("If the URL is missing for the default datasource, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") public void testBootSucceedsButLiquibaseDeactivated() { assertThatThrownBy(() -> liquibase.get().getConfiguration()) - .isInstanceOf(CreationException.class) - .cause() - .hasMessageContainingAll("Unable to find datasource 'users' for Liquibase", + .isInstanceOf(InactiveBeanException.class) + .hasMessageContainingAll( + "Liquibase for datasource 'users' was deactivated automatically because this datasource was deactivated", "Datasource 'users' was deactivated automatically because its URL is not set.", "To avoid this exception while keeping the bean inactive", // Message from Arc with generic hints "To activate the datasource, set configuration property 'quarkus.datasource.\"users\".jdbc.url'.", diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java index 3df8108e58c61..8334c79ae0aaa 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java @@ -2,14 +2,15 @@ import java.util.Locale; import java.util.function.Function; +import java.util.function.Supplier; import javax.sql.DataSource; -import jakarta.enterprise.inject.UnsatisfiedResolutionException; - import io.quarkus.agroal.runtime.AgroalDataSourceUtil; -import io.quarkus.arc.ClientProxy; -import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.ActiveResult; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InactiveBeanException; +import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.runtime.ResettableSystemProperties; @@ -27,21 +28,39 @@ public LiquibaseRecorder(RuntimeValue config) { this.config = config; } + public Supplier liquibaseCheckActiveSupplier(String dataSourceName) { + return new Supplier() { + @Override + public ActiveResult get() { + // Flyway beans are inactive when the datasource itself is inactive. + var dataSourceBean = AgroalDataSourceUtil.dataSourceInstance(dataSourceName).getHandle().getBean(); + var dataSourceActive = dataSourceBean.checkActive(); + if (!dataSourceActive.value()) { + return ActiveResult.inactive( + String.format(Locale.ROOT, + "Liquibase for datasource '%s' was deactivated automatically because this datasource was deactivated.", + dataSourceName), + dataSourceActive); + } + + // Note: When quarkus.liquibase.enabled is set to false, Liquibase beans are still available. + // The property only controls automatic execution on startup. + // TODO should we change quarkus.liquibase.enabled (see ^) to align on other extensions? + // See https://github.com/quarkusio/quarkus/issues/42244. + // We'd have something like quarkus.liquibase.startup.enabled controlling startup behavior, + // and *if necessary* quarkus.liquibase.active controlling bean availability + // (though IMO controlling that at the datasource level would be enough). + return ActiveResult.active(); + } + }; + } + public Function, LiquibaseFactory> liquibaseFunction(String dataSourceName) { return new Function, LiquibaseFactory>() { @Override public LiquibaseFactory apply(SyntheticCreationalContext context) { - DataSource dataSource; - try { - // ClientProxy.unwrap is necessary to trigger exceptions on inactive datasources - dataSource = ClientProxy.unwrap(context.getInjectedReference(DataSource.class, - AgroalDataSourceUtil.qualifier(dataSourceName))); - } catch (RuntimeException e) { - throw new UnsatisfiedResolutionException(String.format(Locale.ROOT, - "Unable to find datasource '%s' for Liquibase: %s", - dataSourceName, e.getMessage()), e); - } - + DataSource dataSource = context.getInjectedReference(DataSource.class, + AgroalDataSourceUtil.qualifier(dataSourceName)); LiquibaseFactoryProducer liquibaseProducer = context.getInjectedReference(LiquibaseFactoryProducer.class); return liquibaseProducer.createLiquibaseFactory(dataSource, dataSourceName); } @@ -52,40 +71,42 @@ public void doStartActions(String dataSourceName) { if (!config.getValue().enabled) { return; } - // Liquibase is only active when the datasource itself is active. - if (AgroalDataSourceUtil.dataSourceIfActive(dataSourceName).isEmpty()) { + + var dataSourceConfig = config.getValue().getConfigForDataSourceName(dataSourceName); + if (!dataSourceConfig.cleanAtStart && !dataSourceConfig.migrateAtStart) { + return; + } + + InjectableInstance liquibaseFactoryInstance = Arc.container().select(LiquibaseFactory.class, + LiquibaseFactoryUtil.getLiquibaseFactoryQualifier(dataSourceName)); + if (!liquibaseFactoryInstance.isResolvable() + || !liquibaseFactoryInstance.getHandle().getBean().isActive()) { return; } - InstanceHandle liquibaseFactoryHandle = LiquibaseFactoryUtil.getLiquibaseFactory(dataSourceName); - try { - LiquibaseFactory liquibaseFactory = liquibaseFactoryHandle.get(); - var config = liquibaseFactory.getConfiguration(); - if (!config.cleanAtStart && !config.migrateAtStart) { - return; + LiquibaseFactory liquibaseFactory = liquibaseFactoryInstance.get(); + try (Liquibase liquibase = liquibaseFactory.createLiquibase(); + ResettableSystemProperties resettableSystemProperties = liquibaseFactory + .createResettableSystemProperties()) { + if (dataSourceConfig.cleanAtStart) { + liquibase.dropAll(); } - try (Liquibase liquibase = liquibaseFactory.createLiquibase(); - ResettableSystemProperties resettableSystemProperties = liquibaseFactory - .createResettableSystemProperties()) { - if (config.cleanAtStart) { - liquibase.dropAll(); - } - if (config.migrateAtStart) { - var lockService = LockServiceFactory.getInstance() - .getLockService(liquibase.getDatabase()); - lockService.waitForLock(); - try { - if (config.validateOnMigrate) { - liquibase.validate(); - } - liquibase.update(liquibaseFactory.createContexts(), liquibaseFactory.createLabels()); - } finally { - lockService.releaseLock(); + if (dataSourceConfig.migrateAtStart) { + var lockService = LockServiceFactory.getInstance() + .getLockService(liquibase.getDatabase()); + lockService.waitForLock(); + try { + if (dataSourceConfig.validateOnMigrate) { + liquibase.validate(); } + liquibase.update(liquibaseFactory.createContexts(), liquibaseFactory.createLabels()); + } finally { + lockService.releaseLock(); } } - } catch (UnsatisfiedResolutionException e) { - //ignore, the DS is not configured + } catch (InactiveBeanException e) { + // These exceptions should be self-explanatory + throw e; } catch (Exception e) { throw new IllegalStateException("Error starting Liquibase", e); } From 8998217fe44f6737edc9727be56efe0165293ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 30 Sep 2024 16:13:39 +0200 Subject: [PATCH 08/10] Update Hibernate ORM's "active" documentation For consistency with datasource documentation. --- docs/src/main/asciidoc/hibernate-orm.adoc | 6 ++++-- .../runtime/HibernateOrmRuntimeConfigPersistenceUnit.java | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index e248b29fdcbc7..e29f0110abf58 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -506,8 +506,10 @@ by default it is active at runtime, that is Quarkus will start the corresponding Hibernate ORM `SessionFactory` on application startup. To deactivate a persistence unit at runtime, set `quarkus.hibernate-orm[.optional name].active` to `false`. -Then Quarkus will not start the corresponding Hibernate ORM `SessionFactory` on application startup. -Any attempt to use the corresponding persistence unit at runtime will fail with a clear error message. +If a persistence unit is not active: + +* The `SessionFactory` will not start during application startup. +* Accessing the `EntityManagerFactory`/`EntityManager` or `SessionFactory`/`Session` will cause an exception to be thrown. This is in particular useful when you want an application to be able to xref:datasource.adoc#datasource-active[use one of a pre-determined set of datasources at runtime]. diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java index 09cafed1b27fb..279ac0e294458 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java @@ -23,10 +23,6 @@ public interface HibernateOrmRuntimeConfigPersistenceUnit { * * See xref:hibernate-orm.adoc#persistence-unit-active[this section of the documentation]. * - * If the persistence unit is not active, it won't start with the application, - * and accessing the corresponding EntityManagerFactory/EntityManager or SessionFactory/Session - * will not be possible. - * * Note that if Hibernate ORM is disabled (i.e. `quarkus.hibernate-orm.enabled` is set to `false`), * all persistence units are deactivated, and setting this property to `true` will fail. * From 9fbf19185b55e7e1b72420af35c52f4037531038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Oct 2024 13:46:14 +0200 Subject: [PATCH 09/10] Simplify tests involving multiple datasources as alternatives --- ...ourcesAsAlternativesWithActiveDS1Test.java | 95 ----------- ...ourcesAsAlternativesWithActiveDS2Test.java | 95 ----------- ...cesAsAlternativesWithBeanProducerTest.java | 119 ++++++++++++++ ...iplePUAsAlternativesWithActivePU1Test.java | 128 --------------- ...iplePUAsAlternativesWithActivePU2Test.java | 128 --------------- ...ePUAsAlternativesWithBeanProducerTest.java | 150 ++++++++++++++++++ 6 files changed, 269 insertions(+), 446 deletions(-) delete mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java delete mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithBeanProducerTest.java delete mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java delete mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithBeanProducerTest.java diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java deleted file mode 100644 index d37178ddf1d9a..0000000000000 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS1Test.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.quarkus.agroal.test; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.agroal.DataSource; -import io.quarkus.arc.Arc; -import io.quarkus.arc.InjectableInstance; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Tests a use case where multiple datasources are defined at build time, - * but only one is used at runtime. - *

- * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, - * so we use the h2 db-kind everywhere here to keep test dependencies simpler. - *

- * See {@link MultipleDataSourcesAsAlternativesWithActiveDS1Test} for the counterpart where PU2 is used at runtime. - */ -public class MultipleDataSourcesAsAlternativesWithActiveDS1Test { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addClass(MyProducer.class)) - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-1.active", "false") - .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-2.active", "false") - // This is where we select datasource 1 - .overrideRuntimeConfigKey("quarkus.datasource.ds-1.active", "true") - .overrideRuntimeConfigKey("quarkus.datasource.ds-1.jdbc.url", "jdbc:h2:mem:testds1"); - - @Inject - @DataSource("ds-1") - AgroalDataSource explicitDatasourceBean; - - @Inject - AgroalDataSource customIndirectDatasourceBean; - - @Test - public void testExplicitDatasourceBeanUsable() { - doTestDatasource(explicitDatasourceBean); - } - - @Test - public void testCustomIndirectDatasourceBeanUsable() { - doTestDatasource(customIndirectDatasourceBean); - } - - @Test - public void testInactiveDatasourceBeanUnusable() { - assertThatThrownBy(() -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral("ds-2")).get() - .getConnection()) - .hasMessageContaining("Datasource 'ds-2' was deactivated through configuration properties."); - } - - private static void doTestDatasource(AgroalDataSource dataSource) { - assertThatCode(() -> { - try (var connection = dataSource.getConnection()) { - } - }) - .doesNotThrowAnyException(); - } - - private static class MyProducer { - @Inject - @DataSource("ds-1") - InjectableInstance dataSource1Bean; - - @Inject - @DataSource("ds-2") - InjectableInstance dataSource2Bean; - - @Produces - @ApplicationScoped - public AgroalDataSource dataSource() { - if (dataSource1Bean.getHandle().getBean().isActive()) { - return dataSource1Bean.get(); - } else if (dataSource2Bean.getHandle().getBean().isActive()) { - return dataSource2Bean.get(); - } else { - throw new RuntimeException("No active datasource!"); - } - } - } -} diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java deleted file mode 100644 index 497953764b919..0000000000000 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithActiveDS2Test.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.quarkus.agroal.test; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.agroal.DataSource; -import io.quarkus.arc.Arc; -import io.quarkus.arc.InjectableInstance; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Tests a use case where multiple datasources are defined at build time, - * but only one is used at runtime. - *

- * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, - * so we use the h2 db-kind everywhere here to keep test dependencies simpler. - *

- * See {@link MultipleDataSourcesAsAlternativesWithActiveDS1Test} for the counterpart where PU2 is used at runtime. - */ -public class MultipleDataSourcesAsAlternativesWithActiveDS2Test { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addClass(MyProducer.class)) - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-1.active", "false") - .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-2.active", "false") - // This is where we select datasource 2 - .overrideRuntimeConfigKey("quarkus.datasource.ds-2.active", "true") - .overrideRuntimeConfigKey("quarkus.datasource.ds-2.jdbc.url", "jdbc:h2:mem:testds2"); - - @Inject - @DataSource("ds-2") - AgroalDataSource explicitDatasourceBean; - - @Inject - AgroalDataSource customIndirectDatasourceBean; - - @Test - public void testExplicitDatasourceBeanUsable() { - doTestDatasource(explicitDatasourceBean); - } - - @Test - public void testCustomIndirectDatasourceBeanUsable() { - doTestDatasource(customIndirectDatasourceBean); - } - - @Test - public void testInactiveDatasourceBeanUnusable() { - assertThatThrownBy(() -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral("ds-1")).get() - .getConnection()) - .hasMessageContaining("Datasource 'ds-1' was deactivated through configuration properties."); - } - - private static void doTestDatasource(AgroalDataSource dataSource) { - assertThatCode(() -> { - try (var connection = dataSource.getConnection()) { - } - }) - .doesNotThrowAnyException(); - } - - private static class MyProducer { - @Inject - @DataSource("ds-1") - InjectableInstance dataSource1Bean; - - @Inject - @DataSource("ds-2") - InjectableInstance dataSource2Bean; - - @Produces - @ApplicationScoped - public AgroalDataSource dataSource() { - if (dataSource1Bean.getHandle().getBean().isActive()) { - return dataSource1Bean.get(); - } else if (dataSource2Bean.getHandle().getBean().isActive()) { - return dataSource2Bean.get(); - } else { - throw new RuntimeException("No active datasource!"); - } - } - } -} diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithBeanProducerTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithBeanProducerTest.java new file mode 100644 index 0000000000000..6c36a5e2a55bb --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithBeanProducerTest.java @@ -0,0 +1,119 @@ +package io.quarkus.agroal.test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests a use case where multiple datasources are defined at build time, + * but only one is used at runtime, + * and a custom bean producer is used to retrieve the active datasource. + *

+ * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, + * so we use the h2 db-kind everywhere here to keep test dependencies simpler. + */ +public abstract class MultipleDataSourcesAsAlternativesWithBeanProducerTest { + + public static class Ds1ActiveTest extends MultipleDataSourcesAsAlternativesWithBeanProducerTest { + @RegisterExtension + static QuarkusUnitTest runner = runner("ds-1"); + + public Ds1ActiveTest() { + super("ds-1", "ds-2"); + } + } + + public static class Ds2ActiveTest extends MultipleDataSourcesAsAlternativesWithBeanProducerTest { + @RegisterExtension + static QuarkusUnitTest runner = runner("ds-2"); + + public Ds2ActiveTest() { + super("ds-2", "ds-1"); + } + } + + static QuarkusUnitTest runner(String activeDsName) { + return new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyProducer.class)) + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.ds-1.active", "false") + .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.ds-2.active", "false") + // This is where we select the active datasource + .overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".active", "true") + .overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".jdbc.url", "jdbc:h2:mem:testds1"); + } + + private final String activeDsName; + private final String inactiveDsName; + + protected MultipleDataSourcesAsAlternativesWithBeanProducerTest(String activeDsName, String inactiveDsName) { + this.activeDsName = activeDsName; + this.inactiveDsName = inactiveDsName; + } + + @Inject + AgroalDataSource customIndirectDatasourceBean; + + @Test + public void testExplicitDatasourceBeanUsable() { + doTestDatasource(Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral(activeDsName)).get()); + } + + @Test + public void testCustomIndirectDatasourceBeanUsable() { + doTestDatasource(customIndirectDatasourceBean); + } + + @Test + public void testInactiveDatasourceBeanUnusable() { + assertThatThrownBy( + () -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral(inactiveDsName)).get() + .getConnection()) + .hasMessageContaining( + "Datasource '" + inactiveDsName + "' was deactivated through configuration properties."); + } + + private static void doTestDatasource(AgroalDataSource dataSource) { + assertThatCode(() -> { + try (var connection = dataSource.getConnection()) { + } + }) + .doesNotThrowAnyException(); + } + + private static class MyProducer { + @Inject + @DataSource("ds-1") + InjectableInstance dataSource1Bean; + + @Inject + @DataSource("ds-2") + InjectableInstance dataSource2Bean; + + @Produces + @ApplicationScoped + public AgroalDataSource dataSource() { + if (dataSource1Bean.getHandle().getBean().isActive()) { + return dataSource1Bean.get(); + } else if (dataSource2Bean.getHandle().getBean().isActive()) { + return dataSource2Bean.get(); + } else { + throw new RuntimeException("No active datasource!"); + } + } + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java deleted file mode 100644 index 00e75da7c2723..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU1Test.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.quarkus.hibernate.orm.config.datasource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; - -import org.hibernate.Session; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.agroal.DataSource; -import io.quarkus.arc.InjectableInstance; -import io.quarkus.hibernate.orm.PersistenceUnit; -import io.quarkus.hibernate.orm.config.namedpu.MyEntity; -import io.quarkus.narayana.jta.QuarkusTransaction; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Tests a use case where multiple PU/datasources are defined at build time, - * but only one is used at runtime. - *

- * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, - * so we use the h2 db-kind everywhere here to keep test dependencies simpler. - *

- * See {@link MultiplePUAsAlternativesWithActivePU2Test} for the counterpart where PU2 is used at runtime. - */ -public class MultiplePUAsAlternativesWithActivePU1Test { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addPackage(MyEntity.class.getPackage().getName()) - .addClass(MyProducer.class)) - .overrideConfigKey("quarkus.hibernate-orm.pu-1.packages", MyEntity.class.getPackageName()) - .overrideConfigKey("quarkus.hibernate-orm.pu-1.datasource", "ds-1") - .overrideConfigKey("quarkus.hibernate-orm.pu-1.database.generation", "drop-and-create") - .overrideConfigKey("quarkus.hibernate-orm.pu-1.active", "false") - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-1.active", "false") - .overrideConfigKey("quarkus.hibernate-orm.pu-2.packages", MyEntity.class.getPackageName()) - .overrideConfigKey("quarkus.hibernate-orm.pu-2.datasource", "ds-2") - .overrideConfigKey("quarkus.hibernate-orm.pu-2.database.generation", "drop-and-create") - .overrideConfigKey("quarkus.hibernate-orm.pu-2.active", "false") - .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-2.active", "false") - // This is where we select PU1 / datasource 1 - .overrideRuntimeConfigKey("quarkus.hibernate-orm.pu-1.active", "true") - .overrideRuntimeConfigKey("quarkus.datasource.ds-1.active", "true") - .overrideRuntimeConfigKey("quarkus.datasource.ds-1.jdbc.url", "jdbc:h2:mem:testds1"); - - @Inject - @PersistenceUnit("pu-1") - Session explicitSessionBean; - - @Inject - Session customIndirectSessionBean; - - @Inject - @PersistenceUnit("pu-2") - Session inactiveSessionBean; - - @Test - public void testExplicitSessionBeanUsable() { - doTestPersistRetrieve(explicitSessionBean, 1L); - } - - @Test - public void testCustomIndirectSessionBeanUsable() { - doTestPersistRetrieve(customIndirectSessionBean, 2L); - } - - @Test - public void testInactiveSessionBeanUnusable() { - QuarkusTransaction.requiringNew().run(() -> { - assertThatThrownBy(() -> inactiveSessionBean.find(MyEntity.class, 3L)) - .hasMessageContainingAll( - "Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit pu-2", - "Hibernate ORM was deactivated through configuration properties"); - }); - } - - private static void doTestPersistRetrieve(Session session, long id) { - QuarkusTransaction.requiringNew().run(() -> { - MyEntity entity = new MyEntity(); - entity.setId(id); - entity.setName("text" + id); - session.persist(entity); - }); - QuarkusTransaction.requiringNew().run(() -> { - MyEntity entity = session.get(MyEntity.class, id); - assertThat(entity.getName()).isEqualTo("text" + id); - }); - } - - private static class MyProducer { - @Inject - @DataSource("ds-1") - InjectableInstance dataSource1Bean; - - @Inject - @DataSource("ds-2") - InjectableInstance dataSource2Bean; - - @Inject - @PersistenceUnit("pu-1") - Session pu1SessionBean; - - @Inject - @PersistenceUnit("pu-2") - Session pu2SessionBean; - - @Produces - @ApplicationScoped - public Session session() { - if (dataSource1Bean.getHandle().getBean().isActive()) { - return pu1SessionBean; - } else if (dataSource2Bean.getHandle().getBean().isActive()) { - return pu2SessionBean; - } else { - throw new RuntimeException("No active datasource!"); - } - } - } -} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java deleted file mode 100644 index 333354627274e..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithActivePU2Test.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.quarkus.hibernate.orm.config.datasource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; - -import org.hibernate.Session; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.agroal.api.AgroalDataSource; -import io.quarkus.agroal.DataSource; -import io.quarkus.arc.InjectableInstance; -import io.quarkus.hibernate.orm.PersistenceUnit; -import io.quarkus.hibernate.orm.config.namedpu.MyEntity; -import io.quarkus.narayana.jta.QuarkusTransaction; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Tests a use case where multiple PU/datasources are defined at build time, - * but only one is used at runtime. - *

- * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, - * so we use the h2 db-kind everywhere here to keep test dependencies simpler. - *

- * See {@link MultiplePUAsAlternativesWithActivePU1Test} for the counterpart where PU1 is used at runtime. - */ -public class MultiplePUAsAlternativesWithActivePU2Test { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addPackage(MyEntity.class.getPackage().getName()) - .addClass(MyProducer.class)) - .overrideConfigKey("quarkus.hibernate-orm.pu-1.packages", MyEntity.class.getPackageName()) - .overrideConfigKey("quarkus.hibernate-orm.pu-1.datasource", "ds-1") - .overrideConfigKey("quarkus.hibernate-orm.pu-1.database.generation", "drop-and-create") - .overrideConfigKey("quarkus.hibernate-orm.pu-1.active", "false") - .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-1.active", "false") - .overrideConfigKey("quarkus.hibernate-orm.pu-2.packages", MyEntity.class.getPackageName()) - .overrideConfigKey("quarkus.hibernate-orm.pu-2.datasource", "ds-2") - .overrideConfigKey("quarkus.hibernate-orm.pu-2.database.generation", "drop-and-create") - .overrideConfigKey("quarkus.hibernate-orm.pu-2.active", "false") - .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") - .overrideConfigKey("quarkus.datasource.ds-2.active", "false") - // This is where we select PU1 / datasource 2 - .overrideRuntimeConfigKey("quarkus.hibernate-orm.pu-2.active", "true") - .overrideRuntimeConfigKey("quarkus.datasource.ds-2.active", "true") - .overrideRuntimeConfigKey("quarkus.datasource.ds-2.jdbc.url", "jdbc:h2:mem:testds2"); - - @Inject - @PersistenceUnit("pu-2") - Session explicitSessionBean; - - @Inject - Session customIndirectSessionBean; - - @Inject - @PersistenceUnit("pu-1") - Session inactiveSessionBean; - - @Test - public void testExplicitSessionBeanUsable() { - doTestPersistRetrieve(explicitSessionBean, 1L); - } - - @Test - public void testCustomIndirectSessionBeanUsable() { - doTestPersistRetrieve(customIndirectSessionBean, 2L); - } - - @Test - public void testInactiveSessionBeanUnusable() { - QuarkusTransaction.requiringNew().run(() -> { - assertThatThrownBy(() -> inactiveSessionBean.find(MyEntity.class, 3L)) - .hasMessageContainingAll( - "Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit pu-1", - "Hibernate ORM was deactivated through configuration properties"); - }); - } - - private static void doTestPersistRetrieve(Session session, long id) { - QuarkusTransaction.requiringNew().run(() -> { - MyEntity entity = new MyEntity(); - entity.setId(id); - entity.setName("text" + id); - session.persist(entity); - }); - QuarkusTransaction.requiringNew().run(() -> { - MyEntity entity = session.get(MyEntity.class, id); - assertThat(entity.getName()).isEqualTo("text" + id); - }); - } - - private static class MyProducer { - @Inject - @DataSource("ds-1") - InjectableInstance dataSource1Bean; - - @Inject - @DataSource("ds-2") - InjectableInstance dataSource2Bean; - - @Inject - @PersistenceUnit("pu-1") - Session pu1SessionBean; - - @Inject - @PersistenceUnit("pu-2") - Session pu2SessionBean; - - @Produces - @ApplicationScoped - public Session session() { - if (dataSource1Bean.getHandle().getBean().isActive()) { - return pu1SessionBean; - } else if (dataSource2Bean.getHandle().getBean().isActive()) { - return pu2SessionBean; - } else { - throw new RuntimeException("No active datasource!"); - } - } - } -} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithBeanProducerTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithBeanProducerTest.java new file mode 100644 index 0000000000000..689532017a678 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/MultiplePUAsAlternativesWithBeanProducerTest.java @@ -0,0 +1,150 @@ +package io.quarkus.hibernate.orm.config.datasource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import org.hibernate.Session; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.hibernate.orm.PersistenceUnit; +import io.quarkus.hibernate.orm.config.namedpu.MyEntity; +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests a use case where multiple PU/datasources are defined at build time, + * but only one is used at runtime. + *

+ * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, + * so we use the h2 db-kind everywhere here to keep test dependencies simpler. + */ +public abstract class MultiplePUAsAlternativesWithBeanProducerTest { + + public static class Pu1ActiveTest extends MultiplePUAsAlternativesWithBeanProducerTest { + @RegisterExtension + static QuarkusUnitTest runner = runner("pu-1", "ds-1"); + + public Pu1ActiveTest() { + super("pu-1", "pu-2"); + } + } + + public static class Pu2ActiveTest extends MultiplePUAsAlternativesWithBeanProducerTest { + @RegisterExtension + static QuarkusUnitTest runner = runner("pu-2", "ds-2"); + + public Pu2ActiveTest() { + super("pu-2", "pu-1"); + } + } + + static QuarkusUnitTest runner(String activePuName, String activeDsName) { + return new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addPackage(MyEntity.class.getPackage().getName()) + .addClass(MyProducer.class)) + .overrideConfigKey("quarkus.hibernate-orm.pu-1.packages", MyEntity.class.getPackageName()) + .overrideConfigKey("quarkus.hibernate-orm.pu-1.datasource", "ds-1") + .overrideConfigKey("quarkus.hibernate-orm.pu-1.database.generation", "drop-and-create") + .overrideConfigKey("quarkus.hibernate-orm.pu-1.active", "false") + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.ds-1.active", "false") + .overrideConfigKey("quarkus.hibernate-orm.pu-2.packages", MyEntity.class.getPackageName()) + .overrideConfigKey("quarkus.hibernate-orm.pu-2.datasource", "ds-2") + .overrideConfigKey("quarkus.hibernate-orm.pu-2.database.generation", "drop-and-create") + .overrideConfigKey("quarkus.hibernate-orm.pu-2.active", "false") + .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.ds-2.active", "false") + // This is where we select the active PU / datasource + .overrideRuntimeConfigKey("quarkus.hibernate-orm." + activePuName + ".active", "true") + .overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".active", "true") + .overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".jdbc.url", "jdbc:h2:mem:testds1"); + } + + private final String activePuName; + private final String inactivePuName; + + protected MultiplePUAsAlternativesWithBeanProducerTest(String activePuName, String inactivePuName) { + this.activePuName = activePuName; + this.inactivePuName = inactivePuName; + } + + @Inject + Session customIndirectSessionBean; + + @Test + public void testExplicitSessionBeanUsable() { + doTestPersistRetrieve(Arc.container() + .select(Session.class, new PersistenceUnit.PersistenceUnitLiteral(activePuName)).get(), + 1L); + } + + @Test + public void testCustomIndirectSessionBeanUsable() { + doTestPersistRetrieve(customIndirectSessionBean, 2L); + } + + @Test + public void testInactiveSessionBeanUnusable() { + QuarkusTransaction.requiringNew().run(() -> { + assertThatThrownBy(() -> Arc.container() + .select(Session.class, new PersistenceUnit.PersistenceUnitLiteral(inactivePuName)).get() + .find(MyEntity.class, 3L)) + .hasMessageContainingAll( + "Cannot retrieve the EntityManagerFactory/SessionFactory for persistence unit " + inactivePuName, + "Hibernate ORM was deactivated through configuration properties"); + }); + } + + private static void doTestPersistRetrieve(Session session, long id) { + QuarkusTransaction.requiringNew().run(() -> { + MyEntity entity = new MyEntity(); + entity.setId(id); + entity.setName("text" + id); + session.persist(entity); + }); + QuarkusTransaction.requiringNew().run(() -> { + MyEntity entity = session.get(MyEntity.class, id); + assertThat(entity.getName()).isEqualTo("text" + id); + }); + } + + private static class MyProducer { + @Inject + @DataSource("ds-1") + InjectableInstance dataSource1Bean; + + @Inject + @DataSource("ds-2") + InjectableInstance dataSource2Bean; + + @Inject + @PersistenceUnit("pu-1") + Session pu1SessionBean; + + @Inject + @PersistenceUnit("pu-2") + Session pu2SessionBean; + + @Produces + @ApplicationScoped + public Session session() { + if (dataSource1Bean.getHandle().getBean().isActive()) { + return pu1SessionBean; + } else if (dataSource2Bean.getHandle().getBean().isActive()) { + return pu2SessionBean; + } else { + throw new RuntimeException("No active datasource!"); + } + } + } +} From 093b8375f29d3076071d0a52eba838b2af27b9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 17 Oct 2024 13:59:34 +0200 Subject: [PATCH 10/10] Improve documentation for multiple-datasource-alternative setups Mention the "getActive()" solution in particular. --- docs/src/main/asciidoc/cdi-integration.adoc | 1 + docs/src/main/asciidoc/datasource.adoc | 43 ++++-- docs/src/main/asciidoc/hibernate-orm.adoc | 32 +++-- ...ourcesAsAlternativesWithGetActiveTest.java | 122 ++++++++++++++++++ 4 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithGetActiveTest.java diff --git a/docs/src/main/asciidoc/cdi-integration.adoc b/docs/src/main/asciidoc/cdi-integration.adoc index a68baa17eeadc..63c09b4572bc1 100644 --- a/docs/src/main/asciidoc/cdi-integration.adoc +++ b/docs/src/main/asciidoc/cdi-integration.adoc @@ -419,6 +419,7 @@ public class TestRecorder { ---- <1> Pass a contextual reference of `Bar` to the constructor of `Foo`. +[[inactive-synthetic-beans]] === Inactive Synthetic Beans In the case when one needs to register multiple synthetic beans at build time but only wants a subset of them active at runtime, it is useful to be able to mark a synthetic bean as _inactive_. diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index 5616316c330cb..85714c94877f6 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -501,24 +501,41 @@ xref:config-reference.adoc#multiple-profiles[setting `quarkus.profile`]: ---- ==== -[TIP] -==== -It can also be useful to define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] redirecting to the currently active datasource, like this: +With such a setup, you will need to take care to only ever access the _active_ datasource. +To do so, you can inject an `InjectableInstance` or `InjectableInstance` with an `@Any` qualifier, and call xref:cdi-integration.adoc#inactive-synthetic-beans[`getActive()`]: + +[source,java] +---- +import io.quarkus.arc.InjectableInstance; +@ApplicationScoped +public class MyConsumer { + @Inject + @Any + InjectableInstance dataSource; + + public void doSomething() { + DataSource activeDataSource = dataSource.getActive(); + // ... + } +} +---- + +Alternatively, you may define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] for the default datasource redirecting to the currently active named datasource, so that it can be injected directly, like this: [source,java,indent=0] ---- public class MyProducer { @Inject @DataSource("pg") - InjectableInstance pgDataSourceBean; // <1> + InjectableInstance pgDataSourceBean; // <1> @Inject @DataSource("oracle") - InjectableInstance oracleDataSourceBean; + InjectableInstance oracleDataSourceBean; @Produces // <2> @ApplicationScoped - public AgroalDataSource dataSource() { + public DataSource dataSource() { if (pgDataSourceBean.getHandle().getBean().isActive()) { // <3> return pgDataSourceBean.get(); } else if (oracleDataSourceBean.getHandle().getBean().isActive()) { // <3> @@ -528,14 +545,24 @@ public class MyProducer { } } } + +@ApplicationScoped +public class MyConsumer { + @Inject + DataSource dataSource; // <4> + + public void doSomething() { + // .. just use the injected datasource ... + } +} ---- <1> Don't inject a `DataSource` or `AgroalDatasource` directly, because that would lead to a failure on startup (can't inject inactive beans). -Instead, inject `InjectableInstance` or `InjectableInstance` +Instead, inject `InjectableInstance` or `InjectableInstance`. <2> Declare a CDI producer method that will define the default datasource as either PostgreSQL or Oracle, depending on what is active. <3> Check whether beans are active before retrieving them. -==== +<4> This will get injected with the (only) active datasource. [[datasource-multiple-single-transaction]] === Use multiple datasources in a single transaction diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index e29f0110abf58..b3f6e69e96c04 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -560,17 +560,15 @@ xref:config-reference.adoc#multiple-profiles[setting `quarkus.profile`]: ---- ==== -[TIP] -==== -It can also be useful to define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] redirecting to the currently active persistence unit, -like this: +With such a setup, you will need to take care to only ever access the _active_ persistence unit. +To do so, you may define a xref:cdi.adoc#ok-you-said-that-there-are-several-kinds-of-beans[CDI bean producer] for the default `Session` redirecting to the currently active named `Session`, so that it can be injected directly, like this: [source,java,indent=0] ---- public class MyProducer { @Inject @DataSource("pg") - InjectableInstance pgDataSourceBean; + InjectableInstance pgDataSourceBean; // <1> @Inject @DataSource("oracle") @@ -584,20 +582,36 @@ public class MyProducer { @PersistenceUnit("oracle") Session oracleSessionBean; - @Produces + @Produces // <2> @ApplicationScoped public Session session() { - if (pgDataSourceBean.getHandle().getBean().isActive()) { + if (pgDataSourceBean.getHandle().getBean().isActive()) { // <3> return pgSessionBean; - } else if (oracleDataSourceBean.getHandle().getBean().isActive()) { + } else if (oracleDataSourceBean.getHandle().getBean().isActive()) { // <3> return oracleSessionBean; } else { throw new RuntimeException("No active datasource!"); } } } + +@ApplicationScoped +public class MyConsumer { + @Inject + Session session; // <4> + + public void doSomething() { + // .. just use the injected session ... + } +} ---- -==== +<1> Don't inject a `DataSource` or `AgroalDatasource` directly, +because that would lead to a failure on startup (can't inject inactive beans). +Instead, inject `InjectableInstance` or `InjectableInstance`. +<2> Declare a CDI producer method that will define the default session +as either PostgreSQL or Oracle, depending on what is active. +<3> Check whether datasource beans are active before retrieving the corresponding session. +<4> This will get injected with the (only) active session. [[persistence-xml]] == Setting up and configuring Hibernate ORM with a `persistence.xml` diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithGetActiveTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithGetActiveTest.java new file mode 100644 index 0000000000000..7e26214bc78ce --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesAsAlternativesWithGetActiveTest.java @@ -0,0 +1,122 @@ +package io.quarkus.agroal.test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests a use case where multiple datasources are defined at build time, + * but only one is used at runtime, + * and Arc's {@code getActive()} method is used to retrieve the active datasource. + *

+ * This is mostly useful when each datasource has a distinct db-kind, but in theory that shouldn't matter, + * so we use the h2 db-kind everywhere here to keep test dependencies simpler. + */ +public abstract class MultipleDataSourcesAsAlternativesWithGetActiveTest { + + public static class Ds1ActiveTest extends MultipleDataSourcesAsAlternativesWithBeanProducerTest { + @RegisterExtension + static QuarkusUnitTest runner = runner("ds-1"); + + public Ds1ActiveTest() { + super("ds-1", "ds-2"); + } + } + + public static class Ds2ActiveTest extends MultipleDataSourcesAsAlternativesWithBeanProducerTest { + @RegisterExtension + static QuarkusUnitTest runner = runner("ds-2"); + + public Ds2ActiveTest() { + super("ds-2", "ds-1"); + } + } + + static QuarkusUnitTest runner(String activeDsName) { + return new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyProducer.class)) + .overrideConfigKey("quarkus.datasource.ds-1.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.ds-1.active", "false") + .overrideConfigKey("quarkus.datasource.ds-2.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.ds-2.active", "false") + // This is where we select the active datasource + .overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".active", "true") + .overrideRuntimeConfigKey("quarkus.datasource." + activeDsName + ".jdbc.url", "jdbc:h2:mem:testds1"); + } + + private final String activeDsName; + private final String inactiveDsName; + + protected MultipleDataSourcesAsAlternativesWithGetActiveTest(String activeDsName, String inactiveDsName) { + this.activeDsName = activeDsName; + this.inactiveDsName = inactiveDsName; + } + + @Inject + @Any + InjectableInstance datasourceInjectableInstance; + + @Test + public void testExplicitDatasourceBeanUsable() { + doTestDatasource(Arc.container() + .select(AgroalDataSource.class, new DataSource.DataSourceLiteral(activeDsName)).get()); + } + + @Test + public void testInjectableInstanceGetActiveBeanUsable() { + doTestDatasource(datasourceInjectableInstance.getActive()); + } + + @Test + public void testInactiveDatasourceBeanUnusable() { + assertThatThrownBy( + () -> Arc.container().select(AgroalDataSource.class, new DataSource.DataSourceLiteral(inactiveDsName)).get() + .getConnection()) + .hasMessageContaining( + "Datasource '" + inactiveDsName + "' was deactivated through configuration properties."); + } + + private static void doTestDatasource(AgroalDataSource dataSource) { + assertThatCode(() -> { + try (var connection = dataSource.getConnection()) { + } + }) + .doesNotThrowAnyException(); + } + + private static class MyProducer { + @Inject + @DataSource("ds-1") + InjectableInstance dataSource1Bean; + + @Inject + @DataSource("ds-2") + InjectableInstance dataSource2Bean; + + @Produces + @ApplicationScoped + public AgroalDataSource dataSource() { + if (dataSource1Bean.getHandle().getBean().isActive()) { + return dataSource1Bean.get(); + } else if (dataSource2Bean.getHandle().getBean().isActive()) { + return dataSource2Bean.get(); + } else { + throw new RuntimeException("No active datasource!"); + } + } + } +}