PathMatchingResourcePatternResolverTests.java (spring-framework-5.3.23) | : | PathMatchingResourcePatternResolverTests.java (spring-framework-5.3.24) | ||
---|---|---|---|---|
/* | /* | |||
* Copyright 2002-2019 the original author or authors. | * Copyright 2002-2022 the original author or authors. | |||
* | * | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | * Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | * you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | * You may obtain a copy of the License at | |||
* | * | |||
* https://www.apache.org/licenses/LICENSE-2.0 | * https://www.apache.org/licenses/LICENSE-2.0 | |||
* | * | |||
* Unless required by applicable law or agreed to in writing, software | * Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | * distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | |||
* limitations under the License. | * limitations under the License. | |||
*/ | */ | |||
package org.springframework.core.io.support; | package org.springframework.core.io.support; | |||
import java.io.File; | ||||
import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | |||
import java.io.IOException; | import java.io.IOException; | |||
import java.util.ArrayList; | import java.io.UncheckedIOException; | |||
import java.nio.file.Path; | ||||
import java.nio.file.Paths; | ||||
import java.util.Arrays; | import java.util.Arrays; | |||
import java.util.List; | import java.util.List; | |||
import java.util.stream.Collectors; | ||||
import org.junit.jupiter.api.Disabled; | import org.junit.jupiter.api.Nested; | |||
import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | |||
import org.springframework.core.io.FileSystemResource; | ||||
import org.springframework.core.io.Resource; | import org.springframework.core.io.Resource; | |||
import org.springframework.util.StringUtils; | import org.springframework.util.StringUtils; | |||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | |||
/** | /** | |||
* If this test case fails, uncomment diagnostics in the | * Tests for {@link PathMatchingResourcePatternResolver}. | |||
* {@link #assertProtocolAndFilenames} method. | * | |||
* <p>If tests fail, uncomment the diagnostics in {@link #assertFilenames(String | ||||
, boolean, String...)}. | ||||
* | * | |||
* @author Oliver Hutchison | * @author Oliver Hutchison | |||
* @author Juergen Hoeller | * @author Juergen Hoeller | |||
* @author Chris Beams | * @author Chris Beams | |||
* @author Sam Brannen | * @author Sam Brannen | |||
* @since 17.11.2004 | * @since 17.11.2004 | |||
*/ | */ | |||
class PathMatchingResourcePatternResolverTests { | class PathMatchingResourcePatternResolverTests { | |||
private static final String[] CLASSES_IN_CORE_IO_SUPPORT = | private static final String[] CLASSES_IN_CORE_IO_SUPPORT = { "EncodedReso | |||
new String[] {"EncodedResource.class", "LocalizedResource | urce.class", | |||
Helper.class", | "LocalizedResourceHelper.class", "PathMatchingResourcePat | |||
"PathMatchingResourcePatternResolver.clas | ternResolver.class", "PropertiesLoaderSupport.class", | |||
s", "PropertiesLoaderSupport.class", | "PropertiesLoaderUtils.class", "ResourceArrayPropertyEdit | |||
"PropertiesLoaderUtils.class", "ResourceA | or.class", "ResourcePatternResolver.class", | |||
rrayPropertyEditor.class", | "ResourcePatternUtils.class", "SpringFactoriesLoader.clas | |||
"ResourcePatternResolver.class", "Resourc | s" }; | |||
ePatternUtils.class"}; | ||||
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT = { "PathMa | ||||
private static final String[] TEST_CLASSES_IN_CORE_IO_SUPPORT = | tchingResourcePatternResolverTests.class" }; | |||
new String[] {"PathMatchingResourcePatternResolverTests.c | ||||
lass"}; | private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = { "Non | |||
Null.class", "NonNullApi.class", "Nullable.class" }; | ||||
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATIONS = | ||||
new String[] {"NonNull.class", "NonNullApi.class", "Nulla | private final PathMatchingResourcePatternResolver resolver = new PathMatc | |||
ble.class"}; | hingResourcePatternResolver(); | |||
private PathMatchingResourcePatternResolver resolver = new PathMatchingRe | @Nested | |||
sourcePatternResolver(); | class InvalidPatterns { | |||
@Test | @Test | |||
void invalidPrefixWithPatternElementInIt() throws IOException { | void invalidPrefixWithPatternElementInItThrowsException() { | |||
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy | assertThatExceptionOfType(FileNotFoundException.class).is | |||
(() -> | ThrownBy(() -> resolver.getResources("xx**:**/*.xy")); | |||
resolver.getResources("xx**:**/*.xy")); | } | |||
} | } | |||
@Test | @Nested | |||
void singleResourceOnFileSystem() throws IOException { | class FileSystemResources { | |||
Resource[] resources = | ||||
resolver.getResources("org/springframework/core/i | @Test | |||
o/support/PathMatchingResourcePatternResolverTests.class"); | void singleResourceOnFileSystem() { | |||
assertThat(resources.length).isEqualTo(1); | String pattern = "org/springframework/core/io/support/Pat | |||
assertProtocolAndFilenames(resources, "file", "PathMatchingResour | hMatchingResourcePatternResolverTests.class"; | |||
cePatternResolverTests.class"); | assertExactFilenames(pattern, "PathMatchingResourcePatter | |||
} | nResolverTests.class"); | |||
} | ||||
@Test | ||||
void singleResourceInJar() throws IOException { | @Test | |||
Resource[] resources = resolver.getResources("org/reactivestreams | void classpathStarWithPatternOnFileSystem() { | |||
/Publisher.class"); | String pattern = "classpath*:org/springframework/core/io/ | |||
assertThat(resources.length).isEqualTo(1); | sup*/*.class"; | |||
assertProtocolAndFilenames(resources, "jar", "Publisher.class"); | String[] expectedFilenames = StringUtils.concatenateStrin | |||
} | gArrays(CLASSES_IN_CORE_IO_SUPPORT, TEST_CLASSES_IN_CORE_IO_SUPPORT); | |||
assertFilenames(pattern, expectedFilenames); | ||||
@Disabled | } | |||
@Test | ||||
void classpathStarWithPatternOnFileSystem() throws IOException { | @Nested | |||
Resource[] resources = resolver.getResources("classpath*:org/spri | class WithHashtagsInTheirFileNames { | |||
ngframework/core/io/sup*/*.class"); | ||||
// Have to exclude Clover-generated class files here, | @Test | |||
// as we might be running as part of a Clover test run. | void usingClasspathStarProtocol() { | |||
List<Resource> noCloverResources = new ArrayList<>(); | String pattern = "classpath*:org/springframework/ | |||
for (Resource resource : resources) { | core/io/**/resource#test*.txt"; | |||
if (!resource.getFilename().contains("$__CLOVER_")) { | String pathPrefix = ".+org/springframework/core/i | |||
noCloverResources.add(resource); | o/"; | |||
} | ||||
} | assertExactFilenames(pattern, "resource#test1.txt | |||
resources = noCloverResources.toArray(new Resource[0]); | ", "resource#test2.txt"); | |||
assertProtocolAndFilenames(resources, "file", | assertExactSubPaths(pattern, pathPrefix, "support | |||
StringUtils.concatenateStringArrays(CLASSES_IN_CO | /resource#test1.txt", "support/resource#test2.txt"); | |||
RE_IO_SUPPORT, TEST_CLASSES_IN_CORE_IO_SUPPORT)); | } | |||
} | ||||
@Test | ||||
@Test | void usingClasspathStarProtocolWithWildcardInPatternAndNo | |||
void getResourcesOnFileSystemContainingHashtagsInTheirFileNames() throws | tEndingInSlash() throws Exception { | |||
IOException { | String pattern = "classpath*:org/springframework/ | |||
Resource[] resources = resolver.getResources("classpath*:org/spri | core/io/sup*"; | |||
ngframework/core/io/**/resource#test*.txt"); | String pathPrefix = ".+org/springframework/core/i | |||
assertThat(resources).extracting(Resource::getFile).extracting(Fi | o/"; | |||
le::getName) | ||||
.containsExactlyInAnyOrder("resource#test1.txt", "resourc | List<String> actualSubPaths = getSubPathsIgnoring | |||
e#test2.txt"); | ClassFiles(pattern, pathPrefix); | |||
} | ||||
// We DO find "support" if the pattern does NOT e | ||||
@Test | nd with a slash. | |||
void classpathWithPatternInJar() throws IOException { | assertThat(actualSubPaths).containsExactly("suppo | |||
Resource[] resources = resolver.getResources("classpath:reactor/u | rt"); | |||
til/annotation/*.class"); | } | |||
assertProtocolAndFilenames(resources, "jar", CLASSES_IN_REACTOR_U | ||||
TIL_ANNOTATIONS); | @Test | |||
} | void usingFileProtocolWithWildcardInPatternAndNotEndingIn | |||
Slash() throws Exception { | ||||
@Test | Path testResourcesDir = Paths.get("src/test/resou | |||
void classpathStarWithPatternInJar() throws IOException { | rces").toAbsolutePath(); | |||
Resource[] resources = resolver.getResources("classpath*:reactor/ | String pattern = String.format("file:%s/org/sprin | |||
util/annotation/*.class"); | gframework/core/io/sup*", testResourcesDir); | |||
assertProtocolAndFilenames(resources, "jar", CLASSES_IN_REACTOR_U | String pathPrefix = ".+org/springframework/core/i | |||
TIL_ANNOTATIONS); | o/"; | |||
} | ||||
List<String> actualSubPaths = getSubPathsIgnoring | ||||
@Test | ClassFiles(pattern, pathPrefix); | |||
void rootPatternRetrievalInJarFiles() throws IOException { | ||||
Resource[] resources = resolver.getResources("classpath*:*.dtd"); | // We DO find "support" if the pattern does NOT e | |||
boolean found = false; | nd with a slash. | |||
for (Resource resource : resources) { | assertThat(actualSubPaths).containsExactly("suppo | |||
if (resource.getFilename().equals("aspectj_1_5_0.dtd")) { | rt"); | |||
found = true; | } | |||
break; | ||||
} | @Test | |||
} | void usingClasspathStarProtocolWithWildcardInPatternAndEn | |||
assertThat(found).as("Could not find aspectj_1_5_0.dtd in the roo | dingInSlash() throws Exception { | |||
t of the aspectjweaver jar").isTrue(); | String pattern = "classpath*:org/springframework/ | |||
} | core/io/sup*/"; | |||
String pathPrefix = ".+org/springframework/core/i | ||||
private void assertProtocolAndFilenames(Resource[] resources, String prot | o/"; | |||
ocol, String... filenames) | ||||
throws IOException { | List<String> actualSubPaths = getSubPathsIgnoring | |||
ClassFiles(pattern, pathPrefix); | ||||
// Uncomment the following if you encounter problems with matchin | ||||
g against the file system | // We do NOT find "support" if the pattern ENDS w | |||
// It shows file locations. | ith a slash. | |||
// String[] actualNames = new String[resources.length]; | assertThat(actualSubPaths).isEmpty(); | |||
// for (int i = 0; i < resources.length; i++) { | } | |||
// actualNames[i] = resources[i].getFilename(); | ||||
// } | @Test | |||
// List sortedActualNames = new LinkedList(Arrays.asList(actualNames | void usingFileProtocolWithWildcardInPatternAndEndingInSla | |||
)); | sh() throws Exception { | |||
// List expectedNames = new LinkedList(Arrays.asList(fileNames)); | Path testResourcesDir = Paths.get("src/test/resou | |||
// Collections.sort(sortedActualNames); | rces").toAbsolutePath(); | |||
// Collections.sort(expectedNames); | String pattern = String.format("file:%s/org/sprin | |||
// | gframework/core/io/sup*/", testResourcesDir); | |||
// System.out.println("-----------"); | String pathPrefix = ".+org/springframework/core/i | |||
// System.out.println("Expected: " + StringUtils.collectionToCommaDe | o/"; | |||
limitedString(expectedNames)); | ||||
// System.out.println("Actual: " + StringUtils.collectionToCommaDeli | List<String> actualSubPaths = getSubPathsIgnoring | |||
mitedString(sortedActualNames)); | ClassFiles(pattern, pathPrefix); | |||
// for (int i = 0; i < resources.length; i++) { | ||||
// System.out.println(resources[i]); | // We do NOT find "support" if the pattern ENDS w | |||
// } | ith a slash. | |||
assertThat(actualSubPaths).isEmpty(); | ||||
assertThat(resources.length).as("Correct number of files found"). | } | |||
isEqualTo(filenames.length); | ||||
for (Resource resource : resources) { | @Test | |||
String actualProtocol = resource.getURL().getProtocol(); | void usingClasspathStarProtocolWithWildcardInPatternAndEn | |||
assertThat(actualProtocol).isEqualTo(protocol); | dingWithSuffixPattern() throws Exception { | |||
assertFilenameIn(resource, filenames); | String pattern = "classpath*:org/springframework/ | |||
} | core/io/sup*/*.txt"; | |||
} | String pathPrefix = ".+org/springframework/core/i | |||
o/"; | ||||
private void assertFilenameIn(Resource resource, String... filenames) { | ||||
String filename = resource.getFilename(); | List<String> actualSubPaths = getSubPathsIgnoring | |||
assertThat(Arrays.stream(filenames).anyMatch(filename::endsWith)) | ClassFiles(pattern, pathPrefix); | |||
.as(resource + " does not have a filename that matches any of the specified name | ||||
s").isTrue(); | assertThat(actualSubPaths) | |||
.containsExactlyInAnyOrder("suppo | ||||
rt/resource#test1.txt", "support/resource#test2.txt"); | ||||
} | ||||
private List<String> getSubPathsIgnoringClassFiles(String | ||||
pattern, String pathPrefix) throws IOException { | ||||
return Arrays.stream(resolver.getResources(patter | ||||
n)) | ||||
.map(resource -> getPath(resource | ||||
).replaceFirst(pathPrefix, "")) | ||||
.filter(name -> !name.endsWith(". | ||||
class")) | ||||
.distinct() | ||||
.sorted() | ||||
.collect(Collectors.toList()); | ||||
} | ||||
@Test | ||||
void usingFileProtocolWithoutWildcardInPatternAndEndingIn | ||||
SlashStarStar() { | ||||
Path testResourcesDir = Paths.get("src/test/resou | ||||
rces").toAbsolutePath(); | ||||
String pattern = String.format("file:%s/scanned-r | ||||
esources/**", testResourcesDir); | ||||
String pathPrefix = ".+?resources/"; | ||||
// We do NOT find "scanned-resources" if the patt | ||||
ern ENDS with "/**" AND does NOT otherwise contain a wildcard. | ||||
assertExactFilenames(pattern, "resource#test1.txt | ||||
", "resource#test2.txt"); | ||||
assertExactSubPaths(pattern, pathPrefix, "scanned | ||||
-resources/resource#test1.txt", | ||||
"scanned-resources/resource#test2 | ||||
.txt"); | ||||
} | ||||
@Test | ||||
void usingFileProtocolWithWildcardInPatternAndEndingInSla | ||||
shStarStar() { | ||||
Path testResourcesDir = Paths.get("src/test/resou | ||||
rces").toAbsolutePath(); | ||||
String pattern = String.format("file:%s/scanned*r | ||||
esources/**", testResourcesDir); | ||||
String pathPrefix = ".+?resources/"; | ||||
// We DO find "scanned-resources" if the pattern | ||||
ENDS with "/**" AND DOES otherwise contain a wildcard. | ||||
assertExactFilenames(pattern, "scanned-resources" | ||||
, "resource#test1.txt", "resource#test2.txt"); | ||||
assertExactSubPaths(pattern, pathPrefix, "scanned | ||||
-resources", "scanned-resources/resource#test1.txt", | ||||
"scanned-resources/resource#test2 | ||||
.txt"); | ||||
} | ||||
@Test | ||||
void usingFileProtocolAndAssertingUrlAndUriSyntax() throw | ||||
s Exception { | ||||
Path testResourcesDir = Paths.get("src/test/resou | ||||
rces").toAbsolutePath(); | ||||
String pattern = String.format("file:%s/scanned-r | ||||
esources/**/resource#test1.txt", testResourcesDir); | ||||
Resource[] resources = resolver.getResources(patt | ||||
ern); | ||||
assertThat(resources).hasSize(1); | ||||
Resource resource = resources[0]; | ||||
assertThat(resource.getFilename()).isEqualTo("res | ||||
ource#test1.txt"); | ||||
// The following assertions serve as regression t | ||||
ests for the lack of the | ||||
// "authority component" (//) in the returned URI | ||||
/URL. For example, we are | ||||
// expecting file:/my/path (or file:/C:/My/Path) | ||||
instead of file:///my/path. | ||||
assertThat(resource.getURL().toString()).matches( | ||||
"^file:\\/[^\\/].+test1\\.txt$"); | ||||
assertThat(resource.getURI().toString()).matches( | ||||
"^file:\\/[^\\/].+test1\\.txt$"); | ||||
} | ||||
} | ||||
} | ||||
@Nested | ||||
class JarResources { | ||||
@Test | ||||
void singleResourceInJar() { | ||||
String pattern = "org/reactivestreams/Publisher.class"; | ||||
assertExactFilenames(pattern, "Publisher.class"); | ||||
} | ||||
@Test | ||||
void singleResourceInRootOfJar() { | ||||
String pattern = "aspectj_1_5_0.dtd"; | ||||
assertExactFilenames(pattern, "aspectj_1_5_0.dtd"); | ||||
} | ||||
@Test | ||||
void classpathWithPatternInJar() { | ||||
String pattern = "classpath:reactor/util/annotation/*.cla | ||||
ss"; | ||||
assertExactFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANN | ||||
OTATION); | ||||
} | ||||
@Test | ||||
void classpathStarWithPatternInJar() { | ||||
String pattern = "classpath*:reactor/util/annotation/*.cl | ||||
ass"; | ||||
assertExactFilenames(pattern, CLASSES_IN_REACTOR_UTIL_ANN | ||||
OTATION); | ||||
} | ||||
// Fails in a native image -- https://github.com/oracle/graal/iss | ||||
ues/5020 | ||||
@Test | ||||
void rootPatternRetrievalInJarFiles() throws IOException { | ||||
assertThat(resolver.getResources("classpath*:aspectj*.dtd | ||||
")).extracting(Resource::getFilename) | ||||
.as("Could not find aspectj_1_5_0.dtd in the root | ||||
of the aspectjweaver jar") | ||||
.containsExactly("aspectj_1_5_0.dtd"); | ||||
} | ||||
} | ||||
private void assertFilenames(String pattern, String... filenames) { | ||||
assertFilenames(pattern, false, filenames); | ||||
} | ||||
private void assertExactFilenames(String pattern, String... filenames) { | ||||
assertFilenames(pattern, true, filenames); | ||||
} | ||||
private void assertFilenames(String pattern, boolean exactly, String... f | ||||
ilenames) { | ||||
try { | ||||
Resource[] resources = resolver.getResources(pattern); | ||||
List<String> actualNames = Arrays.stream(resources) | ||||
.map(Resource::getFilename) | ||||
.sorted() | ||||
.collect(Collectors.toList()); | ||||
// Uncomment the following if you encounter problems with | ||||
matching against the file system. | ||||
// List<String> expectedNames = Arrays.stream(filenames). | ||||
sorted().toList(); | ||||
// System.out.println("---------------------------------- | ||||
------------------------------------"); | ||||
// System.out.println("Expected: " + expectedNames); | ||||
// System.out.println("Actual: " + actualNames); | ||||
// Arrays.stream(resources).forEach(System.out::println); | ||||
if (exactly) { | ||||
assertThat(actualNames).as("subset of files found | ||||
").containsExactlyInAnyOrder(filenames); | ||||
} | ||||
else { | ||||
assertThat(actualNames).as("subset of files found | ||||
").contains(filenames); | ||||
} | ||||
} | ||||
catch (IOException ex) { | ||||
throw new UncheckedIOException(ex); | ||||
} | ||||
} | ||||
private void assertExactSubPaths(String pattern, String pathPrefix, Strin | ||||
g... subPaths) { | ||||
try { | ||||
Resource[] resources = resolver.getResources(pattern); | ||||
List<String> actualSubPaths = Arrays.stream(resources) | ||||
.map(resource -> getPath(resource).replac | ||||
eFirst(pathPrefix, "")) | ||||
.sorted() | ||||
.collect(Collectors.toList()); | ||||
assertThat(actualSubPaths).containsExactlyInAnyOrder(subP | ||||
aths); | ||||
} | ||||
catch (IOException ex) { | ||||
throw new UncheckedIOException(ex); | ||||
} | ||||
} | ||||
private String getPath(Resource resource) { | ||||
// Tests fail if we use resouce.getURL().getPath(). They would al | ||||
so fail on Mac OS when | ||||
// using resouce.getURI().getPath() if the resource paths are not | ||||
Unicode normalized. | ||||
// | ||||
// On the JVM, all tests should pass when using resouce.getFile() | ||||
.getPath(); however, | ||||
// we use FileSystemResource#getPath since this test class is som | ||||
etimes run within a | ||||
// GraalVM native image which cannot support Path#toFile. | ||||
// | ||||
// See: https://github.com/spring-projects/spring-framework/issue | ||||
s/29243 | ||||
return ((FileSystemResource) resource).getPath(); | ||||
} | } | |||
} | } | |||
End of changes. 8 change blocks. | ||||
152 lines changed or deleted | 355 lines changed or added |