Skip to content

Commit f751b38

Browse files
dsyersbrannen
authored andcommitted
Use java.nio and FileSystems to resolve files in PathMatchingResourcePatternResolver
The JDK has decent support for walking a file system (via the FileSystem API in java.nio.file), but prior to this commit Spring still used hand-rolled pre-Java 7 code. Also, in principle, FileSystem can be an abstraction used to express any hierarchy of resources through their URIs. This is a good fit with the Spring Resource abstraction, and for instance would mean that GraalVM native images could potentially do classpath scanning through a custom URI and FileSystem already provided by GraalVM. In light of the above, this commit overhauls the implementation of PathMatchingResourcePatternResolver to use a java.nio.file.FileSystem to find matching resources within a file system. As a side effect, several obsolete `protected` methods have been removed from PathMatchingResourcePatternResolver. See spring-projectsgh-29163
1 parent c5fc0a5 commit f751b38

File tree

1 file changed

+40
-126
lines changed

1 file changed

+40
-126
lines changed

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

+40-126
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.core.io.support;
1818

1919
import java.io.File;
20-
import java.io.FileNotFoundException;
2120
import java.io.IOException;
2221
import java.io.UncheckedIOException;
2322
import java.lang.module.ModuleFinder;
@@ -31,11 +30,16 @@
3130
import java.net.URL;
3231
import java.net.URLClassLoader;
3332
import java.net.URLConnection;
34-
import java.util.Arrays;
33+
import java.nio.file.FileSystem;
34+
import java.nio.file.FileSystems;
35+
import java.nio.file.Files;
36+
import java.nio.file.NoSuchFileException;
37+
import java.nio.file.Path;
3538
import java.util.Collections;
36-
import java.util.Comparator;
3739
import java.util.Enumeration;
40+
import java.util.HashSet;
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;
@@ -193,6 +197,7 @@
193197
* @author Phillip Webb
194198
* @author Sam Brannen
195199
* @author Sebastien Deleuze
200+
* @author Dave Syer
196201
* @since 1.0.2
197202
* @see #CLASSPATH_ALL_URL_PREFIX
198203
* @see org.springframework.util.AntPathMatcher
@@ -736,139 +741,48 @@ protected JarFile getJarFile(String jarFileUrl) throws IOException {
736741
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
737742
throws IOException {
738743

739-
File rootDir;
744+
Set<Resource> result = new HashSet<>();
745+
FileSystem fileSystem;
740746
try {
741-
rootDir = rootDirResource.getFile().getAbsoluteFile();
747+
fileSystem = FileSystems.getFileSystem(rootDirResource.getURI().resolve("/"));
748+
} catch (Exception e) {
749+
fileSystem = FileSystems.newFileSystem(rootDirResource.getURI().resolve("/"), Map.of(),
750+
ClassUtils.getDefaultClassLoader());
742751
}
743-
catch (FileNotFoundException ex) {
744-
if (logger.isDebugEnabled()) {
745-
logger.debug("Cannot search for matching files underneath " + rootDirResource +
746-
" in the file system: " + ex.getMessage());
747-
}
748-
return Collections.emptySet();
749-
}
750-
catch (Exception ex) {
751-
if (logger.isInfoEnabled()) {
752-
logger.info("Failed to resolve " + rootDirResource + " in the file system: " + ex);
753-
}
754-
return Collections.emptySet();
755-
}
756-
return doFindMatchingFileSystemResources(rootDir, subPattern);
757-
}
758-
759-
/**
760-
* Find all resources in the file system that match the given location pattern
761-
* via the Ant-style PathMatcher.
762-
* @param rootDir the root directory in the file system
763-
* @param subPattern the sub pattern to match (below the root directory)
764-
* @return a mutable Set of matching Resource instances
765-
* @throws IOException in case of I/O errors
766-
* @see #retrieveMatchingFiles
767-
* @see org.springframework.util.PathMatcher
768-
*/
769-
protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
770-
if (logger.isTraceEnabled()) {
771-
logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
772-
}
773-
Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
774-
Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
775-
for (File file : matchingFiles) {
776-
result.add(new FileSystemResource(file));
777-
}
778-
return result;
779-
}
780-
781-
/**
782-
* Retrieve files that match the given path pattern,
783-
* checking the given directory and its subdirectories.
784-
* @param rootDir the directory to start from
785-
* @param pattern the pattern to match against,
786-
* relative to the root directory
787-
* @return a mutable Set of matching Resource instances
788-
* @throws IOException if directory contents could not be retrieved
789-
*/
790-
protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
791-
if (!rootDir.exists()) {
792-
// Silently skip non-existing directories.
793-
if (logger.isDebugEnabled()) {
794-
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
752+
String rootPath = rootDirResource.getURI().getRawPath();
753+
if (!("file").equals(rootDirResource.getURI().getScheme()) && rootPath.startsWith("/")) {
754+
rootPath = rootPath.substring(1);
755+
if (rootPath.length()==0) {
756+
return result;
795757
}
796-
return Collections.emptySet();
797-
}
798-
if (!rootDir.isDirectory()) {
799-
// Complain louder if it exists but is no directory.
800-
if (logger.isInfoEnabled()) {
801-
logger.info("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
758+
if (rootPath.endsWith("/")) {
759+
rootPath = rootPath.substring(0, rootPath.length()-1);
802760
}
803-
return Collections.emptySet();
804-
}
805-
if (!rootDir.canRead()) {
806-
if (logger.isInfoEnabled()) {
807-
logger.info("Skipping search for matching files underneath directory [" + rootDir.getAbsolutePath() +
808-
"] because the application is not allowed to read the directory");
761+
if (rootPath.length()==0) {
762+
return result;
809763
}
810-
return Collections.emptySet();
811764
}
812-
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
813-
if (!pattern.startsWith("/")) {
814-
fullPattern += "/";
815-
}
816-
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
817-
Set<File> result = new LinkedHashSet<>(8);
818-
doRetrieveMatchingFiles(fullPattern, rootDir, result);
819-
return result;
820-
}
821-
822-
/**
823-
* Recursively retrieve files that match the given pattern,
824-
* adding them to the given result list.
825-
* @param fullPattern the pattern to match against,
826-
* with prepended root directory path
827-
* @param dir the current directory
828-
* @param result the Set of matching File instances to add to
829-
* @throws IOException if directory contents could not be retrieved
830-
*/
831-
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
832-
if (logger.isTraceEnabled()) {
833-
logger.trace("Searching directory [" + dir.getAbsolutePath() +
834-
"] for files matching pattern [" + fullPattern + "]");
835-
}
836-
for (File content : listDirectory(dir)) {
837-
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
838-
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
839-
if (!content.canRead()) {
840-
if (logger.isDebugEnabled()) {
841-
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
842-
"] because the application is not allowed to read the directory");
765+
Path path = fileSystem.getPath(rootPath);
766+
Path patternPath = path.resolve(subPattern);
767+
try (Stream<Path> files = Files.walk(path)) {
768+
files.forEach(file -> {
769+
if (getPathMatcher().match(patternPath.toString(), file.toString())) {
770+
try {
771+
result.add(new UrlResource(file.toUri()));
772+
} catch (MalformedURLException e) {
773+
// ignore
843774
}
844775
}
845-
else {
846-
doRetrieveMatchingFiles(fullPattern, content, result);
847-
}
848-
}
849-
if (getPathMatcher().match(fullPattern, currPath)) {
850-
result.add(content);
851-
}
776+
});
777+
} catch (NoSuchFileException e) {
778+
// ignore
852779
}
853-
}
854-
855-
/**
856-
* Determine a sorted list of files in the given directory.
857-
* @param dir the directory to introspect
858-
* @return the sorted list of files (by default in alphabetical order)
859-
* @since 5.1
860-
* @see File#listFiles()
861-
*/
862-
protected File[] listDirectory(File dir) {
863-
File[] files = dir.listFiles();
864-
if (files == null) {
865-
if (logger.isInfoEnabled()) {
866-
logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
867-
}
868-
return new File[0];
780+
try {
781+
fileSystem.close();
782+
} catch (UnsupportedOperationException e) {
783+
// ignore
869784
}
870-
Arrays.sort(files, Comparator.comparing(File::getName));
871-
return files;
785+
return result;
872786
}
873787

874788
/**

0 commit comments

Comments
 (0)