package org.hsweb.web.starter;
import org.hsweb.commons.StringUtils;
import org.hsweb.expands.script.engine.DynamicScriptEngine;
import org.hsweb.expands.script.engine.DynamicScriptEngineFactory;
import org.hsweb.expands.script.engine.ExecuteResult;
import org.hsweb.ezorm.rdb.RDBDatabase;
import org.hsweb.ezorm.rdb.RDBTable;
import org.hsweb.ezorm.rdb.executor.SqlExecutor;
import org.hsweb.ezorm.rdb.meta.converter.ClobValueConverter;
import org.hsweb.ezorm.rdb.meta.converter.JSONValueConverter;
import org.hsweb.ezorm.rdb.render.SqlAppender;
import org.hsweb.ezorm.rdb.simple.wrapper.BeanWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import java.io.*;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Predicate;
import static org.hsweb.web.starter.SystemVersion.Property.*;
/**
* @author zhouhao
*/
public class SystemInitialize implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(SystemInitialize.class);
static final Predicate<Resource> allWayIsTrue = (resource) -> true;
private SqlExecutor sqlExecutor;
private RDBDatabase database;
private String databaseUserName;
private SystemVersion targetVersion;
private SystemVersion installedVersion;
private String installGroovyScriptPackage = "classpath*:org/hsweb/start/scripts/install/install.groovy";
private String installSqlScriptPackage = "classpath*:org/hsweb/start/scripts/install/sql/{db}/install.sql";
private String upgradeGroovyScriptPackage = "classpath*:org/hsweb/start/scripts/upgrade/*.groovy";
private String upgradeSqlScriptPackage = "classpath*:org/hsweb/start/scripts/upgrade/sql/{db}/*.sql";
private String initializeScriptPackage = "classpath*:org/hsweb/start/scripts/initialize/initialize.groovy";
private String initializeSqlPackage = "classpath*:org/hsweb/start/scripts/initialize/sql/{db}/initialize.sql";
private String customInitializeSqlScriptPackage = "classpath*:scripts/install/sql/{db}/*.sql";
private String customInitializeScriptPackage = "classpath*:scripts/initialize/initialize.groovy";
private String customUpgradeSqlScriptPackage = "classpath*:scripts/upgrade/sql/{db}/*.sql";
private String customUpgradeScriptPackage = "classpath*:scripts/upgrade/*.groovy";
public SystemInitialize(SystemVersion targetVersion,
SqlExecutor sqlExecutor,
RDBDatabase database,
String databaseUserName, String dbType) {
Assert.notNull(sqlExecutor);
Assert.notNull(database);
Assert.notNull(databaseUserName);
Assert.notNull(targetVersion);
this.sqlExecutor = sqlExecutor;
this.database = database;
this.databaseUserName = databaseUserName;
this.targetVersion = targetVersion;
installSqlScriptPackage = installSqlScriptPackage.replace("{db}", dbType);
upgradeSqlScriptPackage = upgradeSqlScriptPackage.replace("{db}", dbType);
initializeSqlPackage = initializeSqlPackage.replace("{db}", dbType);
customInitializeSqlScriptPackage = customInitializeSqlScriptPackage.replace("{db}", dbType);
customUpgradeSqlScriptPackage = customUpgradeSqlScriptPackage.replace("{db}", dbType);
}
Predicate<Resource> systemVersionFilter = (resource) -> {
try {
String name = resource.getFilename();
name = name.substring(0, name.lastIndexOf("."));
boolean snapshot = name.toLowerCase().contains("snapshot");
name = name.toLowerCase().replace(".snapshot", "").replace("-snapshot", "");
String[] ver = name.split("[.]");
SystemVersion systemVersion = new SystemVersion();
systemVersion.setVersion(Integer.parseInt(ver[0])
, Integer.parseInt(ver[1])
, Integer.parseInt(ver[2])
, snapshot);
boolean install = systemVersion.compareTo(targetVersion) <= 0;
if (installedVersion != null) {
install = systemVersion.compareTo(installedVersion) == 1;
}
return install;
} catch (Exception e) {
logger.warn("parse file {} version error", resource, e);
return false;
}
};
Predicate<Resource> frameworkVersionFilter = (resource) -> {
try {
String name = resource.getFilename();
name = name.substring(0, name.lastIndexOf("."));
boolean snapshot = name.toLowerCase().contains("snapshot");
name = name.toLowerCase().replace(".snapshot", "").replace("-snapshot", "");
String[] ver = name.split("[.]");
SystemVersion.FrameworkVersion systemVersion = new SystemVersion.FrameworkVersion();
systemVersion.setVersion(Integer.parseInt(ver[0]),
Integer.parseInt(ver[1]),
Integer.parseInt(ver[2]), snapshot);
boolean install = systemVersion.compareTo(targetVersion.getFrameworkVersion()) <= 0;
if (installedVersion != null) {
install = systemVersion.compareTo(installedVersion.getFrameworkVersion()) == 1;
}
return install;
} catch (Exception e) {
logger.warn("parse file {} version error", resource, e);
return false;
}
};
protected void install() throws Exception {
boolean sync = false;
if (installedVersion == null) {
tryInitDatabase();
tryUpgradeFramework();
tryCustomInit();
tryCustomUpgrade();
sync = true;
} else {
int frameworkCompare = installedVersion.getFrameworkVersion().compareTo(targetVersion.getFrameworkVersion());
int systemCompare = installedVersion.compareTo(targetVersion);
if (frameworkCompare > 0) {
logger.warn("The installation framework ({}) is newer than the new version ({}).", installedVersion.getFrameworkVersion(), targetVersion.getFrameworkVersion());
} else if (frameworkCompare < 0) {
tryUpgradeFramework();
sync = true;
} else {
if (logger.isInfoEnabled())
logger.info("framework : {}", installedVersion.getFrameworkVersion());
}
if (systemCompare > 0) {
logger.warn("The installation ({}) is newer than the new version ({}).", installedVersion, targetVersion);
} else if (systemCompare < 0) {
tryCustomUpgrade();
sync = true;
} else {
if (logger.isInfoEnabled())
logger.info("system : {}", installedVersion);
}
}
if (sync)
syncSystemVersion();
}
protected void tryInitDatabase() throws Exception {
executeGroovy(getScriptFileContext(installGroovyScriptPackage, allWayIsTrue));
executeSql(getScriptFileContext(installSqlScriptPackage, allWayIsTrue));
executeGroovy(getScriptFileContext(initializeScriptPackage, allWayIsTrue));
executeSql(getScriptFileContext(initializeSqlPackage, allWayIsTrue));
}
protected void executeGroovy(List<String> groovyScript) throws Exception {
DynamicScriptEngine engine = DynamicScriptEngineFactory.getEngine("groovy");
String id = "hsweb.install";
Map<String, Object> var = getScriptVar();
for (String s : groovyScript) {
if (StringUtils.isNullOrEmpty(s)) continue;
engine.compile(id, s);
ExecuteResult result = engine.execute(id, var);
engine.remove(id);
result.getIfSuccess();
}
}
protected Map<String, Object> getScriptVar() {
Map<String, Object> var = new HashMap<>();
var.put("database", database);
var.put("sqlExecutor", sqlExecutor);
return var;
}
protected void tryUpgradeFramework() throws Exception {
executeGroovy(getScriptFileContext(upgradeGroovyScriptPackage, frameworkVersionFilter));
executeSql(getScriptFileContext(upgradeSqlScriptPackage, frameworkVersionFilter));
}
protected void tryCustomInit() throws Exception {
executeGroovy(getScriptFileContext(customInitializeScriptPackage, allWayIsTrue));
executeSql(getScriptFileContext(customInitializeSqlScriptPackage, allWayIsTrue));
}
protected void tryCustomUpgrade() throws Exception {
executeGroovy(getScriptFileContext(customUpgradeScriptPackage, systemVersionFilter));
executeSql(getScriptFileContext(customUpgradeSqlScriptPackage, systemVersionFilter));
}
protected void executeSql(List<String> sqlFile) throws SQLException {
for (String sql : sqlFile) {
if (StringUtils.isNullOrEmpty(sql)) continue;
sqlExecutor.exec(sql);
}
}
protected void syncSystemVersion() throws SQLException {
RDBTable<SystemVersion> rdbTable = database.getTable("s_system");
int total = rdbTable.createQuery().total();
if (total == 0) {
rdbTable.createInsert().value(targetVersion).exec();
} else {
rdbTable.createUpdate().set(targetVersion).where().sql("1=1").exec();
}
}
protected SystemVersion getSystemVersion() throws SQLException {
boolean tableInstall = sqlExecutor.tableExists("s_system");
database.createOrAlter("s_system")
.addColumn().name("name").varchar(128).notNull().comment("系统名称").commit()
.addColumn().name("major_version").alias(majorVersion).number(32).javaType(Integer.class).notNull().comment("主版本号").commit()
.addColumn().name("minor_version").alias(minorVersion).number(32).javaType(Integer.class).notNull().comment("次版本号").commit()
.addColumn().name("revision_version").alias(revisionVersion).number(32).javaType(Integer.class).notNull().comment("修订版").commit()
.addColumn().name("snapshot").number(1).javaType(Boolean.class).notNull().comment("是否快照版").commit()
.addColumn().name("comment").varchar(2000).comment("系统说明").commit()
.addColumn().name("website").varchar(2000).comment("系统网址").commit()
.addColumn().name("framework_version").notNull().alias(frameworkVersion).clob()
.custom(column -> column.setValueConverter(new JSONValueConverter(SystemVersion.FrameworkVersion.class, new ClobValueConverter()))).notNull().comment("框架版本").commit()
.comment("系统信息")
.custom(table -> table.setObjectWrapper(new BeanWrapper<SystemVersion>(SystemVersion::new, table)))
.commit();
if (!tableInstall) {
if (!sqlExecutor.tableExists("s_user")) {
return null;
} else {
logger.warn("Database Already initialized,but table [s_system] not Exists!");
//直接同步数据库
syncSystemVersion();
return targetVersion;
}
}
RDBTable<SystemVersion> rdbTable = database.getTable("s_system");
return rdbTable.createQuery().single();
}
private int compareFileName(Resource resource, Resource target) {
String name = resource.getFilename().split("[.]", 1)[0];
String targetName = target.getFilename().split("[.]", 1)[0];
if (StringUtils.isDouble(name) && StringUtils.isDouble(targetName)) {
return ((Double) Double.parseDouble(name)).compareTo(Double.parseDouble(targetName));
}
return name.compareTo(targetName);
}
protected List<String> getScriptFileContext(String filePackage, Predicate<Resource> filter) throws IOException {
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(filePackage);
List<String> scripts = new ArrayList<>();
Arrays.stream(resources)
.filter(filter)
.sorted(this::compareFileName)
.forEach(resource -> {
String name = resource.getFilename();
try {
if (name.endsWith(".sql")) {
scripts.addAll(stream2sqlString(resource.getInputStream()));
} else
scripts.add(stream2string(resource.getInputStream()));
} catch (IOException e) {
logger.error("read file ({}) error", name, e);
}
});
return scripts;
}
protected List<String> stream2sqlString(InputStream stream) throws UnsupportedEncodingException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "utf-8"));
List<String> sqlList = new ArrayList<>();
SqlAppender tmp = new SqlAppender();
String uname = databaseUserName;
bufferedReader.lines().forEach((line) -> {
if (line.startsWith("--")) return;
line = line.replace("${jdbc.username}", uname);
//去除sql中的;
if (line.endsWith(";"))
tmp.add(line.substring(0, line.length() - 1));
else
tmp.add(line);
tmp.add("\n");
if (line.endsWith(";")) {
sqlList.add(tmp.toString());
tmp.clear();
}
});
return sqlList;
}
protected String stream2string(InputStream stream) throws IOException {
return StreamUtils.copyToString(stream, Charset.forName("utf-8"));
}
@Override
public void afterPropertiesSet() throws Exception {
this.installedVersion = getSystemVersion();
install();
}
}