diff --git a/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProvider.java b/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProvider.java index d5065b9f689f..51dbf9e84412 100644 --- a/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProvider.java +++ b/module/spring-boot-flyway/src/main/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProvider.java @@ -38,6 +38,7 @@ import org.springframework.core.NativeDetector; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.PathMatcher; /** * A Flyway {@link ResourceProvider} which supports GraalVM native-image. @@ -46,6 +47,8 @@ * {@link PathMatchingResourcePatternResolver} to find migration files in a native image. * * @author Moritz Halbritter + * @author Dongliang Xie + * @author Stephane Nicoll */ class NativeImageResourceProvider implements ResourceProvider { @@ -106,9 +109,8 @@ public Collection getResources(String prefix, String[] suffixe } private ClassPathResource asClassPathResource(LocatedResource locatedResource) { - Location location = locatedResource.location(); - String fileNameWithAbsolutePath = location.getRootPath() + "/" + locatedResource.resource().getFilename(); - return new ClassPathResource(location, fileNameWithAbsolutePath, this.classLoader, this.encoding); + return new ClassPathResource(locatedResource.location(), locatedResource.path(), this.classLoader, + this.encoding); } private void ensureInitialized() { @@ -140,11 +142,28 @@ private void initialize() { } Resource[] resources = getResources(resolver, location, root); for (Resource resource : resources) { - this.locatedResources.add(new LocatedResource(resource, location)); + this.locatedResources.add(new LocatedResource(resource, location, + getClassPathResourcePath(resolver.getPathMatcher(), location, root, resource))); } } } + private String getClassPathResourcePath(PathMatcher pathMatcher, Location location, Resource root, + Resource resource) { + if (resource instanceof org.springframework.core.io.ClassPathResource classPathResource) { + return classPathResource.getPath(); + } + try { + String rootPath = location.getRootPath(); + String relativePath = pathMatcher.extractPathWithinPattern(root.getURI() + "/**/*", + resource.getURI().toString()); + return (rootPath.isEmpty()) ? relativePath : rootPath + "/" + relativePath; + } + catch (IOException ex) { + throw new UncheckedIOException("Failed to determine path for " + resource, ex); + } + } + private Resource[] getResources(PathMatchingResourcePatternResolver resolver, Location location, Resource root) { try { return resolver.getResources(root.getURI() + "/**/*"); @@ -154,7 +173,7 @@ private Resource[] getResources(PathMatchingResourcePatternResolver resolver, Lo } } - private record LocatedResource(Resource resource, Location location) { + private record LocatedResource(Resource resource, Location location, String path) { } diff --git a/module/spring-boot-flyway/src/test/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProviderTests.java b/module/spring-boot-flyway/src/test/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProviderTests.java new file mode 100644 index 000000000000..4fd5853e93be --- /dev/null +++ b/module/spring-boot-flyway/src/test/java/org/springframework/boot/flyway/autoconfigure/NativeImageResourceProviderTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-present 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.flyway.autoconfigure; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.flywaydb.core.api.Location; +import org.flywaydb.core.api.ResourceProvider; +import org.flywaydb.core.api.resource.LoadableResource; +import org.flywaydb.core.internal.scanner.Scanner; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.testsupport.classpath.ForkedClassPath; +import org.springframework.boot.testsupport.classpath.resources.WithResource; +import org.springframework.util.FileCopyUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link NativeImageResourceProvider}. + * + * @author Dongliang Xie + * @author Stephane Nicoll + */ +class NativeImageResourceProviderTests { + + @Test + @ForkedClassPath + @WithResource(name = "db/migration/nested/V2__users.sql", content = "select 1;") + void nativeImageResourceProviderShouldReadNestedMigrations() throws IOException { + System.setProperty("org.graalvm.nativeimage.imagecode", "true"); + try { + Scanner scanner = mock(); + given(scanner.getResources("V", ".sql")).willReturn(Collections.emptyList()); + Location location = Location.fromPath("classpath:", "db/migration"); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ResourceProvider resourceProvider = new NativeImageResourceProvider(scanner, classLoader, List.of(location), + StandardCharsets.UTF_8, true); + Collection migrations = resourceProvider.getResources("V", new String[] { ".sql" }); + assertThat(migrations).hasSize(1); + LoadableResource migration = migrations.iterator().next(); + assertThat(FileCopyUtils.copyToString(migration.read())).isEqualTo("select 1;"); + } + finally { + System.clearProperty("org.graalvm.nativeimage.imagecode"); + } + } + +}