001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.web; 017 018import com.google.common.base.Joiner; 019import com.jfinal.aop.Interceptor; 020import com.jfinal.aop.InterceptorManager; 021import com.jfinal.config.Routes; 022import com.jfinal.core.Action; 023import com.jfinal.core.ActionKey; 024import com.jfinal.core.Controller; 025import com.jfinal.core.NotAction; 026import io.jboot.utils.AntPathMatcher; 027import io.jboot.utils.ArrayUtil; 028 029import java.lang.reflect.Method; 030import java.lang.reflect.Modifier; 031import java.lang.reflect.ParameterizedType; 032import java.lang.reflect.Type; 033import java.util.Map; 034import java.util.concurrent.ConcurrentHashMap; 035 036/** 037 * @author 没牙的小朋友 (mjl@nxu.edu.cn) 038 * @version V1.0 039 */ 040public class PathVariableActionMapping extends JbootActionMapping { 041 private static final String PATH_VARIABLE_URL_PATTERN = ".*\\{[a-zA-Z0-9]+\\}.*"; 042 protected Map<String, Action> pathVariableUrlMapping = new ConcurrentHashMap<>(); 043 private static final AntPathMatcher antPathMatcher = new AntPathMatcher(); 044 045 public PathVariableActionMapping(Routes routes) { 046 super(routes); 047 } 048 049 @Override 050 protected void buildActionMapping() { 051 mapping.clear(); 052 Class<?> dc; 053 InterceptorManager interMan = InterceptorManager.me(); 054 for (Routes routes : getRoutesList()) { 055 for (Routes.Route route : routes.getRouteItemList()) { 056 Class<? extends Controller> controllerClass = route.getControllerClass(); 057 Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass); 058 059 boolean declaredMethods = !routes.getMappingSuperClass() || controllerClass.getSuperclass() == Controller.class; 060 061 Method[] methods = (declaredMethods ? controllerClass.getDeclaredMethods() : controllerClass.getMethods()); 062 for (Method method : methods) { 063 if (declaredMethods) { 064 if (!Modifier.isPublic(method.getModifiers())) 065 continue; 066 } else { 067 dc = method.getDeclaringClass(); 068 if (dc == Controller.class || dc == Object.class) 069 continue; 070 } 071 072 if (method.getAnnotation(NotAction.class) != null) { 073 continue; 074 } 075 076 Interceptor[] actionInters = interMan.buildControllerActionInterceptor(routes.getInterceptors(), controllerInters, controllerClass, method); 077 String controllerPath = route.getControllerPath(); 078 079 String methodName = method.getName(); 080 ActionKey ak = method.getAnnotation(ActionKey.class); 081 String actionKey; 082 if (ak != null) { 083 actionKey = ak.value().trim(); 084 if ("".equals(actionKey)) { 085 throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank."); 086 } 087 if (actionKey.matches(PATH_VARIABLE_URL_PATTERN)) { 088 Action pathVariableAction = new Action(controllerPath, actionKey, controllerClass, method, methodName, actionInters, 089 route.getFinalViewPath(routes.getBaseViewPath())); 090 pathVariableUrlMapping.put(actionKey, pathVariableAction); 091 } 092 if (actionKey.startsWith(SLASH)) { 093 //actionKey = actionKey 094 } else if (actionKey.startsWith("./")) { 095 actionKey = controllerPath + actionKey.substring(1); 096 } else { 097 actionKey = SLASH + actionKey; 098 } 099// if (!actionKey.startsWith(SLASH)) { 100// actionKey = SLASH + actionKey; 101// } 102 } else if (methodName.equals("index")) { 103 actionKey = controllerPath; 104 } else { 105 actionKey = controllerPath.equals(SLASH) ? SLASH + methodName : controllerPath + SLASH + methodName; 106 } 107 108// Action action = new Action(controllerPath, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath())); 109// if (mapping.put(actionKey, action) != null) { 110// throw new RuntimeException(buildMsg(actionKey, controllerClass, method)); 111// } 112 113 Action newAction = new Action(controllerPath, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath())); 114 Action existAction = mapping.get(actionKey); 115 if (existAction == null) { 116 mapping.put(actionKey, newAction); 117 } else { 118 119 Type controllerType = controllerClass.getGenericSuperclass(); 120 Method existActionMethod = existAction.getMethod(); 121 122 // 不是泛型 123 if (!(controllerType instanceof ParameterizedType)) { 124 throw new RuntimeException(buildMsg(actionKey, method, existActionMethod)); 125 } 126 127 if (method.getParameterCount() == 0 128 || method.getParameterCount() != existActionMethod.getParameterCount() 129 || method.getDeclaringClass() != existActionMethod.getDeclaringClass()) { 130 throw new RuntimeException(buildMsg(actionKey, method, existActionMethod)); 131 } 132 133 Type[] argumentTypes = ((ParameterizedType) controllerType).getActualTypeArguments(); 134 135 Class<?>[] paraTypes = method.getParameterTypes(); 136 Class<?>[] existParaTypes = existActionMethod.getParameterTypes(); 137 138 for (int i = 0; i < paraTypes.length; i++) { 139 Class<?> newType = paraTypes[i]; 140 Class<?> existType = existParaTypes[i]; 141 if (newType == existType) { 142 continue; 143 } 144 // newType 是父类 145 else if (newType.isAssignableFrom(existType) && ArrayUtil.contains(argumentTypes, existType)) { 146 break; 147 } 148 // newType 是子类 149 else if (existType.isAssignableFrom(newType) && ArrayUtil.contains(argumentTypes, newType)) { 150 mapping.put(actionKey, newAction); 151 break; 152 } else { 153 throw new RuntimeException(buildMsg(actionKey, method, existActionMethod)); 154 } 155 } 156 157 } 158 } 159 } 160 } 161 routes.clear(); 162 163 // support url = controllerPath + urlParas with "/" of controllerPath 164 Action action = mapping.get("/"); 165 if (action != null) { 166 mapping.put("", action); 167 } 168 } 169 170 171 172 173 /** 174 * Support four types of url 175 * 1: http://abc.com/controllerPath ---> 00 176 * 2: http://abc.com/controllerPath/para ---> 01 177 * 3: http://abc.com/controllerPath/method ---> 10 178 * 4: http://abc.com/controllerPath/method/para ---> 11 179 * 5: http://abc.com/foo/{id}/bar/{name} 180 * The controllerPath can also contains "/" 181 * Example: http://abc.com/uvw/xyz/method/para 182 */ 183 @Override 184 public Action getAction(String url, String[] urlPara) { 185 Action action = mapping.get(url); 186 if (action != null) { 187 return action; 188 } 189 for (String pattern : pathVariableUrlMapping.keySet()) { 190 //判断是否有匹配包含路径参数的URL映射 191 if (antPathMatcher.match(pattern, url)) { 192 Action pathVariableUrlAction = pathVariableUrlMapping.get(pattern); 193 Map<String, String> pathVariableValues = antPathMatcher.extractUriTemplateVariables(pattern, url); 194 urlPara[0] = null; 195 if (urlPara.length > 1) { 196 //urlPara[1]作为路径参数传入controller 197 urlPara[1] = Joiner.on("&").withKeyValueSeparator("=").join(pathVariableValues); 198 } 199 return pathVariableUrlAction; 200 } 201 } 202 // -------- 203 int i = url.lastIndexOf('/'); 204 if (i != -1) { 205 action = mapping.get(url.substring(0, i)); 206 if (action != null) { 207 urlPara[0] = url.substring(i + 1); 208 } 209 } 210 211 return action; 212 } 213}