Skip to content

Commit df58c00

Browse files
committed
Restore GraalVM native image resource scanning support
This commit restores support for the GraalVM native image file system in PathMatchingResourcePatternResolver. Closes gh-29226
1 parent 51875cd commit df58c00

File tree

1 file changed

+75
-43
lines changed

1 file changed

+75
-43
lines changed

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

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@
3131
import java.net.URL;
3232
import java.net.URLClassLoader;
3333
import java.net.URLConnection;
34+
import java.nio.file.FileSystem;
35+
import java.nio.file.FileSystemNotFoundException;
36+
import java.nio.file.FileSystems;
3437
import java.nio.file.Files;
3538
import java.nio.file.Path;
3639
import java.util.Collections;
3740
import java.util.Enumeration;
3841
import java.util.LinkedHashSet;
42+
import java.util.Map;
3943
import java.util.Objects;
4044
import java.util.Set;
4145
import java.util.function.Predicate;
@@ -736,6 +740,19 @@ protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource
736740
URI rootDirUri;
737741
try {
738742
rootDirUri = rootDirResource.getURI();
743+
// If the URI is for a "resource" in the GraalVM native image file system, we have to
744+
// ensure that the root directory does not end in a slash while simultaneously ensuring
745+
// that the root directory is not an empty string (since Path#resolve throws an
746+
// ArrayIndexOutOfBoundsException in a native image if the initial Path is created
747+
// from an empty string).
748+
String scheme = rootDirUri.getScheme();
749+
String path = rootDirUri.getPath();
750+
if ("resource".equals(scheme) && (path.length() > 1) && path.endsWith("/")) {
751+
path = path.substring(0, path.length() - 1);
752+
// Retain the fragment as well, since root folders in the native image
753+
// file system are indexed via the fragment (e.g., resource:/#1).
754+
rootDirUri = new URI(scheme, path, rootDirUri.getFragment());
755+
}
739756
}
740757
catch (Exception ex) {
741758
if (logger.isInfoEnabled()) {
@@ -744,60 +761,75 @@ protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource
744761
return Collections.emptySet();
745762
}
746763

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

766-
String rootDir = StringUtils.cleanPath(rootPath.toString());
767-
if (!rootDir.endsWith("/")) {
768-
rootDir += "/";
769-
}
792+
String rootDir = StringUtils.cleanPath(rootPath.toString());
793+
if (!rootDir.endsWith("/")) {
794+
rootDir += "/";
795+
}
770796

771-
String resourcePattern = rootDir + StringUtils.cleanPath(subPattern);
772-
Predicate<Path> isMatchingFile = (path -> Files.isRegularFile(path) &&
773-
getPathMatcher().match(resourcePattern, StringUtils.cleanPath(path.toString())));
797+
String resourcePattern = rootDir + StringUtils.cleanPath(subPattern);
798+
Predicate<Path> isMatchingFile = path -> (Files.isRegularFile(path) &&
799+
getPathMatcher().match(resourcePattern, StringUtils.cleanPath(path.toString())));
774800

775-
if (logger.isTraceEnabled()) {
776-
logger.trace("Searching directory [%s] for files matching pattern [%s]"
777-
.formatted(rootPath.toAbsolutePath(), subPattern));
778-
}
801+
if (logger.isTraceEnabled()) {
802+
logger.trace("Searching directory [%s] for files matching pattern [%s]"
803+
.formatted(rootPath.toAbsolutePath(), subPattern));
804+
}
779805

780-
Set<Resource> result = new LinkedHashSet<>();
781-
try (Stream<Path> files = Files.walk(rootPath)) {
782-
files.filter(isMatchingFile).sorted().forEach(file -> {
783-
try {
784-
result.add(new FileSystemResource(file));
785-
}
786-
catch (Exception ex) {
787-
if (logger.isDebugEnabled()) {
788-
logger.debug("Failed to convert file %s to an org.springframework.core.io.Resource: %s"
789-
.formatted(file, ex));
806+
Set<Resource> result = new LinkedHashSet<>();
807+
try (Stream<Path> files = Files.walk(rootPath)) {
808+
files.filter(isMatchingFile).sorted().forEach(file -> {
809+
try {
810+
result.add(new FileSystemResource(file));
811+
}
812+
catch (Exception ex) {
813+
if (logger.isDebugEnabled()) {
814+
logger.debug("Failed to convert file %s to an org.springframework.core.io.Resource: %s"
815+
.formatted(file, ex));
816+
}
790817
}
818+
});
819+
}
820+
catch (Exception ex) {
821+
if (logger.isDebugEnabled()) {
822+
logger.debug("Failed to complete search in directory [%s] for files matching pattern [%s]: %s"
823+
.formatted(rootPath.toAbsolutePath(), subPattern, ex));
791824
}
792-
});
825+
}
826+
return result;
793827
}
794-
catch (Exception ex) {
795-
if (logger.isDebugEnabled()) {
796-
logger.debug("Failed to complete search in directory [%s] for files matching pattern [%s]: %s"
797-
.formatted(rootPath.toAbsolutePath(), subPattern, ex));
828+
finally {
829+
if (fileSystem != null) {
830+
fileSystem.close();
798831
}
799832
}
800-
return result;
801833
}
802834

803835
/**

0 commit comments

Comments
 (0)