7 changed files with 285 additions and 212 deletions
@ -0,0 +1,228 @@
@@ -0,0 +1,228 @@
|
||||
/* |
||||
* Copyright 2002-2011 the original author or authors. |
||||
* |
||||
* 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 org.springframework.web.servlet.mvc.method; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Comparator; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.util.PathMatcher; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException; |
||||
import org.springframework.web.HttpMediaTypeNotSupportedException; |
||||
import org.springframework.web.HttpRequestMethodNotSupportedException; |
||||
import org.springframework.web.bind.annotation.RequestMethod; |
||||
import org.springframework.web.method.HandlerMethod; |
||||
import org.springframework.web.servlet.HandlerMapping; |
||||
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; |
||||
|
||||
/** |
||||
* An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo} to represent request |
||||
* mapping conditions. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @author Rossen Stoyanchev |
||||
* @since 3.1.0 |
||||
*/ |
||||
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> { |
||||
|
||||
@Override |
||||
protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) { |
||||
List<RequestMappingInfo> mappings = new ArrayList<RequestMappingInfo>(handlerMethods.keySet()); |
||||
while (mappings.size() > 1) { |
||||
RequestMappingInfo mapping = mappings.remove(0); |
||||
for (RequestMappingInfo otherMapping : mappings) { |
||||
// further validate mapping conditions
|
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected Set<String> getMappingPaths(RequestMappingInfo mapping) { |
||||
return mapping.getPatterns(); |
||||
} |
||||
|
||||
/** |
||||
* Returns a new {@link RequestMappingInfo} with attributes matching to the current request or {@code null}. |
||||
* |
||||
* @see RequestMappingInfo#getMatchingRequestMapping(String, HttpServletRequest, PathMatcher) |
||||
*/ |
||||
@Override |
||||
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo mapping, |
||||
String lookupPath, |
||||
HttpServletRequest request) { |
||||
return mapping.getMatchingRequestMapping(lookupPath, request, getPathMatcher()); |
||||
} |
||||
|
||||
/** |
||||
* Returns a {@link Comparator} that can be used to sort and select the best matching {@link RequestMappingInfo}. |
||||
*/ |
||||
@Override |
||||
protected Comparator<RequestMappingInfo> getMappingComparator(String lookupPath, HttpServletRequest request) { |
||||
return new RequestMappingInfoComparator(lookupPath, request); |
||||
} |
||||
|
||||
/** |
||||
* Exposes URI template variables and producible media types as request attributes. |
||||
* |
||||
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE |
||||
* @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE |
||||
*/ |
||||
@Override |
||||
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) { |
||||
super.handleMatch(info, lookupPath, request); |
||||
|
||||
String pattern = info.getPatterns().iterator().next(); |
||||
Map<String, String> uriTemplateVariables = getPathMatcher().extractUriTemplateVariables(pattern, lookupPath); |
||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); |
||||
|
||||
if (!info.getProduces().isEmpty()) { |
||||
Set<MediaType> mediaTypes = info.getProduces().getMediaTypes(); |
||||
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Iterates all {@link RequestMappingInfo}s looking for mappings that match by URL but not by HTTP method. |
||||
* |
||||
* @throws HttpRequestMethodNotSupportedException if there are matches by URL but not by HTTP method |
||||
*/ |
||||
@Override |
||||
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos, |
||||
String lookupPath, |
||||
HttpServletRequest request) throws ServletException { |
||||
Set<String> allowedMethods = new HashSet<String>(6); |
||||
Set<MediaType> consumableMediaTypes = new HashSet<MediaType>(); |
||||
Set<MediaType> producibleMediaTypes = new HashSet<MediaType>(); |
||||
for (RequestMappingInfo info : requestMappingInfos) { |
||||
for (String pattern : info.getPatterns()) { |
||||
if (getPathMatcher().match(pattern, lookupPath)) { |
||||
if (!info.getMethods().match(request)) { |
||||
for (RequestMethod method : info.getMethods().getMethods()) { |
||||
allowedMethods.add(method.name()); |
||||
} |
||||
} |
||||
if (!info.getConsumes().match(request)) { |
||||
consumableMediaTypes.addAll(info.getConsumes().getMediaTypes()); |
||||
} |
||||
if (!info.getProduces().match(request)) { |
||||
producibleMediaTypes.addAll(info.getProduces().getMediaTypes()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
if (!allowedMethods.isEmpty()) { |
||||
throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods); |
||||
} |
||||
else if (!consumableMediaTypes.isEmpty()) { |
||||
MediaType contentType = null; |
||||
if (StringUtils.hasLength(request.getContentType())) { |
||||
contentType = MediaType.parseMediaType(request.getContentType()); |
||||
} |
||||
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes)); |
||||
} |
||||
else if (!producibleMediaTypes.isEmpty()) { |
||||
throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes)); |
||||
} |
||||
else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context of a specific |
||||
* request. For example not all {@link RequestMappingInfo} patterns may apply to the current request. Therefore an |
||||
* HttpServletRequest is required as input. |
||||
* |
||||
* <p>Furthermore, the following assumptions are made about the input RequestMappings: <ul><li>Each RequestMappingInfo |
||||
* has been fully matched to the request <li>The RequestMappingInfo contains matched patterns only <li>Patterns are |
||||
* ordered with the best matching pattern at the top </ul> |
||||
* |
||||
* @see RequestMappingInfoHandlerMapping#getMatchingMapping(RequestMappingInfo, String, HttpServletRequest) |
||||
*/ |
||||
private class RequestMappingInfoComparator implements Comparator<RequestMappingInfo> { |
||||
|
||||
private Comparator<String> patternComparator; |
||||
|
||||
private List<MediaType> requestAcceptHeader; |
||||
|
||||
public RequestMappingInfoComparator(String lookupPath, HttpServletRequest request) { |
||||
this.patternComparator = getPathMatcher().getPatternComparator(lookupPath); |
||||
String acceptHeader = request.getHeader("Accept"); |
||||
this.requestAcceptHeader = MediaType.parseMediaTypes(acceptHeader); |
||||
MediaType.sortByQualityValue(this.requestAcceptHeader); |
||||
} |
||||
|
||||
public int compare(RequestMappingInfo mapping, RequestMappingInfo otherMapping) { |
||||
int result = comparePatterns(mapping.getPatterns(), otherMapping.getPatterns()); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
result = mapping.getParams().compareTo(otherMapping.getParams()); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
result = mapping.getHeaders().compareTo(otherMapping.getHeaders()); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
result = mapping.getConsumes().compareTo(otherMapping.getConsumes()); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
result = mapping.getProduces().compareTo(otherMapping.getProduces(), this.requestAcceptHeader); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
result = mapping.getMethods().compareTo(otherMapping.getMethods()); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
private int comparePatterns(Set<String> patterns, Set<String> otherPatterns) { |
||||
Iterator<String> iterator = patterns.iterator(); |
||||
Iterator<String> iteratorOther = otherPatterns.iterator(); |
||||
while (iterator.hasNext() && iteratorOther.hasNext()) { |
||||
int result = patternComparator.compare(iterator.next(), iteratorOther.next()); |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
} |
||||
if (iterator.hasNext()) { |
||||
return -1; |
||||
} |
||||
else if (iteratorOther.hasNext()) { |
||||
return 1; |
||||
} |
||||
else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue