All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinygroup.springmvc.support.ServletHandlerMethodResolver Maven / Gradle / Ivy

The newest version!
/**
 *  Copyright (c) 1997-2013, www.tinygroup.org ([email protected]).
 *
 *  Licensed under the GPL, Version 3.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.gnu.org/licenses/gpl.html
 *
 *  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 org.tinygroup.springmvc.support;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.util.UrlPathHelper;
import org.tinygroup.springmvc.coc.ConventionHelper;
import org.tinygroup.springmvc.coc.CustomHandlerMethodResolver;
import org.tinygroup.springmvc.util.ServletAnnotationMappingUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 类方法解析类
 * @author renhui
 *
 */
public class ServletHandlerMethodResolver extends HandlerMethodResolver {

	private final Map mappings = new HashMap();
	protected UrlPathHelper urlPathHelper;
	protected PathMatcher pathMatcher;
	protected MethodNameResolver methodNameResolver;
	private Class handlerType;
	private ConventionHelper conventionHelper;

	public ServletHandlerMethodResolver(Class handlerType,
			UrlPathHelper urlPathHelper, PathMatcher pathMatcher,
			MethodNameResolver methodNameResolver,
			ConventionHelper conventionHelper) {
		init(handlerType);
		this.handlerType = handlerType;
		this.urlPathHelper = urlPathHelper;
		this.pathMatcher = pathMatcher;
		this.methodNameResolver = methodNameResolver;
		this.conventionHelper = conventionHelper;
	}

	@Override
	protected boolean isHandlerMethod(Method method) {
		if (this.mappings.containsKey(method)) {
			return true;
		}
		RequestMapping mapping = AnnotationUtils.findAnnotation(method,
				RequestMapping.class);
		if (mapping != null) {
			String[] patterns = mapping.value();
			RequestMethod[] methods = new RequestMethod[0];
			String[] params = new String[0];
			String[] headers = new String[0];
			if (!hasTypeLevelMapping()
					|| !Arrays.equals(mapping.method(), getTypeLevelMapping()
							.method())) {
				methods = mapping.method();
			}
			if (!hasTypeLevelMapping()
					|| !Arrays.equals(mapping.params(), getTypeLevelMapping()
							.params())) {
				params = mapping.params();
			}
			if (!hasTypeLevelMapping()
					|| !Arrays.equals(mapping.headers(), getTypeLevelMapping()
							.headers())) {
				headers = mapping.headers();
			}
			RequestMappingInfo mappingInfo = new RequestMappingInfo(patterns,
					methods, params, headers);
			this.mappings.put(method, mappingInfo);
			return true;
		}
		return false;
	}

