java.lang.Objectorg.springframework.core.io.support.PathMatchingResourcePatternResolver
All Implemented Interfaces:
ResourcePatternResolver
Direct Known Subclasses:
PortletContextResourcePatternResolver, ServletContextResourcePatternResolver
classpath*:" prefix and/or
internal Ant-style regular expressions (matched using Spring's
org.springframework.util.AntPathMatcher utility).
Both of the latter are effectively wildcards.
No Wildcards:
In the simple case, if the specified location path does not start with the
"classpath*:" prefix, and does not contain a PathMatcher pattern,
this resolver will simply return a single resource via a
getResource() call on the underlying ResourceLoader.
Examples are real URLs such as "file:C:/context.xml", pseudo-URLs
such as "classpath:/context.xml", and simple unprefixed paths
such as "/WEB-INF/context.xml". The latter will resolve in a
fashion specific to the underlying ResourceLoader (e.g.
ServletContextResource for a WebApplicationContext).
Ant-style Patterns:
When the path location contains an Ant-style pattern, e.g.:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xmlthe resolver follows a more complex but defined procedure to try to resolve the wildcard. It produces a
Resource for the path up to the last
non-wildcard segment and obtains a URL from it. If this URL is
not a "jar:" URL or container-specific variant (e.g.
"zip:" in WebLogic, "wsjar" in WebSphere", etc.),
then a java.io.File is obtained from it, and used to resolve the
wildcard by walking the filesystem. In the case of a jar URL, the resolver
either gets a java.net.JarURLConnection from it, or manually parse
the jar URL, and then traverse the contents of the jar file, to resolve the
wildcards.
Implications on portability:
If the specified path is already a file URL (either explicitly, or
implicitly because the base ResourceLoader is a filesystem one,
then wildcarding is guaranteed to work in a completely portable fashion.
If the specified path is a classpath location, then the resolver must
obtain the last non-wildcard path segment URL via a
Classloader.getResource() call. Since this is just a
node of the path (not the file at the end) it is actually undefined
(in the ClassLoader Javadocs) exactly what sort of a URL is returned in
this case. In practice, it is usually a java.io.File representing
the directory, where the classpath resource resolves to a filesystem
location, or a jar URL of some sort, where the classpath resource resolves
to a jar location. Still, there is a portability concern on this operation.
If a jar URL is obtained for the last non-wildcard segment, the resolver
must be able to get a java.net.JarURLConnection from it, or
manually parse the jar URL, to be able to walk the contents of the jar,
and resolve the wildcard. This will work in most environments, but will
fail in others, and it is strongly recommended that the wildcard
resolution of resources coming from jars be thoroughly tested in your
specific environment before you rely on it.
classpath*: Prefix:
There is special support for retrieving multiple class path resources with
the same name, via the "classpath*:" prefix. For example,
"classpath*:META-INF/beans.xml" will find all "beans.xml"
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
at the same location within each jar file. Internally, this happens via a
ClassLoader.getResources() call, and is completely portable.
The "classpath*:" prefix can also be combined with a PathMatcher pattern in
the rest of the location path, for example "classpath*:META-INF/*-beans.xml".
In this case, the resolution strategy is fairly simple: a
ClassLoader.getResources() call is used on the last non-wildcard
path segment to get all the matching resources in the class loader hierarchy,
and then off each resource the same PathMatcher resolution strategy described
above is used for the wildcard subpath.
Other notes:
WARNING: Note that "classpath*:" when combined with
Ant-style patterns will only work reliably with at least one root directory
before the pattern starts, unless the actual target files reside in the file
system. This means that a pattern like "classpath*:*.xml" will
not retrieve files from the root of jar files but rather only from the
root of expanded directories. This originates from a limitation in the JDK's
ClassLoader.getResources() method which only returns file system
locations for a passed-in empty String (indicating potential roots to search).
WARNING: Ant-style patterns with "classpath:" resources are not guaranteed to find matching resources if the root package to search is available in multiple class path locations. This is because a resource such as
com/mycompany/package1/service-context.xmlmay be in only one location, but when a path such as
classpath:com/mycompany/**/service-context.xmlis used to try to resolve it, the resolver will work off the (first) URL returned by
getResource("com/mycompany");. If this base package
node exists in multiple classloader locations, the actual end resource may
not be underneath. Therefore, preferably, use "classpath*:" with the same
Ant-style pattern in such a case, which will search all class path
locations that contain the root package.Also see:
- CLASSPATH_ALL_URL_PREFIX
- org.springframework.util.AntPathMatcher
- org.springframework.core.io.ResourceLoader#getResource(String)
- java.lang.ClassLoader#getResources(String)
- author:
Juergen - Hoeller
- author:
Colin - Sampaleanu
- since:
1.0.2 -
Constructor:
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
ClassLoader access will happen via the thread context class loader.
public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
this.resourceLoader = new DefaultResourceLoader(classLoader);
}
Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
Parameters:
classLoader - the ClassLoader to load classpath resources with,
or null for using the thread context class loader
Also see:
- org.springframework.core.io.DefaultResourceLoader
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
Create a new PathMatchingResourcePatternResolver.
ClassLoader access will happen via the thread context class loader.
Parameters:
resourceLoader - the ResourceLoader to load root directories and
actual resources with
Method from org.springframework.core.io.support.PathMatchingResourcePatternResolver Summary:
convertClassLoaderURL, determineRootDir, doFindMatchingFileSystemResources, doFindPathMatchingFileResources, doFindPathMatchingJarResources, doRetrieveMatchingFiles, findAllClassPathResources, findPathMatchingResources, getClassLoader, getJarFile, getPathMatcher, getResource, getResourceLoader, getResources, isJarResource, resolveRootDirResource, retrieveMatchingFiles, setPathMatcher
Methods from java.lang.Object:
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Method from org.springframework.core.io.support.PathMatchingResourcePatternResolver Detail:
protected Resource convertClassLoaderURL(URL url) {
return new UrlResource(url);
}
Convert the given URL as returned from the ClassLoader into a Resource object.
The default implementation simply creates a UrlResource instance.
protected String determineRootDir(String location) {
int prefixEnd = location.indexOf(":") + 1;
int rootDirEnd = location.length();
while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
rootDirEnd = location.lastIndexOf('/", rootDirEnd - 2) + 1;
}
if (rootDirEnd == 0) {
rootDirEnd = prefixEnd;
}
return location.substring(0, rootDirEnd);
}
Determine the root directory for the given location.
Used for determining the starting point for file matching,
resolving the root directory location to a java.io.File
and passing it into retrieveMatchingFiles, with the
remainder of the location as pattern.
Will return "/WEB-INF" for the pattern "/WEB-INF/*.xml",
for example.
protected Set doFindMatchingFileSystemResources(File rootDir,
String subPattern) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
}
Set matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set result = new LinkedHashSet(matchingFiles.size());
for (Iterator it = matchingFiles.iterator(); it.hasNext();) {
File file = (File) it.next();
result.add(new FileSystemResource(file));
}
return result;
}
Find all resources in the file system that match the given location pattern
via the Ant-style PathMatcher.
protected Set doFindPathMatchingFileResources(Resource rootDirResource,
String subPattern) throws IOException {
File rootDir = null;
try {
rootDir = rootDirResource.getFile().getAbsoluteFile();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot search for matching files underneath " + rootDirResource +
" because it does not correspond to a directory in the file system", ex);
}
return Collections.EMPTY_SET;
}
return doFindMatchingFileSystemResources(rootDir, subPattern);
}
Find all resources in the file system that match the given location pattern
via the Ant-style PathMatcher.
protected Set doFindPathMatchingJarResources(Resource rootDirResource,
String subPattern) throws IOException {
URLConnection con = rootDirResource.getURL().openConnection();
JarFile jarFile = null;
String jarFileUrl = null;
String rootEntryPath = null;
boolean newJarFile = false;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
JarURLConnection jarCon = (JarURLConnection) con;
jarCon.setUseCaches(false);
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
}
else {
// No JarURLConnection - > need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
String urlFile = rootDirResource.getURL().getFile();
int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
jarFile = getJarFile(jarFileUrl);
}
else {
jarFile = new JarFile(urlFile);
jarFileUrl = urlFile;
rootEntryPath = "";
}
newJarFile = true;
}
try {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
}
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
Set result = new LinkedHashSet(8);
for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = (JarEntry) entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
if (getPathMatcher().match(subPattern, relativePath)) {
result.add(rootDirResource.createRelative(relativePath));
}
}
}
return result;
}
finally {
// Close jar file, but only if freshly obtained -
// not from JarURLConnection, which might cache the file reference.
if (newJarFile) {
jarFile.close();
}
}
}
Find all resources in jar files that match the given location pattern
via the Ant-style PathMatcher.
protected void doRetrieveMatchingFiles(String fullPattern,
File dir,
Set result) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
File[] dirContents = dir.listFiles();
if (dirContents == null) {
throw new IOException("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
for (int i = 0; i < dirContents.length; i++) {
File content = dirContents[i];
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
doRetrieveMatchingFiles(fullPattern, content, result);
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
Recursively retrieve files that match the given pattern,
adding them to the given result list.
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
Enumeration resourceUrls = getClassLoader().getResources(path);
Set result = new LinkedHashSet(16);
while (resourceUrls.hasMoreElements()) {
URL url = (URL) resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
return (Resource[]) result.toArray(new Resource[result.size()]);
}
Find all class location resources with the given location via the ClassLoader.
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
String rootDirPath = determineRootDir(locationPattern);
String subPattern = locationPattern.substring(rootDirPath.length());
Resource[] rootDirResources = getResources(rootDirPath);
Set result = new LinkedHashSet(16);
for (int i = 0; i < rootDirResources.length; i++) {
Resource rootDirResource = resolveRootDirResource(rootDirResources[i]);
if (isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return (Resource[]) result.toArray(new Resource[result.size()]);
}
Find all resources that match the given location pattern via the
Ant-style PathMatcher. Supports resources in jar files and zip files
and in the file system.
public ClassLoader getClassLoader() {
return getResourceLoader().getClassLoader();
}
Return the ClassLoader that this pattern resolver works with
(never null).
protected JarFile getJarFile(String jarFileUrl) throws IOException {
if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
try {
return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
}
catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
}
}
else {
return new JarFile(jarFileUrl);
}
}
Resolve the given jar file URL into a JarFile object.
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
Return the PathMatcher that this resource pattern resolver uses.
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
Return the ResourceLoader that this pattern resolver works with.
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
protected boolean isJarResource(Resource resource) throws IOException {
return ResourceUtils.isJarURL(resource.getURL());
}
Return whether the given resource handle indicates a jar resource
that the doFindPathMatchingJarResources method can handle.
The default implementation checks against the URL protocols
"jar", "zip" and "wsjar" (the latter are used by BEA WebLogic Server
and IBM WebSphere, respectively, but can be treated like jar files).
protected Resource resolveRootDirResource(Resource original) throws IOException {
if (equinoxResolveMethod != null) {
URL url = original.getURL();
if (url.getProtocol().startsWith("bundle")) {
return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, new Object[] {url}));
}
}
return original;
}
Resolve the specified resource for path matching.
The default implementation detects an Equinox OSGi "bundleresource:"
/ "bundleentry:" URL and resolves it into a standard jar file URL that
can be traversed using Spring's standard jar file traversal algorithm.
protected Set retrieveMatchingFiles(File rootDir,
String pattern) throws IOException {
if (!rootDir.isDirectory()) {
throw new IllegalArgumentException("Resource path [" + rootDir + "] does not denote a directory");
}
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
if (!pattern.startsWith("/")) {
fullPattern += "/";
}
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
Set result = new LinkedHashSet(8);
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}
Retrieve files that match the given path pattern,
checking the given directory and its subdirectories.
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
}
Set the PathMatcher implementation to use for this
resource pattern resolver. Default is AntPathMatcher.