Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(flyway): multiple datasource migration does not work on native #8065

Merged
merged 3 commits into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {

Expand All @@ -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
Expand All @@ -72,11 +66,9 @@ void build(BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer,
BuildProducer<FeatureBuildItem> featureProducer,
BuildProducer<NativeImageResourceBuildItem> resourceProducer,
BuildProducer<BeanContainerListenerBuildItem> containerListenerProducer,
BuildProducer<GeneratedResourceBuildItem> generatedResourceProducer,
FlywayRecorder recorder,
List<JdbcDataSourceBuildItem> jdbcDataSourceBuildItems,
BuildProducer<GeneratedBeanBuildItem> generatedBeanBuildItem,
RecorderContext recorderContext) throws IOException, URISyntaxException {
BuildProducer<GeneratedBeanBuildItem> generatedBeanBuildItem) throws IOException, URISyntaxException {

featureProducer.produce(new FeatureBuildItem(FeatureBuildItem.FLYWAY));

Expand All @@ -88,22 +80,16 @@ void build(BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer,
.collect(Collectors.toSet());
new FlywayDatasourceBeanGenerator(dataSourceNames, generatedBeanBuildItem).createFlywayProducerBean();

registerNativeImageResources(resourceProducer, generatedResourceProducer,
discoverApplicationMigrations(getMigrationLocations(dataSourceNames)));
List<String> 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,
Expand All @@ -120,47 +106,25 @@ ServiceStartBuildItem configureRuntimeProperties(FlywayRecorder recorder,
return new ServiceStartBuildItem("flyway");
}

private void registerNativeImageResources(BuildProducer<NativeImageResourceBuildItem> resource,
BuildProducer<GeneratedResourceBuildItem> generatedResourceProducer,
List<String> applicationMigrations)
throws IOException, URISyntaxException {
final List<String> 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.
* <p>
* A {@link LinkedHashSet} is used to avoid duplications.
*
* @param dataSourceInitializedBuildItem {@link JdbcDataSourceInitializedBuildItem}
* @return {@link Collection} of {@link String}s
*/
private Collection<String> getMigrationLocations(Collection<String> dataSourceNames) {
Collection<String> 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<String> discoverApplicationMigrations(Collection<String> locations)
throws IOException, URISyntaxException {
List<String> resources = new ArrayList<>();
private List<String> discoverApplicationMigrations(Collection<String> locations) throws IOException, URISyntaxException {
try {
List<String> applicationMigrationResources = new ArrayList<>();
// Locations can be a comma separated list
for (String location : locations) {
// Strip any 'classpath:' protocol prefixes because they are assumed
Expand All @@ -187,11 +151,11 @@ private List<String> discoverApplicationMigrations(Collection<String> locations)
applicationMigrations = null;
}
if (applicationMigrations != null) {
resources.addAll(applicationMigrations);
applicationMigrationResources.addAll(applicationMigrations);
}
}
}
return resources;
return applicationMigrationResources;
} catch (IOException | URISyntaxException e) {
throw e;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String> migrationFiles) {
QuarkusPathLocationScanner.setApplicationMigrationFiles(migrationFiles);
}

public BeanContainerListener setFlywayBuildConfig(FlywayBuildTimeConfig flywayBuildConfig) {
return beanContainer -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> applicationMigrationFiles;

private final Collection<LoadableResource> scannedResources;

public QuarkusPathLocationScanner(Collection<Location> 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<LoadableResource> 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<String> migrations = reader.lines().collect(Collectors.toList());
Set<LoadableResource> 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<Location> 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;
}

/**
Expand All @@ -60,4 +67,8 @@ public Collection<Class<?>> scanForClasses() {
// Classes are not supported in native mode
return Collections.emptyList();
}

public static void setApplicationMigrationFiles(List<String> applicationMigrationFiles) {
QuarkusPathLocationScanner.applicationMigrationFiles = applicationMigrationFiles;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public final class ScannerSubstitutions {
@Substitute
public ScannerSubstitutions(Class<?> implementedInterface, Collection<Location> locations, ClassLoader classLoader,
Charset encoding, ResourceNameCache resourceNameCache) {
ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner();
ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(locations);
resources.addAll(quarkusScanner.scanForResources());
classes.addAll(quarkusScanner.scanForClasses());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@
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)
public class FlywayFunctionalityResource {
@Inject
Flyway flyway;

@Inject
@FlywayDataSource("second-datasource")
Flyway flyway2;

@GET
@Path("migrate")
public String doMigrateAuto() {
Expand All @@ -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<String, String> returnPlaceholders() {
return flyway.getConfiguration().getPlaceholders();
}

}
}
15 changes: 13 additions & 2 deletions integration-tests/flyway/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
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
Original file line number Diff line number Diff line change
@@ -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');
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down