From 345edb13013095f104cbdf2fee4c6bc3e7dd4933 Mon Sep 17 00:00:00 2001 From: Maziz Date: Sun, 19 May 2024 13:05:55 +0800 Subject: [PATCH 1/2] Fix Flyway 10 in a GraalVM native image See gh-40821 --- ...NativeImageResourceProviderCustomizer.java | 47 +++++++++++++++-- ...eImageResourceProviderCustomizerTests.java | 52 +++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java index 1dd7b6e55ec..4e7668379eb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.flyway; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import org.flywaydb.core.api.configuration.FluentConfiguration; @@ -24,21 +26,21 @@ import org.flywaydb.core.internal.scanner.LocationScannerCache; import org.flywaydb.core.internal.scanner.ResourceNameCache; import org.flywaydb.core.internal.scanner.Scanner; +import org.springframework.util.ClassUtils; + /** * Registers {@link NativeImageResourceProvider} as a Flyway * {@link org.flywaydb.core.api.ResourceProvider}. * * @author Moritz Halbritter + * @author Maziz Esa */ class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer { @Override public void customize(FluentConfiguration configuration) { if (configuration.getResourceProvider() == null) { - Scanner scanner = new Scanner<>(JavaMigration.class, - Arrays.asList(configuration.getLocations()), configuration.getClassLoader(), - configuration.getEncoding(), configuration.isDetectEncoding(), false, new ResourceNameCache(), - new LocationScannerCache(), configuration.isFailOnMissingLocations()); + final var scanner = getFlyway9OrFallbackTo10ScannerObject(configuration); NativeImageResourceProvider resourceProvider = new NativeImageResourceProvider(scanner, configuration.getClassLoader(), Arrays.asList(configuration.getLocations()), configuration.getEncoding(), configuration.isFailOnMissingLocations()); @@ -46,4 +48,41 @@ class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer { } } + private static Scanner getFlyway9OrFallbackTo10ScannerObject(FluentConfiguration configuration) { + Scanner scanner; + try { + scanner = getFlyway9Scanner(configuration); + } + catch (NoSuchMethodError noSuchMethodError) { + // happens when scanner is flyway version 10, which the constructor accepts + // different number of parameters. + scanner = getFlyway10Scanner(configuration); + } + return scanner; + } + + private static Scanner getFlyway10Scanner(FluentConfiguration configuration) { + final Constructor scannerConstructor; + final Scanner scanner; + try { + scannerConstructor = ClassUtils.forName("org.flywaydb.core.internal.scanner.Scanner", null) + .getDeclaredConstructors()[0]; + scanner = (Scanner) scannerConstructor.newInstance(JavaMigration.class, false, new ResourceNameCache(), + new LocationScannerCache(), configuration); + } + catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + return scanner; + } + + private static Scanner getFlyway9Scanner(FluentConfiguration configuration) { + Scanner scanner; + scanner = new Scanner<>(JavaMigration.class, Arrays.asList(configuration.getLocations()), + configuration.getClassLoader(), configuration.getEncoding(), configuration.isDetectEncoding(), false, + new ResourceNameCache(), new LocationScannerCache(), configuration.isFailOnMissingLocations()); + return scanner; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java new file mode 100644 index 00000000000..9e3e5e4db3b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.flyway; + +import java.util.Collection; + +import org.flywaydb.core.api.ResourceProvider; +import org.flywaydb.core.api.configuration.FluentConfiguration; +import org.flywaydb.core.api.resource.LoadableResource; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.testsupport.classpath.ClassPathOverrides; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NativeImageResourceProviderCustomizer} with Flyway 10. + * + * @author Moritz Halbritter + * @author Andy Wilkinson + * @author Maziz + */ +@ClassPathOverrides("org.flywaydb:flyway-core:10.12.0") +class Flyway10NativeImageResourceProviderCustomizerTests { + + private final NativeImageResourceProviderCustomizer customizer = new NativeImageResourceProviderCustomizer(); + + @Test + void nativeImageResourceProviderShouldFindMigrations() { + FluentConfiguration configuration = new FluentConfiguration(); + this.customizer.customize(configuration); + ResourceProvider resourceProvider = configuration.getResourceProvider(); + Collection migrations = resourceProvider.getResources("V", new String[] { ".sql" }); + LoadableResource migration = resourceProvider.getResource("V1__init.sql"); + assertThat(migrations).containsExactly(migration); + } + +} From 10e23b8f35520bac6aa83b4a3225257d46b75427 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 29 May 2024 14:17:50 +0100 Subject: [PATCH 2/2] Polish "Fix Flyway 10 in a GraalVM native image" See gh-40821 --- .../flyway/FlywayAutoConfiguration.java | 8 +++- ...NativeImageResourceProviderCustomizer.java | 46 +++++++------------ ...ImageResourceProviderCustomizerTests.java} | 6 +-- 3 files changed, 26 insertions(+), 34 deletions(-) rename spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/{Flyway10NativeImageResourceProviderCustomizerTests.java => Flyway10xNativeImageResourceProviderCustomizerTests.java} (92%) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index a7e93b42157..af714f241d2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,9 +36,11 @@ import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.extensibility.ConfigurationExtension; import org.flywaydb.core.internal.database.postgresql.PostgreSQLConfigurationExtension; +import org.flywaydb.core.internal.scanner.Scanner; import org.flywaydb.database.oracle.OracleConfigurationExtension; import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; +import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.beans.factory.ObjectProvider; @@ -78,6 +80,7 @@ import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -461,6 +464,9 @@ public class FlywayAutoConfiguration { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { hints.resources().registerPattern("db/migration/*"); + if (ClassUtils.isPresent("org.flywaydb.core.extensibility.Tier", classLoader)) { + hints.reflection().registerType(Scanner.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + } } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java index 4e7668379eb..3abbee4bf94 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/NativeImageResourceProviderCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,15 @@ package org.springframework.boot.autoconfigure.flyway; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import org.flywaydb.core.api.configuration.Configuration; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.internal.scanner.LocationScannerCache; import org.flywaydb.core.internal.scanner.ResourceNameCache; import org.flywaydb.core.internal.scanner.Scanner; -import org.springframework.util.ClassUtils; - /** * Registers {@link NativeImageResourceProvider} as a Flyway * {@link org.flywaydb.core.api.ResourceProvider}. @@ -40,7 +38,7 @@ class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer { @Override public void customize(FluentConfiguration configuration) { if (configuration.getResourceProvider() == null) { - final var scanner = getFlyway9OrFallbackTo10ScannerObject(configuration); + Scanner scanner = createScanner(configuration); NativeImageResourceProvider resourceProvider = new NativeImageResourceProvider(scanner, configuration.getClassLoader(), Arrays.asList(configuration.getLocations()), configuration.getEncoding(), configuration.isFailOnMissingLocations()); @@ -48,41 +46,29 @@ class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer { } } - private static Scanner getFlyway9OrFallbackTo10ScannerObject(FluentConfiguration configuration) { - Scanner scanner; + private static Scanner createScanner(FluentConfiguration configuration) { try { - scanner = getFlyway9Scanner(configuration); + return new Scanner<>(JavaMigration.class, Arrays.asList(configuration.getLocations()), + configuration.getClassLoader(), configuration.getEncoding(), configuration.isDetectEncoding(), + false, new ResourceNameCache(), new LocationScannerCache(), + configuration.isFailOnMissingLocations()); } - catch (NoSuchMethodError noSuchMethodError) { - // happens when scanner is flyway version 10, which the constructor accepts - // different number of parameters. - scanner = getFlyway10Scanner(configuration); + catch (NoSuchMethodError ex) { + // Flyway 10 + return createFlyway10Scanner(configuration); } - return scanner; } - private static Scanner getFlyway10Scanner(FluentConfiguration configuration) { - final Constructor scannerConstructor; - final Scanner scanner; + private static Scanner createFlyway10Scanner(FluentConfiguration configuration) throws LinkageError { try { - scannerConstructor = ClassUtils.forName("org.flywaydb.core.internal.scanner.Scanner", null) - .getDeclaredConstructors()[0]; - scanner = (Scanner) scannerConstructor.newInstance(JavaMigration.class, false, new ResourceNameCache(), + Constructor scannerConstructor = Scanner.class.getDeclaredConstructor(Class.class, boolean.class, + ResourceNameCache.class, LocationScannerCache.class, Configuration.class); + return (Scanner) scannerConstructor.newInstance(JavaMigration.class, false, new ResourceNameCache(), new LocationScannerCache(), configuration); } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException - | InvocationTargetException ex) { + catch (Exception ex) { throw new RuntimeException(ex); } - return scanner; - } - - private static Scanner getFlyway9Scanner(FluentConfiguration configuration) { - Scanner scanner; - scanner = new Scanner<>(JavaMigration.class, Arrays.asList(configuration.getLocations()), - configuration.getClassLoader(), configuration.getEncoding(), configuration.isDetectEncoding(), false, - new ResourceNameCache(), new LocationScannerCache(), configuration.isFailOnMissingLocations()); - return scanner; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xNativeImageResourceProviderCustomizerTests.java similarity index 92% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xNativeImageResourceProviderCustomizerTests.java index 9e3e5e4db3b..29d60254b65 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10NativeImageResourceProviderCustomizerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/Flyway10xNativeImageResourceProviderCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Moritz Halbritter * @author Andy Wilkinson - * @author Maziz + * @author Maziz Esa */ @ClassPathOverrides("org.flywaydb:flyway-core:10.12.0") -class Flyway10NativeImageResourceProviderCustomizerTests { +class Flyway10xNativeImageResourceProviderCustomizerTests { private final NativeImageResourceProviderCustomizer customizer = new NativeImageResourceProviderCustomizer();