@ -17,7 +17,6 @@
package org.springframework.core.io.support ;
package org.springframework.core.io.support ;
import java.io.File ;
import java.io.File ;
import java.io.FileNotFoundException ;
import java.io.IOException ;
import java.io.IOException ;
import java.io.UncheckedIOException ;
import java.io.UncheckedIOException ;
import java.lang.module.ModuleFinder ;
import java.lang.module.ModuleFinder ;
@ -27,15 +26,19 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method ;
import java.lang.reflect.Method ;
import java.net.JarURLConnection ;
import java.net.JarURLConnection ;
import java.net.MalformedURLException ;
import java.net.MalformedURLException ;
import java.net.URI ;
import java.net.URISyntaxException ;
import java.net.URISyntaxException ;
import java.net.URL ;
import java.net.URL ;
import java.net.URLClassLoader ;
import java.net.URLClassLoader ;
import java.net.URLConnection ;
import java.net.URLConnection ;
import java.util.Arrays ;
import java.nio.file.FileSystem ;
import java.nio.file.FileSystems ;
import java.nio.file.Files ;
import java.nio.file.Path ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.Enumeration ;
import java.util.Enumeration ;
import java.util.LinkedHashSet ;
import java.util.LinkedHashSet ;
import java.util.Map ;
import java.util.Objects ;
import java.util.Objects ;
import java.util.Set ;
import java.util.Set ;
import java.util.function.Predicate ;
import java.util.function.Predicate ;
@ -96,14 +99,13 @@ import org.springframework.util.StringUtils;
* classpath : com / mycompany / * * & # 47 ; applicationContext . xml < / pre >
* classpath : com / mycompany / * * & # 47 ; applicationContext . xml < / pre >
* the resolver follows a more complex but defined procedure to try to resolve
* the resolver follows a more complex but defined procedure to try to resolve
* the wildcard . It produces a { @code Resource } for the path up to the last
* the wildcard . It produces a { @code Resource } for the path up to the last
* non - wildcard segment and obtains a { @code URL } from it . If this URL is
* non - wildcard segment and obtains a { @code URL } from it . If this URL is not a
* not a "{@code jar:}" URL or container - specific variant ( e . g .
* "{@code jar:}" URL or container - specific variant ( e . g . "{@code zip:}" in WebLogic ,
* "{@code zip:}" in WebLogic , "{@code wsjar}" in WebSphere " , etc . ) ,
* "{@code wsjar}" in WebSphere " , etc . ) , then the root directory of the filesystem
* then a { @code java . io . File } is obtained from it , and used to resolve the
* associated with the URL is obtained and used to resolve the wildcards by walking
* wildcard by walking the filesystem . In the case of a jar URL , the resolver
* the filesystem . In the case of a jar URL , the resolver either gets a
* either gets a { @code java . net . JarURLConnection } from it , or manually parses
* { @code java . net . JarURLConnection } from it , or manually parses the jar URL , and
* the jar URL , and then traverses the contents of the jar file , to resolve the
* then traverses the contents of the jar file , to resolve the wildcards .
* wildcards .
*
*
* < p > < b > Implications on portability : < / b >
* < p > < b > Implications on portability : < / b >
*
*
@ -133,7 +135,7 @@ import org.springframework.util.StringUtils;
*
*
* < p > There is special support for retrieving multiple class path resources with
* < p > There is special support for retrieving multiple class path resources with
* the same name , via the "{@code classpath*:}" prefix . For example ,
* the same name , via the "{@code classpath*:}" prefix . For example ,
* "{@code classpath*:META-INF/beans.xml}" will find all "beans.xml"
* "{@code classpath*:META-INF/beans.xml}" will find all "META-INF/ beans.xml"
* files in the class path , be it in "classes" directories or in JAR files .
* files in the class path , be it in "classes" directories or in JAR files .
* This is particularly useful for autodetecting config files of the same name
* This is particularly useful for autodetecting config files of the same name
* at the same location within each jar file . Internally , this happens via a
* at the same location within each jar file . Internally , this happens via a
@ -145,7 +147,7 @@ import org.springframework.util.StringUtils;
* { @code ClassLoader . getResources ( ) } call is used on the last non - wildcard
* { @code ClassLoader . getResources ( ) } call is used on the last non - wildcard
* path segment to get all the matching resources in the class loader hierarchy ,
* path segment to get all the matching resources in the class loader hierarchy ,
* and then off each resource the same PathMatcher resolution strategy described
* and then off each resource the same PathMatcher resolution strategy described
* above is used for the wildcard subpath .
* above is used for the wildcard sub pattern .
*
*
* < p > < b > Other notes : < / b >
* < p > < b > Other notes : < / b >
*
*
@ -193,6 +195,7 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Phillip Webb
* @author Sam Brannen
* @author Sam Brannen
* @author Sebastien Deleuze
* @author Sebastien Deleuze
* @author Dave Syer
* @since 1 . 0 . 2
* @since 1 . 0 . 2
* @see # CLASSPATH_ALL_URL_PREFIX
* @see # CLASSPATH_ALL_URL_PREFIX
* @see org . springframework . util . AntPathMatcher
* @see org . springframework . util . AntPathMatcher
@ -521,8 +524,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
/ * *
/ * *
* Find all resources that match the given location pattern via the
* Find all resources that match the given location pattern via the
* Ant - style PathMatcher . Supports resources in jar files and zip files
* Ant - style PathMatcher . Supports resources in OSGi bundles , JBoss VFS ,
* and in the file system .
* jar files , zip files , and file systems .
* @param locationPattern the location pattern to match
* @param locationPattern the location pattern to match
* @return the result as Resource array
* @return the result as Resource array
* @throws IOException in case of I / O errors
* @throws IOException in case of I / O errors
@ -563,15 +566,13 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
/ * *
/ * *
* Determine the root directory for the given location .
* Determine the root directory for the given location .
* < p > Used for determining the starting point for file matching ,
* < p > Used for determining the starting point for file matching , resolving the
* resolving the root directory location to a { @code java . io . File }
* root directory location to be passed into { @link # getResources ( String ) } ,
* and passing it into { @code retrieveMatchingFiles } , with the
* with the remainder of the location to be used as the sub pattern .
* remainder of the location as pattern .
* < p > Will return "/WEB-INF/" for the location "/WEB-INF/*.xml" , for example .
* < p > Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml" ,
* for example .
* @param location the location to check
* @param location the location to check
* @return the part of the location that denotes the root directory
* @return the part of the location that denotes the root directory
* @see # retrieveMatchingFiles
* @see # findPathMatchingResources ( String )
* /
* /
protected String determineRootDir ( String location ) {
protected String determineRootDir ( String location ) {
int prefixEnd = location . indexOf ( ':' ) + 1 ;
int prefixEnd = location . indexOf ( ':' ) + 1 ;
@ -724,151 +725,99 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
/ * *
/ * *
* Find all resources in the file system that match the given location pattern
* Find all resources in the file system of the supplied root directory that
* via the Ant - style PathMatcher .
* match the given location sub pattern via the Ant - style PathMatcher .
* @param rootDirResource the root directory as Resource
* @param rootDirResource the root directory as a Resource
* @param subPattern the sub pattern to match ( below the root directory )
* @param subPattern the sub pattern to match ( below the root directory )
* @return a mutable Set of matching Resource instances
* @return a mutable Set of matching Resource instances
* @throws IOException in case of I / O errors
* @throws IOException in case of I / O errors
* @see # retrieveMatchingFiles
* @see org . springframework . util . PathMatcher
* @see org . springframework . util . PathMatcher
* /
* /
protected Set < Resource > doFindPathMatchingFileResources ( Resource rootDirResource , String subPattern )
protected Set < Resource > doFindPathMatchingFileResources ( Resource rootDirResource , String subPattern )
throws IOException {
throws IOException {
File rootDir ;
URI rootDirUri ;
String rootDir ;
try {
try {
rootDir = rootDirResource . getFile ( ) . getAbsoluteFile ( ) ;
rootDirUri = rootDirResource . getURI ( ) ;
rootDir = rootDirUri . getPath ( ) ;
// If the URI is for a "resource" in the GraalVM native image file system, we have to
// ensure that the root directory does not end in a slash while simultaneously ensuring
// that the root directory is not an empty string (since fileSystem.getPath("").resolve(str)
// throws an ArrayIndexOutOfBoundsException in a native image).
if ( "resource" . equals ( rootDirUri . getScheme ( ) ) & & ( rootDir . length ( ) > 1 ) & & rootDir . endsWith ( "/" ) ) {
rootDir = rootDir . substring ( 0 , rootDir . length ( ) - 1 ) ;
}
}
catch ( FileNotFoundException ex ) {
if ( logger . isDebugEnabled ( ) ) {
logger . debug ( "Cannot search for matching files underneath " + rootDirResource +
" in the file system: " + ex . getMessage ( ) ) ;
}
return Collections . emptySet ( ) ;
}
}
catch ( Exception ex ) {
catch ( Exception ex ) {
if ( logger . isInfoEnabled ( ) ) {
if ( logger . isInfoEnabled ( ) ) {
logger . info ( "Failed to resolve " + rootDirResource + " in the file system: " + ex ) ;
logger . info ( "Failed to resolve %s in the file system: %s" . formatted ( rootDirResource , ex ) ) ;
}
}
return Collections . emptySet ( ) ;
return Collections . emptySet ( ) ;
}
}
return doFindMatchingFileSystemResources ( rootDir , subPattern ) ;
FileSystem fileSystem = getFileSystem ( rootDirUri ) ;
if ( fileSystem = = null ) {
return Collections . emptySet ( ) ;
}
}
/ * *
try {
* Find all resources in the file system that match the given location pattern
Path rootPath = fileSystem . getPath ( rootDir ) ;
* via the Ant - style PathMatcher .
String resourcePattern = rootPath . resolve ( subPattern ) . toString ( ) ;
* @param rootDir the root directory in the file system
Predicate < Path > resourcePatternMatches = path - > getPathMatcher ( ) . match ( resourcePattern , path . toString ( ) ) ;
* @param subPattern the sub pattern to match ( below the root directory )
* @return a mutable Set of matching Resource instances
* @throws IOException in case of I / O errors
* @see # retrieveMatchingFiles
* @see org . springframework . util . PathMatcher
* /
protected Set < Resource > doFindMatchingFileSystemResources ( File rootDir , String subPattern ) throws IOException {
if ( logger . isTraceEnabled ( ) ) {
if ( logger . isTraceEnabled ( ) ) {
logger . trace ( "Looking for matching resources in directory tree [" + rootDir . getPath ( ) + "]" ) ;
logger . trace ( "Searching directory [%s] for files matching pattern [%s]"
}
. formatted ( rootPath . toAbsolutePath ( ) , subPattern ) ) ;
Set < File > matchingFiles = retrieveMatchingFiles ( rootDir , subPattern ) ;
Set < Resource > result = new LinkedHashSet < > ( matchingFiles . size ( ) ) ;
for ( File file : matchingFiles ) {
result . add ( new FileSystemResource ( file ) ) ;
}
}
return result ;
Set < Resource > result = new LinkedHashSet < > ( ) ;
try ( Stream < Path > files = Files . walk ( rootPath ) ) {
files . filter ( resourcePatternMatches ) . sorted ( ) . forEach ( file - > {
try {
result . add ( convertToResource ( file . toUri ( ) ) ) ;
}
}
catch ( Exception ex ) {
/ * *
* Retrieve files that match the given path pattern ,
* checking the given directory and its subdirectories .
* @param rootDir the directory to start from
* @param pattern the pattern to match against ,
* relative to the root directory
* @return a mutable Set of matching Resource instances
* @throws IOException if directory contents could not be retrieved
* /
protected Set < File > retrieveMatchingFiles ( File rootDir , String pattern ) throws IOException {
if ( ! rootDir . exists ( ) ) {
// Silently skip non-existing directories.
if ( logger . isDebugEnabled ( ) ) {
if ( logger . isDebugEnabled ( ) ) {
logger . debug ( "Skipping [" + rootDir . getAbsolutePath ( ) + "] because it does not exist" ) ;
logger . debug ( "Failed to convert file %s to an org.springframework.core.io.Resource: %s"
}
. formatted ( file , ex ) ) ;
return Collections . emptySet ( ) ;
}
}
if ( ! rootDir . isDirectory ( ) ) {
// Complain louder if it exists but is no directory.
if ( logger . isInfoEnabled ( ) ) {
logger . info ( "Skipping [" + rootDir . getAbsolutePath ( ) + "] because it does not denote a directory" ) ;
}
return Collections . emptySet ( ) ;
}
}
if ( ! rootDir . canRead ( ) ) {
} ) ;
if ( logger . isInfoEnabled ( ) ) {
logger . info ( "Skipping search for matching files underneath directory [" + rootDir . getAbsolutePath ( ) +
"] because the application is not allowed to read the directory" ) ;
}
}
return Collections . emptySet ( ) ;
catch ( Exception ex ) {
if ( logger . isDebugEnabled ( ) ) {
logger . debug ( "Faild to complete search in directory [%s] for files matching pattern [%s]: %s"
. formatted ( rootPath . toAbsolutePath ( ) , subPattern , ex ) ) ;
}
}
String fullPattern = StringUtils . replace ( rootDir . getAbsolutePath ( ) , File . separator , "/" ) ;
if ( ! pattern . startsWith ( "/" ) ) {
fullPattern + = "/" ;
}
}
fullPattern = fullPattern + StringUtils . replace ( pattern , File . separator , "/" ) ;
Set < File > result = new LinkedHashSet < > ( 8 ) ;
doRetrieveMatchingFiles ( fullPattern , rootDir , result ) ;
return result ;
return result ;
}
}
finally {
/ * *
try {
* Recursively retrieve files that match the given pattern ,
fileSystem . close ( ) ;
* adding them to the given result list .
* @param fullPattern the pattern to match against ,
* with prepended root directory path
* @param dir the current directory
* @param result the Set of matching File instances to add to
* @throws IOException if directory contents could not be retrieved
* /
protected void doRetrieveMatchingFiles ( String fullPattern , File dir , Set < File > result ) throws IOException {
if ( logger . isTraceEnabled ( ) ) {
logger . trace ( "Searching directory [" + dir . getAbsolutePath ( ) +
"] for files matching pattern [" + fullPattern + "]" ) ;
}
for ( File content : listDirectory ( dir ) ) {
String currPath = StringUtils . replace ( content . getAbsolutePath ( ) , File . separator , "/" ) ;
if ( content . isDirectory ( ) & & getPathMatcher ( ) . matchStart ( fullPattern , currPath + "/" ) ) {
if ( ! content . canRead ( ) ) {
if ( logger . isDebugEnabled ( ) ) {
logger . debug ( "Skipping subdirectory [" + dir . getAbsolutePath ( ) +
"] because the application is not allowed to read the directory" ) ;
}
}
catch ( UnsupportedOperationException ex ) {
// ignore
}
}
else {
doRetrieveMatchingFiles ( fullPattern , content , result ) ;
}
}
}
}
if ( getPathMatcher ( ) . match ( fullPattern , currPath ) ) {
result . add ( content ) ;
@Nullable
private FileSystem getFileSystem ( URI uri ) {
try {
URI root = uri . resolve ( "/" ) ;
try {
return FileSystems . getFileSystem ( root ) ;
}
}
catch ( Exception ex ) {
return FileSystems . newFileSystem ( root , Map . of ( ) , ClassUtils . getDefaultClassLoader ( ) ) ;
}
}
}
}
catch ( Exception ex ) {
/ * *
* Determine a sorted list of files in the given directory .
* @param dir the directory to introspect
* @return the sorted list of files ( by default in alphabetical order )
* @since 5 . 1
* @see File # listFiles ( )
* /
protected File [ ] listDirectory ( File dir ) {
File [ ] files = dir . listFiles ( ) ;
if ( files = = null ) {
if ( logger . isInfoEnabled ( ) ) {
if ( logger . isInfoEnabled ( ) ) {
logger . info ( "Could not retrieve contents of directory [" + dir . getAbsolutePath ( ) + "]" ) ;
logger . info ( "Failed to resolve java.nio.file.FileSystem for %s: %s" . formatted ( uri , ex ) ) ;
}
}
return new File [ 0 ] ;
return null ;
}
}
Arrays . sort ( files , Comparator . comparing ( File : : getName ) ) ;
return files ;
}
}
/ * *
/ * *
@ -935,14 +884,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
@Nullable
@Nullable
private static Resource findResource ( ModuleReader moduleReader , String name ) {
private Resource findResource ( ModuleReader moduleReader , String name ) {
try {
try {
return moduleReader . find ( name )
return moduleReader . find ( name )
// If it's a "file:" URI, use FileSystemResource to avoid duplicates
// If it's a "file:" URI, use FileSystemResource to avoid duplicates
// for the same path discovered via class-path scanning.
// for the same path discovered via class-path scanning.
. map ( uri - > ResourceUtils . URL_PROTOCOL_FILE . equals ( uri . getScheme ( ) ) ?
. map ( this : : convertToResource )
new FileSystemResource ( uri . getPath ( ) ) :
UrlResource . from ( uri ) )
. orElse ( null ) ;
. orElse ( null ) ;
}
}
catch ( Exception ex ) {
catch ( Exception ex ) {
@ -953,6 +900,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
}
}
private Resource convertToResource ( URI uri ) {
return ResourceUtils . URL_PROTOCOL_FILE . equals ( uri . getScheme ( ) ) ?
new FileSystemResource ( uri . getPath ( ) ) :
UrlResource . from ( uri ) ;
}
private static String stripLeadingSlash ( String path ) {
private static String stripLeadingSlash ( String path ) {
return ( path . startsWith ( "/" ) ? path . substring ( 1 ) : path ) ;
return ( path . startsWith ( "/" ) ? path . substring ( 1 ) : path ) ;
}
}