package com.erdaoya.springcloud.comx.source;
import com.alibaba.fastjson.JSONPath;
import com.erdaoya.springcloud.comx.schema.onError.Strategy;
import com.erdaoya.springcloud.comx.source.sourcebase.UnmatchedRequestMethodException;
import com.erdaoya.springcloud.comx.utils.config.ConfigException;
import com.erdaoya.springcloud.comx.context.Context;
import com.erdaoya.springcloud.comx.schema.TinyTemplate;
import com.erdaoya.springcloud.comx.source.sourcebase.AbstractSourceBase;
import com.erdaoya.springcloud.comx.source.sourcebase.SourceBaseFactory;
import com.erdaoya.springcloud.comx.utils.config.Config;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Map;
/**
* Created by xue on 12/22/16.
*/
public class Source {
static final Integer DEFAULT_TIMEOUT = 8000;
static final String FIELD_BASE = "base";
static final String FIELD_FIRST_ENTRY_ONLY = "firstEntryOnly";
static final String FIELD_JSON_PATH = "jsonPath";
static final String FIELD_ON_ERROR = "onError";
static final String FIELD_BACKUP = "backup";
public static final String FIELD_URI = "uri";
public static final String FIELD_METHOD = "method";
public static final String FIELD_TIMEOUT = "timeout";
public static final String RESERVED_RENDERED_URI= "reserved_rendered_uri";
static final String RESERVED_TPL_VAR_REQUEST = "request";
static final String FIELD_CACHE = "cache";
static final String SOURCE_CACHE_KEY_PREFIX = "Source:";
static final String SOURCE_CACHE_TTL = "ttl:";
static final String SOURCE_CACHE_KEY = "key";
static final Integer SOURCE_CACHE_TTL_DEFAULT = 300;
/**
* source 是一个对应的source 实例, 提供loadData 返回 Map Object;(或非Map)
* 它将使用 sourcebase 资源以及 sourceCache localCache 缓存
* 同一个source 应对refJsonPath 不同情况下拥有不同运行结果,所以不能是object 属性
* 不拥有变量 reservedVariables
* renderedUri 会随着 reservedVariables 变化而不断变化
* 一个source 是一个最小单位,所以可以直接在variables 添加renderedUri
*/
private Config conf;
public Source(Config conf) { this.conf = conf;}
/**
* loadData 外层 记录日志
* since redis operation, choose nanotime; 1E-9
* @param context context
* @param reservedVariables {ref|request|data|renderedUri} TODO renderedUri 是否在这里存疑
* @return Object data
*/
public Object loadData(Context context, HashMap<String, Object> reservedVariables) throws ConfigException, SourceException{
long time0 = System.nanoTime();
Object result = null;
Exception ex = null;
context.getLogger().debug("Source loading URI:" + this.getUri());
try {
result = doLoadData(context, reservedVariables);
double deltaTime = (System.nanoTime() - time0) * 1.0 / 1000_000_000;
context.getLogger().debug("Source loading used:" + deltaTime + " sec, URI:" + this.getUri());
} catch (Exception ex0){
ex = ex0;
}
if (result != null) return result;
Config backupConf = conf.sub(Source.FIELD_BACKUP);
if (backupConf.rawData().isEmpty()) {
if (ex != null) {
context.getLogger().error("Source loading error & backupConf empty; URI:" + this.getUri());
if (ex instanceof SourceException) throw (SourceException) ex;
else throw new SourceException(ex);
}
return null;
}
Source backSource = new Source(backupConf);
return backSource.loadData(context, reservedVariables);
}
// TODO 确认 cache 中是否可以自设置 key
// cache 包含key 都是属于 decorcache
// TODO 再次确认 request 是否可以放在外层
private Object doLoadData(Context context, HashMap<String, Object> reservedVariables) throws IOException, UnmatchedRequestMethodException, Exception{
try {
String uri = getUri();
TinyTemplate tpl = new TinyTemplate(uri);
reservedVariables.put(RESERVED_TPL_VAR_REQUEST, context.getRequest());
String renderedUri = tpl.render(reservedVariables, context, true);
reservedVariables.put(RESERVED_RENDERED_URI, renderedUri);
Object data = loadCache(context, renderedUri);
if (data == null) {
data = getBase(context).executeLoading(context, conf, reservedVariables);
setCache(context, renderedUri, data);
}
return postHandle(data);
} catch (Exception ex) {
context.getLogger().error("Source loading doLoadData error:" + ex.getMessage() + "; " + ex.getClass());
//TODO 记录详细日志 方式
ex.printStackTrace();
return Strategy.fromConf(conf.sub(FIELD_ON_ERROR)).handleSourceException(ex, context);
}
}
public AbstractSourceBase getBase(Context context) throws ConfigException{
String baseId = conf.str(Source.FIELD_BASE, "default");
return SourceBaseFactory.getSourceBase(baseId);
}
// get cache 失败不影响流程继续进行, 但应加入日志
private Object loadCache(Context context, String renderedUri) throws ConfigException{
// localcache 部分
String localCacheKey = this.getBase(context).getId() + ":" + renderedUri;
if (context.getLocalCacheEnabled() && context.getLocalCache().containsKey(localCacheKey)) {
return context.getLocalCache().get(localCacheKey);
}
// sourcecache 部分
try {
Config cacheConf = conf.sub(FIELD_CACHE);
if (cacheConf.rawData().isEmpty()) return null;
return context.getCache().getMapObject(SOURCE_CACHE_KEY_PREFIX + renderedUri);
} catch (Exception ex) {
context.getLogger().error("Source loading: getCache error:" + ex.getMessage() + "; " + ex.getClass());
// TODO 是否如此记录
ex.printStackTrace();
return null;
}
}
// set cache 失败不影响流程继续进行, 但应加入日志
private void setCache(Context context, String renderedUri, Object data) throws ConfigException {
// localcache 部分
String localCacheKey = this.getBase(context).getId() + ":" + renderedUri;
if (context.getLocalCacheEnabled()) context.getLocalCache().put(localCacheKey, data);
// sourcecache 部分
try {
String key = SOURCE_CACHE_KEY_PREFIX + renderedUri;
context.getLogger().trace("Source loading: setCache key:" + key);
Config cacheConf = conf.sub(FIELD_CACHE);
if (cacheConf.rawData().isEmpty()) return;
Integer ttl = cacheConf.intvalue(SOURCE_CACHE_TTL, SOURCE_CACHE_TTL_DEFAULT);
if (data instanceof Map) {
context.getCache().setMapObject(key, (Map) data, ttl);
} else {
context.getCache().set(key, data.toString(), ttl);
}
} catch (Exception ex) {
context.getLogger().error("Source loading: setCache error:" + ex.getMessage() + "; " + ex.getClass());
// TODO 是否如此记录
ex.printStackTrace();
}
}
protected Object postHandle(Object data) {
String jsonPath = conf.str(FIELD_JSON_PATH, "");
if (jsonPath.isEmpty()) return data;
Object matchedNode = JSONPath.eval(data, jsonPath);
if (conf.bool(FIELD_FIRST_ENTRY_ONLY, false)) {
return (matchedNode.getClass().isArray())? Array.get(matchedNode, 0):null;
}
return matchedNode;
}
protected String getUri() throws ConfigException{
return conf.rstr(Source.FIELD_URI);
}
}