diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index 25c39189209..a1d8ddb6bb5 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -30,27 +30,18 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.context.ApplicationContextException; import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; +import org.springframework.web.util.UrlPathHelper; /** * Abstract base class for {@link org.springframework.web.servlet.HandlerMapping HandlerMapping} implementations that * support mapping requests to {@link HandlerMethod}s rather than to handlers. * - *

Each {@link HandlerMethod} is registered with a unique key. Subclasses define the key type and how to create it - * for a given handler method. Keys represent conditions for matching a handler method to a request. - * - *

Subclasses must also define how to create a key for an incoming request. The resulting key is used to perform - * a {@link HandlerMethod} lookup possibly resulting in a direct match. However, when a map lookup is insufficient, - * the keys of all handler methods are iterated and subclasses are allowed to make an exhaustive check of key - * conditions against the request. - * - *

Since there can be more than one matching key for a request, subclasses must define a comparator for sorting - * the keys of matching handler methods in order to find the most specific match. - * - * @param A unique key for the registration of mapped {@link HandlerMethod}s representing the conditions to - * match a handler method to a request. + * @param Represents a mapping key with conditions for mapping a request to a {@link HandlerMethod}. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -58,8 +49,52 @@ import org.springframework.web.method.HandlerMethodSelector; */ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMapping { + private UrlPathHelper urlPathHelper = new UrlPathHelper(); + + private final MultiValueMap urlMap = new LinkedMultiValueMap(); + private final Map handlerMethods = new LinkedHashMap(); + /** + * Set if URL lookup should always use the full path within the current servlet context. Else, the path within the + * current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml). + *

Default is "false". + * + * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath + */ + public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { + this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); + } + + /** + * Set if context path and request URI should be URL-decoded. Both are returned undecoded by the Servlet API, in + * contrast to the servlet path.

Uses either the request encoding or the default encoding according to the Servlet + * spec (ISO-8859-1). + * + * @see org.springframework.web.util.UrlPathHelper#setUrlDecode + */ + public void setUrlDecode(boolean urlDecode) { + this.urlPathHelper.setUrlDecode(urlDecode); + } + + /** + * Set the UrlPathHelper to use for resolution of lookup paths.

Use this to override the default UrlPathHelper + * with a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and + * MethodNameResolvers. + * + */ + public void setUrlPathHelper(UrlPathHelper urlPathHelper) { + Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); + this.urlPathHelper = urlPathHelper; + } + + /** + * Return the {@link UrlPathHelper} to use for resolution of lookup paths. + */ + public UrlPathHelper getUrlPathHelper() { + return urlPathHelper; + } + /** * Calls the initialization of the superclass and detects handlers. */ @@ -71,14 +106,12 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap /** * Register handler methods found in beans of the current ApplicationContext. - *

The actual key determination for a handler is up to the concrete - * {@link #getKeyForMethod(Method)} implementation. A method in a bean for which no key - * could be determined is simply not considered a handler method. - * @see #getKeyForMethod(Method) + *

The actual mapping for a handler is up to the concrete {@link #getMappingKeyForMethod(String, Method)} + * implementation. */ protected void initHandlerMethods() { if (logger.isDebugEnabled()) { - logger.debug("Looking for URL mappings in application context: " + getApplicationContext()); + logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } for (String beanName : getApplicationContext().getBeanNamesForType(Object.class)) { if (isHandler(beanName)){ @@ -102,81 +135,79 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap Set methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() { public boolean matches(Method method) { - return getKeyForMethod(beanName, method) != null; + return getMappingKeyForMethod(beanName, method) != null; } }); for (Method method : methods) { - T key = getKeyForMethod(beanName, method); HandlerMethod handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); - registerHandlerMethod(key, handlerMethod); + T mapping = getMappingKeyForMethod(beanName, method); + Set paths = getMappingPaths(mapping); + registerHandlerMethod(paths, mapping, handlerMethod); } } /** - * Provides a lookup key for the given bean method. A method for which no key can be determined is + * Provides a mapping key for the given bean method. A method for which no mapping can be determined is * not considered a handler method. * * @param beanName the name of the bean the method belongs to - * @param method the method to create a key for - * @return the lookup key, or {@code null} if the method has none + * @param method the method to create a mapping for + * @return the mapping key, or {@code null} if the method is not mapped */ - protected abstract T getKeyForMethod(String beanName, Method method); + protected abstract T getMappingKeyForMethod(String beanName, Method method); /** - * Registers a {@link HandlerMethod} under the given key. - * - * @param key the key to register the method under + * Registers a {@link HandlerMethod} with the given mapping. + * + * @param paths URL paths mapped to this method + * @param mappingKey the mapping key for the method * @param handlerMethod the handler method to register - * @throws IllegalStateException if another method was already register under the key + * @throws IllegalStateException if another method was already register under the same mapping */ - protected void registerHandlerMethod(T key, HandlerMethod handlerMethod) { - Assert.notNull(key, "'key' must not be null"); + protected void registerHandlerMethod(Set paths, T mappingKey, HandlerMethod handlerMethod) { + Assert.notNull(mappingKey, "'mapping' must not be null"); Assert.notNull(handlerMethod, "'handlerMethod' must not be null"); - HandlerMethod mappedHandlerMethod = handlerMethods.get(key); + HandlerMethod mappedHandlerMethod = handlerMethods.get(mappingKey); if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) { throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean() - + "' bean method \n" + handlerMethod + "\nto " + key + ": There is already '" + + "' bean method \n" + handlerMethod + "\nto " + mappingKey + ": There is already '" + mappedHandlerMethod.getBean() + "' bean method\n" + mappedHandlerMethod + " mapped."); } - handlerMethods.put(key, handlerMethod); + handlerMethods.put(mappingKey, handlerMethod); if (logger.isInfoEnabled()) { - logger.info("Mapped \"" + key + "\" onto " + handlerMethod); + logger.info("Mapped \"" + mappingKey + "\" onto " + handlerMethod); + } + for (String path : paths) { + urlMap.add(path, mappingKey); } } + /** + * Get the URL paths for the given mapping. + */ + protected abstract Set getMappingPaths(T mappingKey); + @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { - T key = getKeyForRequest(request); - if (key == null) { - return null; - } + String lookupPath = urlPathHelper.getLookupPathForRequest(request); if (logger.isDebugEnabled()) { - logger.debug("Looking up handler method with key [" + key + "]"); + logger.debug("Looking up handler method for path " + lookupPath); } - HandlerMethod handlerMethod = lookupHandlerMethod(key, request); + HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (logger.isDebugEnabled()) { if (handlerMethod != null) { logger.debug("Returning handler method [" + handlerMethod + "]"); } else { - logger.debug("Did not find handler method for [" + key + "]"); + logger.debug("Did not find handler method for [" + lookupPath + "]"); } } return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null; } - /** - * Abstract template method that returns the lookup key for the given HTTP servlet request. - * - * @param request the request to look up the key for - * @return the key, or {@code null} if the request does not have one - * @throws Exception in case of errors - */ - protected abstract T getKeyForRequest(HttpServletRequest request) throws Exception; - /** * Looks up the best-matching {@link HandlerMethod} for the given request. * @@ -185,119 +216,126 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap * returns the 1st entry, if any. If no matches are found, {@link #handleNoMatch(Set, HttpServletRequest)} is * invoked. * - * @param lookupKey current lookup key + * @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param request the current HTTP servlet request * @return the best-matching handler method, or {@code null} if there is no match */ - protected HandlerMethod lookupHandlerMethod(T lookupKey, HttpServletRequest request) throws Exception { - if (handlerMethods.containsKey(lookupKey)) { + protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { + List keys = urlMap.get(lookupPath); + if (keys == null) { + keys = new ArrayList(handlerMethods.keySet()); + } + + List matches = new ArrayList(); + + for (T key : keys) { + T match = getMatchingMappingKey(key, lookupPath, request); + if (match != null) { + matches.add(new Match(match, handlerMethods.get(key))); + } + } + + if (!matches.isEmpty()) { + Comparator comparator = new MatchComparator(getMappingKeyComparator(lookupPath, request)); + Collections.sort(matches, comparator); + if (logger.isTraceEnabled()) { - logger.trace("Found direct match for [" + lookupKey + "]"); + logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches); } - handleMatch(lookupKey, request); - return handlerMethods.get(lookupKey); + Match bestMatch = matches.get(0); + if (matches.size() > 1) { + Match secondBestMatch = matches.get(1); + if (comparator.compare(bestMatch, secondBestMatch) == 0) { + Method m1 = bestMatch.handlerMethod.getMethod(); + Method m2 = secondBestMatch.handlerMethod.getMethod(); + throw new IllegalStateException( + "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + + m1 + ", " + m2 + "}"); + } + } + + handleMatch(bestMatch.mappingKey, lookupPath, request); + return bestMatch.handlerMethod; } else { - List matches = new ArrayList(); - - for (Map.Entry entry : handlerMethods.entrySet()) { - T match = getMatchingKey(entry.getKey(), request); - if (match != null) { - matches.add(new Match(match, entry.getValue())); - } - } - - if (!matches.isEmpty()) { - Comparator comparator = getMatchComparator(request); - Collections.sort(matches, comparator); - - if (logger.isTraceEnabled()) { - logger.trace("Found " + matches.size() + " matching key(s) for [" + lookupKey + "] : " + matches); - } - - Match bestMatch = matches.get(0); - if (matches.size() > 1) { - Match secondBestMatch = matches.get(1); - if (comparator.compare(bestMatch, secondBestMatch) == 0) { - Method m1 = bestMatch.handlerMethod.getMethod(); - Method m2 = secondBestMatch.handlerMethod.getMethod(); - throw new IllegalStateException( - "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + - m1 + ", " + m2 + "}"); - } - } - - handleMatch(bestMatch.key, request); - return bestMatch.handlerMethod; - } - else { - return handleNoMatch(handlerMethods.keySet(), request); - } + return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } } /** - * Invoked when a key matching to a request has been identified. + * Invoked when a request has been matched to a mapping. * - * @param key the key selected for the request returned by {@link #getMatchingKey(Object, HttpServletRequest)}. + * @param mappingKey the mapping selected for the request returned by + * {@link #getMatchingMappingKey(Object, String, HttpServletRequest)}. + * @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param request the current request */ - protected void handleMatch(T key, HttpServletRequest request) { + protected void handleMatch(T mappingKey, String lookupPath, HttpServletRequest request) { } /** - * Returns the matching variant of the given key, given the current HTTP servlet request. + * Checks if the mapping matches the current request and returns a mapping updated to contain only conditions + * relevant to the current request (for example a mapping may have several HTTP methods, the matching mapping + * will contain only 1). * - * @param key the key to get the matches for + * @param mappingKey the mapping key to get a match for + * @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param request the current HTTP servlet request - * @return the matching key, or {@code null} if the given key does not match against the servlet request + * @return a matching mapping, or {@code null} if the given mapping does not match the request */ - protected abstract T getMatchingKey(T key, HttpServletRequest request); - - private Comparator getMatchComparator(HttpServletRequest request) { - final Comparator keyComparator = getKeyComparator(request); - return new Comparator() { - public int compare(Match m1, Match m2) { - return keyComparator.compare(m1.key, m2.key); - } - }; - } + protected abstract T getMatchingMappingKey(T mappingKey, String lookupPath, HttpServletRequest request); /** - * Returns a comparator to sort the keys with. The returned comparator should sort 'better' matches higher. + * Returns a comparator to sort mapping keys with. The returned comparator should sort 'better' matches higher. * + * @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param request the current HTTP servlet request * @return the comparator */ - protected abstract Comparator getKeyComparator(HttpServletRequest request); + protected abstract Comparator getMappingKeyComparator(String lookupPath, HttpServletRequest request); /** * Invoked when no match was found. Default implementation returns {@code null}. * - * @param requestKeys the registered request keys + * @param mappingKeys all registered mappings + * @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param request the current HTTP request * @throws ServletException in case of errors */ - protected HandlerMethod handleNoMatch(Set requestKeys, HttpServletRequest request) throws Exception { + protected HandlerMethod handleNoMatch(Set mappingKeys, String lookupPath, HttpServletRequest request) + throws Exception { return null; } private class Match { - private final T key; + private final T mappingKey; private final HandlerMethod handlerMethod; - private Match(T key, HandlerMethod handlerMethod) { - this.key = key; + private Match(T mapping, HandlerMethod handlerMethod) { + this.mappingKey = mapping; this.handlerMethod = handlerMethod; } @Override public String toString() { - return key.toString(); + return mappingKey.toString(); } } -} + private class MatchComparator implements Comparator { + + private final Comparator comparator; + + public MatchComparator(Comparator comparator) { + this.comparator = comparator; + } + + public int compare(Match match1, Match match2) { + return comparator.compare(match1.mappingKey, match2.mappingKey); + } + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java index 5300a6c48c3..aeb1d7e7d91 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet.mvc.method.annotation; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; @@ -42,71 +43,22 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.handler.MappedInterceptors; -import org.springframework.web.util.UrlPathHelper; +import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; /** - * An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestKey}s for the registration and the lookup - * of {@link HandlerMethod}s. - * - *

A {@link RequestKey} for an incoming request contains the URL and the HTTP method of the request. - * A {@link RequestKey} for a handler method contains all conditions found in the method @{@link RequestMapping} - * annotation combined with all conditions found in the type @{@link RequestMapping} annotation, if present. - * - *

An incoming request matches to a handler method directly when a @{@link RequestMapping} annotation contains - * a single, non-pattern URL and a single HTTP method. When a {@link RequestKey} contains additional conditions - * (e.g. more URL patterns, request parameters, headers, etc) those conditions must be checked against the - * request rather than against the key that represents it. This results in the creation of a new handler method - * {@link RequestKey} with the subset of conditions relevant to the current request (see - * {@link RequestKey#getMatchingKey(HttpServletRequest, PathMatcher, UrlPathHelper)}). - * Such keys can then be compared against each other, in the context of the current request, making it possible - * to select to the best matching {@link RequestKey} in case of multiple matches and also the best matching - * pattern within the selected key. + * An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingKey}s for the registration and + * the lookup of {@link HandlerMethod}s. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1.0 */ -public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMapping { - - private UrlPathHelper urlPathHelper = new UrlPathHelper(); +public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMapping { private PathMatcher pathMatcher = new AntPathMatcher(); private MappedInterceptors mappedInterceptors; - /** - * Set if URL lookup should always use the full path within the current servlet context. Else, the path within the - * current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml). - *

Default is "false". - * - * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath - */ - public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { - this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); - } - - /** - * Set if context path and request URI should be URL-decoded. Both are returned undecoded by the Servlet API, in - * contrast to the servlet path.

Uses either the request encoding or the default encoding according to the Servlet - * spec (ISO-8859-1). - * - * @see org.springframework.web.util.UrlPathHelper#setUrlDecode - */ - public void setUrlDecode(boolean urlDecode) { - this.urlPathHelper.setUrlDecode(urlDecode); - } - - /** - * Set the UrlPathHelper to use for resolution of lookup paths.

Use this to override the default UrlPathHelper - * with a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and - * MethodNameResolvers. - * - */ - public void setUrlPathHelper(UrlPathHelper urlPathHelper) { - Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); - this.urlPathHelper = urlPathHelper; - } - /** * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is * AntPathMatcher. @@ -145,18 +97,7 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap } /** - * Returns a {@link RequestKey} instances that represents the given HTTP servlet request. - * - * @param request the request to look up the key for - * @return the key, never null - */ - @Override - protected RequestKey getKeyForRequest(HttpServletRequest request) { - return RequestKey.createFromServletRequest(request, urlPathHelper); - } - - /** - * Provides a {@link RequestKey} for the given method. + * Provides a {@link RequestMappingKey} for the given method. *

Only {@link RequestMapping @RequestMapping}-annotated methods are considered. * Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their * attributes combined with method-level {@link RequestMapping @RequestMapping} attributes. @@ -164,16 +105,16 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap * @param beanName the name of the bean the method belongs to * @param method the method to create a key for * @return the key, or {@code null} - * @see RequestKey#combine(RequestKey, PathMatcher) + * @see RequestMappingKey#combine(RequestMappingKey, PathMatcher) */ @Override - protected RequestKey getKeyForMethod(String beanName, Method method) { + protected RequestMappingKey getMappingKeyForMethod(String beanName, Method method) { RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (annotation != null) { - RequestKey methodKey = RequestKey.createFromRequestMapping(annotation); + RequestMappingKey methodKey = createFromRequestMapping(annotation); RequestMapping typeAnnot = getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class); if (typeAnnot != null) { - RequestKey typeKey = RequestKey.createFromRequestMapping(typeAnnot); + RequestMappingKey typeKey = createFromRequestMapping(typeAnnot); return typeKey.combine(methodKey, pathMatcher); } else { @@ -185,41 +126,52 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap } } - /** - * Returns a new {@link RequestKey} with attributes matching to the current request or {@code null}. - * @see RequestKey#getMatchingKey(HttpServletRequest, PathMatcher, UrlPathHelper) - */ + private static RequestMappingKey createFromRequestMapping(RequestMapping annotation) { + return new RequestMappingKey(Arrays.asList(annotation.value()), Arrays.asList(annotation.method()), + RequestConditionFactory.parseParams(annotation.params()), + RequestConditionFactory.parseHeaders(annotation.headers()), + RequestConditionFactory.parseConsumes(annotation.consumes()) + ); + } + @Override - protected RequestKey getMatchingKey(RequestKey key, HttpServletRequest request) { - return key.getMatchingKey(request, pathMatcher, urlPathHelper); + protected Set getMappingPaths(RequestMappingKey key) { + return key.getPatterns(); } /** - * Returns a {@link Comparator} that can be used to sort and select the best matching {@link RequestKey}. + * Returns a new {@link RequestMappingKey} with attributes matching to the current request or {@code null}. + * @see RequestMappingKey#getMatchingKey(String, HttpServletRequest, PathMatcher) */ @Override - protected Comparator getKeyComparator(HttpServletRequest request) { - return new RequestKeyComparator(request); + protected RequestMappingKey getMatchingMappingKey(RequestMappingKey key, String lookupPath, HttpServletRequest request) { + return key.getMatchingKey(lookupPath, request, pathMatcher); + } + + /** + * Returns a {@link Comparator} that can be used to sort and select the best matching {@link RequestMappingKey}. + */ + @Override + protected Comparator getMappingKeyComparator(String lookupPath, HttpServletRequest request) { + return new RequestKeyComparator(lookupPath, request); } @Override - protected void handleMatch(RequestKey key, HttpServletRequest request) { + protected void handleMatch(RequestMappingKey key, String lookupPath, HttpServletRequest request) { String pattern = key.getPatterns().iterator().next(); - String lookupPath = urlPathHelper.getLookupPathForRequest(request); Map uriTemplateVariables = pathMatcher.extractUriTemplateVariables(pattern, lookupPath); request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); } /** - * Iterates all {@link RequestKey}s looking for keys that match by URL but not by HTTP method. + * Iterates all {@link RequestMappingKey}s looking for keys that match by URL but not by HTTP method. * @exception HttpRequestMethodNotSupportedException if there are matches by URL but not by HTTP method */ @Override - protected HandlerMethod handleNoMatch(Set requestKeys, HttpServletRequest request) + protected HandlerMethod handleNoMatch(Set requestKeys, String lookupPath, HttpServletRequest request) throws HttpRequestMethodNotSupportedException { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); Set allowedMethods = new HashSet(6); - for (RequestKey requestKey : requestKeys) { + for (RequestMappingKey requestKey : requestKeys) { for (String pattern : requestKey.getPatterns()) { if (pathMatcher.match(pattern, lookupPath)) { for (RequestMethod method : requestKey.getMethods()) { @@ -244,7 +196,7 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request); if (this.mappedInterceptors != null) { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); + String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); HandlerInterceptor[] handlerInterceptors = mappedInterceptors.getInterceptors(lookupPath, pathMatcher); if (handlerInterceptors.length > 0) { chain.addInterceptors(handlerInterceptors); @@ -254,31 +206,30 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap } /** - * A comparator for {@link RequestKey}s. Effective comparison can only be done in the context of a - * specific request. For example not all {@link RequestKey} patterns may apply to the current request. + * A comparator for {@link RequestMappingKey}s. Effective comparison can only be done in the context of a + * specific request. For example not all {@link RequestMappingKey} patterns may apply to the current request. * Therefore an HttpServletRequest is required as input. * *

Furthermore, the following assumptions are made about the input RequestKeys: *

* - * @see RequestMappingHandlerMethodMapping#getMatchingKey(RequestKey, HttpServletRequest) + * @see RequestMappingHandlerMethodMapping#getMatchingKey(RequestMappingKey, HttpServletRequest) */ - private class RequestKeyComparator implements Comparator { + private class RequestKeyComparator implements Comparator { private Comparator patternComparator; private List requestAcceptHeader; - public RequestKeyComparator(HttpServletRequest request) { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); + public RequestKeyComparator(String lookupPath, HttpServletRequest request) { this.patternComparator = pathMatcher.getPatternComparator(lookupPath); String acceptHeader = request.getHeader("Accept"); this.requestAcceptHeader = MediaType.parseMediaTypes(acceptHeader); MediaType.sortByQualityValue(this.requestAcceptHeader); } - public int compare(RequestKey key, RequestKey otherKey) { + public int compare(RequestMappingKey key, RequestMappingKey otherKey) { int result = comparePatterns(key.getPatterns(), otherKey.getPatterns()); if (result != 0) { return result; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingKey.java similarity index 75% rename from org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java rename to org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingKey.java index 16158fe8d21..e46bab74700 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingKey.java @@ -17,37 +17,35 @@ package org.springframework.web.servlet.mvc.method.annotation; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; + import javax.servlet.http.HttpServletRequest; import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.method.condition.RequestCondition; import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; -import org.springframework.web.util.UrlPathHelper; /** * Contains a set of conditions to match to a given request such as URL patterns, HTTP methods, request * parameters and headers. * - *

A {@link RequestKey} can be combined with another {@link RequestKey} resulting in a new {@link RequestKey} - * with conditions from both (see {@link #combine(RequestKey, PathMatcher)}). + *

A {@link RequestMappingKey} can be combined with another {@link RequestMappingKey} resulting in a new {@link RequestMappingKey} + * with conditions from both (see {@link #combine(RequestMappingKey, PathMatcher)}). * - *

A {@link RequestKey} can be matched to a request resulting in a new {@link RequestKey} with the subset of - * conditions relevant to the request (see {@link #getMatchingKey(HttpServletRequest, PathMatcher, UrlPathHelper)}). + *

A {@link RequestMappingKey} can be matched to a request resulting in a new {@link RequestMappingKey} with the subset of + * conditions relevant to the request (see {@link #getMatchingKey(String, HttpServletRequest, PathMatcher)}). * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */ -public final class RequestKey { +public final class RequestMappingKey { private final Set patterns; @@ -66,20 +64,18 @@ public final class RequestKey { * *

Package protected for testing purposes. */ - RequestKey(Collection patterns, Collection methods) { + RequestMappingKey(Collection patterns, Collection methods) { this(patterns, methods, null, null, null); } /** * Creates a new {@code RequestKey} instance with a full set of conditions. - * - *

Package protected for testing purposes. */ - RequestKey(Collection patterns, - Collection methods, - RequestCondition paramsCondition, - RequestCondition headersCondition, - RequestCondition consumesCondition) { + public RequestMappingKey(Collection patterns, + Collection methods, + RequestCondition paramsCondition, + RequestCondition headersCondition, + RequestCondition consumesCondition) { this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns)); this.methods = asUnmodifiableSet(methods); this.paramsCondition = paramsCondition != null ? paramsCondition : RequestConditionFactory.trueCondition(); @@ -109,34 +105,6 @@ public final class RequestKey { return Collections.unmodifiableSet(result); } - /** - * Creates a new {@code RequestKey} from a {@link RequestMapping @RequestMapping} annotation. - * - * @param annotation the annotation - * @return the request key created from the annotation - */ - public static RequestKey createFromRequestMapping(RequestMapping annotation) { - return new RequestKey(Arrays.asList(annotation.value()), Arrays.asList(annotation.method()), - RequestConditionFactory.parseParams(annotation.params()), - RequestConditionFactory.parseHeaders(annotation.headers()), - RequestConditionFactory.parseConsumes(annotation.consumes()) - ); - } - - /** - * Creates a new {@code RequestKey} from a {@link HttpServletRequest}. - * - * @param request the servlet request - * @param urlPathHelper to create the {@linkplain UrlPathHelper#getLookupPathForRequest(HttpServletRequest) lookup - * path} - * @return the request key created from the servlet request - */ - public static RequestKey createFromServletRequest(HttpServletRequest request, UrlPathHelper urlPathHelper) { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); - RequestMethod method = RequestMethod.valueOf(request.getMethod()); - return new RequestKey(Collections.singleton(lookupPath), Collections.singleton(method)); - } - /** * Returns the patterns of this request key. */ @@ -183,14 +151,14 @@ public final class RequestKey { * @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns * @return a new request key containing conditions from both keys */ - public RequestKey combine(RequestKey methodKey, PathMatcher pathMatcher) { + public RequestMappingKey combine(RequestMappingKey methodKey, PathMatcher pathMatcher) { Set patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher); Set methods = union(this.methods, methodKey.methods); RequestCondition params = RequestConditionFactory.and(this.paramsCondition, methodKey.paramsCondition); RequestCondition headers = RequestConditionFactory.and(this.headersCondition, methodKey.headersCondition); RequestCondition consumes = RequestConditionFactory.mostSpecific(methodKey.consumesCondition, this.consumesCondition); - return new RequestKey(patterns, methods, params, headers, consumes); + return new RequestMappingKey(patterns, methods, params, headers, consumes); } private static Set combinePatterns(Collection typePatterns, @@ -231,21 +199,21 @@ public final class RequestKey { *

  • Request parameter and request header conditions are included in full. *
  • The list of consumes conditions is trimmed and sorted to match the request "Content-Type" header. * + * @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param request the current request * @param pathMatcher to check for matching patterns - * @param urlPathHelper to derive the lookup path for the request * @return a new request key that contains all matching attributes, or {@code null} if not all conditions match */ - public RequestKey getMatchingKey(HttpServletRequest request, PathMatcher pathMatcher, UrlPathHelper urlPathHelper) { + public RequestMappingKey getMatchingKey(String lookupPath, HttpServletRequest request, PathMatcher pathMatcher) { if (!checkMethod(request) || !paramsCondition.match(request) || !headersCondition.match(request) || !consumesCondition.match(request)) { return null; } else { - List matchingPatterns = getMatchingPatterns(request, pathMatcher, urlPathHelper); + List matchingPatterns = getMatchingPatterns(lookupPath, request, pathMatcher); if (!matchingPatterns.isEmpty()) { Set matchingMethods = getMatchingMethod(request); - return new RequestKey(matchingPatterns, matchingMethods, this.paramsCondition, this.headersCondition, + return new RequestMappingKey(matchingPatterns, matchingMethods, this.paramsCondition, this.headersCondition, this.consumesCondition); } else { @@ -254,10 +222,9 @@ public final class RequestKey { } } - private List getMatchingPatterns(HttpServletRequest request, - PathMatcher pathMatcher, - UrlPathHelper urlPathHelper) { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); + private List getMatchingPatterns(String lookupPath, + HttpServletRequest request, + PathMatcher pathMatcher) { List matchingPatterns = new ArrayList(); for (String pattern : this.patterns) { @@ -308,8 +275,8 @@ public final class RequestKey { if (this == obj) { return true; } - if (obj != null && obj instanceof RequestKey) { - RequestKey other = (RequestKey) obj; + if (obj != null && obj instanceof RequestMappingKey) { + RequestMappingKey other = (RequestMappingKey) obj; return (this.patterns.equals(other.patterns) && this.methods.equals(other.methods) && this.paramsCondition.equals(other.paramsCondition) && this.headersCondition.equals(other.headersCondition)); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java index 7a016b822c8..26fa8880980 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java @@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletRequest; /** * Defines the contract for conditions that must be met before an incoming request matches a {@link - * org.springframework.web.servlet.mvc.method.annotation.RequestKey RequestKey}. + * org.springframework.web.servlet.mvc.method.annotation.RequestMappingKey RequestKey}. * *

    Implementations of this interface are created by the {@link RequestConditionFactory}. * diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java index e8128ae9d56..c4de71a4842 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/handler/HandlerMethodMappingTests.java @@ -16,20 +16,21 @@ package org.springframework.web.servlet.handler; +import static org.junit.Assert.assertEquals; + import java.lang.reflect.Method; import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; - import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.web.method.HandlerMethod; -import org.springframework.web.util.UrlPathHelper; - -import static org.junit.Assert.*; /** * Test for {@link AbstractHandlerMethodMapping}. @@ -54,14 +55,14 @@ public class HandlerMethodMappingTests { @Test(expected = IllegalStateException.class) public void registerDuplicates() { - mapping.registerHandlerMethod("foo", handlerMethod1); - mapping.registerHandlerMethod("foo", handlerMethod2); + mapping.registerHandlerMethod(new HashSet(), "foo", handlerMethod1); + mapping.registerHandlerMethod(new HashSet(), "foo", handlerMethod2); } @Test public void directMatch() throws Exception { String key = "foo"; - mapping.registerHandlerMethod(key, handlerMethod1); + mapping.registerHandlerMethod(new HashSet(), key, handlerMethod1); HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", key)); assertEquals(handlerMethod1, result); @@ -69,8 +70,8 @@ public class HandlerMethodMappingTests { @Test public void patternMatch() throws Exception { - mapping.registerHandlerMethod("/fo*", handlerMethod1); - mapping.registerHandlerMethod("/f*", handlerMethod1); + mapping.registerHandlerMethod(new HashSet(), "/fo*", handlerMethod1); + mapping.registerHandlerMethod(new HashSet(), "/f*", handlerMethod1); HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); assertEquals(handlerMethod1, result); @@ -78,40 +79,29 @@ public class HandlerMethodMappingTests { @Test(expected = IllegalStateException.class) public void ambiguousMatch() throws Exception { - mapping.registerHandlerMethod("/f?o", handlerMethod1); - mapping.registerHandlerMethod("/fo?", handlerMethod2); + mapping.registerHandlerMethod(new HashSet(), "/f?o", handlerMethod1); + mapping.registerHandlerMethod(new HashSet(), "/fo?", handlerMethod2); mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo")); } private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping { - private UrlPathHelper urlPathHelper = new UrlPathHelper(); - private PathMatcher pathMatcher = new AntPathMatcher(); @Override - protected String getKeyForRequest(HttpServletRequest request) throws Exception { - return urlPathHelper.getLookupPathForRequest(request); - } - - @Override - protected String getMatchingKey(String pattern, HttpServletRequest request) { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); - + protected String getMatchingMappingKey(String pattern, String lookupPath, HttpServletRequest request) { return pathMatcher.match(pattern, lookupPath) ? pattern : null; } @Override - protected String getKeyForMethod(String beanName, Method method) { + protected String getMappingKeyForMethod(String beanName, Method method) { String methodName = method.getName(); return methodName.startsWith("handler") ? methodName : null; } @Override - protected Comparator getKeyComparator(HttpServletRequest request) { - String lookupPath = urlPathHelper.getLookupPathForRequest(request); - + protected Comparator getMappingKeyComparator(String lookupPath, HttpServletRequest request) { return pathMatcher.getPatternComparator(lookupPath); } @@ -119,6 +109,11 @@ public class HandlerMethodMappingTests { protected boolean isHandler(String beanName) { return true; } + + @Override + protected Set getMappingPaths(String key) { + return new HashSet(); + } } private static class MyHandler { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java index 6c91865eaec..555a820cba5 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java @@ -27,12 +27,13 @@ import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; +import org.springframework.web.util.UrlPathHelper; import static java.util.Arrays.*; import static org.junit.Assert.*; /** - * Test fixture with {@link RequestMappingHandlerMethodMapping} testing its {@link RequestKey} comparator. + * Test fixture with {@link RequestMappingHandlerMethodMapping} testing its {@link RequestMappingKey} comparator. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -52,9 +53,10 @@ public class RequestKeyComparatorTests { @Test public void moreSpecificPatternWins() { request.setRequestURI("/foo"); - Comparator comparator = handlerMapping.getKeyComparator(request); - RequestKey key1 = new RequestKey(asList("/fo*"), null); - RequestKey key2 = new RequestKey(asList("/foo"), null); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); + Comparator comparator = handlerMapping.getMappingKeyComparator(lookupPath, request); + RequestMappingKey key1 = new RequestMappingKey(asList("/fo*"), null); + RequestMappingKey key2 = new RequestMappingKey(asList("/foo"), null); assertEquals(1, comparator.compare(key1, key2)); } @@ -62,9 +64,10 @@ public class RequestKeyComparatorTests { @Test public void equalPatterns() { request.setRequestURI("/foo"); - Comparator comparator = handlerMapping.getKeyComparator(request); - RequestKey key1 = new RequestKey(asList("/foo*"), null); - RequestKey key2 = new RequestKey(asList("/foo*"), null); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); + Comparator comparator = handlerMapping.getMappingKeyComparator(lookupPath, request); + RequestMappingKey key1 = new RequestMappingKey(asList("/foo*"), null); + RequestMappingKey key2 = new RequestMappingKey(asList("/foo*"), null); assertEquals(0, comparator.compare(key1, key2)); } @@ -72,34 +75,35 @@ public class RequestKeyComparatorTests { @Test public void greaterNumberOfMatchingPatternsWins() throws Exception { request.setRequestURI("/foo.html"); - RequestKey key1 = new RequestKey(asList("/foo", "*.jpeg"), null); - RequestKey key2 = new RequestKey(asList("/foo", "*.html"), null); - RequestKey match1 = handlerMapping.getMatchingKey(key1, request); - RequestKey match2 = handlerMapping.getMatchingKey(key2, request); - List matches = asList(match1, match2); - Collections.sort(matches, handlerMapping.getKeyComparator(request)); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); + RequestMappingKey key1 = new RequestMappingKey(asList("/foo", "*.jpeg"), null); + RequestMappingKey key2 = new RequestMappingKey(asList("/foo", "*.html"), null); + RequestMappingKey match1 = handlerMapping.getMatchingMappingKey(key1, lookupPath, request); + RequestMappingKey match2 = handlerMapping.getMatchingMappingKey(key2, lookupPath, request); + List matches = asList(match1, match2); + Collections.sort(matches, handlerMapping.getMappingKeyComparator(lookupPath, request)); assertSame(match2.getPatterns(), matches.get(0).getPatterns()); } @Test public void oneMethodWinsOverNone() { - Comparator comparator = handlerMapping.getKeyComparator(request); - RequestKey key1 = new RequestKey(null, null); - RequestKey key2 = new RequestKey(null, asList(RequestMethod.GET)); + Comparator comparator = handlerMapping.getMappingKeyComparator("", request); + RequestMappingKey key1 = new RequestMappingKey(null, null); + RequestMappingKey key2 = new RequestMappingKey(null, asList(RequestMethod.GET)); assertEquals(1, comparator.compare(key1, key2)); } @Test public void methodsAndParams() { - RequestKey empty = new RequestKey(null, null); - RequestKey oneMethod = new RequestKey(null, asList(RequestMethod.GET)); - RequestKey oneMethodOneParam = - new RequestKey(null, asList(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null, null); - List list = asList(empty, oneMethod, oneMethodOneParam); + RequestMappingKey empty = new RequestMappingKey(null, null); + RequestMappingKey oneMethod = new RequestMappingKey(null, asList(RequestMethod.GET)); + RequestMappingKey oneMethodOneParam = + new RequestMappingKey(null, asList(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null, null); + List list = asList(empty, oneMethod, oneMethodOneParam); Collections.shuffle(list); - Collections.sort(list, handlerMapping.getKeyComparator(request)); + Collections.sort(list, handlerMapping.getMappingKeyComparator("", request)); assertEquals(oneMethodOneParam, list.get(0)); assertEquals(oneMethod, list.get(1)); @@ -109,12 +113,12 @@ public class RequestKeyComparatorTests { @Test @Ignore // TODO : remove ignore public void acceptHeaders() { - RequestKey html = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=text/html"), null); - RequestKey xml = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=application/xml"), null); - RequestKey none = new RequestKey(null, null); + RequestMappingKey html = new RequestMappingKey(null, null, null, RequestConditionFactory.parseHeaders("accept=text/html"), null); + RequestMappingKey xml = new RequestMappingKey(null, null, null, RequestConditionFactory.parseHeaders("accept=application/xml"), null); + RequestMappingKey none = new RequestMappingKey(null, null); request.addHeader("Accept", "application/xml, text/html"); - Comparator comparator = handlerMapping.getKeyComparator(request); + Comparator comparator = handlerMapping.getMappingKeyComparator("", request); assertTrue(comparator.compare(html, xml) > 0); assertTrue(comparator.compare(xml, html) < 0); @@ -125,14 +129,14 @@ public class RequestKeyComparatorTests { request = new MockHttpServletRequest(); request.addHeader("Accept", "application/xml, text/*"); - comparator = handlerMapping.getKeyComparator(request); + comparator = handlerMapping.getMappingKeyComparator("", request); assertTrue(comparator.compare(html, xml) > 0); assertTrue(comparator.compare(xml, html) < 0); request = new MockHttpServletRequest(); request.addHeader("Accept", "application/pdf"); - comparator = handlerMapping.getKeyComparator(request); + comparator = handlerMapping.getMappingKeyComparator("", request); assertTrue(comparator.compare(html, xml) == 0); assertTrue(comparator.compare(xml, html) == 0); @@ -140,7 +144,7 @@ public class RequestKeyComparatorTests { // See SPR-7000 request = new MockHttpServletRequest(); request.addHeader("Accept", "text/html;q=0.9,application/xml"); - comparator = handlerMapping.getKeyComparator(request); + comparator = handlerMapping.getMappingKeyComparator("", request); assertTrue(comparator.compare(html, xml) > 0); assertTrue(comparator.compare(xml, html) < 0); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java index fbe16149d22..73f941fdff3 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java @@ -16,22 +16,23 @@ package org.springframework.web.servlet.mvc.method.annotation; -import org.junit.Test; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; import org.springframework.web.util.UrlPathHelper; -import static java.util.Arrays.*; -import static java.util.Collections.*; -import static org.junit.Assert.*; -import static org.springframework.web.bind.annotation.RequestMethod.*; - /** - * Test fixture for {@link RequestKey} tests. + * Test fixture for {@link RequestMappingKey} tests. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -40,8 +41,8 @@ public class RequestKeyTests { @Test public void equals() { - RequestKey key1 = new RequestKey(singleton("/foo"), singleton(GET)); - RequestKey key2 = new RequestKey(singleton("/foo"), singleton(GET)); + RequestMappingKey key1 = new RequestMappingKey(singleton("/foo"), singleton(GET)); + RequestMappingKey key2 = new RequestMappingKey(singleton("/foo"), singleton(GET)); assertEquals(key1, key2); assertEquals(key1.hashCode(), key2.hashCode()); @@ -49,8 +50,8 @@ public class RequestKeyTests { @Test public void equalsPrependSlash() { - RequestKey key1 = new RequestKey(singleton("/foo"), singleton(GET)); - RequestKey key2 = new RequestKey(singleton("foo"), singleton(GET)); + RequestMappingKey key1 = new RequestMappingKey(singleton("/foo"), singleton(GET)); + RequestMappingKey key2 = new RequestMappingKey(singleton("foo"), singleton(GET)); assertEquals(key1, key2); assertEquals(key1.hashCode(), key2.hashCode()); @@ -60,9 +61,9 @@ public class RequestKeyTests { public void combinePatterns() { AntPathMatcher pathMatcher = new AntPathMatcher(); - RequestKey key1 = createKeyFromPatterns("/t1", "/t2"); - RequestKey key2 = createKeyFromPatterns("/m1", "/m2"); - RequestKey key3 = createKeyFromPatterns("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"); + RequestMappingKey key1 = createKeyFromPatterns("/t1", "/t2"); + RequestMappingKey key2 = createKeyFromPatterns("/m1", "/m2"); + RequestMappingKey key3 = createKeyFromPatterns("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"); assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns()); key1 = createKeyFromPatterns("/t1"); @@ -88,154 +89,147 @@ public class RequestKeyTests { @Test public void matchPatternsToRequest() { - UrlPathHelper urlPathHelper = new UrlPathHelper(); + UrlPathHelper pathHelper = new UrlPathHelper(); PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - RequestKey key = new RequestKey(singleton("/foo"), null); - RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + RequestMappingKey key = new RequestMappingKey(singleton("/foo"), null); + RequestMappingKey match = key.getMatchingKey(pathHelper.getLookupPathForRequest(request), request, pathMatcher); assertNotNull(match); request = new MockHttpServletRequest("GET", "/foo/bar"); - key = new RequestKey(singleton("/foo/*"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo/*"), null); + match = key.getMatchingKey(pathHelper.getLookupPathForRequest(request), request, pathMatcher); assertNotNull("Pattern match", match); request = new MockHttpServletRequest("GET", "/foo.html"); - key = new RequestKey(singleton("/foo"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo"), null); + match = key.getMatchingKey(pathHelper.getLookupPathForRequest(request), request, pathMatcher); assertNotNull("Implicit match by extension", match); assertEquals("Contains matched pattern", "/foo.*", match.getPatterns().iterator().next()); request = new MockHttpServletRequest("GET", "/foo/"); - key = new RequestKey(singleton("/foo"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo"), null); + match = key.getMatchingKey(pathHelper.getLookupPathForRequest(request), request, pathMatcher); assertNotNull("Implicit match by trailing slash", match); assertEquals("Contains matched pattern", "/foo/", match.getPatterns().iterator().next()); request = new MockHttpServletRequest("GET", "/foo.html"); - key = new RequestKey(singleton("/foo.jpg"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo.jpg"), null); + match = key.getMatchingKey(pathHelper.getLookupPathForRequest(request), request, pathMatcher); assertNull("Implicit match ignored if pattern has extension", match); request = new MockHttpServletRequest("GET", "/foo.html"); - key = new RequestKey(singleton("/foo.jpg"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo.jpg"), null); + match = key.getMatchingKey(pathHelper.getLookupPathForRequest(request), request, pathMatcher); assertNull("Implicit match ignored on pattern with trailing slash", match); } @Test public void matchRequestMethods() { - UrlPathHelper urlPathHelper = new UrlPathHelper(); PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); - RequestKey key = new RequestKey(singleton("/foo"), null); - RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + RequestMappingKey key = new RequestMappingKey(singleton("/foo"), null); + RequestMappingKey match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNotNull("No method matches any method", match); - key = new RequestKey(singleton("/foo"), singleton(GET)); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo"), singleton(GET)); + match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNotNull("Exact match", match); - key = new RequestKey(singleton("/foo"), singleton(POST)); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo"), singleton(POST)); + match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNull("No match", match); } @Test public void matchingKeyContent() { - UrlPathHelper urlPathHelper = new UrlPathHelper(); PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); - RequestKey key = new RequestKey(asList("/foo*", "/bar"), asList(GET, POST)); - RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); - RequestKey expected = new RequestKey(singleton("/foo*"), singleton(GET)); + RequestMappingKey key = new RequestMappingKey(asList("/foo*", "/bar"), asList(GET, POST)); + RequestMappingKey match = key.getMatchingKey(lookupPath, request, pathMatcher); + RequestMappingKey expected = new RequestMappingKey(singleton("/foo*"), singleton(GET)); assertEquals("Matching RequestKey contains matched patterns and methods only", expected, match); - key = new RequestKey(asList("/**", "/foo*", "/foo"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); - expected = new RequestKey(asList("/foo", "/foo*", "/**"), null); + key = new RequestMappingKey(asList("/**", "/foo*", "/foo"), null); + match = key.getMatchingKey(lookupPath, request, pathMatcher); + expected = new RequestMappingKey(asList("/foo", "/foo*", "/**"), null); assertEquals("Matched patterns are sorted with best match at the top", expected, match); } @Test public void paramsCondition() { - UrlPathHelper urlPathHelper = new UrlPathHelper(); PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setParameter("foo", "bar"); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); - RequestKey key = new RequestKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null, null); - RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + RequestMappingKey key = new RequestMappingKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null, null); + RequestMappingKey match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNotNull(match); - key = new RequestKey(singleton("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null, null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null, null); + match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNull(match); } @Test public void headersCondition() { - UrlPathHelper urlPathHelper = new UrlPathHelper(); PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.addHeader("foo", "bar"); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); - RequestKey key = new RequestKey(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"), null); - RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + RequestMappingKey key = new RequestMappingKey(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"), null); + RequestMappingKey match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNotNull(match); - key = new RequestKey(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"), null); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + key = new RequestMappingKey(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"), null); + match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNull(match); } @Test public void consumesCondition() { - UrlPathHelper urlPathHelper = new UrlPathHelper(); PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setContentType("text/plain"); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); - RequestKey key = new RequestKey(singleton("/foo"), null, null, null, RequestConditionFactory.parseConsumes( + RequestMappingKey key = new RequestMappingKey(singleton("/foo"), null, null, null, RequestConditionFactory.parseConsumes( "text/plain")); - RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + RequestMappingKey match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNotNull(match); - key = new RequestKey(singleton("/foo"), null, null, null, RequestConditionFactory.parseConsumes( + key = new RequestMappingKey(singleton("/foo"), null, null, null, RequestConditionFactory.parseConsumes( "application/xml")); - match = key.getMatchingKey(request, pathMatcher, urlPathHelper); + match = key.getMatchingKey(lookupPath, request, pathMatcher); assertNull(match); } - @Test - public void createFromServletRequest() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - RequestKey key = RequestKey.createFromServletRequest(request, new UrlPathHelper()); - assertEquals(new RequestKey(singleton("/foo"), singleton(RequestMethod.GET), null, null, null), key); - } - - private RequestKey createKeyFromPatterns(String... patterns) { - return new RequestKey(asList(patterns), null); + private RequestMappingKey createKeyFromPatterns(String... patterns) { + return new RequestMappingKey(asList(patterns), null); } } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java index dc24f57000b..bb8254b82ee 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java @@ -27,7 +27,6 @@ import java.util.Arrays; import java.util.Map; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.context.support.StaticApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; @@ -41,6 +40,7 @@ import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.MappedInterceptor; +import org.springframework.web.util.UrlPathHelper; /** * Test fixture with {@link RequestMappingHandlerMethodMapping}. @@ -102,8 +102,6 @@ public class RequestMappingHandlerMethodMappingTests { assertEquals(emptyMethod.getMethod(), hm.getMethod()); } - // TODO: SPR-8247 - @Ignore @Test public void bestMatch() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); @@ -126,10 +124,11 @@ public class RequestMappingHandlerMethodMappingTests { @Test public void uriTemplateVariables() { - RequestKey key = new RequestKey(Arrays.asList("/{path1}/{path2}"), null); + RequestMappingKey key = new RequestMappingKey(Arrays.asList("/{path1}/{path2}"), null); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2"); + String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); - mapping.handleMatch(key, request); + mapping.handleMatch(key, lookupPath, request); @SuppressWarnings("unchecked") Map actual = (Map) request.getAttribute(