Add Flyway native-image support
The ResourceProviderCustomizer, which is used by FlywayAutoConfiguration gets replaced with NativeImageResourceProviderCustomizer when running in AOT mode. The NativeImageResourceProvider does the heavy lifting when running in a native image: it uses PathMatchingResourcePatternResolver to find the migration files. Closes gh-31999
This commit is contained in:
parent
3acdf590b7
commit
b986a9b12e
|
|
@ -234,6 +234,7 @@ dependencies {
|
|||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("org.skyscreamer:jsonassert")
|
||||
testImplementation("org.springframework:spring-test")
|
||||
testImplementation("org.springframework:spring-core-test")
|
||||
testImplementation("org.springframework.graphql:spring-graphql-test")
|
||||
testImplementation("org.springframework.kafka:spring-kafka-test")
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
|
|
@ -258,4 +259,4 @@ tasks.named("checkSpringConfigurationMetadata").configure {
|
|||
"spring.datasource.tomcat.*",
|
||||
"spring.groovy.template.configuration.*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import org.flywaydb.core.api.configuration.FluentConfiguration;
|
|||
import org.flywaydb.core.api.migration.JavaMigration;
|
||||
import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
|
@ -42,6 +44,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
|
||||
|
|
@ -56,6 +59,7 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
|
@ -81,6 +85,7 @@ import org.springframework.util.StringUtils;
|
|||
* @author András Deák
|
||||
* @author Semyon Danilov
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
|
||||
|
|
@ -89,6 +94,7 @@ import org.springframework.util.StringUtils;
|
|||
@Conditional(FlywayDataSourceCondition.class)
|
||||
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
|
||||
@Import(DatabaseInitializationDependencyConfigurer.class)
|
||||
@ImportRuntimeHints(FlywayAutoConfigurationRuntimeHints.class)
|
||||
public class FlywayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
|
@ -108,17 +114,24 @@ public class FlywayAutoConfiguration {
|
|||
@EnableConfigurationProperties(FlywayProperties.class)
|
||||
public static class FlywayConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceProviderCustomizer resourceProviderCustomizer() {
|
||||
return new ResourceProviderCustomizer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader,
|
||||
ObjectProvider<DataSource> dataSource, @FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
|
||||
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
|
||||
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
|
||||
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks,
|
||||
ResourceProviderCustomizer resourceProviderCustomizer) {
|
||||
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
|
||||
configureDataSource(configuration, properties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
|
||||
configureProperties(configuration, properties);
|
||||
configureCallbacks(configuration, callbacks.orderedStream().toList());
|
||||
configureJavaMigrations(configuration, javaMigrations.orderedStream().toList());
|
||||
fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
|
||||
resourceProviderCustomizer.customize(configuration);
|
||||
return configuration.load();
|
||||
}
|
||||
|
||||
|
|
@ -349,4 +362,13 @@ public class FlywayAutoConfiguration {
|
|||
|
||||
}
|
||||
|
||||
static class FlywayAutoConfigurationRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerPattern("db/migration/*");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.flywaydb.core.api.FlywayException;
|
||||
import org.flywaydb.core.api.Location;
|
||||
import org.flywaydb.core.api.ResourceProvider;
|
||||
import org.flywaydb.core.api.resource.LoadableResource;
|
||||
import org.flywaydb.core.internal.resource.classpath.ClassPathResource;
|
||||
import org.flywaydb.core.internal.scanner.Scanner;
|
||||
import org.flywaydb.core.internal.util.StringUtils;
|
||||
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
|
||||
/**
|
||||
* A Flyway {@link ResourceProvider} which supports GraalVM native-image.
|
||||
* <p>
|
||||
* It delegates work to Flyways {@link Scanner}, and additionally uses
|
||||
* {@link PathMatchingResourcePatternResolver} to find migration files in a native image.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class NativeImageResourceProvider implements ResourceProvider {
|
||||
|
||||
private final Scanner<?> scanner;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final Collection<Location> locations;
|
||||
|
||||
private final Charset encoding;
|
||||
|
||||
private final boolean failOnMissingLocations;
|
||||
|
||||
private final List<ResourceWithLocation> resources = new ArrayList<>();
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
NativeImageResourceProvider(Scanner<?> scanner, ClassLoader classLoader, Collection<Location> locations,
|
||||
Charset encoding, boolean failOnMissingLocations) {
|
||||
this.scanner = scanner;
|
||||
this.classLoader = classLoader;
|
||||
this.locations = locations;
|
||||
this.encoding = encoding;
|
||||
this.failOnMissingLocations = failOnMissingLocations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadableResource getResource(String name) {
|
||||
if (!NativeDetector.inNativeImage()) {
|
||||
return this.scanner.getResource(name);
|
||||
}
|
||||
LoadableResource resource = this.scanner.getResource(name);
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
if (this.classLoader.getResource(name) == null) {
|
||||
return null;
|
||||
}
|
||||
return new ClassPathResource(null, name, this.classLoader, this.encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LoadableResource> getResources(String prefix, String[] suffixes) {
|
||||
if (!NativeDetector.inNativeImage()) {
|
||||
return this.scanner.getResources(prefix, suffixes);
|
||||
}
|
||||
ensureInitialized();
|
||||
List<LoadableResource> result = new ArrayList<>(this.scanner.getResources(prefix, suffixes));
|
||||
this.resources.stream().filter((r) -> StringUtils.startsAndEndsWith(r.resource.getFilename(), prefix, suffixes))
|
||||
.map((r) -> (LoadableResource) new ClassPathResource(r.location(),
|
||||
r.location().getPath() + "/" + r.resource().getFilename(), this.classLoader, this.encoding))
|
||||
.forEach(result::add);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ensureInitialized() {
|
||||
this.lock.lock();
|
||||
try {
|
||||
if (!this.initialized) {
|
||||
initialize();
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
for (Location location : this.locations) {
|
||||
if (!location.isClassPath()) {
|
||||
continue;
|
||||
}
|
||||
Resource root = resolver.getResource(location.getDescriptor());
|
||||
if (!root.exists()) {
|
||||
if (this.failOnMissingLocations) {
|
||||
throw new FlywayException("Location " + location.getDescriptor() + " doesn't exist");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Resource[] resources;
|
||||
try {
|
||||
resources = resolver.getResources(root.getURI() + "/*");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException("Failed to list resources for " + location.getDescriptor(), ex);
|
||||
}
|
||||
for (Resource resource : resources) {
|
||||
this.resources.add(new ResourceWithLocation(resource, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record ResourceWithLocation(Resource resource, Location location) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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.Arrays;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Registers {@link NativeImageResourceProvider} as a Flyway
|
||||
* {@link org.flywaydb.core.api.ResourceProvider}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(FluentConfiguration configuration) {
|
||||
if (configuration.getResourceProvider() == null) {
|
||||
Scanner<JavaMigration> scanner = new Scanner<>(JavaMigration.class,
|
||||
Arrays.asList(configuration.getLocations()), configuration.getClassLoader(),
|
||||
configuration.getEncoding(), configuration.isDetectEncoding(), false, new ResourceNameCache(),
|
||||
new LocationScannerCache(), configuration.isFailOnMissingLocations());
|
||||
NativeImageResourceProvider resourceProvider = new NativeImageResourceProvider(scanner,
|
||||
configuration.getClassLoader(), Arrays.asList(configuration.getLocations()),
|
||||
configuration.getEncoding(), configuration.isFailOnMissingLocations());
|
||||
configuration.resourceProvider(resourceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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 org.flywaydb.core.api.configuration.FluentConfiguration;
|
||||
|
||||
/**
|
||||
* A Flyway customizer which gets replaced with
|
||||
* {@link NativeImageResourceProviderCustomizer} when running in a native image.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ResourceProviderCustomizer {
|
||||
|
||||
void customize(FluentConfiguration configuration) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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.lang.reflect.Executable;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedMethod;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
|
||||
/**
|
||||
* Replaces the {@link ResourceProviderCustomizer} bean with a
|
||||
* {@link NativeImageResourceProviderCustomizer} bean.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ResourceProviderCustomizerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
if (registeredBean.getBeanClass().equals(ResourceProviderCustomizer.class)) {
|
||||
return BeanRegistrationAotContribution
|
||||
.withCustomCodeFragments((codeFragments) -> new AotContribution(codeFragments, registeredBean));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class AotContribution extends BeanRegistrationCodeFragmentsDecorator {
|
||||
|
||||
private final RegisteredBean registeredBean;
|
||||
|
||||
protected AotContribution(BeanRegistrationCodeFragments delegate, RegisteredBean registeredBean) {
|
||||
super(delegate);
|
||||
this.registeredBean = registeredBean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("getInstance", (method) -> {
|
||||
method.addJavadoc("Get the bean instance for '$L'.", this.registeredBean.getBeanName());
|
||||
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
|
||||
method.returns(NativeImageResourceProviderCustomizer.class);
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.addStatement("return new $T()", NativeImageResourceProviderCustomizer.class);
|
||||
method.addCode(code.build());
|
||||
});
|
||||
return generatedMethod.toMethodReference().toCodeBlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,3 +3,6 @@ org.springframework.boot.autoconfigure.template.TemplateRuntimeHints
|
|||
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
||||
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingProcessor
|
||||
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||
org.springframework.boot.autoconfigure.flyway.ResourceProviderCustomizerBeanRegistrationAotProcessor
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.UUID;
|
|||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.flywaydb.core.api.Location;
|
||||
import org.flywaydb.core.api.MigrationVersion;
|
||||
|
|
@ -41,9 +42,12 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
|
@ -94,6 +98,7 @@ import static org.mockito.Mockito.mock;
|
|||
* @author András Deák
|
||||
* @author Takaaki Shimbo
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class FlywayAutoConfigurationTests {
|
||||
|
|
@ -656,6 +661,21 @@ class FlywayAutoConfigurationTests {
|
|||
.isEqualTo("SPS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsResourceProviderCustomizer() {
|
||||
this.contextRunner.withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
|
||||
.run((context) -> assertThat(context).hasSingleBean(ResourceProviderCustomizer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterResourceHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
new FlywayAutoConfigurationRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
|
||||
Assertions.assertThat(RuntimeHintsPredicates.resource().forResource("db/migration/")).accepts(runtimeHints);
|
||||
Assertions.assertThat(RuntimeHintsPredicates.resource().forResource("db/migration/V1__init.sql"))
|
||||
.accepts(runtimeHints);
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> validateFlywayTeamsPropertyOnly(String propertyName) {
|
||||
return (context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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.flywaydb.core.internal.resource.NoopResourceProvider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link NativeImageResourceProviderCustomizer}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class NativeImageResourceProviderCustomizerTests {
|
||||
|
||||
private final NativeImageResourceProviderCustomizer customizer = new NativeImageResourceProviderCustomizer();
|
||||
|
||||
@Test
|
||||
void shouldInstallNativeImageResourceProvider() {
|
||||
FluentConfiguration configuration = new FluentConfiguration();
|
||||
assertThat(configuration.getResourceProvider()).isNull();
|
||||
this.customizer.customize(configuration);
|
||||
assertThat(configuration.getResourceProvider()).isInstanceOf(NativeImageResourceProvider.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeImageResourceProviderShouldFindMigrations() {
|
||||
FluentConfiguration configuration = new FluentConfiguration();
|
||||
this.customizer.customize(configuration);
|
||||
ResourceProvider resourceProvider = configuration.getResourceProvider();
|
||||
Collection<LoadableResource> migrations = resourceProvider.getResources("V", new String[] { ".sql" });
|
||||
LoadableResource migration = resourceProvider.getResource("V1__init.sql");
|
||||
assertThat(migrations).containsExactly(migration);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBackOffOnCustomResourceProvider() {
|
||||
FluentConfiguration configuration = new FluentConfiguration();
|
||||
configuration.resourceProvider(NoopResourceProvider.INSTANCE);
|
||||
this.customizer.customize(configuration);
|
||||
assertThat(configuration.getResourceProvider()).isEqualTo(NoopResourceProvider.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.AotServices;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.aot.ApplicationContextAotGenerator;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceProviderCustomizerBeanRegistrationAotProcessor}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ResourceProviderCustomizerBeanRegistrationAotProcessorTests {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
private final ResourceProviderCustomizerBeanRegistrationAotProcessor processor = new ResourceProviderCustomizerBeanRegistrationAotProcessor();
|
||||
|
||||
@Test
|
||||
void beanRegistrationAotProcessorIsRegistered() {
|
||||
assertThat(AotServices.factories().load(BeanRegistrationAotProcessor.class))
|
||||
.anyMatch(ResourceProviderCustomizerBeanRegistrationAotProcessor.class::isInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreNonResourceProviderCustomizerBeans() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
|
||||
this.beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||
BeanRegistrationAotContribution contribution = this.processor
|
||||
.processAheadOfTime(RegisteredBean.of(this.beanFactory, "test"));
|
||||
assertThat(contribution).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void shouldReplaceResourceProviderCustomizer() {
|
||||
compile(createContext(ResourceProviderCustomizerConfiguration.class), (freshContext) -> {
|
||||
freshContext.refresh();
|
||||
ResourceProviderCustomizer bean = freshContext.getBean(ResourceProviderCustomizer.class);
|
||||
assertThat(bean).isInstanceOf(NativeImageResourceProviderCustomizer.class);
|
||||
});
|
||||
}
|
||||
|
||||
private GenericApplicationContext createContext(Class<?>... types) {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
Arrays.stream(types).forEach((type) -> context.registerBean(type));
|
||||
return context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(GenericApplicationContext context, Consumer<GenericApplicationContext> freshContext) {
|
||||
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
|
||||
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext);
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(generationContext).compile((compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
|
||||
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
|
||||
.getInstance(ApplicationContextInitializer.class, className.toString());
|
||||
initializer.initialize(freshApplicationContext);
|
||||
freshContext.accept(freshApplicationContext);
|
||||
});
|
||||
}
|
||||
|
||||
static class TestTarget {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ResourceProviderCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceProviderCustomizer resourceProviderCustomizer() {
|
||||
return new ResourceProviderCustomizer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue