package org.nutz.mvc.adaptor;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.nutz.ioc.Ioc;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.lang.util.MethodParamNamesScaner;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.HttpAdaptor;
import org.nutz.mvc.Scope;
import org.nutz.mvc.adaptor.injector.AllAttrInjector;
import org.nutz.mvc.adaptor.injector.AppAttrInjector;
import org.nutz.mvc.adaptor.injector.HttpInputStreamInjector;
import org.nutz.mvc.adaptor.injector.HttpReaderInjector;
import org.nutz.mvc.adaptor.injector.IocInjector;
import org.nutz.mvc.adaptor.injector.IocObjInjector;
import org.nutz.mvc.adaptor.injector.NameInjector;
import org.nutz.mvc.adaptor.injector.PathArgInjector;
import org.nutz.mvc.adaptor.injector.ReqHeaderInjector;
import org.nutz.mvc.adaptor.injector.RequestAttrInjector;
import org.nutz.mvc.adaptor.injector.RequestInjector;
import org.nutz.mvc.adaptor.injector.ResponseInjector;
import org.nutz.mvc.adaptor.injector.ServletContextInjector;
import org.nutz.mvc.adaptor.injector.SessionAttrInjector;
import org.nutz.mvc.adaptor.injector.SessionInjector;
import org.nutz.mvc.annotation.Attr;
import org.nutz.mvc.annotation.IocObj;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.annotation.ReqHeader;
import org.nutz.mvc.impl.AdaptorErrorContext;
/**
*
* @author zozoh(zozohtnt@gmail.com)
* @author wendal(wendal1985@gmail.com)
* @author juqkai(juqkai@gmail.com)
*/
public abstract class AbstractAdaptor implements HttpAdaptor {
private static final Log log = Logs.get();
protected ParamInjector[] injs;
protected Method method;
protected Class<?>[] argTypes;
public void init(Method method) {
this.method = method;
argTypes = method.getParameterTypes();
injs = new ParamInjector[argTypes.length];
Annotation[][] annss = method.getParameterAnnotations();
Type[] types = method.getGenericParameterTypes();
for (int i = 0; i < annss.length; i++) {
Annotation[] anns = annss[i];
Param param = null;
Attr attr = null;
IocObj iocObj = null;
ReqHeader reqHeader = null;
// find @Param & @Attr & @IocObj in current annotations
for (int x = 0; x < anns.length; x++)
if (anns[x] instanceof Param) {
param = (Param) anns[x];
break;
} else if (anns[x] instanceof Attr) {
attr = (Attr) anns[x];
break;
} else if (anns[x] instanceof IocObj) {
iocObj = (IocObj) anns[x];
break;
} else if (anns[x] instanceof ReqHeader) {
reqHeader = (ReqHeader) anns[x];
break;
}
// If has @Attr
if (null != attr) {
injs[i] = evalInjectorByAttrScope(attr);
continue;
}
// If has @IocObj
if (null != iocObj) {
injs[i] = new IocObjInjector(method.getParameterTypes()[i],
iocObj.value());
continue;
}
if (null != reqHeader) {
injs[i] = new ReqHeaderInjector(reqHeader.value(), argTypes[i]);
continue;
}
// And eval as default suport types
injs[i] = evalInjectorByParamType(argTypes[i]);
if (null != injs[i])
continue;
// Eval by sub-classes
injs[i] = evalInjector(types[i], param);
// 子类也不能确定,如何适配这个参数,那么做一个标记,如果
// 这个参数被 ParamInjector 适配到,就会抛错。
// 这个设计是因为了 "路径参数"
if (null == injs[i]) {
injs[i] = paramNameInject(method, i);
}
}
}
private static ParamInjector evalInjectorByAttrScope(Attr attr) {
if (attr.scope() == Scope.APP)
return new AppAttrInjector(attr.value());
if (attr.scope() == Scope.SESSION)
return new SessionAttrInjector(attr.value());
if (attr.scope() == Scope.REQUEST)
return new RequestAttrInjector(attr.value());
return new AllAttrInjector(attr.value());
}
private static ParamInjector evalInjectorByParamType(Class<?> type) {
// Request
if (ServletRequest.class.isAssignableFrom(type)) {
return new RequestInjector();
}
// Response
else if (ServletResponse.class.isAssignableFrom(type)) {
return new ResponseInjector();
}
// Session
else if (HttpSession.class.isAssignableFrom(type)) {
return new SessionInjector();
}
// ServletContext
else if (ServletContext.class.isAssignableFrom(type)) {
return new ServletContextInjector();
}
// Ioc
else if (Ioc.class.isAssignableFrom(type)) {
return new IocInjector();
}
// InputStream
else if (InputStream.class.isAssignableFrom(type)) {
return new HttpInputStreamInjector();
}
// Reader
else if (Reader.class.isAssignableFrom(type)) {
return new HttpReaderInjector();
}
return null;
}
protected ParamInjector evalInjector(Type type, Param param) {
return evalInjectorBy(type, param);
}
/**
* 子类实现这个方法根据自己具体的逻辑来生产一个参数注入器
*
* @param type
* 参数类型
* @param param
* 参数的注解
* @return 一个新的参数注入器实例
*/
protected abstract ParamInjector evalInjectorBy(Type type, Param param);
public Object[] adapt(ServletContext sc,
HttpServletRequest req,
HttpServletResponse resp,
String[] pathArgs) {
Object[] args = new Object[argTypes.length];
if (args.length != injs.length)
throw new IllegalArgumentException("args.length != injs.length , You get a bug, pls report it!!");
AdaptorErrorContext errCtx = null;
// 也许用户有自己的AdaptorErrorContext实现哦
if (argTypes.length > 0) {
if (AdaptorErrorContext.class.isAssignableFrom(argTypes[argTypes.length - 1]))
errCtx = (AdaptorErrorContext) Mirror.me(argTypes[argTypes.length - 1])
.born(argTypes.length);
}
Object obj;
try {
obj = getReferObject(sc, req, resp, pathArgs);
}
catch (Throwable e) {
if (errCtx != null) {
if (log.isInfoEnabled())
log.info("Adapter Error catched , but I found AdaptorErrorContext param, so, set it to args, and continue", e);
errCtx.setAdaptorError(e, this);
args[args.length - 1] = errCtx;
return args;
}
throw Lang.wrapThrow(e);
}
int len = Math.min(args.length, null == pathArgs ? 0 : pathArgs.length);
for (int i = 0; i < args.length; i++) {
Object value = null;
if (i < len) { // 路径参数
value = null == pathArgs ? null : pathArgs[i];
} else { // 普通参数
value = obj;
}
try {
args[i] = injs[i].get(sc, req, resp, value);
}
catch (Throwable e) {
if (errCtx != null) {
log.infof("Adapter Param Error(%s) index=%d", method, i, e);
errCtx.setError(i, e, method, value, injs[i]); // 先错误保存起来,全部转好了,再判断是否需要抛出
} else
throw Lang.wrapThrow(e);
}
if (args[i] == null && argTypes[i].isPrimitive()) {
args[i] = Lang.getPrimitiveDefaultValue(argTypes[i]);
}
}
// 看看是否有任何错误
if (errCtx == null)
return args;
for (Throwable err : errCtx.getErrors()) {
if (err == null)
continue;
int lastParam = argTypes.length - 1;
if (AdaptorErrorContext.class.isAssignableFrom(argTypes[lastParam])) {
if (log.isInfoEnabled())
log.info("Adapter Param Error catched , but I found AdaptorErrorContext param, so, set it to args, and continue");
args[lastParam] = errCtx;
return args;
}
// 没有AdaptorErrorContext参数? 那好吧,按之前的方式,抛出异常
throw Lang.wrapThrow(err);
}
return args;
}
protected Object getReferObject(ServletContext sc,
HttpServletRequest req,
HttpServletResponse resp,
String[] pathArgs) {
return null;
}
/**
* 这是最后的大招了,查一下形参的名字,作为@Param("形参名")进行处理
*/
protected ParamInjector paramNameInject(Method method, int index) {
if (!Lang.isAndroid) {
List<String> names = MethodParamNamesScaner.getParamNames(method);
if (names != null)
return new NameInjector(names.get(index),
null,
method.getParameterTypes()[index],
null);
else if (log.isInfoEnabled())
log.infof("Complie without debug info? can't deduce param name. fail back to PathArgInjector!! index=%d > %s",
index,
method);
}
return new PathArgInjector(method.getParameterTypes()[index]);
}
}