diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index b065924e34e52..58aea321754c8 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -6,7 +6,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -39,15 +38,12 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; -import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.flyway.runtime.FlywayBuildTimeConfig; import io.quarkus.flyway.runtime.FlywayProducer; import io.quarkus.flyway.runtime.FlywayRecorder; import io.quarkus.flyway.runtime.FlywayRuntimeConfig; -import io.quarkus.flyway.runtime.graal.QuarkusPathLocationScanner; class FlywayProcessor { @@ -56,9 +52,7 @@ class FlywayProcessor { private static final String FILE_APPLICATION_MIGRATIONS_PROTOCOL = "file"; private static final Logger LOGGER = Logger.getLogger(FlywayProcessor.class); - /** - * Flyway build config - */ + FlywayBuildTimeConfig flywayBuildConfig; @BuildStep @@ -72,11 +66,9 @@ void build(BuildProducer additionalBeanProducer, BuildProducer featureProducer, BuildProducer resourceProducer, BuildProducer containerListenerProducer, - BuildProducer generatedResourceProducer, FlywayRecorder recorder, List jdbcDataSourceBuildItems, - BuildProducer generatedBeanBuildItem, - RecorderContext recorderContext) throws IOException, URISyntaxException { + BuildProducer generatedBeanBuildItem) throws IOException, URISyntaxException { featureProducer.produce(new FeatureBuildItem(FeatureBuildItem.FLYWAY)); @@ -88,22 +80,16 @@ void build(BuildProducer additionalBeanProducer, .collect(Collectors.toSet()); new FlywayDatasourceBeanGenerator(dataSourceNames, generatedBeanBuildItem).createFlywayProducerBean(); - registerNativeImageResources(resourceProducer, generatedResourceProducer, - discoverApplicationMigrations(getMigrationLocations(dataSourceNames))); + List applicationMigrations = discoverApplicationMigrations(getMigrationLocations(dataSourceNames)); + recorder.setApplicationMigrationFiles(applicationMigrations); + + resourceProducer.produce(new NativeImageResourceBuildItem(applicationMigrations.toArray(new String[0]))); - containerListenerProducer.produce( - new BeanContainerListenerBuildItem(recorder.setFlywayBuildConfig(flywayBuildConfig))); + containerListenerProducer.produce(new BeanContainerListenerBuildItem(recorder.setFlywayBuildConfig(flywayBuildConfig))); } - /** - * Handles all the operations that can be recorded in the RUNTIME_INIT execution time phase - * - * @param recorder Used to set the runtime config - * @param flywayRuntimeConfig The Flyway configuration - * @param dataSourceInitializedBuildItem Added this dependency to be sure that Agroal is initialized first - */ - @Record(ExecutionTime.RUNTIME_INIT) @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) ServiceStartBuildItem configureRuntimeProperties(FlywayRecorder recorder, FlywayRuntimeConfig flywayRuntimeConfig, BeanContainerBuildItem beanContainer, @@ -120,47 +106,25 @@ ServiceStartBuildItem configureRuntimeProperties(FlywayRecorder recorder, return new ServiceStartBuildItem("flyway"); } - private void registerNativeImageResources(BuildProducer resource, - BuildProducer generatedResourceProducer, - List applicationMigrations) - throws IOException, URISyntaxException { - final List nativeResources = new ArrayList<>(); - nativeResources.addAll(applicationMigrations); - // Store application migration in a generated resource that will be accessed later by the Quarkus-Flyway path scanner - String resourcesList = applicationMigrations - .stream() - .collect(Collectors.joining("\n", "", "\n")); - generatedResourceProducer.produce( - new GeneratedResourceBuildItem( - QuarkusPathLocationScanner.MIGRATIONS_LIST_FILE, - resourcesList.getBytes(StandardCharsets.UTF_8))); - nativeResources.add(QuarkusPathLocationScanner.MIGRATIONS_LIST_FILE); - resource.produce(new NativeImageResourceBuildItem(nativeResources.toArray(new String[0]))); - } - /** * Collects the configured migration locations for the default and all named DataSources. - *

- * A {@link LinkedHashSet} is used to avoid duplications. - * - * @param dataSourceInitializedBuildItem {@link JdbcDataSourceInitializedBuildItem} - * @return {@link Collection} of {@link String}s */ private Collection getMigrationLocations(Collection dataSourceNames) { Collection migrationLocations = dataSourceNames.stream() .map(flywayBuildConfig::getConfigForDataSourceName) .flatMap(config -> config.locations.stream()) .collect(Collectors.toCollection(LinkedHashSet::new)); + if (DataSourceUtil.hasDefault(dataSourceNames)) { migrationLocations.addAll(flywayBuildConfig.defaultDataSource.locations); } + return migrationLocations; } - private List discoverApplicationMigrations(Collection locations) - throws IOException, URISyntaxException { - List resources = new ArrayList<>(); + private List discoverApplicationMigrations(Collection locations) throws IOException, URISyntaxException { try { + List applicationMigrationResources = new ArrayList<>(); // Locations can be a comma separated list for (String location : locations) { // Strip any 'classpath:' protocol prefixes because they are assumed @@ -187,11 +151,11 @@ private List discoverApplicationMigrations(Collection locations) applicationMigrations = null; } if (applicationMigrations != null) { - resources.addAll(applicationMigrations); + applicationMigrationResources.addAll(applicationMigrations); } } } - return resources; + return applicationMigrationResources; } catch (IOException | URISyntaxException e) { throw e; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index b6167ec37d340..3370eb5e94c4a 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -1,6 +1,7 @@ package io.quarkus.flyway.runtime; import java.lang.annotation.Annotation; +import java.util.List; import java.util.Map.Entry; import javax.enterprise.inject.Default; @@ -11,10 +12,14 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.BeanContainerListener; import io.quarkus.flyway.FlywayDataSource; +import io.quarkus.flyway.runtime.graal.QuarkusPathLocationScanner; import io.quarkus.runtime.annotations.Recorder; @Recorder public class FlywayRecorder { + public void setApplicationMigrationFiles(List migrationFiles) { + QuarkusPathLocationScanner.setApplicationMigrationFiles(migrationFiles); + } public BeanContainerListener setFlywayBuildConfig(FlywayBuildTimeConfig flywayBuildConfig) { return beanContainer -> { diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java index 3e19ac296b414..1b50906be9054 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java @@ -1,52 +1,59 @@ package io.quarkus.flyway.runtime.graal; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import org.flywaydb.core.api.logging.Log; -import org.flywaydb.core.api.logging.LogFactory; +import org.flywaydb.core.api.Location; import org.flywaydb.core.internal.resource.LoadableResource; import org.flywaydb.core.internal.resource.classpath.ClassPathResource; import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner; +import org.jboss.logging.Logger; public final class QuarkusPathLocationScanner implements ResourceAndClassScanner { - private static final Log LOG = LogFactory.getLog(QuarkusPathLocationScanner.class); - /** - * File with the migrations list. It is generated dynamically in the Flyway Quarkus Processor - */ - public final static String MIGRATIONS_LIST_FILE = "META-INF/flyway-migrations.txt"; + private static final Logger LOGGER = Logger.getLogger(QuarkusPathLocationScanner.class); + private static final String LOCATION_SEPARATOR = "/"; + private static List applicationMigrationFiles; + + private final Collection scannedResources; + + public QuarkusPathLocationScanner(Collection locations) { + this.scannedResources = new ArrayList<>(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + for (String migrationFile : applicationMigrationFiles) { + if (canHandleMigrationFile(locations, migrationFile)) { + LOGGER.debugf("Loading %s", migrationFile); + scannedResources.add(new ClassPathResource(null, migrationFile, classLoader, StandardCharsets.UTF_8)); + } + } + + } /** - * Returns the migrations loaded into the {@see MIGRATIONS_LIST_FILE} * * @return The resources that were found. */ @Override public Collection scanForResources() { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try (InputStream resource = classLoader.getResourceAsStream(MIGRATIONS_LIST_FILE); - BufferedReader reader = new BufferedReader( - new InputStreamReader(Objects.requireNonNull(resource), StandardCharsets.UTF_8))) { - List migrations = reader.lines().collect(Collectors.toList()); - Set resources = new HashSet<>(); - for (String file : migrations) { - LOG.debug("Loading " + file); - resources.add(new ClassPathResource(null, file, classLoader, StandardCharsets.UTF_8)); + return scannedResources; + } + + private boolean canHandleMigrationFile(Collection locations, String migrationFile) { + for (Location location : locations) { + String locationPath = location.getPath(); + if (!locationPath.endsWith(LOCATION_SEPARATOR)) { + locationPath += "/"; + } + + if (migrationFile.startsWith(locationPath)) { + return true; } - return resources; - } catch (IOException e) { - throw new IllegalStateException(e); } + + return false; } /** @@ -60,4 +67,8 @@ public Collection> scanForClasses() { // Classes are not supported in native mode return Collections.emptyList(); } + + public static void setApplicationMigrationFiles(List applicationMigrationFiles) { + QuarkusPathLocationScanner.applicationMigrationFiles = applicationMigrationFiles; + } } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java index 46ddd977c8b0b..6000dae387abd 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java @@ -36,7 +36,7 @@ public final class ScannerSubstitutions { @Substitute public ScannerSubstitutions(Class implementedInterface, Collection locations, ClassLoader classLoader, Charset encoding, ResourceNameCache resourceNameCache) { - ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(); + ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(locations); resources.addAll(quarkusScanner.scanForResources()); classes.addAll(quarkusScanner.scanForClasses()); } diff --git a/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java index 046e9529485c5..c95001a96b428 100644 --- a/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java +++ b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java @@ -13,6 +13,8 @@ import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationVersion; +import io.quarkus.flyway.FlywayDataSource; + @Path("/") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @@ -20,6 +22,10 @@ public class FlywayFunctionalityResource { @Inject Flyway flyway; + @Inject + @FlywayDataSource("second-datasource") + Flyway flyway2; + @GET @Path("migrate") public String doMigrateAuto() { @@ -29,10 +35,19 @@ public String doMigrateAuto() { return version.toString(); } + @GET + @Path("multiple-flyway-migratation") + public String doMigratationOfSecondDataSource() { + flyway2.migrate(); + MigrationVersion version = Objects.requireNonNull(flyway2.info().current().getVersion(), + "Version is null! Migration was not applied for second datasource"); + return version.toString(); + } + @GET @Path("placeholders") public Map returnPlaceholders() { return flyway.getConfiguration().getPlaceholders(); } -} \ No newline at end of file +} diff --git a/integration-tests/flyway/src/main/resources/application.properties b/integration-tests/flyway/src/main/resources/application.properties index 3951436678121..c755b65541234 100644 --- a/integration-tests/flyway/src/main/resources/application.properties +++ b/integration-tests/flyway/src/main/resources/application.properties @@ -5,8 +5,9 @@ quarkus.log.category."io.quarkus.flyway".level=DEBUG quarkus.datasource.db-kind=h2 quarkus.datasource.username=sa quarkus.datasource.password=sa + +# default flyway configuration properties quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test_quarkus;DB_CLOSE_DELAY=-1 -# Flyway config properties quarkus.flyway.connect-retries=10 quarkus.flyway.schemas=TEST_SCHEMA quarkus.flyway.table=flyway_quarkus_history @@ -15,4 +16,14 @@ quarkus.flyway.sql-migration-prefix=V quarkus.flyway.migrate-at-start=true quarkus.flyway.placeholders.foo=bar -quarkus.hibernate-orm.database.generation=validate \ No newline at end of file +quarkus.hibernate-orm.database.generation=validate + +# second Agroal config +quarkus.datasource.second-datasource.db-kind=h2 +quarkus.datasource.second-datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test_multiple_flyway_datasource;DB_CLOSE_DELAY=-1 + +# configuration to test multiple flyway datasources +quarkus.flyway.second-datasource.locations=db/location3 +quarkus.flyway.second-datasource.sql-migration-prefix=V +quarkus.flyway.second-datasource.migrate-at-start=true +quarkus.flyway.second-datasource.placeholders.mambo=poa diff --git a/integration-tests/flyway/src/main/resources/db/location3/V1.0.0__Quarkus.sql b/integration-tests/flyway/src/main/resources/db/location3/V1.0.0__Quarkus.sql new file mode 100644 index 0000000000000..fb341850919bf --- /dev/null +++ b/integration-tests/flyway/src/main/resources/db/location3/V1.0.0__Quarkus.sql @@ -0,0 +1,7 @@ +CREATE TABLE multiple_flyway_test +( + id INT, + name VARCHAR(255) +); +INSERT INTO multiple_flyway_test(id, name) +VALUES (1, 'Multiple flyway datasources should work seamlessly in JVM and native mode'); \ No newline at end of file diff --git a/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java index 78814e706440f..306e67bf1bbe1 100644 --- a/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java +++ b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java @@ -18,6 +18,12 @@ public void testFlywayQuarkusFunctionality() { when().get("/flyway/migrate").then().body(is("1.0.1")); } + @Test + @DisplayName("Migrates a schema correctly using second instance of Flyway") + public void testMultipleFlywayQuarkusFunctionality() { + when().get("/flyway/multiple-flyway-migratation").then().body(is("1.0.0")); + } + @Test @DisplayName("Returns current placeholders") public void testPlaceholders() {