package org.mvc.framework.servlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.mvc.framework.annotation.XCAutowired;
import org.mvc.framework.annotation.XCController;
import org.mvc.framework.annotation.XCRequestMapping;
import org.mvc.framework.annotation.XCRequestParam;
import org.mvc.framework.annotation.XCService;
import com.alibaba.fastjson.JSON;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
@SuppressWarnings("serial")
public class XCDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private Map<String, Object> iocBean = new HashMap<>();
private Map<String, Object> handerMap = new HashMap<>();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("收到用户请求");
try {
response.setHeader("Content-type", "text/html;charset=UTF-8");
String reqPath = request.getRequestURL().toString();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ request.getContextPath();
String url = reqPath.replace(basePath, "").replaceAll("/+", "/");
HanlerInner inner = (HanlerInner) handerMap.get(url);
if (null == inner) {
response.getWriter().write("404 not Fund reuest");
return;
}
Object object = iocBean.get(inner.getBeanName());
List<Object> invokeMethod = new ArrayList<>();
for (int i=0; i<inner.getParametersize(); i++) {
String paramName = inner.getParameterNames()[i];
Class<?> paramType = inner.getParameterTypes()[i];
if (paramType.isAssignableFrom(HttpServletRequest.class)) {
invokeMethod.add(request);
} else if (paramType.isAssignableFrom(HttpServletResponse.class)) {
invokeMethod.add(response);
} else {
String paramValue = "";
Annotation[][] annotationParam = inner.getMethod().getParameterAnnotations();
if (null != annotationParam[i] && annotationParam[i].length > 0) {
Annotation[] annotations = annotationParam[i];
Annotation annotation = annotations[0];
String annoParamVal = "";
Boolean annoParamRequire = true;
if (annotation instanceof XCRequestParam) {
annoParamVal = ((XCRequestParam) annotation).value();
annoParamRequire = ((XCRequestParam) annotation).required();
if (StringUtils.isNotBlank(annoParamVal)) {
paramName = annoParamVal;
}
paramValue = new String(request.getParameter(paramName).getBytes("ISO-8859-1"), "UTF-8");
if (annoParamRequire && StringUtils.isBlank(paramValue)) {
throw new RuntimeException(Thread.currentThread().getName() + " server exception parameter "
+ paramName + " can not empty or null.");
}
if (StringUtils.isBlank(paramValue) && annoParamRequire) {
throw new RuntimeException("500 exception parameter " + paramName + " is empty or null");
}
}
Object val = invokeMethodParamValue(paramType, paramValue);
invokeMethod.add(val);
} else {
Map<String, String> requestParamsMap = new HashMap<>();
@SuppressWarnings("unchecked")
Enumeration<String> paraNames = request.getParameterNames();
while (paraNames.hasMoreElements()) {
String reqParamName = (String) paraNames.nextElement();
String reqParamValue= new String(request.getParameter(reqParamName).getBytes("ISO-8859-1"), "UTF-8");
requestParamsMap.put(reqParamName, reqParamValue);
}
validateSimpleParam(invokeMethod, paramName, inner.getParameterNames(), paramType, requestParamsMap, request);
}
}
}
Object result = inner.getMethod().invoke(object, invokeMethod.toArray());
String ret = new String(JSON.toJSONString(result).getBytes("UTF-8"));
response.getWriter().write(ret);
} catch (Exception e) {
e.printStackTrace();
}
}
private void validateSimpleParam(List<Object> invokeMethod, String methodParamName, String[] parameters,
Class<?> paramType, Map<String, String> requestParamsMap, HttpServletRequest request) {
if (null == parameters || parameters.length <= 0) {
return;
}
try {
if(requestParamsMap.containsKey(methodParamName)) {
Object val = invokeMethodParamValue(paramType, requestParamsMap.get(methodParamName));
invokeMethod.add(val);
} else {
Object instance = paramType.newInstance();
for(Field field : paramType.getDeclaredFields()) {
field.setAccessible(true);
if (requestParamsMap.containsKey(field.getName())) {
setMethodFieldValue(field.getName(), requestParamsMap.get(field.getName()), paramType, instance);
}
}
invokeMethod.add(instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void setMethodFieldValue(String methodFieldName, String methodFieldVal, Class<?> invokeCls, Object instance) {
String methodName = "set" + upperFirstCase(methodFieldName);
System.out.println("===== " + methodFieldName + " " + methodFieldVal + "=====");
try {
for(Field field0 : invokeCls.getDeclaredFields()) {
field0.setAccessible(true);
Field field = invokeCls.getDeclaredField(methodFieldName);
if (null != field) {
Method method = invokeCls.getMethod(methodName, new Class[]{field.getType()});
method.invoke(instance, invokeMethodParamValue(field.getType(), methodFieldVal));
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean validateFiled(String fildName, Class<?> invokeCls) {
for(Field field0 : invokeCls.getDeclaredFields()) {
field0.setAccessible(true);
if (field0.getName().equals(fildName)) {
return true;
}
}
return false;
}
private Object invokeMethodParamValue(Class<?> paramType, String paramValue) {
System.out.println("start convert request parameter." + JSON.toJSONString(paramType));
try {
if (paramType == String.class) {
return paramValue;
} else if (paramType == Integer.class || paramType == int.class) {
return Integer.valueOf(paramValue);
} else if (paramType == Long.class || paramType == long.class) {
return Long.valueOf(paramValue);
} else if (paramType == Boolean.class || paramType == boolean.class) {
return Boolean.valueOf(paramValue);
} else if (paramType == Short.class || paramType == short.class) {
return Short.valueOf(paramValue);
} else if (paramType == Float.class || paramType == float.class) {
return Float.valueOf(paramValue);
} else if (paramType == Double.class || paramType == double.class) {
return Double.valueOf(paramValue);
} else if (paramType == BigDecimal.class) {
return new BigDecimal(paramValue);
} else if (paramType == Character.class || paramType == char.class) {
char[] cs = paramValue.toCharArray();
if (cs.length > 1) {
throw new IllegalArgumentException("参数长度太大");
}
return paramValue;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String[] methodParameterName(Class<?> clazz, String methodName) {
String nameParams[] = null;
try {
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(clazz));
CtClass ctClass = classPool.get(clazz.getName());
CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute attribute = methodInfo.getCodeAttribute();
LocalVariableAttribute lAttribute = (LocalVariableAttribute) attribute.getAttribute(LocalVariableAttribute.tag);
if (null == lAttribute) {
throw new RuntimeException("500 get parameter");
}
nameParams = new String[ctMethod.getParameterTypes().length];
int pos = Modifier.isStatic(ctMethod.getModifiers())?0 : 1;
for (int i=0; i< nameParams.length; i++) {
nameParams[i] = lAttribute.variableName(i + pos);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(JSON.toJSONString(nameParams));
return nameParams;
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("-------------- init dispatcherServlet start ------------------");
// 1 加载xml
doinitContextConfig(config);
// 2 扫描包 初始化实例化bean IOC
String basePackage = properties.getProperty("basescan.pacakge");
doscanPacakge(basePackage);
// 4 依赖注入bean
doInitIDBean();
// 5. 初始化handlerMapping
doInitHandlerMapping();
System.out.println("-------------- init dispatcherServlet end ------------------");
}
private void doInitHandlerMapping() {
try {
if (iocBean.isEmpty()) {return;}
for (Map.Entry<String, Object> bean : iocBean.entrySet()) {
Class<?> clazz = bean.getValue().getClass();
if (clazz.isAnnotationPresent(XCController.class)) {
String basePath = clazz.getAnnotation(XCRequestMapping.class).value();
Method[] methods = clazz.getMethods();
if (methods.length <= 0) {continue;}
for (Method method : methods) {
String url = "";
HanlerInner inner = null;
if (method.isAnnotationPresent(XCRequestMapping.class)) {
inner = new HanlerInner();
String methodPath = method.getAnnotation(XCRequestMapping.class).value();
url = (basePath + "/" + methodPath).replaceAll("/+", "/");
inner.setUrl(url);
inner.setMethod(method);
inner.setBeanName(bean.getKey());
inner.setParameterTypes(method.getParameterTypes());
inner.setParametersize(method.getParameterTypes().length);
inner.setParameterNames(methodParameterName(clazz, method.getName()));
}
handerMap.put(url, inner);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doInitIDBean() {
try {
if (iocBean.isEmpty()) {return;}
for (Map.Entry<String, Object> entry : iocBean.entrySet()) {
Class<?> claxx = entry.getValue().getClass();
if (claxx.isAnnotationPresent(XCController.class)) {
System.out.println("controller 注入");
Field[] fields = claxx.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(XCAutowired.class)) {
String beanName = field.getAnnotation(XCAutowired.class).value();
if ("".equals(beanName)) {
beanName = field.getName();
}
System.out.println("controller 注入 名称 " + beanName + " " + field.getType().getName());
Object instatnce = iocBean.get(beanName);
field.set(entry.getValue(), instatnce);
}
}
} else if (claxx.isAnnotationPresent(XCService.class)) {
System.out.println("service 注入");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doscanPacakge(String basePackage) {
try {
if ("".equals(basePackage)) {
return;
}
String basePath = basePackage.replace(".", "/");
URL url = this.getClass().getClassLoader().getResource(basePath);
File file = new File(url.getFile());
File[] files = file.listFiles();
for (File pack : files) {
if (pack.isDirectory()) {
String nextPack = basePackage + "." + pack.getName();
doscanPacakge(nextPack);
} else {
String className = pack.getName().replace(".class", "");
Class<?> instance = Class.forName(basePackage + "." + className);
Object object = null;
if (instance.isAnnotationPresent(XCController.class)) {
object = instance.newInstance();
iocBean.put(lowerFirstCase(className), object);
} else if (instance.isAnnotationPresent(XCService.class)) {
object = instance.newInstance();
String beanName = instance.getAnnotation(XCService.class).value();
if ("".equals(beanName)) {
Class<?> instanceInter[] = instance.getInterfaces();
for (Class<?> inter : instanceInter) {
if (inter.isAssignableFrom(instance)) {
beanName = lowerFirstCase(inter.getSimpleName());
}
}
}
iocBean.put(beanName, object);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doinitContextConfig(ServletConfig config) {
try {
String contextPath = config.getInitParameter("applicationContextListener");
System.out.println("==== dispatcherservlet " + contextPath);
InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextPath);
properties.load(in);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 首字母小写
* @return
*/
private String lowerFirstCase (String claxxName) {
char[] chars = claxxName.toCharArray();
chars[0] = (char) (chars[0] + 32);
return String.valueOf(chars);
}
private String upperFirstCase (String claxxName) {
char[] chars = claxxName.toCharArray();
chars[0] = (char) (chars[0] - 32);
return String.valueOf(chars);
}
class HanlerInner {
private String url;
private String beanName;
private Pattern pattern;
private Method method;
private Integer parametersize;
private String[] parameterNames;
private Class<?>[] parameterTypes;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public String[] getParameterNames() {
return parameterNames;
}
public void setParameterNames(String[] parameterNames) {
this.parameterNames = parameterNames;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Integer getParametersize() {
return parametersize;
}
public void setParametersize(Integer parametersize) {
this.parametersize = parametersize;
}
}
}