Skip to content

Commit 3c42363

Browse files
committed
Do not close GraalVM Native image FileSystem after classpath scanning
As can be seen in a modified version of the following example project, attempting to access a resource discovered via classpath scanning within a GraalVM native image -- for example via the Files.exists(...) invocation in FileSystemResource -- currently results in a ClosedFileSystemException since PathMatchingResourcePatternResolver explicitly closes the native image FileSystem that backs `resource:` resources. Sample project: https://github.com/joshlong/spring-boot-3-aot-graphql To address this issue, this commit removes the explicit close() invocation for custom FileSystems after classpath scanning in PathMatchingResourcePatternResolver. See spring-projects/spring-graphql#495 Closes gh-29397
1 parent 2ccfc70 commit 3c42363

File tree

2 files changed

+70
-60
lines changed

2 files changed

+70
-60
lines changed

spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java

Lines changed: 48 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.net.URL;
3232
import java.net.URLClassLoader;
3333
import java.net.URLConnection;
34-
import java.nio.file.FileSystem;
3534
import java.nio.file.FileSystemNotFoundException;
3635
import java.nio.file.FileSystems;
3736
import java.nio.file.Files;
@@ -748,76 +747,68 @@ protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource
748747
return Collections.emptySet();
749748
}
750749

