package com.taobao.tddl.rule;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.taobao.tddl.common.exception.TddlException;
import com.taobao.tddl.common.model.lifecycle.AbstractLifecycle;
import com.taobao.tddl.common.model.lifecycle.Lifecycle;
import com.taobao.tddl.common.utils.TStringUtil;
import com.taobao.tddl.config.ConfigDataHandler;
import com.taobao.tddl.config.ConfigDataHandlerFactory;
import com.taobao.tddl.config.ConfigDataListener;
import com.taobao.tddl.config.impl.UnitConfigDataHandlerFactory;
import com.taobao.tddl.monitor.logger.LoggerInit;
import com.taobao.tddl.rule.config.RuleChangeListener;
import com.taobao.tddl.rule.exceptions.TddlRuleException;
import com.taobao.tddl.rule.utils.StringXmlApplicationContext;
import com.taobao.tddl.common.utils.logger.Logger;
import com.taobao.tddl.common.utils.logger.LoggerFactory;
/**
* tddl rule config管理
*
* @author jianghang 2013-11-5 下午3:34:36
* @since 5.0.0
*/
public class TddlRuleConfig extends AbstractLifecycle implements Lifecycle {
protected static final Logger logger = LoggerFactory.getLogger(TddlRuleConfig.class);
private static final int TIMEOUT = 10 * 1000;
private static final String ROOT_BEAN_NAME = "vtabroot";
private static final String TDDL_RULE_LE_PREFIX = "com.taobao.tddl.rule.le.";
private static final String TDDL_RULE_LE_VERSIONS_FORMAT = "com.taobao.tddl.rule.le.{0}.versions";
private static final String NO_VERSION_NAME = "__VN__";
private String appName;
private String unitName;
// 本地规则
private String appRuleFile;
private String appRuleString;
// 多套规则(动态推)
private volatile ConfigDataHandlerFactory cdhf;
private volatile ConfigDataHandler versionHandler;
private volatile Map<String, ConfigDataHandler> ruleHandlers = Maps.newHashMap();
private volatile List<RuleChangeListener> listeners = Lists.newArrayList();
/**
* key = 0(old),1(new),2,3,4... value= version
*/
private volatile Map<String, VirtualTableRoot> vtrs = Maps.newLinkedHashMap();
private volatile Map<String, String> ruleStrs = Maps.newHashMap();
private volatile Map<Integer, String> versionIndex = Maps.newHashMap();
private volatile Map<String, AbstractXmlApplicationContext> oldCtxs = Maps.newHashMap();
private ClassLoader outerClassLoader = null;
// 是否兼容历史老的rule,主要是tdd5代码修改过类的全路径,针对tddl3之前的rule需要考虑做兼容处理
private boolean compatibleOldRule = true;
public void doInit() {
if (appRuleFile != null) { // 如果存在本地规则
String[] rulePaths = appRuleFile.split(";");
if (rulePaths.length == 1 && !rulePaths[0].matches("^V[0-9]*#.+$")) {
// 本地文件单版本规则
ApplicationContext ctx = buildRuleByFile(NO_VERSION_NAME, appRuleFile);
vtrs.put(NO_VERSION_NAME, (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME));
} else {
// 本地文件存在多版本规则
// 一种文件配置写法: V0#classpath:xxx-rule.xml
for (int i = 0; i < rulePaths.length; i++) {
if (rulePaths[i].matches("^V[0-9]*#.+$")) {
continue;
} else {
throw new TddlRuleException("rule file path \"" + rulePaths[i]
+ " \" does not fit the pattern!");
}
}
for (int i = 0; i < rulePaths.length; i++) {
String rulePath = rulePaths[i];
String[] temp = rulePath.split("#");
ApplicationContext ctx = buildRuleByFile(temp[0], temp[1]);
vtrs.put(temp[0], (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME));
}
}
} else if (appRuleString != null) { // 直接设置了规则字符串
ApplicationContext ctx = buildRuleByStr(NO_VERSION_NAME, appRuleString);
vtrs.put(NO_VERSION_NAME, (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME));
} else if (appName != null) { // 使用动态rule配置
String versionsDataId = getVersionsDataId(appName);
if (cdhf == null) {
cdhf = new UnitConfigDataHandlerFactory(unitName, appName);
}
versionHandler = cdhf.getConfigDataHandler(versionsDataId, new VersionsConfigListener());
String versionData = versionHandler.getData(TIMEOUT, ConfigDataHandler.FIRST_CACHE_THEN_SERVER_STRATEGY);
if (versionData == null) { // fallback处理一下,无版本的rule
String dataId = getNonversionedRuledataId(versionData);
if (!dataSub(dataId, NO_VERSION_NAME, new SingleRuleConfigListener())) {
throw new TddlRuleException("subscribe the rule data or init rule error!check the error log!");
}
} else {
String[] versions = versionData.split(",");
for (String version : versions) {
String dataId = getVersionedRuleDataId(appName, version);
if (!dataSub(dataId, version, new SingleRuleConfigListener())) {
throw new RuntimeException("subscribe the rule data or init rule error!check the error log! the rule version is:"
+ version);
}
}
// 记下日志,方便分析
logRecieveRuleVersions(versionData);
}
}
// 构建versionIndex
int index = 0;
Map<Integer, String> tempIndexMap = new HashMap<Integer, String>();
for (String version : vtrs.keySet()) {
tempIndexMap.put(index, version);
index++;
}
this.versionIndex = tempIndexMap;
}
/**
* <pre>
* 返回当前使用的rule规则
* 1. 如果是本地文件,则直接返回本地文件的版本 (本地文件存在多版本时,直接返回第一个版本)
* 2. 如果是动态规则,则直接返回第一个版本
*
* ps. 正常情况,只有一个版本会处于使用中,也就是在数据库动态切换出现多版本使用中.
* </pre>
*/
public VirtualTableRoot getCurrentRule() {
if (versionIndex.size() == 0) {
throw new TddlRuleException("规则对象为空!请检查是否存在规则!");
}
return vtrs.get(versionIndex.get(0));
}
public VirtualTableRoot getVersionRule(String version) {
VirtualTableRoot vtr = vtrs.get(version);
if (vtr == null) {
throw new TddlRuleException("规则对象为空!请检查是否存在规则!");
}
return vtr;
}
/**
* 获取当前在用的版本,理论上正常只有一个版本(切换时出现两个版本),顺序返回版本,第一个版本为当前正在使用中的旧版本
*/
public List<String> getAllVersions() {
int size = versionIndex.size();
List<String> versions = Lists.newArrayList();
for (int i = 0; i < size; i++) {
versions.add(versionIndex.get(i));
}
return versions;
}
/**
* 初始化某个版本的rule
*/
private synchronized boolean initVersionRule(String data, String version) {
if (version == null) {
version = NO_VERSION_NAME;
}
ApplicationContext ctx = null;
try {
// this rule may be wrong rule,don't throw it but log it,
// and will not change the vtr!
ctx = buildRuleByStr(version, data);
} catch (Exception e) {
logger.error("init rule error,rule str is:" + data, e);
return false;
}
VirtualTableRoot tempvtr = (VirtualTableRoot) ctx.getBean(ROOT_BEAN_NAME);
if (tempvtr != null) {
// 直接覆盖
vtrs.put(version, tempvtr);
ruleStrs.put(version, data);
AbstractXmlApplicationContext oldCtx = this.oldCtxs.get(version);
// 销毁旧有容器
if (oldCtx != null) {
oldCtx.close();
}
// 记录一下当前ctx
this.oldCtxs.remove(version);
this.oldCtxs.put(version, (AbstractXmlApplicationContext) ctx);
} else {
logger.error("rule no vtabroot!!");
return false;
}
return true;
}
/**
* 尝试订阅一下
*
* @throws TddlException
*/
private boolean dataSub(String dataId, String version, ConfigDataListener listener) {
ConfigDataHandler ruleHandler = cdhf.getConfigDataHandler(dataId, listener);
try {
String data = ruleHandler.getData(TIMEOUT, ConfigDataHandler.FIRST_CACHE_THEN_SERVER_STRATEGY);
if (data == null) {
logger.error("use diamond rule config,but recieve no config at all!");
return false;
}
if (initVersionRule(data, version)) {
this.ruleHandlers.put(version, ruleHandler);
return true;
}
} catch (Exception e) {
try {
ruleHandler.destory();
} catch (TddlException e1) {
logger.error("destory failed!", e);
}
logger.error("get diamond data error!", e);
}
return false;
}
/**
* remove listeners
*
* @throws TddlException
*/
public void doDestory() throws TddlException {
if (versionHandler != null) {
versionHandler.destory();
}
for (ConfigDataHandler ruleListener : this.ruleHandlers.values()) {
ruleListener.destory();
}
}
// ======================= help metod ==================
/**
* 基于文件创建rule的spring容器
*
* @param file
* @return
*/
private ApplicationContext buildRuleByFile(String version, String file) {
try {
Resource resource = new PathMatchingResourcePatternResolver().getResource(file);
String ruleStr = StringUtils.join(IOUtils.readLines(resource.getInputStream()), SystemUtils.LINE_SEPARATOR);
return buildRuleByStr(version, ruleStr);
} catch (IOException e) {
throw new TddlRuleException(e);
}
}
/**
* 基于string字符流创建rule的spring容器
*
* @param data
* @return
*/
private ApplicationContext buildRuleByStr(String version, String data) {
if (compatibleOldRule) {
data = RuleCompatibleHelper.compatibleRule(data);
}
ApplicationContext applicationContext = new StringXmlApplicationContext(data, outerClassLoader);
ruleStrs.put(version, data); // 记录一下
return applicationContext;
}
private String getCurrentRuleStr() {
if (this.ruleStrs != null && this.ruleStrs.size() > 0) {
String ruleStr = this.ruleStrs.get(versionIndex.get(0));
return ruleStr;
} else {
throw new TddlRuleException("规则对象为空!请检查diamond上是否存在动态规则!");
}
}
public void logRecieveRuleVersions(String version) {
if (LoggerInit.DYNAMIC_RULE_LOG.isInfoEnabled()) {
SimpleDateFormat df = new SimpleDateFormat("yyy-MM-dd HH:mm:ss:SSS");
String logFieldSep = "#@#";
String linesep = System.getProperty("line.separator");
String time = df.format(new Date());
StringBuilder sb = new StringBuilder().append(appName)
.append(logFieldSep)
.append(version)
.append(logFieldSep)
.append(time)
.append(logFieldSep)
.append(1)
.append(linesep);
LoggerInit.DYNAMIC_RULE_LOG.info(sb.toString());
}
}
private void logReceiveRule(String dataId, String data) {
StringBuilder sb = new StringBuilder("recieve versions data!dataId:");
sb.append(dataId);
sb.append(" data:");
sb.append(data);
logger.info(sb.toString());
}
/**
* 获取appname的versions列表的dataId
*
* @param appName
* @return
*/
public static String getVersionsDataId(String appName) {
String versionsDataId = new MessageFormat(TDDL_RULE_LE_VERSIONS_FORMAT).format(new Object[] { appName });
return versionsDataId;
}
/**
* 获取appname指定version的dataId
*
* @param appName
* @param version
* @return
*/
public static String getVersionedRuleDataId(String appName, String version) {
return TDDL_RULE_LE_PREFIX + appName + "." + version;
}
/**
* 获取appname无版本信息的dataId
*
* @param appName
* @return
*/
public static String getNonversionedRuledataId(String appName) {
return TDDL_RULE_LE_PREFIX + appName;
}
// ================== listener =======================
private class VersionsConfigListener implements ConfigDataListener {
public synchronized void onDataRecieved(String dataId, String data) {
if (TStringUtil.isNotEmpty(data)) {
String[] versions = data.split(",");
Map<String, String> checkMap = new HashMap<String, String>();
// 添加新增的规则订阅
int index = 0;
Map<Integer, String> tempIndexMap = new HashMap<Integer, String>();
for (String version : versions) {
if (ruleHandlers.get(version) == null) {
String ruleDataId = getVersionedRuleDataId(appName, version);
if (!dataSub(ruleDataId, version, new SingleRuleConfigListener())) {
return;
}
}
checkMap.put(version, version);
tempIndexMap.put(index, version);
index++;
}
versionIndex = tempIndexMap;
// 删除没有在version中存在的订阅
List<String> needRemove = new ArrayList<String>();
for (Map.Entry<String, ConfigDataHandler> handler : ruleHandlers.entrySet()) {
if (checkMap.get(handler.getKey()) == null) {
needRemove.add(handler.getKey());
}
}
// 清理
for (String version : needRemove) {
ConfigDataHandler handler = ruleHandlers.get(version);
try {
handler.destory();
} catch (TddlException e) {
logger.error("destory failed!", e);
}
ruleHandlers.remove(version);
vtrs.remove(version);
ruleStrs.remove(version);
oldCtxs.get(version).close();
oldCtxs.remove(version);
}
// 在versions data收到为null,或者为空,不调用,保护AppServer
// 调用listener,但只返回位列第一个的VirtualTableRoot
for (RuleChangeListener listener : listeners) {
try {
// may be wrong,so try catch it ,not to affect
// other!
listener.onRuleRecieve(getCurrentRuleStr());
} catch (Exception e) {
logger.error("one listener error!", e);
}
}
}
// 记下日志,方便分析
logRecieveRuleVersions(data);
}
}
private class SingleRuleConfigListener implements ConfigDataListener {
public synchronized void onDataRecieved(String dataId, String data) {
if (TStringUtil.isNotEmpty(data)) {
logReceiveRule(dataId, data);
String prefix = TDDL_RULE_LE_PREFIX + appName + ".";
int i = dataId.indexOf(prefix);
String version = NO_VERSION_NAME; // non-versioned rule
if (i >= 0) {
version = dataId.substring(i + prefix.length());
}
if (initVersionRule(data, version)) {
for (RuleChangeListener listener : listeners) {
try {
// may be wrong,so try catch it ,not to
// affect other !
listener.onRuleRecieve(getCurrentRuleStr());
} catch (Exception e) {
logger.error("one listener error!", e);
}
}
}
}
}
}
// =================== setter / getter ======================
public void setOuterClassLoader(ClassLoader outerClassLoader) {
this.outerClassLoader = outerClassLoader;
}
public void addRuleChangeListener(RuleChangeListener listener) {
this.listeners.add(listener);
}
public void setAppRuleFile(String appRuleFile) {
this.appRuleFile = appRuleFile;
}
public void setAppRuleString(String appRuleString) {
this.appRuleString = appRuleString;
}
public void setAppName(String appName) {
this.appName = appName;
}
public void setUnitName(String unitName) {
this.unitName = unitName;
}
public void setCompatibleOldRule(boolean compatibleOldRule) {
this.compatibleOldRule = compatibleOldRule;
}
}