org.sagacity.sqltoy.config.ScanEntityAndSqlResource Maven / Gradle / Ivy
package org.sagacity.sqltoy.config;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.sagacity.sqltoy.config.annotation.Entity;
import org.sagacity.sqltoy.config.annotation.SqlToyEntity;
import org.sagacity.sqltoy.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @project sagacity-sqltoy
* @description 扫描classes目录以及jar包中的class文件;以及扫描sql.xml文件
* @author zhongxuchen
* @version v1.0,Date:2012-6-10
* @modify {Date:2017-10-28,修改getResourceUrls方法,返回枚举数组,修复maven做单元测试时只检测testClass路径的问题}
* @modify {Date:2019-09-23,剔除根据方言剔除非本方言sql文件的逻辑,实践证明这个功能价值很低}
* @modify {Date:2020-03-13,调整sql加载策略,jar包中的优先加载,classes下面的加载顺序在jar后面,便于增量发版覆盖}
* @modify {Date:2024-08-10,增加了文件路径存在空格等特殊符号的处理}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ScanEntityAndSqlResource {
//springboot场景下已经支持
//欠缺一个功能:路径增加匹配能力,如:classpath:com/sagframe/**/sqltoy/*.sql.xml
/**
* 定义日志
*/
protected final static Logger logger = LoggerFactory.getLogger(ScanEntityAndSqlResource.class);
/**
* 默认的sql定义文件后缀名,便于区分和查找加载
*/
private static final String SQLTOY_SQL_FILE_SUFFIX = ".sql.xml";
private static final String CLASSPATH = "classpath:";
private static final String JAR = "jar";
private static final String RESOURCE = "resource";
// 常见路径中的特殊字符
private static final String[][] SPECIALCHARACTERS = new String[][] { { "%20", " " }, { "%25", "%" }, { "%23", "#" },
{ "%5B", "[" }, { "%5D", "]" }, { "%2E", "." } };
/**
* @todo 从指定包package中获取所有的sqltoy实体对象(意义已经不大,entity类目前已经改为在使用时加载的解析模式)
* @param entitiesPackage
* @param recursive 是否递归往下钻取
* @param charset
* @return
*/
public static Set> getPackageEntities(String entitiesPackage, boolean recursive, String charset) {
// class类的集合
Set> entities = new LinkedHashSet>();
// 获取包的名字 并进行替换
String packageName = entitiesPackage.trim();
// 剔除第一个字符为目录的符号,并统一packName为xxx.xxx 格式
if (packageName.length() > 0 && packageName.charAt(0) == '/') {
packageName = packageName.substring(1);
}
if (packageName.endsWith("/")) {
packageName = packageName.substring(0, packageName.length() - 1);
}
packageName = packageName.replace("/", ".");
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合,循环向下级目录检索entity类
Enumeration dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 循环迭代下去
URL url;
String protocol;
while (dirs.hasMoreElements()) {
// 获取下一个元素
url = dirs.nextElement();
// 得到协议的名称
protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), (charset == null) ? "UTF-8" : charset);
// 以文件的方式扫描整个包下的文件 并添加到集合中
addEntitiesInPackage(packageName, filePath, recursive, entities);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
logger.debug("jar类型的扫描,加载POJO实体bean");
// 定义一个JarFile
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration entries = jar.entries();
// 同样的进行循环迭代
JarEntry entry;
String name;
String loadClass;
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
entry = entries.nextElement();
name = entry.getName();
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName) && name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
loadClass = name.substring(0, name.length() - 6).replace("/", ".");
try {
// 添加到classes
Class entityClass = Thread.currentThread().getContextClassLoader()
.loadClass(loadClass);
// 判定是否是sqltoy实体对象
if (isSqlToyEntity(entityClass)) {
entities.add(entityClass);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return entities;
}
/**
* @todo 以文件的形式来获取包下的所有Class(意义已经不大,entity类目前已经改为使用时加载解析模式)
* @param packageName 类似com.xxx.xx 格式
* @param packagePath
* @param recursive
* @param entities
*/
public static void addEntitiesInPackage(String packageName, String packagePath, final boolean recursive,
Set> entities) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
@Override
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
// 循环所有文件
String className;
for (File file : dirfiles) {
// 如果是目录 则继续递归扫描
if (file.isDirectory()) {
addEntitiesInPackage(packageName.concat(".").concat(file.getName()), file.getAbsolutePath(), recursive,
entities);
} else {
className = file.getName();
// 如果是java类文件 去掉后面的.class 只留下类名
className = className.substring(0, className.length() - 6);
try {
// 加载class 并判定是否为sqltoy的实体类
Class entityClass = Thread.currentThread().getContextClassLoader()
.loadClass(packageName.concat(".").concat(className));
if (isSqlToyEntity(entityClass)) {
entities.add(entityClass);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
/**
* @todo 判定是否sqltoy的实体对象
* @param entityClass
* @return
*/
public static boolean isSqlToyEntity(Class entityClass) {
if (entityClass.isAnnotationPresent(SqlToyEntity.class)) {
return true;
}
if (entityClass.isAnnotationPresent(Entity.class) && !Modifier.isAbstract(entityClass.getModifiers())) {
return true;
}
return false;
}
/**
* @todo 获取sqltoy配置的sql文件
* @param resourceDir
* @param mappingResources
* @return
* @throws Exception
*/
public static List getSqlResources(String resourceDir, List mappingResources) throws Exception {
List result = new ArrayList();
String realRes;
Enumeration urls;
URL url;
File file;
boolean startClasspath = false;
if (StringUtil.isNotBlank(resourceDir) && !resourceDir.equalsIgnoreCase("none") && !resourceDir.equals("\'\'")
&& !resourceDir.equals("\"\"")) {
// 统一全角半角,用逗号分隔
String[] dirSet = resourceDir.replaceAll("\\;", ",").replaceAll("\\,", ",").replaceAll("\\;", ",")
.split("\\,");
JarFile jar;
Enumeration entries;
JarEntry entry;
String sqlFile;
for (String dir : dirSet) {
realRes = dir.trim();
startClasspath = false;
if (realRes.toLowerCase().startsWith(CLASSPATH)) {
realRes = realRes.substring(10).trim();
startClasspath = true;
}
urls = getResourceUrls(realRes, startClasspath);
if (urls != null) {
while (urls.hasMoreElements()) {
url = urls.nextElement();
if (url.getProtocol().equals(JAR)) {
if (realRes.length() > 0 && realRes.charAt(0) == '/') {
realRes = realRes.substring(1);
}
jar = ((JarURLConnection) url.openConnection()).getJarFile();
entries = jar.entries();
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
entry = entries.nextElement();
sqlFile = entry.getName();
if (sqlFile.startsWith(realRes)
&& sqlFile.toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)
&& !entry.isDirectory()) {
// update 2020-03-13 调整sql加载策略
// jar中的sql顺序放在前面,从而便于classes里面的sql可以在后面加载可以覆盖前面的,便于项目做增量更新
result.add(0, sqlFile);
}
}
} else if (url.getProtocol().equals(RESOURCE)) {
if (!result.contains(realRes) && realRes.toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)) {
result.add(realRes);
}
} else {
getPathFiles(new File(url.toURI()), result);
}
}
}
}
}
if (mappingResources != null && !mappingResources.isEmpty()) {
for (int i = 0; i < mappingResources.size(); i++) {
realRes = mappingResources.get(i).trim();
// 必须是以.sql.xml结尾的文件
if (realRes.toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)) {
startClasspath = false;
if (realRes.toLowerCase().startsWith(CLASSPATH)) {
realRes = realRes.substring(10).trim();
startClasspath = true;
}
urls = getResourceUrls(realRes, startClasspath);
if (null != urls) {
while (urls.hasMoreElements()) {
url = urls.nextElement();
if (realRes.length() > 0 && realRes.charAt(0) == '/') {
realRes = realRes.substring(1);
}
if (url.getProtocol().equals(JAR)) {
if (!result.contains(realRes)) {
// jar中的sql优先加载,从而确保直接放于classes目录下面的sql可以实现对之前的覆盖,便于项目增量发版管理
result.add(0, realRes);
}
} else if (url.getProtocol().equals(RESOURCE)) {
if (!result.contains(realRes)
&& realRes.toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)) {
result.add(realRes);
}
} else {
file = new File(url.toURI());
if (file.getName().toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)
&& !result.contains(file)) {
result.add(file);
}
}
}
}
}
}
}
return result;
}
/**
* @todo 获取资源的URL
* @param resource
* @param startClasspath
* @return
* @throws Exception
*/
public static Enumeration getResourceUrls(String resource, boolean startClasspath) throws Exception {
Enumeration urls = null;
if (null == resource) {
return urls;
}
if (!startClasspath) {
File file = new File(resource);
// 文件不存在,但存在%20 空格、%25 百分号的转义符号(适度兼容,路径中不要搞极端特殊的符号)
if (!file.exists()) {
String fileResource = resource;
boolean hasSpecChar = false;
for (String[] item : SPECIALCHARACTERS) {
if (fileResource.contains(item[0])) {
hasSpecChar = true;
fileResource = fileResource.replace(item[0], item[1]);
}
}
// 存在特殊字符,重新实例化文件
if (hasSpecChar) {
file = new File(fileResource);
}
}
if (file.exists()) {
Vector v = new Vector();
v.add(file.toURI().toURL());
urls = v.elements();
} else {
if (resource.length() > 0 && resource.charAt(0) == '/') {
resource = resource.substring(1);
}
urls = Thread.currentThread().getContextClassLoader().getResources(resource);
}
} else {
if (resource.length() > 0 && resource.charAt(0) == '/') {
resource = resource.substring(1);
}
urls = Thread.currentThread().getContextClassLoader().getResources(resource);
}
return urls;
}
/**
* @todo 递归获取文件夹下面的以sql.xml结尾的sql文件
* @param parentFile
* @param fileList
*/
private static void getPathFiles(File parentFile, List fileList) {
if (null == parentFile) {
return;
}
String fileName = parentFile.getName();
// 路径,取路径下的文件
if (parentFile.isDirectory()) {
File[] files = parentFile.listFiles();
File file;
for (int loop = 0; loop < files.length; loop++) {
file = files[loop];
fileName = file.getName();
if (!file.isDirectory() && fileName.toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)) {
fileList.add(file);
} else {
getPathFiles(files[loop], fileList);
}
}
} // 文件,则判断是否以.sql.xml 结尾的文件
else if (fileName.toLowerCase().endsWith(SQLTOY_SQL_FILE_SUFFIX)) {
fileList.add(parentFile);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy