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 4406f50f769..40efb17d53c 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 @@ -37,10 +37,14 @@ import org.springframework.web.method.HandlerMethodSelector; import org.springframework.web.servlet.HandlerMapping; /** - * Abstract base class for {@link org.springframework.web.servlet.HandlerMapping HandlerMapping} implementations that - * support mapping requests to {@link HandlerMethod}s rather than to handlers. + * Abstract base class for {@link HandlerMapping} implementations that define a + * mapping between a request and a {@link HandlerMethod}. * - * @param A type containing request mapping conditions required to match a request to a {@link HandlerMethod}. + *

For each registered handler method, a unique mapping is maintained with + * subclasses defining the details of the mapping type {@code }. + * + * @param The mapping for a {@link HandlerMethod} containing the conditions + * needed to match the handler method to incoming request. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -48,21 +52,19 @@ import org.springframework.web.servlet.HandlerMapping; */ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMapping { - private final MultiValueMap urlMap = new LinkedMultiValueMap(); - private final Map handlerMethods = new LinkedHashMap(); + private final MultiValueMap urlMap = new LinkedMultiValueMap(); /** - * Return the map with all {@link HandlerMethod}s. The key of the map is the generic type - * {@code } containing request mapping conditions. + * Return a map with all handler methods and their mappings. */ public Map getHandlerMethods() { return Collections.unmodifiableMap(handlerMethods); } /** - * Calls the initialization of the superclass and detects handlers. + * ApplicationContext initialization and handler method detection. */ @Override public void initApplicationContext() throws ApplicationContextException { @@ -71,9 +73,10 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Register handler methods found in beans of the current ApplicationContext. - *

The actual mapping for a handler is up to the concrete {@link #getMappingForMethod(String, Method)} - * implementation. + * Scan beans in the ApplicationContext, detect and register handler methods. + * @see #isHandler(Class) + * @see #getMappingForMethod(Method, Class) + * @see #handlerMethodsInitialized(Map) */ protected void initHandlerMethods() { if (logger.isDebugEnabled()) { @@ -88,21 +91,21 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Determines if the given type could contain handler methods. - * @param beanType the type to check - * @return true if this a type that could contain handler methods, false otherwise. + * Whether the given type is a handler with handler methods. + * @param beanType the type of the bean being checked + * @return "true" if this a handler type, "false" otherwise. */ protected abstract boolean isHandler(Class beanType); /** - * Invoked after all handler methods found in beans of the current ApplicationContext have been registered. - * @param handlerMethods a read-only map with mapping conditions (generic type {@code }) and HandlerMethods. + * Invoked after all handler methods have been detected. + * @param handlerMethods a read-only map with handler methods and mappings. */ protected void handlerMethodsInitialized(Map handlerMethods) { } /** - * Detect and register handler methods for the specified handler. + * Look for handler methods in a handler. * @param handler the bean name of a handler or a handler instance */ protected void detectHandlerMethods(final Object handler) { @@ -124,22 +127,24 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Provides a request mapping for the given bean method. A method for which no request mapping can be determined - * is not considered a handler method. + * Provide the mapping for a handler method. A method for which no + * mapping can be provided is not a handler method. * - * @param method the method to create a mapping for - * @param handlerType the actual handler type (possibly a subtype of {@code method.getDeclaringClass()}) + * @param method the method to provide a mapping for + * @param handlerType the handler type, possibly a sub-type of the method's + * declaring class * @return the mapping, or {@code null} if the method is not mapped */ protected abstract T getMappingForMethod(Method method, Class handlerType); /** - * Registers a {@link HandlerMethod} with the given mapping. + * Register a handler method and its unique mapping. * - * @param handler the bean name of the handler or the actual handler instance + * @param handler the bean name of the handler or the handler instance * @param method the method to register * @param mapping the mapping conditions associated with the handler method - * @throws IllegalStateException if another method was already register under the same mapping + * @throws IllegalStateException if another method was already registered + * under the same mapping */ protected void registerHandlerMethod(Object handler, Method method, T mapping) { HandlerMethod handlerMethod; @@ -172,10 +177,13 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Get the URL paths associated with the given mapping. + * Extract and return the URL paths contained in a mapping. */ protected abstract Set getMappingPathPatterns(T mapping); + /** + * Look up a handler method for the given request. + */ @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); @@ -198,16 +206,15 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Looks up the best-matching {@link HandlerMethod} for the given request. - * - *

This implementation iterators through all handler methods, calls - * {@link #getMatchingMapping(Object, String, HttpServletRequest)} for each of them, - * sorts all matches via {@linkplain #getMappingComparator(String, HttpServletRequest)} , and returns the - * top match, if any. If no matches are found, {@link #handleNoMatch(Set, HttpServletRequest)} is invoked. - * - * @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 + * Look up the best-matching handler method for the current request. + * If multiple matches are found, the best match is selected. + * + * @param lookupPath mapping lookup path within the current servlet mapping + * @param request the current request + * @return the best-matching handler method, or {@code null} if no match + * + * @see #handleMatch(Object, String, HttpServletRequest) + * @see #handleNoMatch(Set, String, HttpServletRequest) */ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List mappings = urlMap.get(lookupPath); @@ -253,11 +260,27 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * Invoked when a request has been matched to a mapping. + * Check if a mapping matches the current request and return a (potentially + * new) mapping with conditions relevant to the current request. * - * @param mapping the mapping selected for the request returned by - * {@link #getMatchingMapping(Object, String, HttpServletRequest)}. - * @param lookupPath mapping lookup path within the current servlet mapping if applicable + * @param mapping the mapping to get a match for + * @param request the current HTTP servlet request + * @return the match, or {@code null} if the mapping doesn't match + */ + protected abstract T getMatchingMapping(T mapping, HttpServletRequest request); + + /** + * Return a comparator for sorting matching mappings. + * The returned comparator should sort 'better' matches higher. + * @param request the current request + * @return the comparator, never {@code null} + */ + protected abstract Comparator getMappingComparator(HttpServletRequest request); + + /** + * Invoked when a matching mapping is found. + * @param mapping the matching mapping + * @param lookupPath mapping lookup path within the current servlet mapping * @param request the current request */ protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) { @@ -265,30 +288,10 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } /** - * 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 mapping the mapping to get a match for - * @param request the current HTTP servlet request - * @return a matching mapping, or {@code null} if the given mapping does not match the request - */ - protected abstract T getMatchingMapping(T mapping, HttpServletRequest request); - - /** - * Returns a comparator to sort request mappings with. The returned comparator should sort 'better' matches higher. - * - * @param request the current HTTP servlet request - * @return the comparator - */ - protected abstract Comparator getMappingComparator(HttpServletRequest request); - - /** - * Invoked when no match was found. Default implementation returns {@code null}. - * - * @param mappings all registered request mappings - * @param lookupPath mapping lookup path within the current servlet mapping if applicable - * @param request the current HTTP request + * Invoked when no matching mapping is not found. + * @param mappings all registered mappings + * @param lookupPath mapping lookup path within the current servlet mapping + * @param request the current request * @throws ServletException in case of errors */ protected HandlerMethod handleNoMatch(Set mappings, String lookupPath, HttpServletRequest request) @@ -296,6 +299,9 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap return null; } + /** + * A temporary container for a mapping matched to a request. + */ private class Match { private final T mapping; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java index d6c74efa5f7..98385ae9115 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java @@ -29,8 +29,9 @@ import java.util.Iterator; public abstract class AbstractRequestCondition> implements RequestCondition { /** - * Returns the discrete items a request condition is composed of such as URL patterns, - * HTTP request methods, parameter expressions, etc. + * Return the discrete items a request condition is composed of. + * For example URL patterns, HTTP request methods, param expressions, etc. + * @return a collection of objects, never {@code null} */ protected abstract Collection getContent(); @@ -66,8 +67,8 @@ public abstract class AbstractRequestConditionAn implementation of {@code RequestCondition} itself, a + * {@code RequestConditionHolder} decorates the held request condition allowing + * it to be combined and compared with other custom request conditions while + * ensuring type and null safety. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public final class RequestConditionHolder extends AbstractRequestCondition { + + @SuppressWarnings("rawtypes") + private final RequestCondition condition; + + /** + * Create a new holder to wrap the given request condition. + * @param requestCondition the condition to hold, may be {@code null} + */ + public RequestConditionHolder(RequestCondition requestCondition) { + this.condition = requestCondition; + } + + /** + * Return the held request condition, or {@code null} if not holding one. + */ + public RequestCondition getCondition() { + return condition; + } + + @Override + protected Collection getContent() { + return condition != null ? Collections.singleton(condition) : Collections.emptyList(); + } + + @Override + protected String getToStringInfix() { + return " "; + } + + /** + * Combine the request conditions held by the two RequestConditionHolder + * instances after making sure the conditions are of the same type. + * Or if one holder is empty, the other holder is returned. + */ + @SuppressWarnings("unchecked") + public RequestConditionHolder combine(RequestConditionHolder other) { + if (condition == null && other.condition == null) { + return this; + } + else if (condition == null) { + return other; + } + else if (other.condition == null) { + return this; + } + else { + assertIsCompatible(other); + RequestCondition combined = (RequestCondition) condition.combine(other.condition); + return new RequestConditionHolder(combined); + } + } + + /** + * Ensure the held request conditions are of the same type. + */ + private void assertIsCompatible(RequestConditionHolder other) { + Class clazz = condition.getClass(); + Class otherClazz = other.condition.getClass(); + if (!clazz.equals(otherClazz)) { + throw new ClassCastException("Incompatible request conditions: " + clazz + " and " + otherClazz); + } + } + + /** + * Get the matching condition for the held request condition wrap it in a + * new RequestConditionHolder instance. Or otherwise if this is an empty + * holder, return the same holder instance. + */ + public RequestConditionHolder getMatchingCondition(HttpServletRequest request) { + if (condition == null) { + return this; + } + RequestCondition match = (RequestCondition) condition.getMatchingCondition(request); + return new RequestConditionHolder(match); + } + + /** + * Compare the request conditions held by the two RequestConditionHolder + * instances after making sure the conditions are of the same type. + * Or if one holder is empty, the other holder is preferred. + */ + @SuppressWarnings("unchecked") + public int compareTo(RequestConditionHolder other, HttpServletRequest request) { + if (condition == null && other.condition == null) { + return 0; + } + else if (condition == null) { + return 1; + } + else if (other.condition == null) { + return -1; + } + else { + assertIsCompatible(other); + return condition.compareTo(other.condition, request); + } + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/CustomRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/CustomRequestCondition.java deleted file mode 100644 index 62e05e3fd31..00000000000 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/CustomRequestCondition.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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.Collection; -import java.util.Collections; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition; -import org.springframework.web.servlet.mvc.condition.RequestCondition; - -/** - * Wraps and delegates operations to a {@link RequestCondition} whose type is not known ahead of time. The main goal - * is to provide type-safe and null-safe way of comparing and combining optional custom {@link RequestCondition}s. - * - * @author Rossen Stoyanchev - * @since 3.1 - */ -final class CustomRequestCondition extends AbstractRequestCondition { - - @SuppressWarnings("rawtypes") - private final RequestCondition customCondition; - - /** - * Creates an instance that wraps the given custom request condition. - * @param requestCondition the custom request condition - */ - CustomRequestCondition(RequestCondition requestCondition) { - this.customCondition = requestCondition; - } - - /** - * Creates an instance that does not wrap any custom request condition. - */ - CustomRequestCondition() { - this(null); - } - - public RequestCondition getCondition() { - return customCondition; - } - - @Override - protected Collection getContent() { - return customCondition != null ? Collections.singleton(customCondition) : Collections.emptyList(); - } - - @Override - protected String getToStringInfix() { - return " "; - } - - /** - * Delegates the operation to the wrapped custom request conditions. May also return "this" instance - * if the "other" does not contain a custom request condition and vice versa. - */ - @SuppressWarnings("unchecked") - public CustomRequestCondition combine(CustomRequestCondition other) { - if (customCondition == null && other.customCondition == null) { - return this; - } - else if (customCondition == null) { - return other; - } - else if (other.customCondition == null) { - return this; - } - else { - assertCompatible(other); - RequestCondition combined = (RequestCondition) customCondition.combine(other.customCondition); - return new CustomRequestCondition(combined); - } - } - - private void assertCompatible(CustomRequestCondition other) { - if (customCondition != null && other.customCondition != null) { - Class clazz = customCondition.getClass(); - Class otherClazz = other.customCondition.getClass(); - if (!clazz.equals(otherClazz)) { - throw new ClassCastException("Incompatible custom request conditions: " + clazz + ", " + otherClazz); - } - } - } - - /** - * Delegates the operation to the wrapped custom request condition; or otherwise returns the same - * instance if there is no custom request condition. - */ - public CustomRequestCondition getMatchingCondition(HttpServletRequest request) { - if (customCondition == null) { - return this; - } - RequestCondition match = (RequestCondition) customCondition.getMatchingCondition(request); - return new CustomRequestCondition(match); - } - - /** - * Delegates the operation to the wrapped custom request conditions after checking for the presence - * custom request conditions and asserting type safety. The presence of a custom request condition - * in one instance but not the other will cause it to be selected, and vice versa. - */ - @SuppressWarnings("unchecked") - public int compareTo(CustomRequestCondition other, HttpServletRequest request) { - if (customCondition == null && other.customCondition == null) { - return 0; - } - else if (customCondition == null) { - return 1; - } - else if (other.customCondition == null) { - return -1; - } - else { - assertCompatible(other); - return customCondition.compareTo(other.customCondition, request); - } - } - -} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java index 448a6e3563f..26ffe935792 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java @@ -24,26 +24,26 @@ import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestCondition; +import org.springframework.web.servlet.mvc.condition.RequestConditionHolder; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; /** - * A RequestMapingInfo encapsulates and operates on the following request mapping conditions: - *

- * - * Optionally a custom request condition may be provided. + * Encapsulates the following request mapping conditions: + *
    + *
  1. {@link PatternsRequestCondition} + *
  2. {@link RequestMethodsRequestCondition} + *
  3. {@link ParamsRequestCondition} + *
  4. {@link HeadersRequestCondition} + *
  5. {@link ConsumesRequestCondition} + *
  6. {@link ProducesRequestCondition} + *
  7. {@code RequestCondition} (optional, custom request condition) + *
* * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */ -public final class RequestMappingInfo { +public final class RequestMappingInfo implements RequestCondition { private final PatternsRequestCondition patternsCondition; @@ -57,12 +57,12 @@ public final class RequestMappingInfo { private final ProducesRequestCondition producesCondition; - private final CustomRequestCondition customCondition; + private final RequestConditionHolder customConditionHolder; private int hash; /** - * Creates a new {@code RequestMappingInfo} instance. + * Creates a new instance with the given request conditions. */ public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods, @@ -77,11 +77,11 @@ public final class RequestMappingInfo { this.headersCondition = headers != null ? headers : new HeadersRequestCondition(); this.consumesCondition = consumes != null ? consumes : new ConsumesRequestCondition(); this.producesCondition = produces != null ? produces : new ProducesRequestCondition(); - this.customCondition = custom != null ? new CustomRequestCondition(custom) : new CustomRequestCondition(); + this.customConditionHolder = new RequestConditionHolder(custom); } /** - * Re-create a {@link RequestMappingInfo} with the given custom {@link RequestCondition}. + * Re-create a RequestMappingInfo with the given custom request condition. */ public RequestMappingInfo(RequestMappingInfo info, RequestCondition customRequestCondition) { this(info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition, @@ -140,7 +140,7 @@ public final class RequestMappingInfo { * Returns the "custom" condition of this {@link RequestMappingInfo}; or {@code null} */ public RequestCondition getCustomCondition() { - return customCondition.getCondition(); + return customConditionHolder.getCondition(); } /** @@ -155,7 +155,7 @@ public final class RequestMappingInfo { HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); - CustomRequestCondition custom = this.customCondition.combine(other.customCondition); + RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition()); } @@ -167,7 +167,7 @@ public final class RequestMappingInfo { * the current request, sorted with best matching patterns on top. * @return a new instance in case all conditions match; or {@code null} otherwise */ - public RequestMappingInfo getMatchingInfo(HttpServletRequest request) { + public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request); ParamsRequestCondition params = paramsCondition.getMatchingCondition(request); HeadersRequestCondition headers = headersCondition.getMatchingCondition(request); @@ -183,7 +183,7 @@ public final class RequestMappingInfo { return null; } - CustomRequestCondition custom = customCondition.getMatchingCondition(request); + RequestConditionHolder custom = customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } @@ -194,7 +194,7 @@ public final class RequestMappingInfo { /** * Compares "this" info (i.e. the current instance) with another info in the context of a request. *

Note: it is assumed both instances have been obtained via - * {@link #getMatchingInfo(HttpServletRequest)} to ensure they have conditions with + * {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with * content relevant to current request. */ public int compareTo(RequestMappingInfo other, HttpServletRequest request) { @@ -222,7 +222,7 @@ public final class RequestMappingInfo { if (result != 0) { return result; } - result = customCondition.compareTo(other.customCondition, request); + result = customConditionHolder.compareTo(other.customConditionHolder, request); if (result != 0) { return result; } @@ -242,7 +242,7 @@ public final class RequestMappingInfo { this.headersCondition.equals(other.headersCondition) && this.consumesCondition.equals(other.consumesCondition) && this.producesCondition.equals(other.producesCondition) && - this.customCondition.equals(other.customCondition)); + this.customConditionHolder.equals(other.customConditionHolder)); } return false; } @@ -257,7 +257,7 @@ public final class RequestMappingInfo { result = 31 * result + headersCondition.hashCode(); result = 31 * result + consumesCondition.hashCode(); result = 31 * result + producesCondition.hashCode(); - result = 31 * result + customCondition.hashCode(); + result = 31 * result + customConditionHolder.hashCode(); hash = result; } return result; @@ -272,7 +272,7 @@ public final class RequestMappingInfo { builder.append(",headers=").append(headersCondition); builder.append(",consumes=").append(consumesCondition); builder.append(",produces=").append(producesCondition); - builder.append(",custom=").append(customCondition); + builder.append(",custom=").append(customConditionHolder); builder.append('}'); return builder.toString(); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index 21a9d5993d0..cf4c41e2b33 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -36,8 +36,8 @@ 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. + * Abstract base class for classes for which {@link RequestMappingInfo} defines + * the mapping between a request and a handler method. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -54,19 +54,18 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe } /** - * Checks if the given RequestMappingInfo matches the current request and returns a potentially new - * RequestMappingInfo instances tailored to the current request, for example containing the subset - * of URL patterns or media types that match the request. - * - * @returns a RequestMappingInfo instance in case of a match; or {@code null} in case of no match. + * Check if the given RequestMappingInfo matches the current request and + * return a (potentially new) instance with conditions that match the + * current request -- for example with a subset of URL patterns. + * @returns an info in case of a match; or {@code null} otherwise. */ @Override protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) { - return info.getMatchingInfo(request); + return info.getMatchingCondition(request); } /** - * Returns a {@link Comparator} for sorting {@link RequestMappingInfo} in the context of the given request. + * Provide a Comparator to sort RequestMappingInfos matched to a request. */ @Override protected Comparator getMappingComparator(final HttpServletRequest request) { @@ -78,8 +77,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe } /** - * Exposes URI template variables and producible media types as request attributes. - * + * Expose URI template variables and producible media types in the request. * @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE * @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE */ @@ -98,14 +96,15 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe } /** - * Iterates all {@link RequestMappingInfo}s looking for mappings that match by URL but not by HTTP method. - * + * Iterate all RequestMappingInfos once again, look if any match by URL at + * least and raise exceptions accordingly. + * * @throws HttpRequestMethodNotSupportedException * if there are matches by URL but not by HTTP method * @throws HttpMediaTypeNotAcceptableException - * if there are matches by URL but the consumable media types don't match the 'Content-Type' header + * if there are matches by URL but not by consumable media types * @throws HttpMediaTypeNotAcceptableException - * if there are matches by URL but the producible media types don't match the 'Accept' header + * if there are matches by URL but not by producible media types */ @Override protected HandlerMethod handleNoMatch(Set requestMappingInfos, diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index a2f0dec7c14..95478692581 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -32,8 +32,9 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; /** - * A sub-class of {@link RequestMappingInfoHandlerMapping} that prepares {@link RequestMappingInfo}s - * from @{@link RequestMapping} annotations on @{@link Controller} classes. + * Creates {@link RequestMappingInfo} instances from type and method-level + * {@link RequestMapping @RequestMapping} annotations in + * {@link Controller @Controller} classes. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -44,25 +45,24 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi private boolean useSuffixPatternMatch = true; /** - * Set whether to use a suffix pattern match (".*") when matching patterns to URLs. - * If enabled a method mapped to "/users" will also match to "/users.*". - *

Default is "true". Turn this convention off if you intend to interpret path mappings strictly. + * Whether to use suffix pattern match (".*") when matching patterns to + * requests. If enabled a method mapped to "/users" also matches to + * "/users.*". The default value is "true". */ public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { this.useSuffixPatternMatch = useSuffixPatternMatch; } /** - * Returns the value of the useSuffixPatternMatch flag, see {@link #setUseSuffixPatternMatch(boolean)}. + * Whether to use suffix pattern matching. */ - public boolean isUseSuffixPatternMatch() { - return useSuffixPatternMatch; + public boolean useSuffixPatternMatch() { + return this.useSuffixPatternMatch; } /** * {@inheritDoc} - * The default implementation checks for the presence of a type-level {@link Controller} - * annotation via {@link AnnotationUtils#findAnnotation(Class, Class)}. + * Expects a handler to have a type-level @{@link Controller} annotation. */ @Override protected boolean isHandler(Class beanType) { @@ -70,58 +70,68 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } /** - * Determines if the given method is a handler method and creates a {@link RequestMappingInfo} for it. + * Uses method and type-level @{@link RequestMapping} annotations to create + * the RequestMappingInfo. * - *

The default implementation expects the presence of a method-level @{@link RequestMapping} - * annotation via {@link AnnotationUtils#findAnnotation(Class, Class)}. The presence of - * type-level annotations is also checked and if present a RequestMappingInfo is created for each type- - * and method-level annotations and combined via {@link RequestMappingInfo#combine(RequestMappingInfo)}. - * - * @param method the method to create a RequestMappingInfo for - * @param handlerType the actual handler type, possibly a sub-type of {@code method.getDeclaringClass()} - * @return the info, or {@code null} + * @return the created RequestMappingInfo, or {@code null} if the method + * does not have a {@code @RequestMapping} annotation. + * + * @see #getCustomMethodCondition(Method) + * @see #getCustomTypeCondition(Class) */ @Override protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { - RequestMapping methodAnnot = AnnotationUtils.findAnnotation(method, RequestMapping.class); - if (methodAnnot != null) { - RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); - RequestMappingInfo methodInfo = createRequestMappingInfo(methodAnnot, true, method, handlerType); - if (typeAnnot != null) { - RequestMappingInfo typeInfo = createRequestMappingInfo(typeAnnot, false, method, handlerType); - return typeInfo.combine(methodInfo); - } - else { - return methodInfo; + RequestMappingInfo info = null; + RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); + if (methodAnnotation != null) { + RequestCondition methodCondition = getCustomMethodCondition(method); + info = createRequestMappingInfo(methodAnnotation, methodCondition); + RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); + if (typeAnnotation != null) { + RequestCondition typeCondition = getCustomTypeCondition(handlerType); + info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); } } + return info; + } + + /** + * Provide a custom method-level request condition. + * The custom {@link RequestCondition} can be of any type so long as the + * same condition type is returned from all calls to this method in order + * to ensure custom request conditions can be combined and compared. + * @param method the handler method for which to create the condition + * @return the condition, or {@code null} + */ + protected RequestCondition getCustomMethodCondition(Method method) { + return null; + } + + /** + * Provide a custom type-level request condition. + * The custom {@link RequestCondition} can be of any type so long as the + * same condition type is returned from all calls to this method in order + * to ensure custom request conditions can be combined and compared. + * @param method the handler method for which to create the condition + * @return the condition, or {@code null} + */ + protected RequestCondition getCustomTypeCondition(Class handlerType) { return null; } /** - * Override this method to create a {@link RequestMappingInfo} from a @{@link RequestMapping} annotation. The main - * reason for doing so is to provide a custom {@link RequestCondition} to the RequestMappingInfo constructor. - * - *

This method is invoked both for type- and method-level @{@link RequestMapping} annotations. The resulting - * {@link RequestMappingInfo}s are combined via {@link RequestMappingInfo#combine(RequestMappingInfo)}. - * - * @param annot a type- or a method-level {@link RequestMapping} annotation - * @param isMethodAnnotation {@code true} if this is a method annotation; {@code false} if it is a type annotation - * @param method the method with which the created RequestMappingInfo will be combined - * @param handlerType the handler type - * @return a {@link RequestMappingInfo} instance; never {@code null} + * Created a RequestMappingInfo from a RequestMapping annotation. */ - protected RequestMappingInfo createRequestMappingInfo(RequestMapping annot, - boolean isMethodAnnotation, - Method method, - Class handlerType) { + private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition customCondition) { return new RequestMappingInfo( - new PatternsRequestCondition(annot.value(), getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch), - new RequestMethodsRequestCondition(annot.method()), - new ParamsRequestCondition(annot.params()), - new HeadersRequestCondition(annot.headers()), - new ConsumesRequestCondition(annot.consumes(), annot.headers()), - new ProducesRequestCondition(annot.produces(), annot.headers()), null); + new PatternsRequestCondition(annotation.value(), + getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch), + new RequestMethodsRequestCondition(annotation.method()), + new ParamsRequestCondition(annotation.params()), + new HeadersRequestCondition(annotation.headers()), + new ConsumesRequestCondition(annotation.consumes(), annotation.headers()), + new ProducesRequestCondition(annotation.produces(), annotation.headers()), + customCondition); } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/CustomRequestConditionTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolderTests.java similarity index 62% rename from org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/CustomRequestConditionTests.java rename to org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolderTests.java index 39cfb4dd145..6dc6e0d88d1 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/CustomRequestConditionTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolderTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.web.servlet.mvc.method; +package org.springframework.web.servlet.mvc.condition; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; @@ -26,36 +26,40 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition; import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition; +import org.springframework.web.servlet.mvc.condition.RequestConditionHolder; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; /** + * A test fixture for + * {code org.springframework.web.servlet.mvc.method.RequestConditionHolder} tests. + * * @author Rossen Stoyanchev */ -public class CustomRequestConditionTests { +public class RequestConditionHolderTests { @Test public void combineEmpty() { - CustomRequestCondition empty = new CustomRequestCondition(); - CustomRequestCondition custom = new CustomRequestCondition(new ParamsRequestCondition("name")); + RequestConditionHolder empty = new RequestConditionHolder(null); + RequestConditionHolder notEmpty = new RequestConditionHolder(new ParamsRequestCondition("name")); - assertSame(empty, empty.combine(new CustomRequestCondition())); - assertSame(custom, custom.combine(empty)); - assertSame(custom, empty.combine(custom)); + assertSame(empty, empty.combine(new RequestConditionHolder(null))); + assertSame(notEmpty, notEmpty.combine(empty)); + assertSame(notEmpty, empty.combine(notEmpty)); } @Test public void combine() { - CustomRequestCondition params1 = new CustomRequestCondition(new ParamsRequestCondition("name1")); - CustomRequestCondition params2 = new CustomRequestCondition(new ParamsRequestCondition("name2")); - CustomRequestCondition expected = new CustomRequestCondition(new ParamsRequestCondition("name1", "name2")); + RequestConditionHolder params1 = new RequestConditionHolder(new ParamsRequestCondition("name1")); + RequestConditionHolder params2 = new RequestConditionHolder(new ParamsRequestCondition("name2")); + RequestConditionHolder expected = new RequestConditionHolder(new ParamsRequestCondition("name1", "name2")); assertEquals(expected, params1.combine(params2)); } @Test(expected=ClassCastException.class) public void combineIncompatible() { - CustomRequestCondition params = new CustomRequestCondition(new ParamsRequestCondition("name")); - CustomRequestCondition headers = new CustomRequestCondition(new HeadersRequestCondition("name")); + RequestConditionHolder params = new RequestConditionHolder(new ParamsRequestCondition("name")); + RequestConditionHolder headers = new RequestConditionHolder(new HeadersRequestCondition("name")); params.combine(headers); } @@ -65,7 +69,7 @@ public class CustomRequestConditionTests { request.setParameter("name1", "value1"); RequestMethodsRequestCondition rm = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST); - CustomRequestCondition custom = new CustomRequestCondition(rm); + RequestConditionHolder custom = new RequestConditionHolder(rm); RequestMethodsRequestCondition expected = new RequestMethodsRequestCondition(RequestMethod.GET); assertEquals(expected, custom.getMatchingCondition(request).getCondition()); @@ -73,7 +77,7 @@ public class CustomRequestConditionTests { @Test public void matchEmpty() { - CustomRequestCondition empty = new CustomRequestCondition(); + RequestConditionHolder empty = new RequestConditionHolder(null); assertSame(empty, empty.getMatchingCondition(new MockHttpServletRequest())); } @@ -81,8 +85,8 @@ public class CustomRequestConditionTests { public void compare() { HttpServletRequest request = new MockHttpServletRequest(); - CustomRequestCondition params11 = new CustomRequestCondition(new ParamsRequestCondition("1")); - CustomRequestCondition params12 = new CustomRequestCondition(new ParamsRequestCondition("1", "2")); + RequestConditionHolder params11 = new RequestConditionHolder(new ParamsRequestCondition("1")); + RequestConditionHolder params12 = new RequestConditionHolder(new ParamsRequestCondition("1", "2")); assertEquals(1, params11.compareTo(params12, request)); assertEquals(-1, params12.compareTo(params11, request)); @@ -92,19 +96,19 @@ public class CustomRequestConditionTests { public void compareEmpty() { HttpServletRequest request = new MockHttpServletRequest(); - CustomRequestCondition empty = new CustomRequestCondition(); - CustomRequestCondition empty2 = new CustomRequestCondition(); - CustomRequestCondition custom = new CustomRequestCondition(new ParamsRequestCondition("name")); + RequestConditionHolder empty = new RequestConditionHolder(null); + RequestConditionHolder empty2 = new RequestConditionHolder(null); + RequestConditionHolder notEmpty = new RequestConditionHolder(new ParamsRequestCondition("name")); assertEquals(0, empty.compareTo(empty2, request)); - assertEquals(-1, custom.compareTo(empty, request)); - assertEquals(1, empty.compareTo(custom, request)); + assertEquals(-1, notEmpty.compareTo(empty, request)); + assertEquals(1, empty.compareTo(notEmpty, request)); } @Test(expected=ClassCastException.class) public void compareIncompatible() { - CustomRequestCondition params = new CustomRequestCondition(new ParamsRequestCondition("name")); - CustomRequestCondition headers = new CustomRequestCondition(new HeadersRequestCondition("name")); + RequestConditionHolder params = new RequestConditionHolder(new ParamsRequestCondition("name")); + RequestConditionHolder headers = new RequestConditionHolder(new HeadersRequestCondition("name")); params.compareTo(headers, new MockHttpServletRequest()); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java index 11e550d4d65..03cb119fc89 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java @@ -54,11 +54,10 @@ import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.util.UrlPathHelper; /** - * Test fixture with {@link RequestMappingHandlerMapping}. + * Test fixture with {@link RequestMappingInfoHandlerMapping}. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -275,6 +274,7 @@ public class RequestMappingInfoHandlerMappingTests { new ConsumesRequestCondition(annotation.consumes(), annotation.headers()), new ProducesRequestCondition(annotation.produces(), annotation.headers()), null); } + } } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java index 135786f264f..0c429e11af6 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java @@ -67,14 +67,14 @@ public class RequestMappingInfoTests { RequestMappingInfo expected = new RequestMappingInfo( new PatternsRequestCondition("/foo*"), null, null, null, null, null, null); - assertEquals(expected, info.getMatchingInfo(request)); + assertEquals(expected, info.getMatchingCondition(request)); info = new RequestMappingInfo( new PatternsRequestCondition("/**", "/foo*", "/foo"), null, null, null, null, null, null); expected = new RequestMappingInfo( new PatternsRequestCondition("/foo", "/foo*", "/**"), null, null, null, null, null, null); - assertEquals(expected, info.getMatchingInfo(request)); + assertEquals(expected, info.getMatchingCondition(request)); } @Test @@ -86,14 +86,14 @@ public class RequestMappingInfoTests { new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, new ParamsRequestCondition("foo=bar"), null, null, null, null); - RequestMappingInfo match = info.getMatchingInfo(request); + RequestMappingInfo match = info.getMatchingCondition(request); assertNotNull(match); info = new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, new ParamsRequestCondition("foo!=bar"), null, null, null, null); - match = info.getMatchingInfo(request); + match = info.getMatchingCondition(request); assertNull(match); } @@ -107,14 +107,14 @@ public class RequestMappingInfoTests { new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, new HeadersRequestCondition("foo=bar"), null, null, null); - RequestMappingInfo match = info.getMatchingInfo(request); + RequestMappingInfo match = info.getMatchingCondition(request); assertNotNull(match); info = new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, new HeadersRequestCondition("foo!=bar"), null, null, null); - match = info.getMatchingInfo(request); + match = info.getMatchingCondition(request); assertNull(match); } @@ -128,14 +128,14 @@ public class RequestMappingInfoTests { new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, null, new ConsumesRequestCondition("text/plain"), null, null); - RequestMappingInfo match = info.getMatchingInfo(request); + RequestMappingInfo match = info.getMatchingCondition(request); assertNotNull(match); info = new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, null, new ConsumesRequestCondition("application/xml"), null, null); - match = info.getMatchingInfo(request); + match = info.getMatchingCondition(request); assertNull(match); } @@ -149,14 +149,14 @@ public class RequestMappingInfoTests { new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, null, null, new ProducesRequestCondition("text/plain"), null); - RequestMappingInfo match = info.getMatchingInfo(request); + RequestMappingInfo match = info.getMatchingCondition(request); assertNotNull(match); info = new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, null, null, new ProducesRequestCondition("application/xml"), null); - match = info.getMatchingInfo(request); + match = info.getMatchingCondition(request); assertNull(match); } @@ -170,7 +170,7 @@ public class RequestMappingInfoTests { new RequestMappingInfo( new PatternsRequestCondition("/foo"), null, null, null, null, null, new ParamsRequestCondition("foo=bar")); - RequestMappingInfo match = info.getMatchingInfo(request); + RequestMappingInfo match = info.getMatchingCondition(request); assertNotNull(match); @@ -178,7 +178,7 @@ public class RequestMappingInfoTests { new PatternsRequestCondition("/foo"), null, new ParamsRequestCondition("foo!=bar"), null, null, null, new ParamsRequestCondition("foo!=bar")); - match = info.getMatchingInfo(request); + match = info.getMatchingCondition(request); assertNull(match); }