/*
* Copyright im.longkai@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.newgxu.ng.core.mvc;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import cn.newgxu.bbs.common.util.Util;
import cn.newgxu.ng.core.mvc.annotation.MVCExceptionpHandler;
import cn.newgxu.ng.core.mvc.annotation.MVCHandler;
import cn.newgxu.ng.core.mvc.annotation.MVCInterceptor;
import cn.newgxu.ng.core.mvc.annotation.MVCMapping;
import cn.newgxu.ng.core.mvc.annotation.MVCParamMapping;
import cn.newgxu.ng.util.InfoLevel;
import cn.newgxu.ng.util.Pigeon;
import cn.newgxu.ng.util.RegexUtils;
import cn.newgxu.ng.util.StringUtils;
import cn.newgxu.ng.util.ViewType;
import cn.newgxu.ng.util.WrapperUtils;
/**
* mvc处理,实际上了spring的bean获得controller。
* 动态注入参数的时候采用了类似枚举的做法,可以试试下面的那个方法
* @see java.lang.Class#isAssignableFrom(Class)
* @author longkai
* @since 2013-2-27
* @version 1.0
*/
public class MVCProcess {
private static final Logger L = LoggerFactory
.getLogger(MVCProcess.class);
/** 处理方法与处理器类名称的映射 */
private static Map<String, String> handlers;
/** 请求路径与处理器方法的映射 */
private static Map<String, String> mappedMethods;
/** 处理方法与该方法参数的映射 */
private static Map<String, Class<?>[]> methodParams;
/** 处理方法的参数 */
private static Map<String, MVCParamMapping[]> injectedParams;
/** 处理器类与该类所包含异常处理器的映射 */
private static Map<String, Method[]> exceptionHandlers;
/** 路径与拦截器的映射 */
private static Map<String, Class<?>> interceptors;
private static WebApplicationContext context;
/** 默认的视图存放点 */
private static final String viewPath = "/template/ng/";
static {
handlers = new HashMap<String, String>();
mappedMethods = new HashMap<String, String>();
methodParams = new HashMap<String, Class<?>[]>();
injectedParams = new HashMap<String, MVCParamMapping[]>();
exceptionHandlers = new HashMap<String, Method[]>();
interceptors = new HashMap<String, Class<?>>();
getContext();
init();
interceptorInit();
}
private static WebApplicationContext getContext() {
if (context == null) {
context = Util.getWebApplicationContext();
}
return context;
}
/**
* 初始化
*/
public static void init() {
L.info("控制器初始化!");
Map<String, Object> handlersBeans = getContext().getBeansWithAnnotation(MVCHandler.class);
for (String handler : handlersBeans.keySet()) {
Object obj = handlersBeans.get(handler);
Method[] methods = obj.getClass().getMethods();
List<Method> exceptionHandleMethods = new ArrayList<Method>();
MVCHandler mvcHandler = obj.getClass().getAnnotation(MVCHandler.class);
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
MVCMapping mapping = method.getAnnotation(MVCMapping.class);
if (method.isAnnotationPresent(MVCMapping.class)) {
// 注册响应方法。
String[] paths = mapping.value();
for (int j = 0; j < paths.length; j++) {
// String realPath = mapping.namespace() + paths[j];
String realPath = mvcHandler.namespace() + paths[j];
// 可能会出现两个斜杠的情况。
realPath = realPath.replace("//", "/");
mappedMethods.put(realPath, method.getName());
handlers.put(realPath, handler);
L.debug("path:{}, handled by {}' s {} method", realPath, handler, method.getName());
}
methodParams.put(handler + "." + method.getName(), method.getParameterTypes());
// 请求参数注解映射
MVCParamMapping[] paramsMapping = paramsMapping(method);
injectedParams.put(handler + "." + method.getName(), paramsMapping);
L.debug("请求参数映射 方法:{} 映射:{}", method.getName(), paramsMapping);
} else {
// 暂存异常处理器索引
stageExceptionHandlers(exceptionHandleMethods, method);
}
}
// 注册异常处理器
if (exceptionHandleMethods != null)
exceptionHandlers.put(handler, exceptionHandleMethods.toArray(new Method[0]));
}
L.info("控制器初始化完成!");
}
/**
* 拦截器初始化。
*/
private static void interceptorInit() {
L.info("拦截器初始化!");
Set<String> urls = mappedMethods.keySet();
Map<String, Object> interceptorBeans = getContext().getBeansWithAnnotation(MVCInterceptor.class);
// 暂存所有被排除的url
List<String> excludes = new ArrayList<String>();
for (String beanName : interceptorBeans.keySet()) {
Class<?> interceptor = interceptorBeans.get(beanName).getClass();
MVCInterceptor tag = interceptor.getAnnotation(MVCInterceptor.class);
if (RegexUtils.contains(tag.url(), "[\\^\\$\\+\\?\\.\\*\\+]+")) {
// 添加符合正则表达式的url。
for (String s : urls) {
if (RegexUtils.matches(s, tag.url())) {
interceptors.put(s, interceptor);
}
}
} else {
interceptors.put(tag.url(), interceptor);
}
excludes.addAll(Arrays.asList(tag.excludes()));
L.debug("拦截器:{}", interceptors);
}
// 移除排除的东东
for (String s : excludes) {
L.debug("exclude url: {}", s);
interceptors.remove(s);
}
L.info("拦截器初始化完成!");
}
/**
* 在请求响应前进行拦截。
* @param path
* @param request
* @param response
* @return 拦截成功
* @throws IOException
* @throws ServletException
*/
private static Interceptor intercept(String path, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Interceptor interceptor = null;
// interceptors.clear();
// interceptorInit();
if (interceptors.containsKey(path)) {
interceptor = (Interceptor) context.getBean(interceptors.get(path));
}
return interceptor;
}
/**
* 注解在控制器方法上的拦截器实现,为方法执行之前。
* @param method
* @param request
* @param response
* @return 放行 true, 拦截 false
* @throws ServletException
* @throws IOException
*/
private static boolean interceptorBefore(Method method, HttpServletRequest request, HttpServletResponse response, ModelAndView mav) throws ServletException, IOException {
// 默认放行。
boolean allow = true;
if (method.isAnnotationPresent(MVCInterceptor.class)) {
MVCInterceptor interceptors = method.getAnnotation(MVCInterceptor.class);
Class<? extends Interceptor>[] classes = interceptors.interceptors();
for (int i = 0; i < classes.length; i++) {
Interceptor interceptor = context.getBean(classes[i]);
allow = interceptor.before(request, response, mav);
if (!allow) {
break;
}
}
}
return allow;
}
/**
* 注解在控制器方法上的拦截器实现,为方法执行之后。
* @param method
* @param request
* @param response
* @param mav
* @return 放行 true, 拦截 false
* @throws ServletException
* @throws IOException
*/
private static boolean interceptorAfter(Method method, HttpServletRequest request, HttpServletResponse response, ModelAndView mav) throws ServletException, IOException {
// 默认放行。
boolean allow = true;
if (method.isAnnotationPresent(MVCInterceptor.class)) {
MVCInterceptor interceptors = method.getAnnotation(MVCInterceptor.class);
Class<? extends Interceptor>[] classes = interceptors.interceptors();
for (int i = 0; i < classes.length; i++) {
Interceptor interceptor = context.getBean(classes[i]);
allow = interceptor.after(request, response, mav);
if (!allow) {
break;
}
}
}
return allow;
}
// private static Interceptor interceptorBefore(HttpServletRequest request, HttpServletResponse response) {
//
// }
/**
* 获取方法的参数所包含的注解。
* @param m 方法
* @return @MVCParamMapping 的注解数组
*/
private static MVCParamMapping[] paramsMapping(Method m) {
Annotation[][] pas = m.getParameterAnnotations();
MVCParamMapping[] paramMappings = new MVCParamMapping[m.getParameterTypes().length];
for (int i = 0; i < pas.length; i++) {
for (int j = 0; j < pas[i].length; j++) {
if (pas[i][j].annotationType().equals(MVCParamMapping.class)) {
paramMappings[i] = (MVCParamMapping) pas[i][j];
}
}
}
return paramMappings;
}
/**
* 暂存异常处理器,如果存在则会对list进行初始化。
* @param list
* @param method
*/
private static void stageExceptionHandlers(List<Method> list, Method method) {
if (method.isAnnotationPresent(MVCExceptionpHandler.class)) {
list.add(method);
}
}
/**
* 处理请求并且处理全局异常(若抛出)。
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public static void mvc(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getRequestURI();
L.info("请求映射:{} ", path);
// 准备拦截器
Interceptor interceptor = intercept(path, request, response);
ModelAndView mav = new ModelAndView();
// 方法执行前进行拦截
if (interceptor != null) {
if (!interceptor.before(request, response, mav)) {
return;
}
}
try {
mav = process(request, response, path);
} catch (Throwable e) {
globalExceptionHandler(request, response, e);
return;
}
// 解析试图返回模型前进行拦截
if (interceptor != null) {
if (!interceptor.after(request, response, mav)) {
return;
}
}
injectModel(request, mav.getModel());
render(request, response, mav.getView());
L.info("请求:{} 处理结束...", path);
}
/**
* mvc 请求处理过程。
* @param request
* @param response
* @throws ServletException
* @throws IOException
* @throws MVCException
*/
private static ModelAndView process(HttpServletRequest request,
HttpServletResponse response, String path) throws ServletException, IOException, MVCException {
// 替换url为我们定义的。
if (mappedMethods.containsKey(path)) {
// 准备启动mvc
// Model model = null;
// View view = null;
Object controller = null;
// 先把模型和视图合并在一起,易于操作控制器返回的结果
ModelAndView mav = new ModelAndView();
String methodName = mappedMethods.get(path);
String beanName = handlers.get(path);
Class<?>[] paramTypes = methodParams.get(beanName + "." + methodName);
MVCParamMapping[] paramMappings = injectedParams.get(beanName + "." + methodName);
L.info("控制器名称: {}", beanName);
L.info("方法名:{}", methodName);
controller = context.getBean(beanName);
// 参数注入
Object[] injectedParams = injectMethodParams(paramTypes, paramMappings, request, response, mav);
Method method = null;
Object result = null;
// invoke it...
L.debug("准备调用控制器方法:{},参数类型:{}, 参数:{}", methodName, paramTypes, injectedParams);
try {
method = controller.getClass()
.getMethod(methodName, paramTypes);
// 方法执行之前进行拦截。
if (!interceptorBefore(method, request, response, mav)) {
return mav;
}
result = method.invoke(controller, injectedParams);
} catch (InvocationTargetException e) {
// 处理异常-。-,注意,是捕捉底层调用方法抛出的异常!
L.error("异常处理", e.getCause());
handleException(beanName, controller, e.getCause(), request, response, mav);
L.info("异常处理完毕!异常:{},原因:{}", e.getCause().getClass(), e.getCause().getMessage());
return mav;
} catch (Exception e) {
// 其余的索性都一并扔这里了。
throw new MVCException("控制器方法调用异常", e);
}
// 解析结果
resolveResult(method, result, mav);
// 解析结果前返回。
interceptorAfter(method, request, response, mav);
return mav;
// 分离模型和试图
// model = mav.getModel();
// view = mav.getView();
// 注入模型
// injectModel(request, mav.getModel());
// 返回客户端
// render(request, response, mav.getView());
} else {
throw new MVCException("对不起,您所请求的资源不存在!");
}
}
/**
* 回应客户端。
* @param request
* @param response
* @param view
* @throws IOException
* @throws ServletException
*/
private static void render(HttpServletRequest request,
HttpServletResponse response, View view) throws IOException,
ServletException {
String header = request.getHeader("Accept");
if (request.getParameterMap().containsKey("callback") || header.contains("application/jsonp")) {
view.setType(ViewType.JSONP);
} else if (header.contentEquals("application/json") || header.contains("application/javascript")
|| header.contains("text/javascript")) {
view.setType(ViewType.JSON);
}
L.debug("view:{}", view);
PrintWriter writer = response.getWriter();
switch (view.getType()) {
// TODO: enable FREEMARKER!
// case FREEMARKER:
// request.getRequestDispatcher(view.getViewName()).forward(request,
// response);
// break;
case JSONP:
String callback = request.getParameter("callback");
if (callback == null || callback.equals("")) {
writer.write(view.getContent());
} else {
writer.write("try{" + callback + "(" + view.getContent() + ")}catch(e){}");
}
writer.close();
break;
case JSON:
case AJAX:
writer.write(view.getContent());
writer.close();
break;
case HTML:
case REDIRECT:
response.sendRedirect(view.getViewName());
break;
default:
request.getRequestDispatcher(viewPath + view.getViewName()).forward(
request, response);
break;
}
}
/**
* 把模型注入到request中。
* @param request
* @param model
*/
private static void injectModel(HttpServletRequest request, Model model) {
if (model != null) {
if (!model.isInjected()) {
Map<String, Object> map = model.toMap();
for (String key : map.keySet()) {
request.setAttribute(key, map.get(key));
}
}
}
}
/**
* 控制器方法参数注入
* @param paramTypes
* @param paramMappings
* @param request
* @param response
* @param model
* @param view
* @return
* @throws MVCException
*/
private static Object[] injectMethodParams(Class<?>[] paramTypes, MVCParamMapping[] paramMappings, HttpServletRequest request, HttpServletResponse response,
ModelAndView mav) throws MVCException {
Object[] injectedParams = new Object[paramTypes.length];
Map<String, String[]> requestParams = request.getParameterMap();
for (int i = 0; i < paramTypes.length; i++) {
// TODO: can we inject super class in the future if needed?
Class<?> type = paramTypes[i];
L.debug("正在注入第{}个参数, type: {}", (i + 1), type);
if (type.equals(HttpServletRequest.class)) {
injectedParams[i] = request;
} else if (type.equals(HttpServletResponse.class)) {
injectedParams[i] = response;
} else if (type.equals(HttpSession.class)) {
// 注意,这个session,如果不存在,就会新建一个
injectedParams[i] = request.getSession();
} else if (type.equals(Model.class)) {
// 注入模型
mav.setModel(new Model(request));
injectedParams[i] = mav.getModel();
} else if (type.equals(View.class)) {
// 注入视图
mav.setView(new View());
injectedParams[i] = mav.getView();
} else if (type.equals(ModelAndView.class)) {
// 注入视图和模型
injectedParams[i] = mav;
} else if (type.isPrimitive() || type.equals(String.class)
|| WrapperUtils.isWrapper(type) || type.equals(Date.class)
|| type.getSuperclass().equals(Date.class)) {
// 如果是基本类型及其包装器类型,字符串,日期,时间等等,基本类型及其包装器都会有默认值。
injectedParams[i] = injectParam(type, requestParams, paramMappings[i]);
} else {
// 注入自定义Javabean
injectedParams[i] = injectJavaBean(type, requestParams);
}
}
return injectedParams;
}
/**
* 注入一般类型参数(基本类型及其包装类型,日期,字符串本身)。
* @param paramType
* @param requestParams
* @param paramMappings
* @param index
* @param set 暂存已经注入过的参数。
* @throws MVCException
*/
private static Object injectParam(Class<?> paramType, Map<String, String[]> requestParams, MVCParamMapping paramMapping) throws MVCException {
Object obj = null;
if (paramMapping != null) {
// obj = StringUtils.parse(paramType, requestParams.get(paramMapping.value())[0]);
String param = null;
try {
param = requestParams.get(paramMapping.value())[0];
} catch (Exception e) {}
obj = StringUtils.convert(paramType, param);
L.debug("请求参数绑定 key:{}, value: {}", paramMapping.value(), obj);
if (obj == null) {
throw new MVCException("请求参数 " + paramMapping.value() + " 不能为空!");
}
} else {
// 在参数类型不相同的时候可用, 因为此时是通过类型判断!警告,这个算法不够完善,当出现true,flase,1,0,时间日期并且转换类型是字符串的时候容易出错!慎用!
// for (String key : requestParams.keySet()) {
// Object tmp = null;
// if (!set.contains(key)) {
//// 如果暂存set中没有这个的话,也就是说这个key的值还没有被注入过的话
// tmp = StringUtils.parse(paramType, requestParams.get(key)[0]);
// }
// if (tmp != null) {
// obj = tmp;
// set.add(key);
// break;
// }
// }
// L.warn("系统绑定参数 type: {}, vlaue: {}", paramType.getName(), obj);
throw new RuntimeException("请指明查询参数的名字!");
}
return obj;
}
/**
* 注入自定义的JavaBean
* @param paramType
* @param requestParams
* @return
* @throws MVCException
*/
private static Object injectJavaBean(Class<?> paramType, Map<String, String[]> requestParams) throws MVCException {
Field[] fields = paramType.getDeclaredFields();
Object bean = null;
try {
bean = paramType.newInstance();
} catch (Exception e) {
throw new MVCException("实例化参数对象时异常!", e);
}
for (int i = 0; i < fields.length; i++) {
Object field = null;
String fieldName = fields[i].getName();
if (requestParams.containsKey(fieldName)) {
Class<?> fieldType = fields[i].getType();
String[] values = requestParams.get(fieldName);
if (values == null) {
throw new MVCException("请求参数 " + fieldName + " 不能为空!");
}
Class<?> arrayType = fieldType.getComponentType();
// 这里,暂时没有考虑集合类型的注入。so,TODO,collections inject(注意集合类型的初始化)...
if (arrayType == null) {
// 不是数组类型
if (values[0] == null) {
throw new MVCException("请求参数 " + fieldName + " 不能为空!");
}
field = StringUtils.convert(fieldType, values[0]);
} else {
// 数组类型
Object[] arrayField = (Object[]) Array.newInstance(arrayType, values.length);
for (int k = 0; k < values.length; k++) {
arrayField[k] = StringUtils.convert(arrayType, values[k]);
}
field = arrayField;
}
// setter...
String setter = StringUtils.setter(fieldName);
L.debug("setter:{}, valuetype: {}, value: {}", setter, fieldType, field);
try {
Method method = paramType.getMethod(setter, fieldType);
method.invoke(bean, field);
} catch (Exception e) {
throw new MVCException("setters 参数注入异常!", e);
}
}
}
return bean;
}
/**
* 解析响应结果。
* @param producer
* @param result
* @return ModelAndView 响应的视图和模型。
* @throws MVCException 无法解析结果
*/
private static ModelAndView resolveResult(Method producer, Object result, ModelAndView mav) throws MVCException {
Class<?> returnType = producer.getReturnType();
if (returnType.equals(ModelAndView.class)) {
// 如果不是同一个的话
if (mav != result) {
ModelAndView tmp = (ModelAndView) result;
mav.setView(tmp.getView());
mav.setModel(tmp.getModel());
}
} else if (returnType.equals(String.class)) {
// string, 那视图就是一般的请求(非ajax),返回值代表viewname
mav.setViewName(result.toString());
mav.getView().setContent(result.toString());
} else if (returnType.equals(View.class)) {
// 直接返回view
mav.setView((View) result);
} else if (returnType.equals(Model.class)) {
// 返回了一个模型
mav.setModel((Model) result);
} else {
L.warn("没有检测到可以解析的模型和视图!返回类型:{},返回值:{}", returnType, result);
}
L.info("视图解析:{}", mav.getView());
L.debug("模型解析: {}", mav.getModel());
return mav;
}
/**
* 自定义异常处理。
* @param controllerName 控制器名字
* @param handler 控制器对象
* @param handleFor 待处理异常
* @param request
* @param response
* @param mav
* @throws MVCException 异常处理的时候发生错误-.-
* @throws ServletException
* @throws IOException
*/
private static void handleException(String controllerName, Object handler, Throwable handleFor, HttpServletRequest request, HttpServletResponse response, ModelAndView mav) throws MVCException, IOException, ServletException {
Method[] methods = exceptionHandlers.get(controllerName);
for (int i = 0; i < methods.length; i++) {
MVCExceptionpHandler expHandler = methods[i].getAnnotation(MVCExceptionpHandler.class);
Class<?>[] handledExceptions = expHandler.value();
for (int j = 0; j < handledExceptions.length; j++) {
if (handledExceptions[j].isAssignableFrom(handleFor.getClass())) {
try {
Class<?>[] cs = methods[i].getParameterTypes();
Object[] paramst = injectMethodParams(cs, null, request, response, mav);
paramst[0] = handleFor;
Object o = methods[i].invoke(handler, paramst);
resolveResult(methods[i], o, mav);
// injectModel(request, mav.getModel());
// render(request, response, mav.getView());
} catch (Throwable t) {
// 这时再遇上异常,没办法了-/-
throw new MVCException("返回错误的时候出错!", t);
}
return;
}
}
}
// globalExceptionHandler(request, response, handleFor);
throw new MVCException(handleFor);
}
/**
* 默认的全局异常处理器,使用了信使作为信息的传递。
* @param request
* @param response
* @param t 异常
* @throws IOException
* @throws ServletException
*/
private static void globalExceptionHandler(HttpServletRequest request, HttpServletResponse response, Throwable t) throws IOException, ServletException {
L.error("全局异常抛出!{}", t);
Pigeon msg = new Pigeon().setLevel(InfoLevel.ERROR)
.setInfo(t.getMessage())
// .setReason(t.getCause().getMessage())
.setReason(t.getMessage())
.setSolutions("请查看异常信息并检查您是否误操作!", "请稍后再试!", "请联系管理员!");
request.setAttribute("msg", msg);
request.getRequestDispatcher(viewPath + "mobile/error.jsp").forward(request, response);
}
}