	public Method resolveHandlerMethod(HttpServletRequest request)
			throws ServletException {
		String lookupPath = urlPathHelper.getLookupPathForRequest(request);
		Comparator pathComparator = pathMatcher
				.getPatternComparator(lookupPath);
		Map targetHandlerMethods = new LinkedHashMap();
		Set allowedMethods = new LinkedHashSet(7);
		String resolvedMethodName = null;
		for (Method handlerMethod : getHandlerMethods()) {
			RequestSpecificMappingInfo mappingInfo = new RequestSpecificMappingInfo(
					this.mappings.get(handlerMethod));
			boolean match = false;
			if (mappingInfo.hasPatterns()) {
				for (String pattern : mappingInfo.getPatterns()) {
					if (!hasTypeLevelMapping() && !pattern.startsWith("/")) {
						pattern = "/" + pattern;
					}
					String combinedPattern = getCombinedPattern(pattern,
							lookupPath, request);
					if (combinedPattern != null) {
						if (mappingInfo.matches(request)) {
							match = true;
							mappingInfo.addMatchedPattern(combinedPattern);
						} else {
							if (!mappingInfo.matchesRequestMethod(request)) {
								allowedMethods
										.addAll(mappingInfo.methodNames());// 允许的httpMethods
							}
							break;
						}
					}
				}
				mappingInfo.sortMatchedPatterns(pathComparator);
			} else {
				// No paths specified: parameter match sufficient.
				match = mappingInfo.matches(request);
				if (match && mappingInfo.getMethodCount() == 0
						&& mappingInfo.getParamCount() == 0
						&& resolvedMethodName != null
						&& !resolvedMethodName.equals(handlerMethod.getName())) {
					match = false;
				} else {
					if (!mappingInfo.matchesRequestMethod(request)) {
						allowedMethods.addAll(mappingInfo.methodNames());
					}
				}
			}
			if (match) {
				Method oldMappedMethod = targetHandlerMethods.put(mappingInfo,
						handlerMethod);
				if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
					if (methodNameResolver != null
							&& !mappingInfo.hasPatterns()) {
						if (!oldMappedMethod.getName().equals(
								handlerMethod.getName())) {
							if (resolvedMethodName == null) {
								resolvedMethodName = methodNameResolver
										.getHandlerMethodName(request);
							}
							if (!resolvedMethodName.equals(oldMappedMethod
									.getName())) {
								oldMappedMethod = null;
							}
							if (!resolvedMethodName.equals(handlerMethod
									.getName())) {
								if (oldMappedMethod != null) {
									targetHandlerMethods.put(mappingInfo,
											oldMappedMethod);
									oldMappedMethod = null;
								} else {
									targetHandlerMethods.remove(mappingInfo);
								}
							}
						}
					}
					if (oldMappedMethod != null) {
						throw new IllegalStateException(
								"Ambiguous handler methods mapped for HTTP path '"
										+ lookupPath
										+ "': {"
										+ oldMappedMethod
										+ ", "
										+ handlerMethod
										+ "}. If you intend to handle the same path in multiple methods, then factor "
										+ "them out into a dedicated handler class with that path mapped at the type level!");
					}
				}
			}
		}
		if (!targetHandlerMethods.isEmpty()) {
			List matches = new ArrayList(
					targetHandlerMethods.keySet());
			RequestSpecificMappingInfoComparator requestMappingInfoComparator = new RequestSpecificMappingInfoComparator(
					pathComparator, request);
			Collections.sort(matches, requestMappingInfoComparator);
			RequestSpecificMappingInfo bestMappingMatch = matches.get(0);
			String bestMatchedPath = bestMappingMatch.bestMatchedPattern();
			if (bestMatchedPath != null) {
				extractHandlerMethodUriTemplates(bestMatchedPath, lookupPath,
						request);
			}
			return targetHandlerMethods.get(bestMappingMatch);
		} else {

			// +++ try to get coc handlerMethod
			CustomHandlerMethodResolver conventionHandlerMethodResolver = conventionHelper
					.getConventionHandlerMethodResolver(handlerType);
			if (conventionHandlerMethodResolver != null) {
				Method method = conventionHandlerMethodResolver
						.getHandlerMethod(request);
				if (method != null) {
					return method;
				}
			}

			if (!allowedMethods.isEmpty()) {
				throw new HttpRequestMethodNotSupportedException(
						request.getMethod(),
						StringUtils.toStringArray(allowedMethods),
						"the requestMethod ["
								+ request.getMethod()
								+ "] is not supported by handlers,you can try with ["
								+ StringUtils.arrayToDelimitedString(
										StringUtils
												.toStringArray(allowedMethods),
										",") + "]!");
			}
			throw new NoSuchRequestHandlingMethodException(lookupPath,
					request.getMethod(), request.getParameterMap());
		}
	}

	/**
	 * Determines the combined pattern for the given methodLevelPattern and
	 * path.
	 * 

* Uses the following algorithm: *

    *
  1. If there is a type-level mapping with path information, it is * {@linkplain PathMatcher#combine(String, String) combined} with the * method-level pattern.
  2. *
  3. If there is a * {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching * pattern} in the request, it is combined with the method-level pattern.
  4. *
  5. Otherwise, the method-level pattern is returned.
  6. *
*/ private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) { if (hasTypeLevelMapping() && (!ObjectUtils.isEmpty(getTypeLevelMapping().value()))) { String[] typeLevelPatterns = getTypeLevelMapping().value(); for (String typeLevelPattern : typeLevelPatterns) { if (!typeLevelPattern.startsWith("/")) { typeLevelPattern = "/" + typeLevelPattern; } String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern); if (isPathMatchInternal(combinedPattern, lookupPath)) { return combinedPattern; } } return null; } String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) { String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern); if (!combinedPattern.equals(bestMatchingPattern) && (isPathMatchInternal(combinedPattern, lookupPath))) { return combinedPattern; } } if (isPathMatchInternal(methodLevelPattern, lookupPath)) { return methodLevelPattern; } return null; } private boolean isPathMatchInternal(String pattern, String lookupPath) { if (pattern.equals(lookupPath) || pathMatcher.match(pattern, lookupPath)) { return true; } boolean hasSuffix = pattern.indexOf('.') != -1; if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) { return true; } boolean endsWithSlash = pattern.endsWith("/"); return !endsWithSlash && pathMatcher.match(pattern + "/", lookupPath); } @SuppressWarnings("unchecked") private void extractHandlerMethodUriTemplates(String mappedPattern, String lookupPath, HttpServletRequest request) { // find handler 时已经经过处理 Map variables = (Map) request .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); int patternVariableCount = StringUtils.countOccurrencesOf( mappedPattern, "{"); if ((variables == null || patternVariableCount != variables.size()) && pathMatcher.match(mappedPattern, lookupPath)) { variables = pathMatcher.extractUriTemplateVariables(mappedPattern, lookupPath); // TODO 后期处理 // Map decodedVariables = // urlPathHelper.decodePathVariables(request, // variables); // request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, // decodedVariables); request.setAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, variables); } } /** * Holder for request mapping metadata. */ static class RequestMappingInfo { private final String[] patterns; private final RequestMethod[] methods; private final String[] params; private final String[] headers; RequestMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) { this.patterns = patterns != null ? patterns : new String[0]; this.methods = methods != null ? methods : new RequestMethod[0]; this.params = params != null ? params : new String[0]; this.headers = headers != null ? headers : new String[0]; } public boolean hasPatterns() { return patterns.length > 0; } public String[] getPatterns() { return patterns; } public int getMethodCount() { return methods.length; } public int getParamCount() { return params.length; } public int getHeaderCount() { return headers.length; } public boolean matches(HttpServletRequest request) { return matchesRequestMethod(request) && matchesParameters(request) && matchesHeaders(request); } public boolean matchesHeaders(HttpServletRequest request) { return ServletAnnotationMappingUtils.checkHeaders(this.headers, request); } public boolean matchesParameters(HttpServletRequest request) { return ServletAnnotationMappingUtils.checkParameters(this.params, request); } public boolean matchesRequestMethod(HttpServletRequest request) { return ServletAnnotationMappingUtils.checkRequestMethod( this.methods, request); } public Set methodNames() { Set methodNames = new LinkedHashSet(methods.length); for (RequestMethod method : methods) { methodNames.add(method.name()); } return methodNames; } @Override public boolean equals(Object obj) { RequestMappingInfo other = (RequestMappingInfo) obj; return (Arrays.equals(this.patterns, other.patterns) && Arrays.equals(this.methods, other.methods) && Arrays.equals(this.params, other.params) && Arrays .equals(this.headers, other.headers)); } @Override public int hashCode() { return (Arrays.hashCode(this.patterns) * 23 + Arrays.hashCode(this.methods) * 29 + Arrays.hashCode(this.params) * 31 + Arrays .hashCode(this.headers)); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(Arrays.asList(patterns)); if (methods.length > 0) { builder.append(','); builder.append(Arrays.asList(methods)); } if (headers.length > 0) { builder.append(','); builder.append(Arrays.asList(headers)); } if (params.length > 0) { builder.append(','); builder.append(Arrays.asList(params)); } return builder.toString(); } } /** * Subclass of {@link RequestMappingInfo} that holds request-specific data. */ static class RequestSpecificMappingInfo extends RequestMappingInfo { private final List matchedPatterns = new ArrayList(); RequestSpecificMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) { super(patterns, methods, params, headers); } RequestSpecificMappingInfo(RequestMappingInfo other) { super(other.patterns, other.methods, other.params, other.headers); } public void addMatchedPattern(String matchedPattern) { matchedPatterns.add(matchedPattern); } public void sortMatchedPatterns(Comparator pathComparator) { Collections.sort(matchedPatterns, pathComparator); } public String bestMatchedPattern() { return (!this.matchedPatterns.isEmpty() ? this.matchedPatterns .get(0) : null); } } static class RequestSpecificMappingInfoComparator implements Comparator { private final Comparator pathComparator; private final ServerHttpRequest request; RequestSpecificMappingInfoComparator(Comparator pathComparator, HttpServletRequest request) { this.pathComparator = pathComparator; this.request = new ServletServerHttpRequest(request); } public int compare(RequestSpecificMappingInfo info1, RequestSpecificMappingInfo info2) { int pathComparison = pathComparator.compare( info1.bestMatchedPattern(), info2.bestMatchedPattern()); if (pathComparison != 0) { return pathComparison; } int info1ParamCount = info1.getParamCount(); int info2ParamCount = info2.getParamCount(); if (info1ParamCount != info2ParamCount) { return info2ParamCount - info1ParamCount; } int info1HeaderCount = info1.getHeaderCount(); int info2HeaderCount = info2.getHeaderCount(); if (info1HeaderCount != info2HeaderCount) { return info2HeaderCount - info1HeaderCount; } int acceptComparison = compareAcceptHeaders(info1, info2); if (acceptComparison != 0) { return acceptComparison; } int info1MethodCount = info1.getMethodCount(); int info2MethodCount = info2.getMethodCount(); if (info1MethodCount == 0 && info2MethodCount > 0) { return 1; } else if (info2MethodCount == 0 && info1MethodCount > 0) { return -1; } else if (info1MethodCount == 1 && info2MethodCount > 1) { return -1; } else if (info2MethodCount == 1 && info1MethodCount > 1) { return 1; } return 0; } private int compareAcceptHeaders(RequestMappingInfo info1, RequestMappingInfo info2) { List requestAccepts = request.getHeaders().getAccept(); MediaType.sortByQualityValue(requestAccepts); List info1Accepts = getAcceptHeaderValue(info1); List info2Accepts = getAcceptHeaderValue(info2); for (MediaType requestAccept : requestAccepts) { int pos1 = indexOfIncluded(info1Accepts, requestAccept); int pos2 = indexOfIncluded(info2Accepts, requestAccept); if (pos1 != pos2) { return pos2 - pos1; } } return 0; } private int indexOfIncluded(List infoAccepts, MediaType requestAccept) { for (int i = 0; i < infoAccepts.size(); i++) { MediaType info1Accept = infoAccepts.get(i); if (requestAccept.includes(info1Accept)) { return i; } } return -1; } private List getAcceptHeaderValue(RequestMappingInfo info) { for (String header : info.headers) { int separator = header.indexOf('='); if (separator != -1) { String key = header.substring(0, separator); String value = header.substring(separator + 1); if ("Accept".equalsIgnoreCase(key)) { return MediaType.parseMediaTypes(value); } } } return Collections.emptyList(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy