Skip to content

Commit d21eea7

Browse files
committed
Fix recent regression in PathMatchingResourcePatternResolver
Commit 0eb6678 which introduced generic FileSystem support in PathMatchingResourcePatternResolver also introduced a regression in that a matching folder is now returned in the results. This commit address this by additionally using Files#isRegularFile() in the predicate used to filter candidates. Closes gh-29163
1 parent 7a2110d commit d21eea7

File tree

4 files changed

+71
-36
lines changed

4 files changed

+71
-36
lines changed

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -764,14 +764,15 @@ protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource
764764
try {
765765
Path rootPath = fileSystem.getPath(rootDir);
766766
String resourcePattern = rootPath.resolve(subPattern).toString();
767-
Predicate<Path> resourcePatternMatches = path -> getPathMatcher().match(resourcePattern, path.toString());
767+
Predicate<Path> isMatchingFile =
768+
path -> Files.isRegularFile(path) && getPathMatcher().match(resourcePattern, path.toString());
768769
if (logger.isTraceEnabled()) {
769770
logger.trace("Searching directory [%s] for files matching pattern [%s]"
770771
.formatted(rootPath.toAbsolutePath(), subPattern));
771772
}
772773
Set<Resource> result = new LinkedHashSet<>();
773774
try (Stream<Path> files = Files.walk(rootPath)) {
774-
files.filter(resourcePatternMatches).sorted().forEach(file -> {
775+
files.filter(isMatchingFile).sorted().forEach(file -> {
775776
try {
776777
result.add(convertToResource(file.toUri()));
777778
}

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

+66-34
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.io.FileNotFoundException;
2020
import java.io.IOException;
21+
import java.io.UncheckedIOException;
2122
import java.net.URLDecoder;
23+
import java.nio.file.Path;
2224
import java.util.Arrays;
2325
import java.util.List;
2426

@@ -35,7 +37,7 @@
3537
/**
3638
* Tests for {@link PathMatchingResourcePatternResolver}.
3739
*
38-
* <p>If tests fail, uncomment the diagnostics in {@link #assertFilenames}.
40+
* <p>If tests fail, uncomment the diagnostics in {@link #assertFilenames(String, boolean, String...)}.
3941
*
4042
* @author Oliver Hutchison
4143
* @author Juergen Hoeller
@@ -62,7 +64,7 @@ class PathMatchingResourcePatternResolverTests {
6264
class InvalidPatterns {
6365

6466
@Test
65-
void invalidPrefixWithPatternElementInItThrowsException() throws IOException {
67+
void invalidPrefixWithPatternElementInItThrowsException() {
6668
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> resolver.getResources("xx**:**/*.xy"));
6769
}
6870

@@ -73,22 +75,34 @@ void invalidPrefixWithPatternElementInItThrowsException() throws IOException {
7375
class FileSystemResources {
7476

7577
@Test
76-
void singleResourceOnFileSystem() throws IOException {
78+
void singleResourceOnFileSystem() {
7779
String pattern = "org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.class";
78-
assertFilenames(pattern, "PathMatchingResourcePatternResolverTests.class");
80+
assertExactFilenames(pattern, "PathMatchingResourcePatternResolverTests.class");
7981
}
8082

8183
@Test
82-
void classpathStarWithPatternOnFileSystem() throws IOException {
84+
void classpathStarWithPatternOnFileSystem() {
8385
String pattern = "classpath*:org/springframework/core/io/sup*/*.class";
8486
String[] expectedFilenames = StringUtils.concatenateStringArrays(CLASSES_IN_CORE_IO_SUPPORT, TEST_CLASSES_IN_CORE_IO_SUPPORT);
8587
assertFilenames(pattern, expectedFilenames);
8688
}
8789

88-
@Test
89-
void getResourcesOnFileSystemContainingHashtagsInTheirFileNames() throws IOException {
90-
String pattern = "classpath*:org/springframework/core/io/**/resource#test*.txt";
91-
assertFilenames(pattern, "resource#test1.txt", "resource#test2.txt");
90+
@Nested
91+
class WithHashtagsInTheirFileNames {
92+
93+
@Test
94+
void usingClasspathStarProtocol() {
95+
String pattern = "classpath*:org/springframework/core/io/**/resource#test*.txt";
96+
assertExactFilenames(pattern, "resource#test1.txt", "resource#test2.txt");
97+
}
98+
99+
@Test
100+
void usingFilePrototol() {
101+
Path testResourcesDir = Path.of("src/test/resources").toAbsolutePath();
102+
String pattern = "file:%s/scanned-resources/**".formatted(testResourcesDir);
103+
assertExactFilenames(pattern, "resource#test1.txt", "resource#test2.txt");
104+
}
105+
92106
}
93107

94108
}
@@ -98,57 +112,75 @@ void getResourcesOnFileSystemContainingHashtagsInTheirFileNames() throws IOExcep
98112
class JarResources {
99113

100114
@Test
101-
void singleResourceInJar() throws IOException {
115+
void singleResourceInJar() {
102116
String pattern = "org/reactivestreams/Publisher.class";
103-
assertFilenames(pattern, "Publisher.class");
117+
assertExactFilenames(pattern, "Publisher.class");
104118
}
105119

106120
@Test
107-
void singleResourceInRootOfJar() throws IOException {
121+
void singleResourceInRootOfJar() {
108122
String pattern = "aspectj_1_5_0.dtd";
109-
assertFilenames(pattern, "aspectj_1_5_0.dtd");
123+
assertExactFilenames(pattern, "aspectj_1_5_0.dtd");
110124
}
111125

112126
@Test
113-
void classpathWithPatternInJar() throws IOException {
127+
void classpathWithPatternInJar() {
114128
String pattern = "classpath:reactor/util/annotation/*.class";
115-
assertFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANNOTATION);
129+
assertExactFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANNOTATION);
116130
}
117131

118132
@Test
119-
void classpathStarWithPatternInJar() throws IOException {
133+
void classpathStarWithPatternInJar() {
120134
String pattern = "classpath*:reactor/util/annotation/*.class";
121-
assertFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANNOTATION);
135+
assertExactFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANNOTATION);
122136
}
123137

124138
// Fails in a native image -- https://github.com/oracle/graal/issues/5020
125139
@Test
126140
void rootPatternRetrievalInJarFiles() throws IOException {
127-
assertThat(resolver.getResources("classpath*:*.dtd")).extracting(Resource::getFilename)
141+
assertThat(resolver.getResources("classpath*:aspectj*.dtd")).extracting(Resource::getFilename)
128142
.as("Could not find aspectj_1_5_0.dtd in the root of the aspectjweaver jar")
129-
.contains("aspectj_1_5_0.dtd");
143+
.containsExactly("aspectj_1_5_0.dtd");
130144
}
131145

132146
}
133147

134148

135-
private void assertFilenames(String pattern, String... filenames) throws IOException {
136-
Resource[] resources = resolver.getResources(pattern);
137-
List<String> actualNames = Arrays.stream(resources)
138-
.map(Resource::getFilename)
139-
// Need to decode within GraalVM native image to get %23 converted to #.
140-
.map(filename -> URLDecoder.decode(filename, UTF_8))
141-
.sorted()
142-
.toList();
149+
private void assertFilenames(String pattern, String... filenames) {
150+
assertFilenames(pattern, false, filenames);
151+
}
143152

144-
// Uncomment the following if you encounter problems with matching against the file system.
145-
// List<String> expectedNames = Arrays.stream(filenames).sorted().toList();
146-
// System.out.println("----------------------------------------------------------------------");
147-
// System.out.println("Expected: " + expectedNames);
148-
// System.out.println("Actual: " + actualNames);
149-
// Arrays.stream(resources).forEach(System.out::println);
153+
private void assertExactFilenames(String pattern, String... filenames) {
154+
assertFilenames(pattern, true, filenames);
155+
}
150156

151-
assertThat(actualNames).as("subset of files found").contains(filenames);
157+
private void assertFilenames(String pattern, boolean exactly, String... filenames) {
158+
try {
159+
Resource[] resources = resolver.getResources(pattern);
160+
List<String> actualNames = Arrays.stream(resources)
161+
.map(Resource::getFilename)
162+
// Need to decode within GraalVM native image to get %23 converted to #.
163+
.map(filename -> URLDecoder.decode(filename, UTF_8))
164+
.sorted()
165+
.toList();
166+
167+
// Uncomment the following if you encounter problems with matching against the file system.
168+
// List<String> expectedNames = Arrays.stream(filenames).sorted().toList();
169+
// System.out.println("----------------------------------------------------------------------");
170+
// System.out.println("Expected: " + expectedNames);
171+
// System.out.println("Actual: " + actualNames);
172+
// Arrays.stream(resources).forEach(System.out::println);
173+
174+
if (exactly) {
175+
assertThat(actualNames).as("subset of files found").containsExactlyInAnyOrder(filenames);
176+
}
177+
else {
178+
assertThat(actualNames).as("subset of files found").contains(filenames);
179+
}
180+
}
181+
catch (IOException ex) {
182+
throw new UncheckedIOException(ex);
183+
}
152184
}
153185

154186
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test 2

0 commit comments

Comments
 (0)