751-
FileSystem fileSystem = null;
752-
try {
753-
Path rootPath = null;
754-
if (rootDirUri.isAbsolute() && !rootDirUri.isOpaque()) {
755-
// Prefer Path resolution from URI if possible
750+
Path rootPath = null;
751+
if (rootDirUri.isAbsolute() && !rootDirUri.isOpaque()) {
752+
// Prefer Path resolution from URI if possible
753+
try {
756754
try {
757-
try {
758-
rootPath = Path.of(rootDirUri);
759-
}
760-
catch (FileSystemNotFoundException ex) {
761-
// If the file system was not found, assume it's a custom file system that needs to be installed.
762-
fileSystem = FileSystems.newFileSystem(rootDirUri, Map.of(), ClassUtils.getDefaultClassLoader());
763-
rootPath = Path.of(rootDirUri);
764-
}
755+
rootPath = Path.of(rootDirUri);
765756
}
766-
catch (Exception ex) {
767-
if (logger.isDebugEnabled()) {
768-
logger.debug("Failed to resolve %s in file system: %s".formatted(rootDirUri, ex));
769-
}
770-
// Fallback via Resource.getFile() below
757+
catch (FileSystemNotFoundException ex) {
758+
// If the file system was not found, assume it's a custom file system that needs to be installed.
759+
FileSystems.newFileSystem(rootDirUri, Map.of(), ClassUtils.getDefaultClassLoader());
760+
rootPath = Path.of(rootDirUri);
771761
}
772762
}
773-
if (rootPath == null) {
774-
// Resource.getFile() resolution as a fallback -
775-
// for custom URI formats and custom Resource implementations
776-
rootPath = Path.of(rootDirResource.getFile().getAbsolutePath());
763+
catch (Exception ex) {
764+
if (logger.isDebugEnabled()) {
765+
logger.debug("Failed to resolve %s in file system: %s".formatted(rootDirUri, ex));
766+
}
767+
// Fallback via Resource.getFile() below
777768
}
769+
}
770+
if (rootPath == null) {
771+
// Resource.getFile() resolution as a fallback -
772+
// for custom URI formats and custom Resource implementations
773+
rootPath = Path.of(rootDirResource.getFile().getAbsolutePath());
774+
}
778775

779-
String rootDir = StringUtils.cleanPath(rootPath.toString());
780-
if (!rootDir.endsWith("/")) {
781-
rootDir += "/";
782-
}
776+
String rootDir = StringUtils.cleanPath(rootPath.toString());
777+
if (!rootDir.endsWith("/")) {
778+
rootDir += "/";
779+
}
783780

784-
Path rootPathForPattern = rootPath;
785-
String resourcePattern = rootDir + StringUtils.cleanPath(subPattern);
786-
Predicate<Path> isMatchingFile = path -> (!path.equals(rootPathForPattern) &&
787-
getPathMatcher().match(resourcePattern, StringUtils.cleanPath(path.toString())));
781+
Path rootPathForPattern = rootPath;
782+
String resourcePattern = rootDir + StringUtils.cleanPath(subPattern);
783+
Predicate<Path> isMatchingFile = path -> (!path.equals(rootPathForPattern) &&
784+
getPathMatcher().match(resourcePattern, StringUtils.cleanPath(path.toString())));
788785

789-
if (logger.isTraceEnabled()) {
790-
logger.trace("Searching directory [%s] for files matching pattern [%s]"
791-
.formatted(rootPath.toAbsolutePath(), subPattern));
792-
}
786+
if (logger.isTraceEnabled()) {
787+
logger.trace("Searching directory [%s] for files matching pattern [%s]"
788+
.formatted(rootPath.toAbsolutePath(), subPattern));
789+
}
793790

794-
Set<Resource> result = new LinkedHashSet<>();
795-
try (Stream<Path> files = Files.walk(rootPath)) {
796-
files.filter(isMatchingFile).sorted().forEach(file -> {
797-
try {
798-
result.add(new FileSystemResource(file));
799-
}
800-
catch (Exception ex) {
801-
if (logger.isDebugEnabled()) {
802-
logger.debug("Failed to convert file %s to an org.springframework.core.io.Resource: %s"
803-
.formatted(file, ex));
804-
}
791+
Set<Resource> result = new LinkedHashSet<>();
792+
try (Stream<Path> files = Files.walk(rootPath)) {
793+
files.filter(isMatchingFile).sorted().forEach(file -> {
794+
try {
795+
result.add(new FileSystemResource(file));
796+
}
797+
catch (Exception ex) {
798+
if (logger.isDebugEnabled()) {
799+
logger.debug("Failed to convert file %s to an org.springframework.core.io.Resource: %s"
800+
.formatted(file, ex));
805801
}
806-
});
807-
}
808-
catch (Exception ex) {
809-
if (logger.isDebugEnabled()) {
810-
logger.debug("Failed to complete search in directory [%s] for files matching pattern [%s]: %s"
811-
.formatted(rootPath.toAbsolutePath(), subPattern, ex));
812802
}
813-
}
814-
return result;
803+
});
815804
}
816-
finally {
817-
if (fileSystem != null) {
818-
fileSystem.close();
805+
catch (Exception ex) {
806+
if (logger.isDebugEnabled()) {
807+
logger.debug("Failed to complete search in directory [%s] for files matching pattern [%s]: %s"
808+
.formatted(rootPath.toAbsolutePath(), subPattern, ex));
819809
}
820810
}
811+
return result;
821812
}
822813

823814
/**

spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.FileNotFoundException;
2020
import java.io.IOException;
2121
import java.io.UncheckedIOException;
22+
import java.net.URL;
2223
import java.nio.file.Path;
2324
import java.nio.file.Paths;
2425
import java.util.Arrays;
@@ -129,8 +130,16 @@ void usingClasspathStarProtocolWithWildcardInPatternAndEndingInSlash() throws Ex
129130

130131
List<String> actualSubPaths = getSubPathsIgnoringClassFilesEtc(pattern, pathPrefix);
131132

132-
// We do NOT find "support" if the pattern ENDS with a slash.
133-
assertThat(actualSubPaths).isEmpty();
133+
URL url = getClass().getClassLoader().getResource("org/springframework/core/io/support/EncodedResource.class");
134+
if (!url.getProtocol().equals("jar")) {
135+
// We do NOT find "support" if the pattern ENDS with a slash if org/springframework/core/io/support
136+
// is in the local file system.
137+
assertThat(actualSubPaths).isEmpty();
138+
}
139+
else {
140+
// But we do find "support/" if org/springframework/core/io/support is found in a JAR on the classpath.
141+
assertThat(actualSubPaths).containsExactly("support/");
142+
}
134143
}
135144

136145
@Test
@@ -258,6 +267,7 @@ private void assertFilenames(String pattern, boolean exactly, String... filename
258267
try {
259268
Resource[] resources = resolver.getResources(pattern);
260269
List<String> actualNames = Arrays.stream(resources)
270+
.peek(resource -> assertThat(resource.exists()).as(resource + " exists").isTrue())
261271
.map(Resource::getFilename)
262272
.sorted()
263273
.toList();
@@ -304,7 +314,16 @@ private String getPath(Resource resource) {
304314
// GraalVM native image which cannot support Path#toFile.
305315
//
306316
// See: https://github.com/spring-projects/spring-framework/issues/29243
307-
return ((FileSystemResource) resource).getPath();
317+
if (resource instanceof FileSystemResource fileSystemResource) {
318+
return fileSystemResource.getPath();
319+
}
320+
try {
321+
// Fall back to URL in case the resource came from a JAR
322+
return resource.getURL().getPath();
323+
}
324+
catch (IOException ex) {
325+
throw new UncheckedIOException(ex);
326+
}
308327
}
309328

310329
}

0 commit comments

Comments
 (0)