From c1203d131ba30c707020aaa2c650a43f72e468f8 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 29 Jan 2021 14:20:28 +1100 Subject: [PATCH] DevDB Support DevDB allows for zero configuration databases in dev and test mode, either starting the databases using testcontainers, or creating them in process. --- bom/application/pom.xml | 39 +++ build-parent/pom.xml | 2 - .../runner/bootstrap/AugmentActionImpl.java | 52 ++++ docs/src/main/asciidoc/datasource.adoc | 53 ++++- .../agroal/deployment/AgroalProcessor.java | 24 +- .../MultipleDevDBDataSourcesConfigTest.java | 57 +++++ ...tion-multiple-devdb-datasources.properties | 2 + .../quarkus/agroal/runtime/DataSources.java | 5 +- .../runtime/UnconfiguredDataSource.java | 104 ++++++++ .../spi/DefaultDataSourceDbKindBuildItem.java | 21 -- extensions/datasource/deployment-spi/pom.xml | 30 +++ .../spi/DefaultDataSourceDbKindBuildItem.java | 93 ++++++++ .../deployment/spi/DevDBProvider.java | 44 ++++ .../DevDbConfigurationHandlerBuildItem.java | 109 +++++++++ .../deployment/spi/DevDbResultBuildItem.java | 55 +++++ extensions/datasource/deployment/pom.xml | 4 + .../deployment/DummyForJavadoc.java | 8 - .../deployment/devdb/DevDBProcessor.java | 225 ++++++++++++++++++ .../devdb/DevDBProviderBuildItem.java | 26 ++ extensions/datasource/pom.xml | 1 + .../runtime/DataSourceBuildTimeConfig.java | 6 + .../runtime/DataSourceRuntimeConfig.java | 9 + .../runtime/DevDbBuildTimeConfig.java | 37 +++ extensions/devdb/devdb-derby/pom.xml | 54 +++++ .../deployment/DerbyDevDBProcessor.java | 68 ++++++ extensions/devdb/devdb-h2/pom.xml | 50 ++++ .../deployment/H2DevDBProcessor.java | 61 +++++ extensions/devdb/devdb-mariadb/pom.xml | 54 +++++ .../deployment/MariaDBDebDBProcessor.java | 40 ++++ extensions/devdb/devdb-mysql/pom.xml | 54 +++++ .../deployment/MySQLDevDBProcessor.java | 40 ++++ extensions/devdb/devdb-postgresql/pom.xml | 54 +++++ .../deployment/PostgresqlDevDBProcessor.java | 40 ++++ extensions/devdb/pom.xml | 28 +++ .../orm/EntitiesWithoutDataSourceTest.java | 3 +- .../FastBootHibernatePersistenceProvider.java | 11 +- .../HibernateReactiveProcessor.java | 9 +- extensions/jdbc/jdbc-db2/deployment/pom.xml | 4 + .../jdbc/db2/deployment/JDBCDB2Processor.java | 4 +- extensions/jdbc/jdbc-derby/deployment/pom.xml | 18 ++ .../derby/deployment/JDBCDerbyProcessor.java | 11 + .../DevDBDerbyDatasourceTestCase.java | 51 ++++ extensions/jdbc/jdbc-h2/deployment/pom.xml | 8 + .../jdbc/h2/deployment/JDBCH2Processor.java | 12 + .../jdbc/jdbc-mariadb/deployment/pom.xml | 48 ++++ .../deployment/JDBCMariaDBProcessor.java | 10 +- .../DevDBMariaDBDatasourceTestCase.java | 52 ++++ extensions/jdbc/jdbc-mssql/deployment/pom.xml | 4 + .../jdbc/mssql/deployment/MsSQLProcessor.java | 4 +- extensions/jdbc/jdbc-mysql/deployment/pom.xml | 47 ++++ .../mysql/deployment/JDBCMySQLProcessor.java | 10 +- .../DevDBMySQLDatasourceTestCase.java | 52 ++++ .../jdbc/jdbc-postgresql/deployment/pom.xml | 51 ++++ .../deployment/JDBCPostgreSQLProcessor.java | 10 +- .../DevDBPostgresqlDatasourceTestCase.java | 52 ++++ .../ErroneousConfigHotReloadTestCase.java | 2 +- .../application-commented-out.properties | 1 + extensions/pom.xml | 1 + .../ReactiveDB2ClientProcessor.java | 46 +++- .../reactive-mysql-client/deployment/pom.xml | 4 + .../ReactiveMySQLClientProcessor.java | 54 +++-- .../reactive-pg-client/deployment/pom.xml | 4 + .../deployment/ReactivePgClientProcessor.java | 58 ++++- .../quarkus/bootstrap/app/AugmentAction.java | 3 + .../src/main/resources/application.properties | 2 - .../io/quarkus/it/jpa/h2/TestResources.java | 8 - .../src/main/resources/application.properties | 1 - test-framework/junit5/pom.xml | 4 + .../test/junit/NativeDevDbHandler.java | 27 +++ .../test/junit/NativeTestExtension.java | 106 ++++++++- 70 files changed, 2228 insertions(+), 113 deletions(-) create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDevDBDataSourcesConfigTest.java create mode 100644 extensions/agroal/deployment/src/test/resources/application-multiple-devdb-datasources.properties create mode 100644 extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/UnconfiguredDataSource.java delete mode 100644 extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/DefaultDataSourceDbKindBuildItem.java create mode 100644 extensions/datasource/deployment-spi/pom.xml create mode 100644 extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DefaultDataSourceDbKindBuildItem.java create mode 100644 extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDBProvider.java create mode 100644 extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbConfigurationHandlerBuildItem.java create mode 100644 extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbResultBuildItem.java delete mode 100644 extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DummyForJavadoc.java create mode 100644 extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProcessor.java create mode 100644 extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProviderBuildItem.java create mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DevDbBuildTimeConfig.java create mode 100644 extensions/devdb/devdb-derby/pom.xml create mode 100644 extensions/devdb/devdb-derby/src/main/java/io/quarkus/devdb/postgresql/deployment/DerbyDevDBProcessor.java create mode 100644 extensions/devdb/devdb-h2/pom.xml create mode 100644 extensions/devdb/devdb-h2/src/main/java/io/quarkus/devdb/postgresql/deployment/H2DevDBProcessor.java create mode 100644 extensions/devdb/devdb-mariadb/pom.xml create mode 100644 extensions/devdb/devdb-mariadb/src/main/java/io/quarkus/devdb/postgresql/deployment/MariaDBDebDBProcessor.java create mode 100644 extensions/devdb/devdb-mysql/pom.xml create mode 100644 extensions/devdb/devdb-mysql/src/main/java/io/quarkus/devdb/postgresql/deployment/MySQLDevDBProcessor.java create mode 100644 extensions/devdb/devdb-postgresql/pom.xml create mode 100644 extensions/devdb/devdb-postgresql/src/main/java/io/quarkus/devdb/postgresql/deployment/PostgresqlDevDBProcessor.java create mode 100644 extensions/devdb/pom.xml create mode 100644 extensions/jdbc/jdbc-derby/deployment/src/test/java/io/quarkus/jdbc/derby/deployment/DevDBDerbyDatasourceTestCase.java create mode 100644 extensions/jdbc/jdbc-mariadb/deployment/src/test/java/io/quarkus/jdbc/mariadb/deployment/DevDBMariaDBDatasourceTestCase.java create mode 100644 extensions/jdbc/jdbc-mysql/deployment/src/test/java/io/quarkus/jdbc/mysql/deployment/DevDBMySQLDatasourceTestCase.java create mode 100644 extensions/jdbc/jdbc-postgresql/deployment/src/test/java/io/quarkus/jdbc/postgresql/deployment/DevDBPostgresqlDatasourceTestCase.java delete mode 100644 integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeDevDbHandler.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5a180cc11aba8..b8e8284ac0e4a 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -208,6 +208,7 @@ 1.0.0.Beta1 1.2.0.Final 1.10.0 + 1.15.1 @@ -321,6 +322,14 @@ import + + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import + @@ -506,6 +515,11 @@ quarkus-datasource-common ${project.version} + + io.quarkus + quarkus-datasource-deployment-spi + ${project.version} + io.quarkus quarkus-datasource @@ -516,6 +530,31 @@ quarkus-datasource-deployment ${project.version} + + io.quarkus + quarkus-devdb-postgresql + ${project.version} + + + io.quarkus + quarkus-devdb-h2 + ${project.version} + + + io.quarkus + quarkus-devdb-mysql + ${project.version} + + + io.quarkus + quarkus-devdb-mariadb + ${project.version} + + + io.quarkus + quarkus-devdb-derby + ${project.version} + io.quarkus quarkus-elasticsearch-rest-client-common diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 0920251660292..9eee450da23f1 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -481,14 +481,12 @@ io.rest-assured:* org.assertj:* org.junit.jupiter:* - org.testcontainers:* io.quarkus:quarkus-test-*:*:*:test io.rest-assured:*:*:*:test org.assertj:*:*:*:test org.junit.jupiter:*:*:*:test - org.testcontainers:*:*:*:test Found test dependencies with wrong scope: diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java index b73e99541793b..ffeb9cf08e559 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java @@ -1,9 +1,11 @@ package io.quarkus.runner.bootstrap; import java.io.File; +import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -13,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; @@ -132,6 +135,55 @@ public AugmentActionImpl(CuratedApplication curatedApplication, List[] targets = Arrays.stream(finalOutputs) + .map(new Function>() { + @Override + public Class apply(String s) { + try { + return (Class) Class.forName(s, false, classLoader); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + }).toArray(Class[]::new); + BuildResult result = runAugment(true, Collections.emptySet(), null, classLoader, targets); + + String debugSourcesDir = BootstrapDebug.DEBUG_SOURCES_DIR; + if (debugSourcesDir != null) { + for (GeneratedClassBuildItem i : result.consumeMulti(GeneratedClassBuildItem.class)) { + try { + if (i.getSource() != null) { + File debugPath = new File(debugSourcesDir); + if (!debugPath.exists()) { + debugPath.mkdir(); + } + File sourceFile = new File(debugPath, i.getName() + ".zig"); + sourceFile.getParentFile().mkdirs(); + Files.write(sourceFile.toPath(), i.getSource().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE); + log.infof("Wrote source: %s", sourceFile.getAbsolutePath()); + } else { + log.infof("Source not available: %s", i.getName()); + } + } catch (Exception t) { + log.errorf(t, "Failed to write debug source file: %s", i.getName()); + } + } + } + try { + BiConsumer consumer = (BiConsumer) Class + .forName(resultHandler, false, classLoader) + .getConstructor().newInstance(); + consumer.accept(context, result); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException + | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + @Override public AugmentResult createProductionApplication() { if (launchMode != LaunchMode.NORMAL) { diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index eae0285a3ba90..96bf52901d253 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -40,6 +40,22 @@ please refer to the link:reactive-sql-clients[Reactive SQL clients guide]. This is a quick introduction to datasource configuration. If you want a better understanding of how all this works, this guide has a lot more information in the subsequent paragraphs. +=== Zero Config Setup (DevDB) + +When testing or running in dev mode Quarkus can even provide you with a zero config database out of the box, a feature +we refer to as DevDB. Depending on your database type you may need docker installed in order to use this feature. DevDB +is supported for the following open source databases: + +* Postgresql (container) +* MySQL (container) +* MariaDB (container) +* H2 (in-process) +* Apache Derby (in-process) + +If you want to use DevDB then all you need to do is include the relevant extension for the type of database you want (either reactive or +JDBC, or both), and don't configure a database URL, username and password, Quarkus will provide the database and you can just start +coding without worrying about config. + === JDBC datasource Add the `agroal` extension plus one of `jdbc-db2`, `jdbc-derby`, `jdbc-h2`, `jdbc-mariadb`, `jdbc-mssql`, `jdbc-mysql`, or `jdbc-postgresql`. @@ -48,13 +64,16 @@ Then configure your datasource: [source, properties] ---- -quarkus.datasource.db-kind=postgresql +quarkus.datasource.db-kind=postgresql <1> quarkus.datasource.username= quarkus.datasource.password= quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test quarkus.datasource.jdbc.max-size=16 ---- +<1> If you only have a single JDBC extension, or you are running tests and only have a single test scoped JDBC extension installed then this is +optional. If there is only once possible extension we assume this is the correct one, and if a driver has been added with test scope then +we assume that this should be used in testing. === Reactive datasource @@ -64,20 +83,22 @@ Then configure your reactive datasource: [source, properties] ---- -quarkus.datasource.db-kind=postgresql +quarkus.datasource.db-kind=postgresql<1> quarkus.datasource.username= quarkus.datasource.password= quarkus.datasource.reactive.url=postgresql:///your_database quarkus.datasource.reactive.max-size=20 ---- +<1> As specified above this is optional. == Default datasource A datasource can be either a JDBC datasource, a reactive one or both. It all depends on how you configure it and which extensions you added to your project. -To define a datasource, start with the following: +To define a datasource, start with the following (note that this is only required if you have more than one +database type installed): [source, properties] ---- @@ -382,6 +403,10 @@ quarkus.datasource.inventory.jdbc.max-size=12 Notice there is an extra bit in the key. The syntax is as follows: `quarkus.datasource.[optional name.][datasource property]`. +NOTE: Even when only one database extension is installed named databases need to specify at least one build time +property so that Quarkus knows they exist. Generally this will be the `db-kind` property, although you can also +specify DevDB properties to create named datasources (covered later in this guide). + === Named Datasource Injection When using multiple datasources, each `DataSource` also has the `io.quarkus.agroal.DataSource` qualifier with the name of the datasource as the value. @@ -435,6 +460,28 @@ If the Narayana JTA extension is also available, integration is automatic. You can override this by setting the `transactions` configuration property - see the <> below. +== DevDb (Configuration Free Databases) + +As mentioned above Quarkus supports a feature called DevDB that allows you to create datasources without any config. If +you have a database extension that supports it and not config is provided Quarkus all automatically start a database (either +using Testcontainers, or by starting a Java DB in process), and automatically configure a connection to this database. + +Production databases need to be configured as normal, so if you want to include a production database config in your +application.properties and continue to use DevDB we recommended that you use the `%prod.` profile to define your +database settings. + +=== Configuring DevDB + +DevDB supports the following config options: + +include::{generated-dir}/config/quarkus-datasource-config-group-dev-db-build-time-config.adoc[opts=optional, leveloffset=+1] + +=== Named Datasources + +When using DevDB the default datasource will always be created, but to specify a named datasource you need to have +at least one build time property so Quarkus knows to create the datasource. In general you will usually either specify +the `db-kind` property, or explicitly enable DevDb: `quarkus.datasource."named".devdb=true`. + [[in-memory-databases]] == Testing with in-memory databases 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 87f20d5fe2d22..d953511a950b9 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 @@ -24,13 +24,13 @@ import io.quarkus.agroal.runtime.DataSources; import io.quarkus.agroal.runtime.DataSourcesJdbcBuildTimeConfig; import io.quarkus.agroal.runtime.TransactionIntegration; -import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.processor.DotNames; import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; @@ -47,6 +47,7 @@ import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; @@ -73,7 +74,8 @@ void build( BuildProducer reflectiveClass, BuildProducer resource, BuildProducer sslNativeSupport, - BuildProducer aggregatedConfig) throws Exception { + BuildProducer aggregatedConfig, + CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception { if (dataSourcesBuildTimeConfig.driver.isPresent() || dataSourcesBuildTimeConfig.url.isPresent()) { throw new ConfigurationException( "quarkus.datasource.url and quarkus.datasource.driver have been deprecated in Quarkus 1.3 and removed in 1.9. " @@ -82,7 +84,7 @@ void build( List aggregatedDataSourceBuildTimeConfigs = getAggregatedConfigBuildItems( dataSourcesBuildTimeConfig, - dataSourcesJdbcBuildTimeConfig, + dataSourcesJdbcBuildTimeConfig, curateOutcomeBuildItem, jdbcDriverBuildItems, defaultDbKinds); if (aggregatedDataSourceBuildTimeConfigs.isEmpty()) { @@ -257,13 +259,12 @@ void generateDataSourceBeans(AgroalRecorder recorder, private List getAggregatedConfigBuildItems( DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, DataSourcesJdbcBuildTimeConfig dataSourcesJdbcBuildTimeConfig, + CurateOutcomeBuildItem curateOutcomeBuildItem, List jdbcDriverBuildItems, List defaultDbKinds) { List dataSources = new ArrayList<>(); - Optional effectiveDbKind = dataSourcesBuildTimeConfig.defaultDataSource.dbKind; - if (!effectiveDbKind.isPresent() && (defaultDbKinds.size() == 1)) { - effectiveDbKind = Optional.of(defaultDbKinds.get(0).getDbKind()); - } + Optional effectiveDbKind = DefaultDataSourceDbKindBuildItem + .resolve(dataSourcesBuildTimeConfig.defaultDataSource.dbKind, defaultDbKinds, curateOutcomeBuildItem); if (effectiveDbKind.isPresent()) { if (dataSourcesJdbcBuildTimeConfig.jdbc.enabled) { @@ -282,11 +283,16 @@ private List getAggregatedConfigBu if (!jdbcBuildTimeConfig.enabled) { continue; } + Optional dbKind = DefaultDataSourceDbKindBuildItem + .resolve(entry.getValue().dbKind, defaultDbKinds, curateOutcomeBuildItem); + if (!dbKind.isPresent()) { + continue; + } dataSources.add(new AggregatedDataSourceBuildTimeConfigBuildItem(entry.getKey(), entry.getValue(), jdbcBuildTimeConfig, - entry.getValue().dbKind.get(), - resolveDriver(entry.getKey(), entry.getValue().dbKind.get(), jdbcBuildTimeConfig, jdbcDriverBuildItems))); + dbKind.get(), + resolveDriver(entry.getKey(), dbKind.get(), jdbcBuildTimeConfig, jdbcDriverBuildItems))); } return dataSources; diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDevDBDataSourcesConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDevDBDataSourcesConfigTest.java new file mode 100644 index 0000000000000..68876c85baad0 --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDevDBDataSourcesConfigTest.java @@ -0,0 +1,57 @@ +package io.quarkus.agroal.test; + +import static io.quarkus.agroal.test.MultipleDataSourcesTestUtil.testDataSource; + +import java.sql.SQLException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.agroal.DataSource; +import io.quarkus.test.QuarkusUnitTest; + +public class MultipleDevDBDataSourcesConfigTest { + + //tag::injection[] + @Inject + AgroalDataSource defaultDataSource; + + @Inject + @DataSource("users") + AgroalDataSource dataSource1; + + @Inject + @DataSource("inventory") + AgroalDataSource dataSource2; + //end::injection[] + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(MultipleDataSourcesTestUtil.class)) + .withConfigurationResource("application-multiple-devdb-datasources.properties"); + + @Test + public void testDataSourceInjection() throws SQLException { + testDataSource("default", defaultDataSource, + "jdbc:h2:tcp://localhost:" + extractPort(defaultDataSource) + "/mem:default;DB_CLOSE_DELAY=-1", "sa", 20); + testDataSource("users", dataSource1, + "jdbc:h2:tcp://localhost:" + extractPort(dataSource1) + "/mem:users;DB_CLOSE_DELAY=-1", "sa", 20); + testDataSource("inventory", dataSource2, + "jdbc:h2:tcp://localhost:" + extractPort(dataSource2) + "/mem:inventory;DB_CLOSE_DELAY=-1", "sa", 20); + } + + public int extractPort(AgroalDataSource ds) { + String url = ds.getConfiguration().connectionPoolConfiguration().connectionFactoryConfiguration().jdbcUrl(); + Matcher matcher = Pattern.compile("jdbc:h2:tcp://localhost:(\\d+)/").matcher(url); + matcher.find(); + return Integer.parseInt(matcher.group(1)); + } +} diff --git a/extensions/agroal/deployment/src/test/resources/application-multiple-devdb-datasources.properties b/extensions/agroal/deployment/src/test/resources/application-multiple-devdb-datasources.properties new file mode 100644 index 0000000000000..cda50210c4014 --- /dev/null +++ b/extensions/agroal/deployment/src/test/resources/application-multiple-devdb-datasources.properties @@ -0,0 +1,2 @@ +quarkus.datasource.users.devdb=true +quarkus.datasource.inventory.devdb=true 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 b5d8973b95c1d..411f2aed72677 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 @@ -47,7 +47,6 @@ import io.quarkus.datasource.runtime.DataSourceRuntimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; -import io.quarkus.runtime.configuration.ConfigurationException; /** * This class is sort of a producer for {@link AgroalDataSource}. @@ -133,7 +132,9 @@ public AgroalDataSource doCreateDataSource(String dataSourceName) { } else { errorMessage = "quarkus.datasource." + dataSourceName + ".jdbc.url has not been defined"; } - throw new ConfigurationException(errorMessage); + //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(errorMessage); } // 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 new file mode 100644 index 0000000000000..0e30f2a092dff --- /dev/null +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/UnconfiguredDataSource.java @@ -0,0 +1,104 @@ +package io.quarkus.agroal.runtime; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.AgroalDataSourceMetrics; +import io.agroal.api.AgroalPoolInterceptor; +import io.agroal.api.configuration.AgroalDataSourceConfiguration; +import io.quarkus.runtime.configuration.ConfigurationException; + +public class UnconfiguredDataSource implements AgroalDataSource { + + private final String errorMessage; + + public UnconfiguredDataSource(String errorMessage) { + this.errorMessage = errorMessage; + } + + public void throwException() { + throw new ConfigurationException(errorMessage); + } + + @Override + public AgroalDataSourceConfiguration getConfiguration() { + throw new ConfigurationException(errorMessage); + } + + @Override + public AgroalDataSourceMetrics getMetrics() { + throw new ConfigurationException(errorMessage); + } + + @Override + public void flush(FlushMode mode) { + throw new ConfigurationException(errorMessage); + } + + @Override + public void setPoolInterceptors(Collection interceptors) { + //noop + } + + @Override + public List getPoolInterceptors() { + return Collections.emptyList(); + } + + @Override + public void close() { + + } + + @Override + public Connection getConnection() throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public int getLoginTimeout() throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new ConfigurationException(errorMessage); + } + + @Override + public T unwrap(Class iface) throws SQLException { + throw new ConfigurationException(errorMessage); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } +} diff --git a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/DefaultDataSourceDbKindBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/DefaultDataSourceDbKindBuildItem.java deleted file mode 100644 index edae0ec659f9c..0000000000000 --- a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/DefaultDataSourceDbKindBuildItem.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.quarkus.agroal.spi; - -import io.quarkus.builder.item.MultiBuildItem; - -/** - * A build item that represents the "quarkus.datasource.db-kind" value. - * This is generated by specific extensions that are meant to take away the burden of - * configuring anything datasource related from the user. - */ -public final class DefaultDataSourceDbKindBuildItem extends MultiBuildItem { - - private final String dbKind; - - public DefaultDataSourceDbKindBuildItem(String dbKind) { - this.dbKind = dbKind; - } - - public String getDbKind() { - return dbKind; - } -} diff --git a/extensions/datasource/deployment-spi/pom.xml b/extensions/datasource/deployment-spi/pom.xml new file mode 100644 index 0000000000000..c5fe1bd7c7c4d --- /dev/null +++ b/extensions/datasource/deployment-spi/pom.xml @@ -0,0 +1,30 @@ + + + + quarkus-datasource-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-datasource-deployment-spi + Quarkus - Datasource - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-datasource-common + + + + diff --git a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DefaultDataSourceDbKindBuildItem.java b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DefaultDataSourceDbKindBuildItem.java new file mode 100644 index 0000000000000..52a25ccdb2cd0 --- /dev/null +++ b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DefaultDataSourceDbKindBuildItem.java @@ -0,0 +1,93 @@ +package io.quarkus.datasource.deployment.spi; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.deployment.util.ArtifactInfoUtil; + +/** + * A build item that represents the "quarkus.datasource.db-kind" value. + * This is generated by specific extensions that are meant to take away the burden of + * configuring anything datasource related from the user. + */ +public final class DefaultDataSourceDbKindBuildItem extends MultiBuildItem { + + public static final String TEST = "test"; + private final String dbKind; + private final Class callerClass; + private volatile String scope; + + public DefaultDataSourceDbKindBuildItem(String dbKind) { + this.dbKind = dbKind; + String callerClassName = new RuntimeException().getStackTrace()[1].getClassName(); + try { + callerClass = Thread.currentThread().getContextClassLoader().loadClass(callerClassName); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public String getDbKind() { + return dbKind; + } + + public String getScope(CurateOutcomeBuildItem curateOutcomeBuildItem) { + if (scope == null) { + Map.Entry artifact = ArtifactInfoUtil.groupIdAndArtifactId(callerClass, curateOutcomeBuildItem); + for (AppDependency i : curateOutcomeBuildItem.getEffectiveModel().getFullDeploymentDeps()) { + if (i.getArtifact().getArtifactId().equals(artifact.getValue()) + && i.getArtifact().getGroupId().equals(artifact.getKey())) { + scope = i.getScope(); + break; + } + } + if (scope == null) { + throw new RuntimeException("Could not determine scope for " + dbKind); + } + } + return scope; + } + + public static Optional resolve(Optional configured, List defaultDbKinds, + CurateOutcomeBuildItem curateOutcomeBuildItem) { + if (configured.isPresent()) { + return Optional.of(DatabaseKind.normalize(configured.get())); + } + return resolve(defaultDbKinds, curateOutcomeBuildItem); + } + + /** + * Attempts to resolve the default DB kind for the case where none has been specified. + */ + public static Optional resolve(List defaultDbKinds, + CurateOutcomeBuildItem curateOutcomeBuildItem) { + if (defaultDbKinds.isEmpty()) { + return Optional.empty(); + } else if (defaultDbKinds.size() == 1) { + return Optional.of(defaultDbKinds.get(0).dbKind); + } else { + //if we have one and only one test scoped driver we assume it is the default + //if is commmon to use a different DB such as H2 in tests + DefaultDataSourceDbKindBuildItem testScopedDriver = null; + for (DefaultDataSourceDbKindBuildItem i : defaultDbKinds) { + if (i.getScope(curateOutcomeBuildItem).equals(TEST)) { + if (testScopedDriver == null) { + testScopedDriver = i; + } else { + return Optional.empty(); + } + } + } + if (testScopedDriver == null) { + return Optional.empty(); + } else { + return Optional.of(testScopedDriver.dbKind); + } + } + } +} diff --git a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDBProvider.java b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDBProvider.java new file mode 100644 index 0000000000000..092b2da83da63 --- /dev/null +++ b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDBProvider.java @@ -0,0 +1,44 @@ +package io.quarkus.datasource.deployment.spi; + +import java.io.Closeable; +import java.util.Map; +import java.util.Optional; + +public interface DevDBProvider { + + RunningDevDb startDatabase(Optional username, Optional password, Optional datasourceName, + Optional imageName, Map additionalProperties); + + class RunningDevDb { + + private final String url; + private final String username; + private final String password; + private final Closeable closeTask; + + public RunningDevDb(String url, String username, String password, Closeable closeTask) { + this.url = url; + this.username = username; + this.password = password; + this.closeTask = closeTask; + } + + public String getUrl() { + return url; + } + + public Closeable getCloseTask() { + return closeTask; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + } + +} diff --git a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbConfigurationHandlerBuildItem.java b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbConfigurationHandlerBuildItem.java new file mode 100644 index 0000000000000..8db5ff9ce10b2 --- /dev/null +++ b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbConfigurationHandlerBuildItem.java @@ -0,0 +1,109 @@ +package io.quarkus.datasource.deployment.spi; + +import java.util.Collections; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A handler that can map an automatic datasource to the relevant config properties. + */ +public final class DevDbConfigurationHandlerBuildItem extends MultiBuildItem { + + /** + * The type of database this is for + */ + private final String dbKind; + /** + * The function that provides the runtime config given a running DevDB database + */ + private final BiFunction> configProviderFunction; + + /** + * Function that checks if a given datasource has been configured. If it has been configured generally the + * DevDB will not be started. + */ + private final Predicate checkConfiguredFunction; + + public DevDbConfigurationHandlerBuildItem(String dbKind, + BiFunction> configProviderFunction, + Predicate checkConfiguredFunction) { + this.dbKind = dbKind; + this.configProviderFunction = configProviderFunction; + this.checkConfiguredFunction = checkConfiguredFunction; + } + + public BiFunction> getConfigProviderFunction() { + return configProviderFunction; + } + + public String getDbKind() { + return dbKind; + } + + public Predicate getCheckConfiguredFunction() { + return checkConfiguredFunction; + } + + public static DevDbConfigurationHandlerBuildItem jdbc(String dbKind) { + return new DevDbConfigurationHandlerBuildItem(dbKind, + new BiFunction>() { + @Override + public Map apply(String dsName, DevDBProvider.RunningDevDb runningDevDb) { + if (dsName == null) { + return Collections.singletonMap("quarkus.datasource.jdbc.url", runningDevDb.getUrl()); + } else { + return Collections.singletonMap("quarkus.datasource.\"" + dsName + "\".jdbc.url", + runningDevDb.getUrl()); + } + } + }, new Predicate() { + @Override + public boolean test(String dsName) { + if (dsName == null) { + return ConfigProvider.getConfig().getOptionalValue("quarkus.datasource.jdbc.url", String.class) + .isPresent(); + } else { + return ConfigProvider.getConfig() + .getOptionalValue("quarkus.datasource.\"" + dsName + "\".jdbc.url", String.class) + .isPresent() || + ConfigProvider.getConfig() + .getOptionalValue("quarkus.datasource." + dsName + ".jdbc.url", String.class) + .isPresent(); + } + } + }); + } + + public static DevDbConfigurationHandlerBuildItem reactive(String dbKind) { + return new DevDbConfigurationHandlerBuildItem(dbKind, + new BiFunction>() { + @Override + public Map apply(String dsName, DevDBProvider.RunningDevDb runningDevDb) { + if (dsName == null) { + return Collections.singletonMap("quarkus.datasource.reactive.url", + runningDevDb.getUrl().replaceFirst("jdbc:", "vertx-reactive:")); + } else { + return Collections.singletonMap("quarkus.datasource.\"" + dsName + "\".reactive.url", + runningDevDb.getUrl().replaceFirst("jdbc:", "vertx-reactive:")); + } + } + }, new Predicate() { + @Override + public boolean test(String dsName) { + if (dsName == null) { + return ConfigProvider.getConfig().getOptionalValue("quarkus.datasource.reactive.url", String.class) + .isPresent(); + } else { + return ConfigProvider.getConfig() + .getOptionalValue("quarkus.datasource.\"" + dsName + "\".reactive.url", String.class) + .isPresent(); + } + } + }); + } +} diff --git a/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbResultBuildItem.java b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbResultBuildItem.java new file mode 100644 index 0000000000000..6fe293865722f --- /dev/null +++ b/extensions/datasource/deployment-spi/src/main/java/io/quarkus/datasource/deployment/spi/DevDbResultBuildItem.java @@ -0,0 +1,55 @@ +package io.quarkus.datasource.deployment.spi; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.datasource.common.runtime.DataSourceUtil; + +public final class DevDbResultBuildItem extends SimpleBuildItem { + + final DbResult defaultDatasource; + final Map namedDatasources; + + public DevDbResultBuildItem(DbResult defaultDatasource, Map namedDatasources) { + this.defaultDatasource = defaultDatasource; + this.namedDatasources = Collections.unmodifiableMap(namedDatasources); + } + + public DbResult getDefaultDatasource() { + return defaultDatasource; + } + + public Map getNamedDatasources() { + return namedDatasources; + } + + public static DbResult resolve(Optional devDbResultBuildItem, String dataSourceName) { + if (devDbResultBuildItem.isPresent()) { + if (dataSourceName.equals(DataSourceUtil.DEFAULT_DATASOURCE_NAME)) { + return devDbResultBuildItem.get().defaultDatasource; + } + return devDbResultBuildItem.get().namedDatasources.get(dataSourceName); + } + return null; + } + + public static class DbResult { + final String dbType; + final Map configProperties; + + public DbResult(String dbType, Map configProperties) { + this.dbType = dbType; + this.configProperties = Collections.unmodifiableMap(configProperties); + } + + public String getDbType() { + return dbType; + } + + public Map getConfigProperties() { + return configProperties; + } + } +} diff --git a/extensions/datasource/deployment/pom.xml b/extensions/datasource/deployment/pom.xml index 4036672d29110..5306f76c8e6b3 100644 --- a/extensions/datasource/deployment/pom.xml +++ b/extensions/datasource/deployment/pom.xml @@ -25,6 +25,10 @@ io.quarkus quarkus-datasource + + io.quarkus + quarkus-datasource-deployment-spi + diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DummyForJavadoc.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DummyForJavadoc.java deleted file mode 100644 index 02399e135310f..0000000000000 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/DummyForJavadoc.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.quarkus.datasource.deployment; - -/** - * Quick workaround to have at least one public class and generate a Javadoc jar. - */ -public class DummyForJavadoc { - -} diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProcessor.java new file mode 100644 index 0000000000000..55b0cd8c69b54 --- /dev/null +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProcessor.java @@ -0,0 +1,225 @@ +package io.quarkus.datasource.deployment.devdb; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.logging.Logger; + +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbResultBuildItem; +import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; +import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; +import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.runtime.LaunchMode; + +public class DevDBProcessor { + + private static final Logger log = Logger.getLogger(DevDBProcessor.class); + + static volatile List databases; + + static volatile Map cachedProperties; + + static volatile boolean first = true; + + @BuildStep(onlyIfNot = IsNormal.class) + DevDbResultBuildItem launchDatabases(CurateOutcomeBuildItem curateOutcomeBuildItem, + List installedDrivers, + List devDBProviders, + DataSourcesBuildTimeConfig dataSourceBuildTimeConfig, + LaunchModeBuildItem launchMode, + BuildProducer runTimeConfigurationDefaultBuildItemBuildProducer, + List configurationHandlerBuildItems, + BuildProducer serviceStartBuildItemBuildProducer) { + //figure out if we need to shut down and restart existing databases + //if not and the DB's have already started we just return + if (databases != null) { + boolean restartRequired = launchMode.getLaunchMode() == LaunchMode.TEST; + if (!restartRequired) { + for (Map.Entry i : cachedProperties.entrySet()) { + if (!Objects.equals(i.getValue(), + ConfigProvider.getConfig().getOptionalValue(i.getKey(), String.class).orElse(null))) { + restartRequired = true; + break; + } + } + } + if (!restartRequired) { + return null; + } + for (Closeable i : databases) { + try { + i.close(); + } catch (Throwable e) { + log.error("Failed to stop database", e); + } + } + databases = null; + cachedProperties = null; + } + DevDbResultBuildItem.DbResult defaultResult; + Map namedResults = new HashMap<>(); + //now we need to figure out if we need to launch some databases + //note that because we run in dev and test mode only we know the runtime + //config at build time, as they both execute in the same JVM + + //to keep things simpler for now we are only going to support this for the default datasource + //support for named datasources will come later + + Map propertiesMap = new HashMap<>(); + List closeableList = new ArrayList<>(); + Map> configHandlersByDbType = configurationHandlerBuildItems.stream() + .collect(Collectors.toMap(DevDbConfigurationHandlerBuildItem::getDbKind, Collections::singletonList, + (configurationHandlerBuildItems1, configurationHandlerBuildItems2) -> { + List ret = new ArrayList<>(); + ret.addAll(configurationHandlerBuildItems1); + ret.addAll(configurationHandlerBuildItems2); + return ret; + })); + Map devDBProviderMap = devDBProviders.stream() + .collect(Collectors.toMap(DevDBProviderBuildItem::getDatabase, DevDBProviderBuildItem::getDevDBProvider)); + + defaultResult = startDevDb(null, curateOutcomeBuildItem, installedDrivers, devDBProviderMap, + dataSourceBuildTimeConfig.defaultDataSource, + configHandlersByDbType, propertiesMap, closeableList); + if (defaultResult != null) { + for (Map.Entry i : defaultResult.getConfigProperties().entrySet()) { + runTimeConfigurationDefaultBuildItemBuildProducer + .produce(new RunTimeConfigurationDefaultBuildItem(i.getKey(), i.getValue())); + } + } + for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) { + DevDbResultBuildItem.DbResult result = startDevDb(entry.getKey(), curateOutcomeBuildItem, installedDrivers, + devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, closeableList); + if (result != null) { + namedResults.put(entry.getKey(), result); + for (Map.Entry i : result.getConfigProperties().entrySet()) { + runTimeConfigurationDefaultBuildItemBuildProducer + .produce(new RunTimeConfigurationDefaultBuildItem(i.getKey(), i.getValue())); + } + } + } + + if (first) { + first = false; + Runnable closeTask = new Runnable() { + @Override + public void run() { + if (databases != null) { + for (Closeable i : databases) { + try { + i.close(); + } catch (Throwable t) { + log.error("Failed to stop database", t); + } + } + } + first = true; + databases = null; + cachedProperties = null; + } + }; + QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); + ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask); + Thread closeHookThread = new Thread(closeTask, "Database shutdown thread"); + Runtime.getRuntime().addShutdownHook(closeHookThread); + ((QuarkusClassLoader) cl.parent()).addCloseTask(new Runnable() { + @Override + public void run() { + Runtime.getRuntime().removeShutdownHook(closeHookThread); + } + }); + } + databases = closeableList; + cachedProperties = propertiesMap; + return new DevDbResultBuildItem(defaultResult, namedResults); + } + + private DevDbResultBuildItem.DbResult startDevDb(String dbName, CurateOutcomeBuildItem curateOutcomeBuildItem, + List installedDrivers, + Map devDBProviders, DataSourceBuildTimeConfig dataSourceBuildTimeConfig, + Map> configurationHandlerBuildItems, + Map propertiesMap, List closeableList) { + Optional defaultDbKind = DefaultDataSourceDbKindBuildItem.resolve( + dataSourceBuildTimeConfig.dbKind, + installedDrivers, curateOutcomeBuildItem); + + if (!defaultDbKind.isPresent()) { + //nothing we can do + log.warn("Unable to determine a database type for " + (dbName == null ? "default datasource" : dbName)); + return null; + } + DevDBProvider devDbProvider = devDBProviders.get(defaultDbKind.get()); + List configHandlers = configurationHandlerBuildItems.get(defaultDbKind.get()); + if (devDbProvider == null || configHandlers == null) { + log.warn("Unable to start devdb for " + (dbName == null ? "default datasource" : dbName) + + " as this datasource type (" + defaultDbKind.get() + ") does not support devdb"); + return null; + } + + Optional enabled = dataSourceBuildTimeConfig.devdb.enabled; + if (enabled.isPresent()) { + if (!enabled.get()) { + //explicitly disabled + log.debug("Not starting devdb for " + (dbName == null ? "default datasource" : dbName) + + " as it has been disabled in the config"); + return null; + } + } else { + for (DevDbConfigurationHandlerBuildItem i : configHandlers) { + if (i.getCheckConfiguredFunction().test(dbName)) { + //this database has explicit configuration + //we don't start the devdb + log.debug("Not starting devdb for " + (dbName == null ? "default datasource" : dbName) + + " as it has explicit configuration"); + return null; + } + } + } + //ok, so we know we need to start one + + String prefix = "quarkus.datasource."; + if (dbName != null) { + prefix = prefix + dbName + "."; + } + + DevDBProvider.RunningDevDb datasource = devDbProvider + .startDatabase(ConfigProvider.getConfig().getOptionalValue(prefix + "username", String.class), + ConfigProvider.getConfig().getOptionalValue(prefix + "password", String.class), + Optional.ofNullable(dbName), dataSourceBuildTimeConfig.devdb.imageName, + dataSourceBuildTimeConfig.devdb.properties); + closeableList.add(datasource.getCloseTask()); + + Map devDebProperties = new HashMap<>(); + propertiesMap.put(prefix + "db-kind", dataSourceBuildTimeConfig.dbKind.orElse(null)); + for (DevDbConfigurationHandlerBuildItem devDbConfigurationHandlerBuildItem : configHandlers) { + devDebProperties.putAll(devDbConfigurationHandlerBuildItem.getConfigProviderFunction() + .apply(dbName, datasource)); + } + devDebProperties.put(prefix + "db-kind", defaultDbKind.get()); + if (datasource.getUsername() != null) { + devDebProperties.put(prefix + "username", datasource.getUsername()); + } + if (datasource.getPassword() != null) { + devDebProperties.put(prefix + "password", datasource.getPassword()); + } + return new DevDbResultBuildItem.DbResult(defaultDbKind.get(), devDebProperties); + } +} diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProviderBuildItem.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProviderBuildItem.java new file mode 100644 index 0000000000000..8e55797fe8d84 --- /dev/null +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devdb/DevDBProviderBuildItem.java @@ -0,0 +1,26 @@ +package io.quarkus.datasource.deployment.devdb; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; + +/** + * A provider that knows how to start a database of a specific type. + */ +public final class DevDBProviderBuildItem extends MultiBuildItem { + + private final String database; + private final DevDBProvider devDBProvider; + + public DevDBProviderBuildItem(String database, DevDBProvider devDBProvider) { + this.database = database; + this.devDBProvider = devDBProvider; + } + + public String getDatabase() { + return database; + } + + public DevDBProvider getDevDBProvider() { + return devDBProvider; + } +} diff --git a/extensions/datasource/pom.xml b/extensions/datasource/pom.xml index f4b99a7a33b0d..3831e40d41a48 100644 --- a/extensions/datasource/pom.xml +++ b/extensions/datasource/pom.xml @@ -17,5 +17,6 @@ common deployment runtime + deployment-spi diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceBuildTimeConfig.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceBuildTimeConfig.java index af5286e0d65f2..49ecb2bacdbe7 100644 --- a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceBuildTimeConfig.java +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DataSourceBuildTimeConfig.java @@ -16,4 +16,10 @@ public class DataSourceBuildTimeConfig { @ConvertWith(DatabaseKindConverter.class) public Optional dbKind = Optional.empty(); + /** + * Configuration for DevDB. DevDB allows Quarkus to automatically start a database in dev and test mode. + */ + @ConfigItem + public DevDbBuildTimeConfig devdb; + } 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 82c9f351e8ce0..843523b3b0849 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 @@ -36,4 +36,13 @@ public class DataSourceRuntimeConfig { */ @ConfigItem public Optional credentialsProviderName = Optional.empty(); + + /** + * If this is true then when running in dev or test mode Quarkus will attempt to start a testcontainers based + * database with these provided settings. + * + * This is not supported for all databases, and will not work in production. + */ + @ConfigItem(defaultValue = "false") + public boolean startContainer; } diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DevDbBuildTimeConfig.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DevDbBuildTimeConfig.java new file mode 100644 index 0000000000000..90db447d88997 --- /dev/null +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DevDbBuildTimeConfig.java @@ -0,0 +1,37 @@ +package io.quarkus.datasource.runtime; + +import java.util.Map; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigGroup +public class DevDbBuildTimeConfig { + + /** + * If DevDb has been explicitly enabled or disabled. DevDB is generally enabled + * by default, unless there is an existing configuration present. + * + * When DevDB is enabled Quarkus will attempt to automatically configure and start + * a database when running in Dev or Test mode. + */ + @ConfigItem(name = ConfigItem.PARENT) + public Optional enabled = Optional.empty(); + + /** + * The container image name to use, for container based DevDB providers. + * + * If the provider is not container based (e.g. a H2 Database) then this has no effect. + */ + @ConfigItem + public Optional imageName; + + /** + * Generic properties that are passed into the DevDB database provider. These properties are provider + * speficic. + */ + @ConfigItem + public Map properties; + +} diff --git a/extensions/devdb/devdb-derby/pom.xml b/extensions/devdb/devdb-derby/pom.xml new file mode 100644 index 0000000000000..2f1640a200dd9 --- /dev/null +++ b/extensions/devdb/devdb-derby/pom.xml @@ -0,0 +1,54 @@ + + + + quarkus-devdb-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devdb-derby + Quarkus - DevDB - Derby + + + io.quarkus + quarkus-datasource-deployment + + + org.apache.derby + derbynet + + + org.apache.derby + derbytools + + + org.junit.jupiter + junit-jupiter + test + + + org.jboss.logmanager + jboss-logmanager-embedded + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devdb/devdb-derby/src/main/java/io/quarkus/devdb/postgresql/deployment/DerbyDevDBProcessor.java b/extensions/devdb/devdb-derby/src/main/java/io/quarkus/devdb/postgresql/deployment/DerbyDevDBProcessor.java new file mode 100644 index 0000000000000..beb0b43f0b4ff --- /dev/null +++ b/extensions/devdb/devdb-derby/src/main/java/io/quarkus/devdb/postgresql/deployment/DerbyDevDBProcessor.java @@ -0,0 +1,68 @@ +package io.quarkus.devdb.postgresql.deployment; + +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Map; +import java.util.Optional; + +import org.apache.derby.drda.NetworkServerControl; + +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.devdb.DevDBProviderBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; +import io.quarkus.deployment.annotations.BuildStep; + +public class DerbyDevDBProcessor { + + static final int NUMBER_OF_PINGS = 10; + static final int SLEEP_BETWEEN_PINGS = 500; + + @BuildStep + DevDBProviderBuildItem setupDerby() { + return new DevDBProviderBuildItem(DatabaseKind.DERBY, new DevDBProvider() { + @Override + public RunningDevDb startDatabase(Optional username, Optional password, + Optional datasourceName, Optional imageName, Map additionalProperties) { + try { + NetworkServerControl server = new NetworkServerControl(); + server.start(new PrintWriter(System.out)); + for (int i = 1; i <= NUMBER_OF_PINGS; i++) { + try { + System.out.println("[INFO] Attempt " + i + " to see if Derby Network server started"); + server.ping(); + break; + } catch (Exception ex) { + if (i == NUMBER_OF_PINGS) { + System.out.println("Derby Network server failed to start"); + ex.printStackTrace(); + throw ex; + } + try { + Thread.sleep(SLEEP_BETWEEN_PINGS); + } catch (InterruptedException ignore) { + } + } + } + return new RunningDevDb( + "jdbc:derby://localhost:1527/memory:" + datasourceName.orElse("quarkus") + ";create=true", null, + null, + new Closeable() { + @Override + public void close() throws IOException { + try { + NetworkServerControl server = new NetworkServerControl(); + server.shutdown(); + System.out.println("[INFO] Derby database was shut down"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } catch (Exception throwable) { + throw new RuntimeException(throwable); + } + } + }); + } +} diff --git a/extensions/devdb/devdb-h2/pom.xml b/extensions/devdb/devdb-h2/pom.xml new file mode 100644 index 0000000000000..6f03a709a75ff --- /dev/null +++ b/extensions/devdb/devdb-h2/pom.xml @@ -0,0 +1,50 @@ + + + + quarkus-devdb-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devdb-h2 + Quarkus - DevDB - H2 + + + io.quarkus + quarkus-datasource-deployment + + + com.h2database + h2 + + + org.junit.jupiter + junit-jupiter + test + + + org.jboss.logmanager + jboss-logmanager-embedded + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devdb/devdb-h2/src/main/java/io/quarkus/devdb/postgresql/deployment/H2DevDBProcessor.java b/extensions/devdb/devdb-h2/src/main/java/io/quarkus/devdb/postgresql/deployment/H2DevDBProcessor.java new file mode 100644 index 0000000000000..bbb9dbec846b0 --- /dev/null +++ b/extensions/devdb/devdb-h2/src/main/java/io/quarkus/devdb/postgresql/deployment/H2DevDBProcessor.java @@ -0,0 +1,61 @@ +package io.quarkus.devdb.postgresql.deployment; + +import java.io.Closeable; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.Optional; + +import org.h2.tools.Server; + +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.devdb.DevDBProviderBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; +import io.quarkus.deployment.annotations.BuildStep; + +public class H2DevDBProcessor { + + @BuildStep + DevDBProviderBuildItem setupH2() { + return new DevDBProviderBuildItem(DatabaseKind.H2, new DevDBProvider() { + @Override + public RunningDevDb startDatabase(Optional username, Optional password, + Optional datasourceName, Optional imageName, Map additionalProperties) { + try { + final Server tcpServer = Server.createTcpServer("-tcpPort", "0"); + tcpServer.start(); + System.out + .println("[INFO] H2 database started in TCP server mode; server status: " + tcpServer.getStatus()); + String connectionUrl = "jdbc:h2:tcp://localhost:" + tcpServer.getPort() + "/mem:" + + datasourceName.orElse("default") + + ";DB_CLOSE_DELAY=-1"; + return new RunningDevDb( + connectionUrl, + "sa", + "sa", + new Closeable() { + @Override + public void close() throws IOException { + //make sure the DB is removed on close + try (Connection connection = DriverManager.getConnection(connectionUrl, "sa", "sa")) { + try (Statement statement = connection.createStatement()) { + statement.execute("SET DB_CLOSE_DELAY 0"); + } + } catch (SQLException t) { + t.printStackTrace(); + } + tcpServer.stop(); + System.out.println( + "[INFO] H2 database was shut down; server status: " + tcpServer.getStatus()); + } + }); + } catch (SQLException throwables) { + throw new RuntimeException(throwables); + } + } + }); + } +} diff --git a/extensions/devdb/devdb-mariadb/pom.xml b/extensions/devdb/devdb-mariadb/pom.xml new file mode 100644 index 0000000000000..3d48d23855f4c --- /dev/null +++ b/extensions/devdb/devdb-mariadb/pom.xml @@ -0,0 +1,54 @@ + + + + quarkus-devdb-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devdb-mariadb + Quarkus - DevDB - MariaDB + + + io.quarkus + quarkus-datasource-deployment + + + org.testcontainers + mariadb + + + org.mariadb.jdbc + mariadb-java-client + + + org.junit.jupiter + junit-jupiter + test + + + org.jboss.logmanager + jboss-logmanager-embedded + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devdb/devdb-mariadb/src/main/java/io/quarkus/devdb/postgresql/deployment/MariaDBDebDBProcessor.java b/extensions/devdb/devdb-mariadb/src/main/java/io/quarkus/devdb/postgresql/deployment/MariaDBDebDBProcessor.java new file mode 100644 index 0000000000000..9eee4e75d578c --- /dev/null +++ b/extensions/devdb/devdb-mariadb/src/main/java/io/quarkus/devdb/postgresql/deployment/MariaDBDebDBProcessor.java @@ -0,0 +1,40 @@ +package io.quarkus.devdb.postgresql.deployment; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +import org.testcontainers.containers.MariaDBContainer; + +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.devdb.DevDBProviderBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; +import io.quarkus.deployment.annotations.BuildStep; + +public class MariaDBDebDBProcessor { + + @BuildStep + DevDBProviderBuildItem setupMariaDB() { + return new DevDBProviderBuildItem(DatabaseKind.MARIADB, new DevDBProvider() { + @Override + public RunningDevDb startDatabase(Optional username, Optional password, + Optional datasourceName, Optional imageName, Map additionalProperties) { + MariaDBContainer container = new MariaDBContainer( + imageName.orElse(MariaDBContainer.IMAGE + ":" + MariaDBContainer.DEFAULT_TAG)) + .withPassword(password.orElse("quarkus")) + .withUsername(username.orElse("quarkus")) + .withDatabaseName(datasourceName.orElse("default")); + container.start(); + return new RunningDevDb(container.getJdbcUrl(), container.getUsername(), container.getPassword(), + new Closeable() { + @Override + public void close() throws IOException { + container.stop(); + } + }); + } + }); + } + +} diff --git a/extensions/devdb/devdb-mysql/pom.xml b/extensions/devdb/devdb-mysql/pom.xml new file mode 100644 index 0000000000000..dc33e60a34029 --- /dev/null +++ b/extensions/devdb/devdb-mysql/pom.xml @@ -0,0 +1,54 @@ + + + + quarkus-devdb-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devdb-mysql + Quarkus - DevDB - MySQL + + + io.quarkus + quarkus-datasource-deployment + + + org.testcontainers + mysql + + + org.junit.jupiter + junit-jupiter + test + + + org.jboss.logmanager + jboss-logmanager-embedded + test + + + mysql + mysql-connector-java + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devdb/devdb-mysql/src/main/java/io/quarkus/devdb/postgresql/deployment/MySQLDevDBProcessor.java b/extensions/devdb/devdb-mysql/src/main/java/io/quarkus/devdb/postgresql/deployment/MySQLDevDBProcessor.java new file mode 100644 index 0000000000000..3ced01da9c05e --- /dev/null +++ b/extensions/devdb/devdb-mysql/src/main/java/io/quarkus/devdb/postgresql/deployment/MySQLDevDBProcessor.java @@ -0,0 +1,40 @@ +package io.quarkus.devdb.postgresql.deployment; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +import org.testcontainers.containers.MySQLContainer; + +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.devdb.DevDBProviderBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; +import io.quarkus.deployment.annotations.BuildStep; + +public class MySQLDevDBProcessor { + + @BuildStep + DevDBProviderBuildItem setupMysql() { + return new DevDBProviderBuildItem(DatabaseKind.MYSQL, new DevDBProvider() { + @Override + public RunningDevDb startDatabase(Optional username, Optional password, + Optional datasourceName, Optional imageName, Map additionalProperties) { + MySQLContainer container = new MySQLContainer( + imageName.orElse(MySQLContainer.IMAGE + ":" + MySQLContainer.DEFAULT_TAG)) + .withPassword(password.orElse("quarkus")) + .withUsername(username.orElse("quarkus")) + .withDatabaseName(datasourceName.orElse("default")); + container.start(); + return new RunningDevDb(container.getJdbcUrl(), container.getUsername(), container.getPassword(), + new Closeable() { + @Override + public void close() throws IOException { + container.stop(); + } + }); + } + }); + } + +} diff --git a/extensions/devdb/devdb-postgresql/pom.xml b/extensions/devdb/devdb-postgresql/pom.xml new file mode 100644 index 0000000000000..827b0aebf7195 --- /dev/null +++ b/extensions/devdb/devdb-postgresql/pom.xml @@ -0,0 +1,54 @@ + + + + quarkus-devdb-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-devdb-postgresql + Quarkus - DevDB - Postgresql + + + io.quarkus + quarkus-datasource-deployment + + + org.testcontainers + postgresql + + + org.postgresql + postgresql + + + org.junit.jupiter + junit-jupiter + test + + + org.jboss.logmanager + jboss-logmanager-embedded + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/devdb/devdb-postgresql/src/main/java/io/quarkus/devdb/postgresql/deployment/PostgresqlDevDBProcessor.java b/extensions/devdb/devdb-postgresql/src/main/java/io/quarkus/devdb/postgresql/deployment/PostgresqlDevDBProcessor.java new file mode 100644 index 0000000000000..259c59281e235 --- /dev/null +++ b/extensions/devdb/devdb-postgresql/src/main/java/io/quarkus/devdb/postgresql/deployment/PostgresqlDevDBProcessor.java @@ -0,0 +1,40 @@ +package io.quarkus.devdb.postgresql.deployment; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +import org.testcontainers.containers.PostgreSQLContainer; + +import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.devdb.DevDBProviderBuildItem; +import io.quarkus.datasource.deployment.spi.DevDBProvider; +import io.quarkus.deployment.annotations.BuildStep; + +public class PostgresqlDevDBProcessor { + + @BuildStep + DevDBProviderBuildItem setupPostgres() { + return new DevDBProviderBuildItem(DatabaseKind.POSTGRESQL, new DevDBProvider() { + @Override + public RunningDevDb startDatabase(Optional username, Optional password, + Optional datasourceName, Optional imageName, Map additionalProperties) { + PostgreSQLContainer container = new PostgreSQLContainer( + imageName.orElse(PostgreSQLContainer.IMAGE + ":" + PostgreSQLContainer.DEFAULT_TAG)) + .withPassword(password.orElse("quarkus")) + .withUsername(username.orElse("quarkus")) + .withDatabaseName(datasourceName.orElse("default")); + container.start(); + return new RunningDevDb(container.getJdbcUrl(), container.getUsername(), container.getPassword(), + new Closeable() { + @Override + public void close() throws IOException { + container.stop(); + } + }); + } + }); + } + +} diff --git a/extensions/devdb/pom.xml b/extensions/devdb/pom.xml new file mode 100644 index 0000000000000..fe0e4f0be9201 --- /dev/null +++ b/extensions/devdb/pom.xml @@ -0,0 +1,28 @@ + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-devdb-parent + Quarkus - DevDB + + DevDB allows Quarkus to automatically configure and start databases in + dev and test mode. + + pom + + devdb-postgresql + devdb-mysql + devdb-mariadb + devdb-h2 + devdb-derby + + + diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/EntitiesWithoutDataSourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/EntitiesWithoutDataSourceTest.java index 274b4af66484c..64afeae96009f 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/EntitiesWithoutDataSourceTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/EntitiesWithoutDataSourceTest.java @@ -18,8 +18,9 @@ public class EntitiesWithoutDataSourceTest { .assertException(t -> { assertThat(t).isInstanceOf(ConfigurationException.class); assertThat(t).hasMessageStartingWith( - "Model classes are defined for the default persistence unit but no default datasource found"); + "Model classes are defined for the default persistence unit"); }) + .overrideConfigKey("quarkus.datasource.devdb", "false") .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClass(MyEntity.class)); 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 8eb657add40ca..b709c9657d604 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 @@ -20,6 +20,7 @@ import org.jboss.logging.Logger; import io.quarkus.agroal.DataSource.DataSourceLiteral; +import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; import io.quarkus.datasource.common.runtime.DataSourceUtil; @@ -30,6 +31,7 @@ import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener; import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata; import io.quarkus.hibernate.orm.runtime.recording.RecordedState; +import io.quarkus.runtime.configuration.ConfigurationException; /** * This can not inherit from HibernatePersistenceProvider as that would force @@ -307,7 +309,14 @@ private static void injectDataSource(String persistenceUnitName, String dataSour "No datasource " + dataSource + " has been defined for persistence unit " + persistenceUnitName); } - runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, dataSourceHandle.get()); + DataSource ds = dataSourceHandle.get(); + if (ds instanceof UnconfiguredDataSource) { + throw new ConfigurationException( + "Model classes are defined for the default persistence unit " + persistenceUnitName + + " but configured datasource " + dataSource + + " not found: the default EntityManagerFactory will not be created."); + } + runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, ds); } private static void injectRuntimeConfiguration(String persistenceUnitName, diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index a83a76665adf5..36694f7f8cd6b 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -24,6 +24,7 @@ import org.hibernate.loader.BatchFetchStyle; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -40,6 +41,7 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.hibernate.orm.deployment.HibernateConfigUtil; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig; @@ -119,7 +121,9 @@ public void buildReactivePersistenceUnit( BuildProducer systemProperties, BuildProducer nativeImageResources, BuildProducer hotDeploymentWatchedFiles, - BuildProducer persistenceUnitDescriptors) { + BuildProducer persistenceUnitDescriptors, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { final boolean enableHR = hasEntities(domainObjects, nonJpaModelBuildItems); if (!enableHR) { @@ -139,7 +143,8 @@ public void buildReactivePersistenceUnit( } // we only support the default pool for now - Optional dbKindOptional = dataSourcesBuildTimeConfig.defaultDataSource.dbKind; + Optional dbKindOptional = DefaultDataSourceDbKindBuildItem.resolve( + dataSourcesBuildTimeConfig.defaultDataSource.dbKind, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); if (dbKindOptional.isPresent()) { final String dbKind = dbKindOptional.get(); ParsedPersistenceXmlDescriptor reactivePU = generateReactivePersistenceUnit( diff --git a/extensions/jdbc/jdbc-db2/deployment/pom.xml b/extensions/jdbc/jdbc-db2/deployment/pom.xml index 9bfe433e2ca74..4b25ac81ce157 100644 --- a/extensions/jdbc/jdbc-db2/deployment/pom.xml +++ b/extensions/jdbc/jdbc-db2/deployment/pom.xml @@ -25,6 +25,10 @@ io.quarkus quarkus-agroal-spi + + io.quarkus + quarkus-datasource-deployment-spi + io.quarkus quarkus-jdbc-db2 diff --git a/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JDBCDB2Processor.java b/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JDBCDB2Processor.java index b2936e9aaaa29..506033c7ec14a 100644 --- a/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JDBCDB2Processor.java +++ b/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JDBCDB2Processor.java @@ -1,10 +1,10 @@ package io.quarkus.jdbc.db2.deployment; -import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -68,7 +68,7 @@ void registerServiceBinding(Capabilities capabilities, serviceProvider.produce( new ServiceProviderBuildItem("io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter", DB2ServiceBindingConverter.class.getName())); - dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.DB2)); } + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.DB2)); } } diff --git a/extensions/jdbc/jdbc-derby/deployment/pom.xml b/extensions/jdbc/jdbc-derby/deployment/pom.xml index 60ac6c285bfa8..0fb78f016980f 100644 --- a/extensions/jdbc/jdbc-derby/deployment/pom.xml +++ b/extensions/jdbc/jdbc-derby/deployment/pom.xml @@ -17,6 +17,10 @@ io.quarkus quarkus-core-deployment + + io.quarkus + quarkus-datasource-deployment-spi + io.quarkus quarkus-arc-deployment @@ -29,6 +33,20 @@ io.quarkus quarkus-jdbc-derby + + io.quarkus + quarkus-devdb-derby + + + io.quarkus + quarkus-agroal-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + diff --git a/extensions/jdbc/jdbc-derby/deployment/src/main/java/io/quarkus/jdbc/derby/deployment/JDBCDerbyProcessor.java b/extensions/jdbc/jdbc-derby/deployment/src/main/java/io/quarkus/jdbc/derby/deployment/JDBCDerbyProcessor.java index ce5c278533ba8..e777f52db3330 100644 --- a/extensions/jdbc/jdbc-derby/deployment/src/main/java/io/quarkus/jdbc/derby/deployment/JDBCDerbyProcessor.java +++ b/extensions/jdbc/jdbc-derby/deployment/src/main/java/io/quarkus/jdbc/derby/deployment/JDBCDerbyProcessor.java @@ -4,6 +4,8 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -28,6 +30,11 @@ void registerDriver(BuildProducer jdbcDriver, "org.apache.derby.jdbc.ClientXADataSource")); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.jdbc(DatabaseKind.DERBY); + } + @BuildStep void configureAgroalConnection(BuildProducer additionalBeans, Capabilities capabilities) { @@ -47,4 +54,8 @@ void registerDriverForReflection(BuildProducer reflect reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, org.apache.derby.jdbc.ClientDriver.class.getName())); } + @BuildStep + void registerDefaultDbType(BuildProducer dbKind) { + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.DERBY)); + } } diff --git a/extensions/jdbc/jdbc-derby/deployment/src/test/java/io/quarkus/jdbc/derby/deployment/DevDBDerbyDatasourceTestCase.java b/extensions/jdbc/jdbc-derby/deployment/src/test/java/io/quarkus/jdbc/derby/deployment/DevDBDerbyDatasourceTestCase.java new file mode 100644 index 0000000000000..dfa59cb73bff7 --- /dev/null +++ b/extensions/jdbc/jdbc-derby/deployment/src/test/java/io/quarkus/jdbc/derby/deployment/DevDBDerbyDatasourceTestCase.java @@ -0,0 +1,51 @@ +package io.quarkus.jdbc.derby.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.util.function.Supplier; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.quarkus.test.QuarkusUnitTest; + +public class DevDBDerbyDatasourceTestCase { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class); + } + }); + + @Inject + AgroalDataSource dataSource; + + @Test + public void testDatasource() throws Exception { + AgroalConnectionPoolConfiguration configuration = null; + + try { + configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + } catch (NullPointerException e) { + // we catch the NPE here as we have a proxycd and we can't test dataSource directly + fail("Datasource should not be null"); + } + assertTrue(configuration.connectionFactoryConfiguration().jdbcUrl().contains("jdbc:derby:")); + assertEquals(20, configuration.maxSize()); + + try (Connection connection = dataSource.getConnection()) { + } + } +} diff --git a/extensions/jdbc/jdbc-h2/deployment/pom.xml b/extensions/jdbc/jdbc-h2/deployment/pom.xml index 96eb9b55fb9cd..67f12f98cfa84 100644 --- a/extensions/jdbc/jdbc-h2/deployment/pom.xml +++ b/extensions/jdbc/jdbc-h2/deployment/pom.xml @@ -21,6 +21,14 @@ io.quarkus quarkus-arc-deployment + + io.quarkus + quarkus-datasource-deployment-spi + + + io.quarkus + quarkus-devdb-h2 + io.quarkus quarkus-agroal-spi diff --git a/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java b/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java index 53c22c5a280ca..17d28be0f4757 100644 --- a/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java +++ b/extensions/jdbc/jdbc-h2/deployment/src/main/java/io/quarkus/jdbc/h2/deployment/JDBCH2Processor.java @@ -4,6 +4,8 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -27,6 +29,11 @@ void registerDriver(BuildProducer jdbcDriver, .produce(new JdbcDriverBuildItem(DatabaseKind.H2, "org.h2.Driver", "org.h2.jdbcx.JdbcDataSource")); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.jdbc(DatabaseKind.H2); + } + @BuildStep void configureAgroalConnection(BuildProducer additionalBeans, Capabilities capabilities) { @@ -37,4 +44,9 @@ void configureAgroalConnection(BuildProducer additional .build()); } } + + @BuildStep + void registerDefaultDbType(BuildProducer dbKind) { + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.H2)); + } } diff --git a/extensions/jdbc/jdbc-mariadb/deployment/pom.xml b/extensions/jdbc/jdbc-mariadb/deployment/pom.xml index 20e38330aa127..126d9cc97684a 100644 --- a/extensions/jdbc/jdbc-mariadb/deployment/pom.xml +++ b/extensions/jdbc/jdbc-mariadb/deployment/pom.xml @@ -21,6 +21,10 @@ io.quarkus quarkus-arc-deployment + + io.quarkus + quarkus-datasource-deployment-spi + io.quarkus quarkus-agroal-spi @@ -29,6 +33,20 @@ io.quarkus quarkus-jdbc-mariadb + + io.quarkus + quarkus-devdb-mariadb + + + io.quarkus + quarkus-agroal-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + @@ -45,6 +63,36 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + test-devdb + + + test-containers + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + + diff --git a/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java b/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java index 21353ab369ce8..29f738b7d576d 100644 --- a/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java +++ b/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/JDBCMariaDBProcessor.java @@ -1,10 +1,11 @@ package io.quarkus.jdbc.mariadb.deployment; -import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -30,6 +31,11 @@ void registerDriver(BuildProducer jdbcDriver, "org.mariadb.jdbc.MySQLDataSource")); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.jdbc(DatabaseKind.MARIADB); + } + @BuildStep void configureAgroalConnection(BuildProducer additionalBeans, Capabilities capabilities) { @@ -49,7 +55,7 @@ void registerServiceBinding(Capabilities capabilities, serviceProvider.produce( new ServiceProviderBuildItem("io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter", MariaDBServiceBindingConverter.class.getName())); - dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MARIADB)); } + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MARIADB)); } } diff --git a/extensions/jdbc/jdbc-mariadb/deployment/src/test/java/io/quarkus/jdbc/mariadb/deployment/DevDBMariaDBDatasourceTestCase.java b/extensions/jdbc/jdbc-mariadb/deployment/src/test/java/io/quarkus/jdbc/mariadb/deployment/DevDBMariaDBDatasourceTestCase.java new file mode 100644 index 0000000000000..96c4661173975 --- /dev/null +++ b/extensions/jdbc/jdbc-mariadb/deployment/src/test/java/io/quarkus/jdbc/mariadb/deployment/DevDBMariaDBDatasourceTestCase.java @@ -0,0 +1,52 @@ +package io.quarkus.jdbc.mariadb.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.util.function.Supplier; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.quarkus.test.QuarkusUnitTest; + +public class DevDBMariaDBDatasourceTestCase { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class); + } + }); + + @Inject + AgroalDataSource dataSource; + + @Test + public void testDatasource() throws Exception { + AgroalConnectionPoolConfiguration configuration = null; + + try { + configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + } catch (NullPointerException e) { + // we catch the NPE here as we have a proxycd and we can't test dataSource directly + fail("Datasource should not be null"); + } + assertTrue(configuration.connectionFactoryConfiguration().jdbcUrl().contains("jdbc:mariadb:")); + assertEquals("quarkus", configuration.connectionFactoryConfiguration().principal().getName()); + assertEquals(20, configuration.maxSize()); + + try (Connection connection = dataSource.getConnection()) { + } + } +} diff --git a/extensions/jdbc/jdbc-mssql/deployment/pom.xml b/extensions/jdbc/jdbc-mssql/deployment/pom.xml index 091ecb2a6fbb6..0d2499bbb6445 100644 --- a/extensions/jdbc/jdbc-mssql/deployment/pom.xml +++ b/extensions/jdbc/jdbc-mssql/deployment/pom.xml @@ -25,6 +25,10 @@ io.quarkus quarkus-agroal-spi + + io.quarkus + quarkus-datasource-deployment-spi + io.quarkus quarkus-jdbc-mssql diff --git a/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java b/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java index 05e50351f1c05..62c37ebabacd7 100644 --- a/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java +++ b/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java @@ -1,10 +1,10 @@ package io.quarkus.jdbc.mssql.deployment; -import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -64,7 +64,7 @@ void registerServiceBinding(Capabilities capabilities, serviceProvider.produce( new ServiceProviderBuildItem("io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter", MsSQLServiceBindingConverter.class.getName())); - dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MSSQL)); } + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MSSQL)); } } diff --git a/extensions/jdbc/jdbc-mysql/deployment/pom.xml b/extensions/jdbc/jdbc-mysql/deployment/pom.xml index fe4f807e89723..ebfd42998b17e 100644 --- a/extensions/jdbc/jdbc-mysql/deployment/pom.xml +++ b/extensions/jdbc/jdbc-mysql/deployment/pom.xml @@ -25,10 +25,28 @@ io.quarkus quarkus-agroal-spi + + io.quarkus + quarkus-datasource-deployment-spi + io.quarkus quarkus-jdbc-mysql + + io.quarkus + quarkus-devdb-mysql + + + io.quarkus + quarkus-agroal-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + @@ -45,6 +63,35 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + test-devdb + + + test-containers + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + + diff --git a/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java b/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java index d959568276e8e..6fc731d16c6a9 100644 --- a/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java +++ b/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java @@ -19,11 +19,12 @@ import com.mysql.cj.jdbc.result.ResultSetInternalMethods; import com.mysql.cj.protocol.Resultset; -import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -56,6 +57,11 @@ void registerDriver(BuildProducer jdbcDriver, "com.mysql.cj.jdbc.MysqlXADataSource")); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.jdbc(DatabaseKind.MYSQL); + } + @BuildStep void configureAgroalConnection(BuildProducer additionalBeans, Capabilities capabilities) { @@ -126,7 +132,7 @@ void registerServiceBinding(Capabilities capabilities, serviceProvider.produce( new ServiceProviderBuildItem("io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter", MySQLServiceBindingConverter.class.getName())); - dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MYSQL)); } + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MYSQL)); } } diff --git a/extensions/jdbc/jdbc-mysql/deployment/src/test/java/io/quarkus/jdbc/mysql/deployment/DevDBMySQLDatasourceTestCase.java b/extensions/jdbc/jdbc-mysql/deployment/src/test/java/io/quarkus/jdbc/mysql/deployment/DevDBMySQLDatasourceTestCase.java new file mode 100644 index 0000000000000..08923c2211115 --- /dev/null +++ b/extensions/jdbc/jdbc-mysql/deployment/src/test/java/io/quarkus/jdbc/mysql/deployment/DevDBMySQLDatasourceTestCase.java @@ -0,0 +1,52 @@ +package io.quarkus.jdbc.mysql.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.util.function.Supplier; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.quarkus.test.QuarkusUnitTest; + +public class DevDBMySQLDatasourceTestCase { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class); + } + }); + + @Inject + AgroalDataSource dataSource; + + @Test + public void testDatasource() throws Exception { + AgroalConnectionPoolConfiguration configuration = null; + + try { + configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + } catch (NullPointerException e) { + // we catch the NPE here as we have a proxycd and we can't test dataSource directly + fail("Datasource should not be null"); + } + assertTrue(configuration.connectionFactoryConfiguration().jdbcUrl().contains("jdbc:mysql:")); + assertEquals("quarkus", configuration.connectionFactoryConfiguration().principal().getName()); + assertEquals(20, configuration.maxSize()); + + try (Connection connection = dataSource.getConnection()) { + } + } +} diff --git a/extensions/jdbc/jdbc-postgresql/deployment/pom.xml b/extensions/jdbc/jdbc-postgresql/deployment/pom.xml index 6f4c0e9adcc11..f8bcc2b896109 100644 --- a/extensions/jdbc/jdbc-postgresql/deployment/pom.xml +++ b/extensions/jdbc/jdbc-postgresql/deployment/pom.xml @@ -13,6 +13,10 @@ Quarkus - JDBC - PostgreSQL - Deployment + + org.testcontainers + postgresql + io.quarkus quarkus-core-deployment @@ -25,10 +29,28 @@ io.quarkus quarkus-agroal-spi + + io.quarkus + quarkus-devdb-postgresql + + + io.quarkus + quarkus-datasource-deployment-spi + io.quarkus quarkus-jdbc-postgresql + + io.quarkus + quarkus-agroal-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + @@ -45,6 +67,35 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + test-devdb + + + test-containers + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + + diff --git a/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/JDBCPostgreSQLProcessor.java b/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/JDBCPostgreSQLProcessor.java index 31b7f0dcc9e3c..9441d22a1690a 100644 --- a/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/JDBCPostgreSQLProcessor.java +++ b/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/JDBCPostgreSQLProcessor.java @@ -1,10 +1,11 @@ package io.quarkus.jdbc.postgresql.deployment; -import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -30,6 +31,11 @@ void registerDriver(BuildProducer jdbcDriver, "org.postgresql.xa.PGXADataSource")); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.jdbc(DatabaseKind.POSTGRESQL); + } + @BuildStep void configureAgroalConnection(BuildProducer additionalBeans, Capabilities capabilities) { @@ -50,7 +56,7 @@ void registerServiceBinding(Capabilities capabilities, serviceProvider.produce( new ServiceProviderBuildItem("io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter", PostgreSqlServiceBindingConverter.class.getName())); - dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.POSTGRESQL)); } + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.POSTGRESQL)); } } diff --git a/extensions/jdbc/jdbc-postgresql/deployment/src/test/java/io/quarkus/jdbc/postgresql/deployment/DevDBPostgresqlDatasourceTestCase.java b/extensions/jdbc/jdbc-postgresql/deployment/src/test/java/io/quarkus/jdbc/postgresql/deployment/DevDBPostgresqlDatasourceTestCase.java new file mode 100644 index 0000000000000..b881423c39dc8 --- /dev/null +++ b/extensions/jdbc/jdbc-postgresql/deployment/src/test/java/io/quarkus/jdbc/postgresql/deployment/DevDBPostgresqlDatasourceTestCase.java @@ -0,0 +1,52 @@ +package io.quarkus.jdbc.postgresql.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.util.function.Supplier; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.quarkus.test.QuarkusUnitTest; + +public class DevDBPostgresqlDatasourceTestCase { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class); + } + }); + + @Inject + AgroalDataSource dataSource; + + @Test + public void testDatasource() throws Exception { + AgroalConnectionPoolConfiguration configuration = null; + + try { + configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + } catch (NullPointerException e) { + // we catch the NPE here as we have a proxycd and we can't test dataSource directly + fail("Datasource should not be null"); + } + assertTrue(configuration.connectionFactoryConfiguration().jdbcUrl().contains("jdbc:postgresql:")); + assertEquals("quarkus", configuration.connectionFactoryConfiguration().principal().getName()); + assertEquals(20, configuration.maxSize()); + + try (Connection connection = dataSource.getConnection()) { + } + } +} diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/ErroneousConfigHotReloadTestCase.java b/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/ErroneousConfigHotReloadTestCase.java index b43d1f38ec4d5..1317e60c9f85e 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/ErroneousConfigHotReloadTestCase.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/deployment/test/ErroneousConfigHotReloadTestCase.java @@ -22,7 +22,7 @@ public class ErroneousConfigHotReloadTestCase { @Test public void test() { - RestAssured.when().get("/unannotatedEntity").then().statusCode(500).body(containsString("default datasource")) + RestAssured.when().get("/unannotatedEntity").then().statusCode(500).body(containsString("No entities were found")) .body(not(containsString("NullPointer"))); TEST.modifyResourceFile("application.properties", new Function() { diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/test/resources/application-commented-out.properties b/extensions/panache/hibernate-orm-panache/deployment/src/test/resources/application-commented-out.properties index c3245a1a07cf5..07c1be71376af 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/test/resources/application-commented-out.properties +++ b/extensions/panache/hibernate-orm-panache/deployment/src/test/resources/application-commented-out.properties @@ -1,4 +1,5 @@ #quarkus.datasource.db-kind=h2 +quarkus.datasource.devdb=false #quarkus.datasource.jdbc.url=jdbc:h2:mem:test # #quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect diff --git a/extensions/pom.xml b/extensions/pom.xml index e5bb63b91e578..1ac5249c865bf 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -111,6 +111,7 @@ artemis-core artemis-jms avro + devdb spring-di 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 b240cf22e744a..3d882b0cbaf7a 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 @@ -1,5 +1,8 @@ package io.quarkus.reactive.db2.client.deployment; +import java.util.List; +import java.util.Optional; + import javax.enterprise.context.ApplicationScoped; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; @@ -7,6 +10,7 @@ import io.quarkus.arc.processor.DotNames; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; @@ -19,6 +23,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.reactive.datasource.deployment.VertxPoolBuildItem; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveBuildTimeConfig; @@ -47,19 +52,22 @@ ServiceStartBuildItem build(BuildProducer feature, DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, DataSourcesReactiveRuntimeConfig dataSourcesReactiveRuntimeConfig, - DataSourcesReactiveDB2Config dataSourcesReactiveDB2Config) { + DataSourcesReactiveDB2Config dataSourcesReactiveDB2Config, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { feature.produce(new FeatureBuildItem(Feature.REACTIVE_DB2_CLIENT)); createPoolIfDefined(recorder, vertx, shutdown, db2Pool, vertxPool, syntheticBeans, DataSourceUtil.DEFAULT_DATASOURCE_NAME, dataSourcesBuildTimeConfig, dataSourcesRuntimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourcesReactiveRuntimeConfig, - dataSourcesReactiveDB2Config); + dataSourcesReactiveDB2Config, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); for (String dataSourceName : dataSourcesBuildTimeConfig.namedDataSources.keySet()) { createPoolIfDefined(recorder, vertx, shutdown, db2Pool, vertxPool, syntheticBeans, dataSourceName, dataSourcesBuildTimeConfig, dataSourcesRuntimeConfig, dataSourcesReactiveBuildTimeConfig, - dataSourcesReactiveRuntimeConfig, dataSourcesReactiveDB2Config); + dataSourcesReactiveRuntimeConfig, dataSourcesReactiveDB2Config, defaultDataSourceDbKindBuildItems, + curateOutcomeBuildItem); } // Enable SSL support by default @@ -76,8 +84,11 @@ ServiceStartBuildItem build(BuildProducer feature, void addHealthCheck( BuildProducer healthChecks, DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig) { - if (!hasPools(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig)) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { + if (!hasPools(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, defaultDataSourceDbKindBuildItems, + curateOutcomeBuildItem)) { return; } @@ -97,9 +108,12 @@ private void createPoolIfDefined(DB2PoolRecorder recorder, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, DataSourcesReactiveRuntimeConfig dataSourcesReactiveRuntimeConfig, - DataSourcesReactiveDB2Config dataSourcesReactiveDB2Config) { + DataSourcesReactiveDB2Config dataSourcesReactiveDB2Config, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { - if (!isReactiveDB2PoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourceName)) { + if (!isReactiveDB2PoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourceName, + defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return; } @@ -138,17 +152,21 @@ private void createPoolIfDefined(DB2PoolRecorder recorder, } private static boolean isReactiveDB2PoolDefined(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, String dataSourceName) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, String dataSourceName, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { DataSourceBuildTimeConfig dataSourceBuildTimeConfig = dataSourcesBuildTimeConfig .getDataSourceRuntimeConfig(dataSourceName); DataSourceReactiveBuildTimeConfig dataSourceReactiveBuildTimeConfig = dataSourcesReactiveBuildTimeConfig .getDataSourceReactiveBuildTimeConfig(dataSourceName); - if (!dataSourceBuildTimeConfig.dbKind.isPresent()) { + Optional dbKind = DefaultDataSourceDbKindBuildItem.resolve(dataSourceBuildTimeConfig.dbKind, + defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); + if (!dbKind.isPresent()) { return false; } - if (!DatabaseKind.isDB2(dataSourceBuildTimeConfig.dbKind.get()) + if (!DatabaseKind.isDB2(dbKind.get()) || !dataSourceReactiveBuildTimeConfig.enabled) { return false; } @@ -157,15 +175,17 @@ private static boolean isReactiveDB2PoolDefined(DataSourcesBuildTimeConfig dataS } private boolean hasPools(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { if (isReactiveDB2PoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, - DataSourceUtil.DEFAULT_DATASOURCE_NAME)) { + DataSourceUtil.DEFAULT_DATASOURCE_NAME, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return true; } for (String dataSourceName : dataSourcesBuildTimeConfig.namedDataSources.keySet()) { if (isReactiveDB2PoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, - dataSourceName)) { + dataSourceName, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return true; } } diff --git a/extensions/reactive-mysql-client/deployment/pom.xml b/extensions/reactive-mysql-client/deployment/pom.xml index 77bc080ee36db..f53f735176cf9 100644 --- a/extensions/reactive-mysql-client/deployment/pom.xml +++ b/extensions/reactive-mysql-client/deployment/pom.xml @@ -56,6 +56,10 @@ io.quarkus quarkus-smallrye-health-spi + + io.quarkus + quarkus-devdb-mysql + io.quarkus 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 a149d296ac395..d1a34a1c2c541 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 @@ -1,5 +1,8 @@ package io.quarkus.reactive.mysql.client.deployment; +import java.util.List; +import java.util.Optional; + import javax.enterprise.context.ApplicationScoped; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; @@ -7,6 +10,8 @@ import io.quarkus.arc.processor.DotNames; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; @@ -19,6 +24,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.reactive.datasource.deployment.VertxPoolBuildItem; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveBuildTimeConfig; @@ -47,19 +53,22 @@ ServiceStartBuildItem build(BuildProducer feature, DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, DataSourcesReactiveRuntimeConfig dataSourcesReactiveRuntimeConfig, - DataSourcesReactiveMySQLConfig dataSourcesReactiveMySQLConfig) { + DataSourcesReactiveMySQLConfig dataSourcesReactiveMySQLConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { feature.produce(new FeatureBuildItem(Feature.REACTIVE_MYSQL_CLIENT)); createPoolIfDefined(recorder, vertx, shutdown, mySQLPool, vertxPool, syntheticBeans, DataSourceUtil.DEFAULT_DATASOURCE_NAME, dataSourcesBuildTimeConfig, dataSourcesRuntimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourcesReactiveRuntimeConfig, - dataSourcesReactiveMySQLConfig); + dataSourcesReactiveMySQLConfig, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); for (String dataSourceName : dataSourcesBuildTimeConfig.namedDataSources.keySet()) { createPoolIfDefined(recorder, vertx, shutdown, mySQLPool, vertxPool, syntheticBeans, dataSourceName, dataSourcesBuildTimeConfig, dataSourcesRuntimeConfig, dataSourcesReactiveBuildTimeConfig, - dataSourcesReactiveRuntimeConfig, dataSourcesReactiveMySQLConfig); + dataSourcesReactiveRuntimeConfig, dataSourcesReactiveMySQLConfig, defaultDataSourceDbKindBuildItems, + curateOutcomeBuildItem); } // Enable SSL support by default @@ -68,6 +77,11 @@ ServiceStartBuildItem build(BuildProducer feature, return new ServiceStartBuildItem("reactive-mysql-client"); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.reactive(DatabaseKind.MYSQL); + } + /** * The health check needs to be produced in a separate method to avoid a circular dependency (the Vert.x instance creation * consumes the AdditionalBeanBuildItems). @@ -76,8 +90,11 @@ ServiceStartBuildItem build(BuildProducer feature, void addHealthCheck( BuildProducer healthChecks, DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig) { - if (!hasPools(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig)) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { + if (!hasPools(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, defaultDataSourceDbKindBuildItems, + curateOutcomeBuildItem)) { return; } @@ -97,9 +114,12 @@ private void createPoolIfDefined(MySQLPoolRecorder recorder, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, DataSourcesReactiveRuntimeConfig dataSourcesReactiveRuntimeConfig, - DataSourcesReactiveMySQLConfig dataSourcesReactiveMySQLConfig) { + DataSourcesReactiveMySQLConfig dataSourcesReactiveMySQLConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { - if (!isReactiveMySQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourceName)) { + if (!isReactiveMySQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourceName, + defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return; } @@ -138,18 +158,22 @@ private void createPoolIfDefined(MySQLPoolRecorder recorder, } private static boolean isReactiveMySQLPoolDefined(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, String dataSourceName) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, String dataSourceName, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { DataSourceBuildTimeConfig dataSourceBuildTimeConfig = dataSourcesBuildTimeConfig .getDataSourceRuntimeConfig(dataSourceName); DataSourceReactiveBuildTimeConfig dataSourceReactiveBuildTimeConfig = dataSourcesReactiveBuildTimeConfig .getDataSourceReactiveBuildTimeConfig(dataSourceName); - if (!dataSourceBuildTimeConfig.dbKind.isPresent()) { + Optional dbKind = DefaultDataSourceDbKindBuildItem.resolve(dataSourceBuildTimeConfig.dbKind, + defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); + if (!dbKind.isPresent()) { return false; } - if ((!DatabaseKind.isMySQL(dataSourceBuildTimeConfig.dbKind.get()) - && !DatabaseKind.isMariaDB(dataSourceBuildTimeConfig.dbKind.get())) + if ((!DatabaseKind.isMySQL(dbKind.get()) + && !DatabaseKind.isMariaDB(dbKind.get())) || !dataSourceReactiveBuildTimeConfig.enabled) { return false; } @@ -158,15 +182,17 @@ private static boolean isReactiveMySQLPoolDefined(DataSourcesBuildTimeConfig dat } private boolean hasPools(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { if (isReactiveMySQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, - DataSourceUtil.DEFAULT_DATASOURCE_NAME)) { + DataSourceUtil.DEFAULT_DATASOURCE_NAME, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return true; } for (String dataSourceName : dataSourcesBuildTimeConfig.namedDataSources.keySet()) { if (isReactiveMySQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, - dataSourceName)) { + dataSourceName, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return true; } } diff --git a/extensions/reactive-pg-client/deployment/pom.xml b/extensions/reactive-pg-client/deployment/pom.xml index f6494c8b7eb1a..e7ee45b513376 100644 --- a/extensions/reactive-pg-client/deployment/pom.xml +++ b/extensions/reactive-pg-client/deployment/pom.xml @@ -24,6 +24,10 @@ io.quarkus quarkus-reactive-datasource-deployment + + io.quarkus + quarkus-devdb-postgresql + io.quarkus quarkus-vertx-deployment 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 976cd9ce12a11..7df814de5a9ca 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 @@ -1,5 +1,8 @@ package io.quarkus.reactive.pg.client.deployment; +import java.util.List; +import java.util.Optional; + import javax.enterprise.context.ApplicationScoped; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; @@ -7,6 +10,8 @@ import io.quarkus.arc.processor.DotNames; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.datasource.common.runtime.DatabaseKind; +import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem; +import io.quarkus.datasource.deployment.spi.DevDbConfigurationHandlerBuildItem; import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig; @@ -20,6 +25,7 @@ import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.reactive.datasource.ReactiveDataSource; import io.quarkus.reactive.datasource.deployment.VertxPoolBuildItem; import io.quarkus.reactive.datasource.runtime.DataSourceReactiveBuildTimeConfig; @@ -41,6 +47,11 @@ NativeImageConfigBuildItem config() { .build(); } + @BuildStep + DevDbConfigurationHandlerBuildItem devDbHandler() { + return DevDbConfigurationHandlerBuildItem.reactive(DatabaseKind.POSTGRESQL); + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) ServiceStartBuildItem build(BuildProducer feature, @@ -54,19 +65,22 @@ ServiceStartBuildItem build(BuildProducer feature, DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, DataSourcesReactiveRuntimeConfig dataSourcesReactiveRuntimeConfig, - DataSourcesReactivePostgreSQLConfig dataSourcesReactivePostgreSQLConfig) { + DataSourcesReactivePostgreSQLConfig dataSourcesReactivePostgreSQLConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { feature.produce(new FeatureBuildItem(Feature.REACTIVE_PG_CLIENT)); createPoolIfDefined(recorder, vertx, shutdown, pgPool, vertxPool, syntheticBeans, DataSourceUtil.DEFAULT_DATASOURCE_NAME, dataSourcesBuildTimeConfig, dataSourcesRuntimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourcesReactiveRuntimeConfig, - dataSourcesReactivePostgreSQLConfig); + dataSourcesReactivePostgreSQLConfig, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); for (String dataSourceName : dataSourcesBuildTimeConfig.namedDataSources.keySet()) { createPoolIfDefined(recorder, vertx, shutdown, pgPool, vertxPool, syntheticBeans, dataSourceName, dataSourcesBuildTimeConfig, dataSourcesRuntimeConfig, dataSourcesReactiveBuildTimeConfig, - dataSourcesReactiveRuntimeConfig, dataSourcesReactivePostgreSQLConfig); + dataSourcesReactiveRuntimeConfig, dataSourcesReactivePostgreSQLConfig, defaultDataSourceDbKindBuildItems, + curateOutcomeBuildItem); } // Enable SSL support by default @@ -83,8 +97,11 @@ ServiceStartBuildItem build(BuildProducer feature, void addHealthCheck( BuildProducer healthChecks, DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig) { - if (!hasPools(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig)) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { + if (!hasPools(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, defaultDataSourceDbKindBuildItems, + curateOutcomeBuildItem)) { return; } @@ -93,6 +110,11 @@ void addHealthCheck( dataSourcesBuildTimeConfig.healthEnabled)); } + @BuildStep + void registerServiceBinding(BuildProducer dbKind) { + dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.POSTGRESQL)); + } + private void createPoolIfDefined(PgPoolRecorder recorder, VertxBuildItem vertx, ShutdownContextBuildItem shutdown, @@ -104,9 +126,12 @@ private void createPoolIfDefined(PgPoolRecorder recorder, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, DataSourcesReactiveRuntimeConfig dataSourcesReactiveRuntimeConfig, - DataSourcesReactivePostgreSQLConfig dataSourcesReactivePostgreSQLConfig) { + DataSourcesReactivePostgreSQLConfig dataSourcesReactivePostgreSQLConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { - if (!isReactivePostgreSQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourceName)) { + if (!isReactivePostgreSQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, dataSourceName, + defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return; } @@ -145,17 +170,22 @@ private void createPoolIfDefined(PgPoolRecorder recorder, } private static boolean isReactivePostgreSQLPoolDefined(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, String dataSourceName) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, String dataSourceName, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { DataSourceBuildTimeConfig dataSourceBuildTimeConfig = dataSourcesBuildTimeConfig .getDataSourceRuntimeConfig(dataSourceName); DataSourceReactiveBuildTimeConfig dataSourceReactiveBuildTimeConfig = dataSourcesReactiveBuildTimeConfig .getDataSourceReactiveBuildTimeConfig(dataSourceName); - if (!dataSourceBuildTimeConfig.dbKind.isPresent()) { + Optional dbKind = DefaultDataSourceDbKindBuildItem.resolve(dataSourceBuildTimeConfig.dbKind, + defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem); + + if (!dbKind.isPresent()) { return false; } - if (!DatabaseKind.isPostgreSQL(dataSourceBuildTimeConfig.dbKind.get()) + if (!DatabaseKind.isPostgreSQL(dbKind.get()) || !dataSourceReactiveBuildTimeConfig.enabled) { return false; } @@ -164,15 +194,17 @@ private static boolean isReactivePostgreSQLPoolDefined(DataSourcesBuildTimeConfi } private boolean hasPools(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, - DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig) { + DataSourcesReactiveBuildTimeConfig dataSourcesReactiveBuildTimeConfig, + List defaultDataSourceDbKindBuildItems, + CurateOutcomeBuildItem curateOutcomeBuildItem) { if (isReactivePostgreSQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, - DataSourceUtil.DEFAULT_DATASOURCE_NAME)) { + DataSourceUtil.DEFAULT_DATASOURCE_NAME, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return true; } for (String dataSourceName : dataSourcesBuildTimeConfig.namedDataSources.keySet()) { if (isReactivePostgreSQLPoolDefined(dataSourcesBuildTimeConfig, dataSourcesReactiveBuildTimeConfig, - dataSourceName)) { + dataSourceName, defaultDataSourceDbKindBuildItems, curateOutcomeBuildItem)) { return true; } } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AugmentAction.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AugmentAction.java index c280e2b5f5ec2..058559227bcfa 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AugmentAction.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AugmentAction.java @@ -3,6 +3,9 @@ import java.util.Set; public interface AugmentAction { + + void performCustomBuild(String resultConsumer, Object context, String... finalOutputs); + AugmentResult createProductionApplication(); StartupAction createInitialRuntimeApplication(); diff --git a/integration-tests/jpa-h2/src/main/resources/application.properties b/integration-tests/jpa-h2/src/main/resources/application.properties index abb95fef00079..bf4226d2762fd 100644 --- a/integration-tests/jpa-h2/src/main/resources/application.properties +++ b/integration-tests/jpa-h2/src/main/resources/application.properties @@ -1,3 +1 @@ -quarkus.datasource.db-kind=h2 -quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test quarkus.datasource.jdbc.max-size=8 diff --git a/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java deleted file mode 100644 index cab3615f001d3..0000000000000 --- a/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.quarkus.it.jpa.h2; - -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.h2.H2DatabaseTestResource; - -@QuarkusTestResource(H2DatabaseTestResource.class) -public class TestResources { -} diff --git a/integration-tests/jpa-postgresql/src/main/resources/application.properties b/integration-tests/jpa-postgresql/src/main/resources/application.properties index eb32fc3b47311..59bf46a494b07 100644 --- a/integration-tests/jpa-postgresql/src/main/resources/application.properties +++ b/integration-tests/jpa-postgresql/src/main/resources/application.properties @@ -1,4 +1,3 @@ -quarkus.datasource.db-kind=postgresql quarkus.datasource.username=hibernate_orm_test quarkus.datasource.password=hibernate_orm_test quarkus.datasource.jdbc.url=${postgres.url} diff --git a/test-framework/junit5/pom.xml b/test-framework/junit5/pom.xml index 6c1b4ae52a777..a0c32c731ba93 100644 --- a/test-framework/junit5/pom.xml +++ b/test-framework/junit5/pom.xml @@ -44,6 +44,10 @@ io.quarkus quarkus-core + + io.quarkus + quarkus-datasource-deployment-spi + com.thoughtworks.xstream xstream diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeDevDbHandler.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeDevDbHandler.java new file mode 100644 index 0000000000000..bcc933b3a7555 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeDevDbHandler.java @@ -0,0 +1,27 @@ +package io.quarkus.test.junit; + +import java.util.Map; +import java.util.function.BiConsumer; + +import io.quarkus.builder.BuildResult; +import io.quarkus.datasource.deployment.spi.DevDbResultBuildItem; + +public class NativeDevDbHandler implements BiConsumer { + @Override + public void accept(Object o, BuildResult buildResult) { + BiConsumer propertyConsumer = (BiConsumer) o; + DevDbResultBuildItem res = buildResult.consumeOptional(DevDbResultBuildItem.class); + if (res != null) { + if (res.getDefaultDatasource() != null) { + for (Map.Entry entry : res.getDefaultDatasource().getConfigProperties().entrySet()) { + propertyConsumer.accept(entry.getKey(), entry.getValue()); + } + } + for (DevDbResultBuildItem.DbResult i : res.getNamedDatasources().values()) { + for (Map.Entry entry : i.getConfigProperties().entrySet()) { + propertyConsumer.accept(entry.getKey(), entry.getValue()); + } + } + } + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java index 06da57d3aa9c5..adb7ce5b4a53b 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java @@ -1,19 +1,28 @@ package io.quarkus.test.junit; +import static io.quarkus.test.common.PathTestHelper.getAppClassLocationForTestLocation; +import static io.quarkus.test.common.PathTestHelper.getTestClassesLocation; + import java.io.Closeable; +import java.io.File; import java.io.IOException; import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.enterprise.inject.Alternative; import javax.inject.Inject; +import org.jboss.jandex.Index; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -22,11 +31,20 @@ import org.junit.platform.commons.JUnitException; import org.opentest4j.TestAbortedException; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.model.PathsCollection; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.utils.BuildToolHelper; +import io.quarkus.datasource.deployment.spi.DevDbResultBuildItem; import io.quarkus.runtime.configuration.ProfileManager; import io.quarkus.runtime.test.TestHttpEndpointProvider; import io.quarkus.test.common.NativeImageLauncher; +import io.quarkus.test.common.PathTestHelper; import io.quarkus.test.common.PropertyTestUtil; import io.quarkus.test.common.RestAssuredURLManager; +import io.quarkus.test.common.TestClassIndexer; import io.quarkus.test.common.TestResourceManager; import io.quarkus.test.common.TestScopeManager; import io.quarkus.test.common.http.TestHTTPResourceManager; @@ -82,7 +100,8 @@ private void ensureNoInjectAnnotationIsUsed(Class testClass) { } - private ExtensionState ensureStarted(ExtensionContext extensionContext) { + private ExtensionState ensureStarted(ExtensionContext extensionContext) throws Exception { + Class testClass = extensionContext.getRequiredTestClass(); ensureNoInjectAnnotationIsUsed(testClass); @@ -118,8 +137,91 @@ private ExtensionState ensureStarted(ExtensionContext extensionContext) { return state; } + private Map handleDevDb(ExtensionContext context) throws Exception { + Class requiredTestClass = context.getRequiredTestClass(); + Path testClassLocation = getTestClassesLocation(requiredTestClass); + final Path appClassLocation = getAppClassLocationForTestLocation(testClassLocation.toString()); + + PathsCollection.Builder rootBuilder = PathsCollection.builder(); + + if (!appClassLocation.equals(testClassLocation)) { + rootBuilder.add(testClassLocation); + // if test classes is a dir, we should also check whether test resources dir exists as a separate dir (gradle) + // TODO: this whole app/test path resolution logic is pretty dumb, it needs be re-worked using proper workspace discovery + final Path testResourcesLocation = PathTestHelper.getResourcesForClassesDirOrNull(testClassLocation, "test"); + if (testResourcesLocation != null) { + rootBuilder.add(testResourcesLocation); + } + } + final QuarkusBootstrap.Builder runnerBuilder = QuarkusBootstrap.builder() + .setIsolateDeployment(true) + .setMode(QuarkusBootstrap.Mode.TEST); + QuarkusTestProfile profileInstance = null; + + final Path projectRoot = Paths.get("").normalize().toAbsolutePath(); + runnerBuilder.setProjectRoot(projectRoot); + Path outputDir; + try { + // this should work for both maven and gradle + outputDir = projectRoot.resolve(projectRoot.relativize(testClassLocation).getName(0)); + } catch (Exception e) { + // this shouldn't happen since testClassLocation is usually found under the project dir + outputDir = projectRoot; + } + runnerBuilder.setTargetDirectory(outputDir); + + rootBuilder.add(appClassLocation); + final Path appResourcesLocation = PathTestHelper.getResourcesForClassesDirOrNull(appClassLocation, "main"); + if (appResourcesLocation != null) { + rootBuilder.add(appResourcesLocation); + } + + // If gradle project running directly with IDE + if (System.getProperty(BootstrapConstants.SERIALIZED_APP_MODEL) == null) { + QuarkusModel model = BuildToolHelper.enableGradleAppModelForTest(projectRoot); + if (model != null) { + final Set classDirectories = model.getWorkspace().getMainModule().getSourceSet() + .getSourceDirectories(); + for (File classes : classDirectories) { + if (classes.exists() && !rootBuilder.contains(classes.toPath())) { + rootBuilder.add(classes.toPath()); + } + } + } + } else if (System.getProperty(BootstrapConstants.OUTPUT_SOURCES_DIR) != null) { + final String[] sourceDirectories = System.getProperty(BootstrapConstants.OUTPUT_SOURCES_DIR).split(","); + for (String sourceDirectory : sourceDirectories) { + final Path directory = Paths.get(sourceDirectory); + if (Files.exists(directory) && !rootBuilder.contains(directory)) { + rootBuilder.add(directory); + } + } + } + runnerBuilder.setApplicationRoot(rootBuilder.build()); + + CuratedApplication curatedApplication = runnerBuilder + .setTest(true) + .build() + .bootstrap(); + + Index testClassesIndex = TestClassIndexer.indexTestClasses(requiredTestClass); + // we need to write the Index to make it reusable from other parts of the testing infrastructure that run in different ClassLoaders + TestClassIndexer.writeIndex(testClassesIndex, requiredTestClass); + + Map propertyMap = new HashMap<>(); + curatedApplication + .createAugmentor().performCustomBuild(NativeDevDbHandler.class.getName(), new BiConsumer() { + @Override + public void accept(String s, String s2) { + propertyMap.put(s, s2); + } + }, DevDbResultBuildItem.class.getName()); + return propertyMap; + } + private ExtensionState doNativeStart(ExtensionContext context, Class profile) throws Throwable { + Map devDbProps = handleDevDb(context); quarkusTestProfile = profile; TestResourceManager testResourceManager = null; try { @@ -130,7 +232,7 @@ private ExtensionState doNativeStart(ExtensionContext context, Class additional = new HashMap<>(); + final Map additional = new HashMap<>(devDbProps); if (profile != null) { profileInstance = profile.newInstance(); additional.putAll(profileInstance.getConfigOverrides());