package io.zbus.rpc;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import io.zbus.kit.ClassKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.zbus.kit.HttpKit;
import io.zbus.kit.HttpKit.UrlInfo;
import io.zbus.kit.JsonKit;
import io.zbus.mq.Protocol;
import io.zbus.rpc.RpcMethod.MethodParam;
import io.zbus.rpc.annotation.Filter;
import io.zbus.rpc.annotation.Param;
import io.zbus.rpc.annotation.Route;
import io.zbus.rpc.doc.DocRender;
import io.zbus.transport.Message;
import io.zbus.transport.http.Http;
import io.zbus.transport.http.Http.FormData;
public class RpcProcessor {
private static final Logger logger = LoggerFactory.getLogger(RpcProcessor.class);
private Map<String, List<MethodInstance>> urlPath2MethodTable = new HashMap<>(); //path => MethodInstance
private boolean docEnabled = true;
private String docUrl = "/doc";
private String docFile = "static/rpc.html";
private String rootUrl = "/";
private boolean stackTraceEnabled = true;
private boolean threadContextEnabled = true;
private boolean embbedPageResource = true;
private RpcFilter beforeFilter;
private RpcFilter afterFilter;
private RpcFilter exceptionFilter;
private Map<String, RpcFilter> annotationFilterTable = new HashMap<>(); //RpcFilter table referred by key
private Map<String, List<RpcFilter>> urlFilterTable = new HashMap<>();
private Map<String, List<RpcFilter>> urlExcludedFilterTable = new HashMap<>();
private Set<String> urlExcludedSet = new HashSet<>();
private Map<String, Object> moduleTable = null;//{module => List<service object> }
/**
* Mount internal moduleTable
* @return
*/
public RpcProcessor mount() {
if(moduleTable == null) return this;
for(Entry<String, Object> e : moduleTable.entrySet()){
mount(e.getKey(), e.getValue());
}
moduleTable = null; //mount just only once
return this;
}
public RpcProcessor mount(String urlPrefix, Object service) {
return mount(urlPrefix, service, true, true, true);
}
public RpcProcessor mount(String urlPrefix, Object service, boolean defaultAuth) {
return mount(urlPrefix, service, defaultAuth, true, true);
}
@SuppressWarnings("unchecked")
public RpcProcessor mount(String urlPrefix, Object service, boolean defaultAuth, boolean enableDoc, boolean overrideMethod) {
if(service instanceof List) {
List<Object> svcList = (List<Object>)service;
for(Object svc : svcList) {
mount(urlPrefix, svc, defaultAuth, enableDoc, overrideMethod);
}
return this;
}
try {
if(service instanceof Class<?>) {
service = ((Class<?>)service).newInstance();
}
List<RpcFilter> classFiltersIncluded = new ArrayList<>();
Filter filter = service.getClass().getAnnotation(Filter.class);
if(filter != null) {
for(String name : filter.value()) {
RpcFilter rpcFilter = annotationFilterTable.get(name);
if(rpcFilter != null) {
classFiltersIncluded.add(rpcFilter);
}
}
}
Method[] methods = service.getClass().getMethods();
Route r = service.getClass().getAnnotation(Route.class);
boolean defaultExcluded = false;
if(r != null) defaultExcluded = r.exclude();
for (Method m : methods) {
if (m.getDeclaringClass() == Object.class) continue;
if(Modifier.isStatic(m.getModifiers())) {
continue;
}
RpcMethod info = new RpcMethod();
String methodName = m.getName();
//default path
String urlPath = HttpKit.joinPath(urlPrefix, methodName);
info.urlPath = urlPath;
info.method = methodName;
info.docEnabled = enableDoc;
info.setGenericReturnType(m.getGenericReturnType());
info.setReturnType(m.getReturnType());
Route p = m.getAnnotation(Route.class);
if(p == null && defaultExcluded) continue;
if (p != null) {
if (p.exclude()) continue;
info.docEnabled = enableDoc && p.docEnabled();
info.urlAnnotation = p;
info.ignoreResult = p.ignoreResult();
urlPath = annoPath(p);
if(urlPath != null) {
info.urlPath = HttpKit.joinPath(urlPrefix, urlPath);
}
}
if(matchUrlExcluded(info.urlPath)) continue; // excluded
info.filters.addAll(classFiltersIncluded);
filter = m.getAnnotation(Filter.class);
if(filter != null) {
for(String name : filter.value()) {
RpcFilter rpcFilter = annotationFilterTable.get(name);
if(rpcFilter != null) {
if(!info.filters.contains(rpcFilter)) {
info.filters.add(rpcFilter);
}
}
}
for(String name : filter.exclude()) {
RpcFilter rpcFilter = annotationFilterTable.get(name);
if(rpcFilter != null) {
info.filters.remove(rpcFilter);
}
}
}
List<RpcFilter> filters = matchFilter(info.urlPath, this.urlFilterTable);
if(filters != null) {
for(RpcFilter f : filters) {
if(info.filters.contains(f)) continue;
info.filters.add(f);
}
}
filters = matchFilter(info.urlPath, this.urlExcludedFilterTable);
if(filters != null) {
for(RpcFilter f : filters) {
info.filters.remove(f);
}
}
m.setAccessible(true);
Class<?>[] paramTypes = m.getParameterTypes();
String[] paramNames = ClassKit.getParameterNames(m);
for (int i = 0; i < paramTypes.length; i++) {
String paramName = paramNames == null ? "arg"+i : paramNames[i];
String paramType = paramTypes[i].getName();
Type paramGenType = m.getGenericParameterTypes()[i];
String paramGenTypeName = paramGenType != null ? paramGenType.getTypeName() : null;
info.addParam(paramTypes[i], paramName, paramType, paramGenTypeName);
}
Annotation[][] paramAnnos = m.getParameterAnnotations();
int size = info.params.size();
for(int i=0; i<size; i++) {
Annotation[] annos = paramAnnos[i];
for(Annotation annotation : annos) {
if(Param.class.isAssignableFrom(annotation.getClass())) {
Param param = (Param)annotation;
String paramName = param.name();
if("".equals(paramName)) paramName = param.value();
info.params.get(i).name = paramName;
info.params.get(i).fromContext = param.ctx();
break;
}
}
}
//register in tables
mount(new MethodInstance(info, m, service), overrideMethod);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return this;
}
public RpcProcessor mount(RpcMethod spec, MethodInvoker service) {
return mount(spec, service, true);
}
public RpcProcessor mount(RpcMethod spec, MethodInvoker service, boolean overrideMethod) {
MethodInstance mi = new MethodInstance(spec, service);
return mount(mi, overrideMethod);
}
public RpcProcessor mount(MethodInstance mi) {
return mount(mi, true);
}
public RpcProcessor mount(MethodInstance mi, boolean overrideMethod) {
RpcMethod spec = mi.info;
String urlPath = spec.getUrlPath();
if(urlPath == null) {
throw new IllegalArgumentException("urlPath can not be null");
}
List<MethodInstance> methodList = urlPath2MethodTable.get(urlPath);
if (methodList != null && !methodList.isEmpty()) {
boolean exists = methodList.contains(mi);
if(!exis