package jef.database.support;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jef.accelerator.asm.ClassReader;
import jef.common.log.LogUtil;
import jef.database.DbClient;
import jef.database.DbUtils;
import jef.database.Field;
import jef.database.IQueryableEntity;
import jef.database.ORMConfig;
import jef.database.annotation.EasyEntity;
import jef.database.dialect.ColumnType;
import jef.database.dialect.type.AutoIncrementMapping;
import jef.database.dialect.type.AutoIncrementMapping.GenerationResolution;
import jef.database.dialect.type.ColumnMapping;
import jef.database.jpa.JefEntityManager;
import jef.database.jpa.JefEntityManagerFactory;
import jef.database.meta.Column;
import jef.database.meta.ColumnModification;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.wrapper.executor.StatementExecutor;
import jef.tools.ArrayUtils;
import jef.tools.ClassScanner;
import jef.tools.IOUtils;
import jef.tools.StringUtils;
import jef.tools.resource.IResource;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
/**
* 自动扫描工具,在构造时可以根据构造方法,自动的将继承DataObject的类检查出来,并载入
*
* @author Administrator
*
*/
public class QuerableEntityScanner {
public static final Set<String> dynamicEnhanced = new HashSet<String>();
// implClasses
private String[] implClasses = new String[] { "jef.database.DataObject" };
/**
* 是否扫描子包
*/
private boolean scanSubPackage = true;
/**
* 是否创建不存在的表
*/
private boolean createTable = true;
/**
* 是否修改存在的表
*/
private boolean alterTable = true;
/**
* 当alterTable=true时,如果修改表时需要删除列,是否允许删除列
*/
private boolean allowDropColumn;
/**
* 是否检查序列
*/
private boolean checkSequence = true;
/**
* 是否检查索引
*/
private boolean checkIndex = true;
/**
* 当创建新表后,是否同时初始化表中的数据
*/
private boolean initDataAfterCreate = true;
/**
* 当扫描到已经存在的表后,是否检查初始化数据。 一般在开发阶段开启
*/
private boolean initDataIfTableExists = false;
/**
* 扫描包
*/
private String[] packageNames = { "jef" };
/**
* EMF
*/
private JefEntityManagerFactory entityManagerFactory;
public String[] getPackageNames() {
return packageNames;
}
public void setPackageNames(String packageNames) {
this.packageNames = packageNames.split(",");
}
public boolean isScanSubPackage() {
return scanSubPackage;
}
public String[] getImplClasses() {
return implClasses;
}
/**
* 设置多个DataObject类
*
* @param implClasses
*/
public void setImplClasses(String[] implClasses) {
this.implClasses = implClasses;
}
@SuppressWarnings("rawtypes")
public void setImplClasses(Class... implClasses) {
String[] result = new String[implClasses.length];
for (int i = 0; i < implClasses.length; i++) {
result[i] = implClasses[i].getName();
}
this.implClasses = result;
}
public void setScanSubPackage(boolean scanSubPackage) {
this.scanSubPackage = scanSubPackage;
}
public void doScan() {
String[] parents = getClassNames();
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null)
cl = QuerableEntityScanner.class.getClassLoader();
// 开始
ClassScanner cs = new ClassScanner();
IResource[] classes = cs.scan(packageNames);
// 循环所有扫描到的类
Map<ITableMetadata, Boolean> tasks = new HashMap<ITableMetadata, Boolean>();
for (IResource s : classes) {
try {
ClassReader cr = getClassInfo(cl,s);
if( cr==null)//NOT found class
continue;
// 根据父类判断
if(isEntiyClz(cl,parents,cr.getSuperName())){
Class<?> clz = loadClass(cl, cr.getClassName().replace('/', '.'));
if (clz != null) {
registeEntity(clz, tasks);
}
};
} catch (IOException e) {
LogUtil.exception(e);
}
}
processInit(tasks);
}
private boolean isEntiyClz(ClassLoader cl ,String[] parents,String superName) throws IOException {
if("java/lang/Object".equals(superName)){
return false;
}
if (ArrayUtils.contains(parents, superName)) {// 是实体
return true;
}
// 读取类
ClassReader cr = getClassInfo(cl,superName);
if(cr==null){
return false;
}
return isEntiyClz(cl,parents, cr.getSuperName());
}
private ClassReader getClassInfo(ClassLoader cl,String s) throws IOException {
URL url = cl.getResource(s.replace('.', '/') + ".class");
if (url == null)
return null;
InputStream stream = url.openStream();
if (stream == null) {
LogUtil.error("The class content [" + s + "] not found!");
return null;
}
return new ClassReader(stream,true);
}
private ClassReader getClassInfo(ClassLoader cl,IResource s) throws IOException {
InputStream stream = s.getInputStream();
if (stream == null) {
LogUtil.error("The class content [" + s + "] not found!");
return null;
}
return new ClassReader(stream,true);
}
private Class<?> loadClass(ClassLoader cl, String s) {
try {
Class<?> c = cl.loadClass(s);
return c;
} catch (ClassNotFoundException e) {
LogUtil.error("Class not found:" + e.getMessage());
return null;
}
}
public boolean registeEntity(String... names) {
if(names==null)return true;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = this.getClass().getClassLoader();
}
Map<ITableMetadata, Boolean> tasks = new HashMap<ITableMetadata, Boolean>();
for (String name : names) {
if (StringUtils.isEmpty(name)) {
continue;
}
Class<?> c;
try {
c = cl.loadClass(name);
registeEntity(c, tasks);
} catch (ClassNotFoundException e) {
LogUtil.error("Class not found:" + e.getMessage());
}
}
try {
processInit(tasks);
return true;
} catch (Exception e) {
LogUtil.exception(e);
return false;
}
}
private void processInit(Map<ITableMetadata, Boolean> tasks) {
boolean manual = ORMConfig.getInstance().isManualSequence();
ORMConfig.getInstance().setManualSequence(true);
try {
for (Entry<ITableMetadata, Boolean> entry : tasks.entrySet()) {
boolean isNew = entry.getValue();
ITableMetadata meta = entry.getKey();
// 初始化表中的数据
if (isNew && initDataAfterCreate) {
URL url = meta.getThisType().getResource(meta.getThisType().getSimpleName() + ".init.json");
if (url != null) {
try {
initData(url, meta, false);
} catch (IOException e1) {
LogUtil.exception(e1);
}
}
} else if (!isNew && initDataIfTableExists) {
URL url = meta.getThisType().getResource(meta.getThisType().getSimpleName() + ".init.json");
if (url != null) {
try {
initData(url, meta, true);
} catch (IOException e1) {
LogUtil.exception(e1);
}
}
}
}
} finally {
ORMConfig.getInstance().setManualSequence(manual);
}
}
private void registeEntity(Class<?> c, Map<ITableMetadata, Boolean> tasks) {
try {
ITableMetadata meta = MetaHolder.getMeta(c);// 用initMeta变为强制初始化。getMeta更优雅一点
if (meta != null) {
LogUtil.info("Table [" + meta.getTableName(true) + "] <--> [" + c.getName() + "]");
} else {
LogUtil.error("Entity [" + c.getName() + "] was not mapping to any table.");
}
EasyEntity ee = c.getAnnotation(EasyEntity.class);
final boolean create = createTable && (ee == null || ee.create());
final boolean refresh = alterTable && (ee == null || ee.refresh());
if (entityManagerFactory != null && (create || refresh)) {
doTableDDL(meta, create, refresh, tasks);
}
} catch (Throwable e) {
LogUtil.error("EntityScanner:[Failure]" + StringUtils.exceptionStack(e));
}
}
private void doTableDDL(ITableMetadata meta, final boolean create, final boolean refresh, Map<ITableMetadata, Boolean> tasks) throws SQLException {
// 不管是否存在,总之先创建一次
boolean exist = true;
boolean isNew = false;
DbClient client = entityManagerFactory.getDefault();
if (create) {
isNew = client.createTable(meta) > 0;
} else {
exist = client.existTable(meta.getTableName(true));
}
if (exist) {
client.refreshTable(meta, new MetadataEventListener() {
public void onTableFinished(ITableMetadata meta, String tablename) {
}
public boolean onTableCreate(ITableMetadata meta, String tablename) {
return create;
}
public boolean onSqlExecuteError(SQLException e, String tablename, String sql, List<String> sqls, int n) {
LogUtil.error("[ALTER-TABLE]. SQL:[{}] ERROR.\nMessage:[{}]", sql, e.getMessage());
return true;
}
public boolean onCompareColumns(String tablename, List<Column> columns, Map<Field, ColumnMapping> defined) {
return refresh;
}
public boolean onColumnsCompared(String tablename, ITableMetadata meta, Map<String, ColumnType> insert,
List<ColumnModification> changed, List<String> delete) {
if (!allowDropColumn) {
delete.clear();
}
return true;
}
public void onAlterSqlFinished(String tablename, String sql, List<String> sqls, int n, long cost) {
}
public boolean beforeTableRefresh(ITableMetadata meta, String table) {
return true;
}
public void beforeAlterTable(String tablename, ITableMetadata meta, StatementExecutor conn, List<String> sql) {
}
});
}
if (!exist) {
return;
}
// 检查Sequence
if (checkSequence) {
for (ColumnMapping f : meta.getColumns()) {
if (f instanceof AutoIncrementMapping) {
AutoIncrementMapping m = (AutoIncrementMapping) f;
GenerationResolution gt = ((AutoIncrementMapping) f).getGenerationType(entityManagerFactory.getDefault().getProfile(
meta.getBindDsName()));
if (gt == GenerationResolution.SEQUENCE || gt == GenerationResolution.TABLE) {
entityManagerFactory.getDefault().getSequenceManager().getSequence(m, meta.getBindDsName());
}
}
}
}
tasks.put(meta, isNew);
}
public static class InitDataModel {
public boolean cascade;
public boolean merge;
private List<?> data;
public List<?> getData() {
return data;
}
public void setData(List<?> data) {
this.data = data;
}
public void set(String key, String value) {
if ("cascade".equals(key)) {
this.cascade = StringUtils.toBoolean(value, false);
} else if ("merge".equals(key)) {
this.merge = StringUtils.toBoolean(value, false);
} else {
LogUtil.warn("Unknown key in file init.json: {}", key);
}
}
}
/*
* 数据初始化
*
* @param url
*
* @param meta
*/
private void initData(URL url, ITableMetadata meta, boolean merge) throws IOException {
LogUtil.info("init data for table {}", meta.getTableName(true));
InitDataModel model = parseData(url, meta);
if (merge || model.merge) {
JefEntityManager em = (JefEntityManager) entityManagerFactory.createEntityManager();
if (model.cascade) {
for (Object o : model.getData()) {
em.mergeCascade(o);
}
} else {
for (Object o : model.getData()) {
em.merge(o);
}
}
em.close();
} else {
try {
if (model.cascade) {
for (Object o : model.getData()) {
entityManagerFactory.getDefault().insertCascade((IQueryableEntity) o);
}
} else {
entityManagerFactory.getDefault().batchInsert(model.getData());
}
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
}
}
private InitDataModel parseData(URL url, ITableMetadata meta) throws IOException {
BufferedReader reader = IOUtils.getReader(url, "UTf-8");
StringWriter sw = new StringWriter(1024);
InitDataModel result = new InitDataModel();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("#")) {
int n = line.indexOf(':');
if (n > 0) {
String key = line.substring(1, n).trim().toLowerCase();
String value = line.substring(n + 1).trim();
result.set(key, value);
}
} else {
sw.write(line);
sw.write("\n");
break;
}
}
IOUtils.copy(reader, sw, true);
String data = sw.toString();
try {
List<?> results = JSON.parseArray(data, meta.getThisType());
result.setData(results);
} catch (JSONException e) {
throw new IllegalArgumentException(url.toString() + " is a invalid json file", e);
}
return result;
}
private String[] getClassNames() {
List<String> clzs = new ArrayList<String>();
for (int i = 0; i < implClasses.length; i++) {
String s = implClasses[i];
s = StringUtils.trimToNull(s);
if (s == null)
continue;
clzs.add(s.replace('.', '/'));
}
return clzs.toArray(new String[clzs.size()]);
}
public boolean isAllowDropColumn() {
return allowDropColumn;
}
public void setAllowDropColumn(boolean allowDropColumn) {
this.allowDropColumn = allowDropColumn;
}
public void setEntityManagerFactory(JefEntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
public boolean isCreateTable() {
return createTable;
}
public void setCreateTable(boolean createTable) {
this.createTable = createTable;
}
public boolean isAlterTable() {
return alterTable;
}
public void setAlterTable(boolean alterTable) {
this.alterTable = alterTable;
}
public boolean isCheckSequence() {
return checkSequence;
}
public void setCheckSequence(boolean checkSequence) {
this.checkSequence = checkSequence;
}
public void setInitDataAfterCreate(boolean initDataAfterCreate) {
this.initDataAfterCreate = initDataAfterCreate;
}
public boolean isInitDataAfterCreate() {
return initDataAfterCreate;
}
public boolean isCheckIndex() {
return checkIndex;
}
public boolean isInitDataIfTableExists() {
return initDataIfTableExists;
}
public void setInitDataIfTableExists(boolean initDataIfTableExists) {
this.initDataIfTableExists = initDataIfTableExists;
}
public void setCheckIndex(boolean checkIndex) {
this.checkIndex = checkIndex;
}
}