package com.lizard.fastdb.io;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.lizard.fastdb.util.ClassLoaderUtils;
/**
* Classpath环境下资源解析器 <br>
* <ul>
* <li>支持解析文件名通配符匹配</li>
* <li>支持匹配jar包中的所有文件</li>
* </ul>
*
* @author SHEN.GANG
*/
public class ClasspathResourceResolver implements ResourceResolver
{
private static final Log logger = LogFactory.getLog(ClasspathResourceResolver.class);
/**
* 获取匹配的多个资源
*
* @param location
* @return
* @throws IOException
*/
public Set<URL> getResources(String location)
{
if ( null == location )
{
return null;
}
location = location.trim().replace(File.separator, "/");
Set<URL> resources = null;
try
{
// 通配符匹配
if ( ResourceUtils.isPattern(location) )
{
resources = findAllPathMatchingResources(location);
}
else
{
resources = findAllClasspathResources(location);
}
} catch (IOException e)
{
logger.error("Resource Resolver parse the ["+ location +"] failed!", e);
}
return resources;
}
/**
* 获取所有通配符匹配的文件资源(包括jar包中的)
*
* @param locationPattern
* 含有通配符的文件路径
* @return 匹配的资源
* @throws IOException
*/
protected Set<URL> findAllPathMatchingResources(String locationPattern) throws IOException
{
// 获取协议
String protocol = ResourceUtils.getProtocol(locationPattern);
// 去除过滤协议后的资源路径
String real_path = ResourceUtils.clearProtocol(locationPattern);
// 顶级目录:config/*.xml --> rootDirPath = config/
String rootDirPath = ResourceUtils.getRootDir(real_path);
// 匹配文件名: config/*.xml --> subPattern = *.xml
String subPattern = real_path.substring(rootDirPath.length());
// 通过 rootDirPath 获取所有目录资源
//Resource[] rootDirResources = getResources( protocol + rootDirPath );
// 由于rootDirPath不可能也不允许含有通配符,所以直接findAllClasspathResources
Set<URL> rootDirResources = findAllClasspathResources( protocol + rootDirPath );
// 所有Jar包root资源
Set<URL> allJarRootResources = null;
// 如果 rootDirPath == "" ,则只会加载当前classpath下的资源,不会加载jar包
// 这里需要对 rootDirPath == "" 在处理jar时进行特殊处理:加载所有jar资源
if( "".equals(rootDirPath) && (ResourceUtils.isJarProtocol(protocol) || protocol.length() == 0 ) )
{
// 获取当前环境下的所有jar包资源
// allJarRootResources = getResources(ResourceUtils.ALL_JAR_PATTERN);
// 由于 ResourceUtils.ALL_JAR_PATTERN 是完整资源路径,所以直接 ResourceUtils.ALL_JAR_PATTERN
allJarRootResources = findAllClasspathResources(ResourceUtils.JAR_URL_PREFIX + ResourceUtils.ALL_JAR_PATTERN);
}
Set<URL> result = new LinkedHashSet<URL>();
// 在获取的所有资源中查找匹配的文件资源
if( null != rootDirResources && rootDirResources.size() > 0 )
{
for ( URL rootDirURL : rootDirResources )
{
// 如果是jar包资源,则在jar包资源中匹配
if ( ResourceUtils.isJarURL(rootDirURL) )
{
result.addAll(doFindPathMatchingJarResources(rootDirURL, subPattern));
}
// 在文件系统中匹配
else
{
result.addAll(doFindPathMatchingFileResources(rootDirURL, subPattern));
}
}
}
// 如果查找了所有jar资源
if( null != allJarRootResources && allJarRootResources.size() > 0 )
{
for ( URL rootJarURL : allJarRootResources )
{
result.addAll(doFindPathMatchingJarResources(rootJarURL, subPattern));
}
}
if ( logger.isDebugEnabled() )
{
logger.debug("Resolved location pattern [" + locationPattern + "] resources: "+ result.size());
}
return result;
}
/**
* 根据资源完整路径(不含通配符),获取classpath下所有相应资源(包括jar包)
*
* @param location 资源完整路径(不含通配符),其可能含有classpath:, jar:协议或没有
* @return 匹配的资源
* @throws IOException
*/
protected Set<URL> findAllClasspathResources(String location) throws IOException
{
String protocol = ResourceUtils.getProtocol(location);
String path = ResourceUtils.clearProtocol(location);
if ( path.startsWith("/") )
{
path = path.substring(1);
}
Set<URL> result = new LinkedHashSet<URL>(16);
Enumeration<URL> resourceUrls = ClassLoaderUtils.getClassLoader(ClasspathResourceResolver.class).getResources(path);
URL url = null;
// 无协议,加载全部资源
if( "".equals(protocol) )
{
while (resourceUrls.hasMoreElements())
{
url = resourceUrls.nextElement();
result.add(url);
}
}
// 只加载jar包资源
else if( ResourceUtils.isJarProtocol(protocol) )
{
while (resourceUrls.hasMoreElements())
{
url = resourceUrls.nextElement();
if( ResourceUtils.isJarURL(url) )
{
result.add(url);
}
}
}
// 只加载classpath资源
else if( ResourceUtils.isClasspathProtocol(protocol) )
{
while (resourceUrls.hasMoreElements())
{
url = resourceUrls.nextElement();
if( ResourceUtils.isFileURL(url) )
{
result.add(url);
}
}
}
return result;
}
/**
* 在jar包资源中查找匹配的资源
*
* @param rootDirResource
* 待查找的jar目录资源
* @param subPattern
* 匹配模式
* @return 匹配的资源
* @throws IOException
*/
protected Set<URL> doFindPathMatchingJarResources(URL rootJarDirURL, String subPattern)
throws IOException
{
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean newJarFile = false;
String jar_path = rootJarDirURL.getPath();
// 如果一个JAR URL资源指向了 META-INF/MANIFEST.MF 说明是为了获取所有的JAR包资源而是用的特殊方式;
// 需要将这个 JAR URL 重置为 去除 META-INF/MANIFEST.MF 后的新 URL
if( jar_path.endsWith(ResourceUtils.ALL_JAR_PATTERN) )
{
jar_path = jar_path.substring(0, jar_path.lastIndexOf(ResourceUtils.ALL_JAR_PATTERN));
rootJarDirURL = new URL( ResourceUtils.JAR_URL_PREFIX + jar_path );
}
URLConnection con = rootJarDirURL.openConnection();
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 = rootJarDirURL.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.
rootEntryPath = rootEntryPath + "/";
}
// 处理匹配正则
String _subpattern = ResourceUtils.dealPattern(subPattern);
Set<URL> result = new LinkedHashSet<URL>(8);
// 遍历jarEntry 匹配文件资源
for ( Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); )
{
JarEntry entry = entries.nextElement();
if( entry.isDirectory() )
{
continue;
}
String entryPath = entry.getName();
if ( entryPath.startsWith(rootEntryPath) )
{
String relativePath = entryPath.substring(rootEntryPath.length());
// 如果文件匹配
if ( relativePath.matches(_subpattern) )
{
result.add(toJarURL(rootJarDirURL, 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();
}
}
}
/**
* 在系统中查找匹配的资源
*
* @param rootDirResource
* 待查找的jar目录资源
* @param subPattern
* 匹配模式
* @return 匹配的资源
*/
protected Set<URL> doFindPathMatchingFileResources(URL rootDirURL, String subPattern)
{
File rootDir;
try
{
rootDir = ResourceUtils.getFile(rootDirURL);
if ( !rootDir.exists() )
{
if ( logger.isDebugEnabled() )
{
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
}
return Collections.emptySet();
}
if ( !rootDir.isDirectory() )
{
if ( logger.isWarnEnabled() )
{
logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
}
return Collections.emptySet();
}
if ( !rootDir.canRead() )
{
if ( logger.isWarnEnabled() )
{
logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath()
+ "] because the application is not allowed to read the directory");
}
return Collections.emptySet();
}
Set<URL> results = new LinkedHashSet<URL>(8);
doRetrieveMatchingFiles(rootDir, subPattern, results);
return results;
} catch (IOException ex)
{
if ( logger.isWarnEnabled() )
{
logger.warn("Cannot search for matching files underneath " + rootDirURL
+ " because it does not correspond to a directory in the file system", ex);
}
return Collections.emptySet();
}
}
/**
* 递归遍历文件系统目录,匹配文件
*
* @param dir 目录
* @param subPattern 匹配模式
* @param results 返回匹配命中的文件
* @throws MalformedURLException
*/
protected void doRetrieveMatchingFiles(File dir, String subPattern, Set<URL> results) throws MalformedURLException
{
if ( logger.isDebugEnabled() )
{
logger.debug("Searching directory [" + dir.getAbsolutePath() + "] for files matching pattern ["+ subPattern + "]");
}
File[] dirContents = dir.listFiles();
if ( dirContents == null || dirContents.length == 0 )
{
return;
}
// 是否需要递归子目录
boolean isCascade = subPattern.startsWith(ResourceUtils.ALL_FILE_PATTERN);
String _subPattern = ResourceUtils.dealPattern( subPattern );
// 遍历目录匹配文件
for ( File _file : dirContents )
{
// 是否需要级联递归子目录
if ( isCascade && _file.isDirectory())
{
if ( !_file.canRead() )
{
if ( logger.isDebugEnabled() )
{
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath()+ "] because the application is not allowed to read the directory");
}
}
else
{
doRetrieveMatchingFiles( _file, subPattern, results);
}
}
if ( _file.isFile() && _file.getName().matches(_subPattern) )
{
results.add(_file.toURI().toURL());
}
}
}
/**
* 对一个jar URL进行特殊处理:对使用ResourceUtils.ALL_JAR_PATTERN特殊获取的所有jar URL 修改为jar root URL.
* <br>
* 例如:使用ResourceUtils.ALL_JAR_PATTERN查找所有jar中的a.xml,返回的URL是:..jar!/META-INF/a.xml,
* 需要处理为:..jar!/a.xml。
*
* @param jarRootUrl
* @param filename
* @return
* @throws MalformedURLException
*/
protected URL toJarURL( URL jarRootUrl, String filename ) throws MalformedURLException
{
if( jarRootUrl.getPath().endsWith(ResourceUtils.ALL_JAR_PATTERN) )
{
String jarPath = jarRootUrl.getPath();
jarPath = jarPath.substring(0, jarPath.lastIndexOf(ResourceUtils.ALL_JAR_PATTERN));
jarPath = ResourceUtils.JAR_URL_PREFIX + jarPath;
return new URL( jarPath + filename );
}
else
{
return new URL( jarRootUrl, filename );
}
}
/**
* 根据jar文件路径,获取jar文件对象
*
* @param jarFilePath
* jar文件路径
* @return JarFile
* @throws IOException
*/
private 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);
}
}
}