提交 4826cae0 编写于 作者: R Rossen Stoyanchev

SPR-7812 Add CustomRequestCondition

上级 7dd69329
......@@ -257,14 +257,14 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
List<Match> matches = new ArrayList<Match>();
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, lookupPath, request);
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, handlerMethods.get(mapping)));
}
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(lookupPath, request));
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
......@@ -309,20 +309,18 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* will contain only 1).
*
* @param mapping the mapping 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 a matching mapping, or {@code null} if the given mapping does not match the request
*/
protected abstract T getMatchingMapping(T mapping, String lookupPath, HttpServletRequest 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 lookupPath mapping lookup path within the current servlet mapping if applicable
* @param request the current HTTP servlet request
* @return the comparator
*/
protected abstract Comparator<T> getMappingComparator(String lookupPath, HttpServletRequest request);
protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
/**
* Invoked when no match was found. Default implementation returns {@code null}.
......
......@@ -18,17 +18,29 @@ package org.springframework.web.servlet.mvc.method;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.CustomRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
/**
* Contains request mapping conditions to be matched to a given request.
* A RequestMapingInfo encapsulates and operates on the following request mapping conditions:
* <ul>
* <li>{@link PatternsRequestCondition}</li>
* <li>{@link RequestMethodsRequestCondition}</li>
* <li>{@link ParamsRequestCondition}</li>
* <li>{@link HeadersRequestCondition}</li>
* <li>{@link ConsumesRequestCondition}</li>
* <li>{@link ProducesRequestCondition}</li>
* </ul>
*
* Optionally a custom request condition may also be provided by wrapping it in an instance
* of {@link CustomRequestCondition}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
......@@ -48,130 +60,183 @@ public final class RequestMappingInfo {
private final ProducesRequestCondition producesCondition;
private CustomRequestCondition customCondition = new CustomRequestCondition();
private int hash;
/**
* Creates a new {@code RequestMappingInfo} instance.
*/
public RequestMappingInfo(PatternsRequestCondition patternsCondition,
RequestMethodsRequestCondition methodsCondition,
ParamsRequestCondition paramsCondition,
HeadersRequestCondition headersCondition,
ConsumesRequestCondition consumesCondition,
ProducesRequestCondition producesCondition) {
this.patternsCondition = patternsCondition != null ? patternsCondition : new PatternsRequestCondition();
this.methodsCondition = methodsCondition != null ? methodsCondition : new RequestMethodsRequestCondition();
this.paramsCondition = paramsCondition != null ? paramsCondition : new ParamsRequestCondition();
this.headersCondition = headersCondition != null ? headersCondition : new HeadersRequestCondition();
this.consumesCondition = consumesCondition != null ? consumesCondition : new ConsumesRequestCondition();
this.producesCondition = producesCondition != null ? producesCondition : new ProducesRequestCondition();
public RequestMappingInfo(PatternsRequestCondition patterns,
RequestMethodsRequestCondition methods,
ParamsRequestCondition params,
HeadersRequestCondition headers,
ConsumesRequestCondition consumes,
ProducesRequestCondition produces) {
this(patterns, methods, params, headers, consumes, produces, null);
}
/**
* Creates a new {@code RequestMappingInfo} instance also providing a custom {@link RequestCondition}.
*/
public RequestMappingInfo(PatternsRequestCondition patterns,
RequestMethodsRequestCondition methods,
ParamsRequestCondition params,
HeadersRequestCondition headers,
ConsumesRequestCondition consumes,
ProducesRequestCondition produces,
CustomRequestCondition custom) {
this.patternsCondition = patterns != null ? patterns : new PatternsRequestCondition();
this.methodsCondition = methods != null ? methods : new RequestMethodsRequestCondition();
this.paramsCondition = params != null ? params : new ParamsRequestCondition();
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 ? custom : new CustomRequestCondition();
}
/**
* Package protected, used for testing.
* Package protected constructor for tests.
*/
RequestMappingInfo(String[] patterns, RequestMethod... methods) {
this(new PatternsRequestCondition(patterns), new RequestMethodsRequestCondition(methods), null, null, null, null);
}
/**
* Returns the patterns of this request mapping info.
* Returns the URL patterns of this request mapping info.
*/
public PatternsRequestCondition getPatternsCondition() {
return patternsCondition;
}
/**
* Returns the request method condition of this request mapping info.
* Returns the HTTP request methods of this {@link RequestMappingInfo}.
*/
public RequestMethodsRequestCondition getMethodsCondition() {
return methodsCondition;
}
/**
* Returns the request parameters condition of this request mapping info.
* Returns the "parameters" condition of this {@link RequestMappingInfo}.
*/
public ParamsRequestCondition getParamsCondition() {
return paramsCondition;
}
/**
* Returns the request headers condition of this request mapping info.
* Returns the "headers" condition of this {@link RequestMappingInfo}.
*/
public HeadersRequestCondition getHeadersCondition() {
return headersCondition;
}
/**
* Returns the request consumes condition of this request mapping info.
* Returns the "consumes" condition of this {@link RequestMappingInfo}.
*/
public ConsumesRequestCondition getConsumesCondition() {
return consumesCondition;
}
/**
* Returns the request produces condition of this request mapping info.
* Returns the "produces" condition of this {@link RequestMappingInfo}.
*/
public ProducesRequestCondition getProducesCondition() {
return producesCondition;
}
/**
* Combines this {@code RequestMappingInfo} with another as follows:
* <ul>
* <li>URL patterns:
* <ul>
* <li>If both have patterns combine them according to the rules of the given {@link PathMatcher}
* <li>If either contains patterns, but not both, use the available pattern
* <li>If neither contains patterns use ""
* </ul>
* <li>HTTP methods are combined as union of all HTTP methods listed in both keys.
* <li>Request parameters are combined as per {@link ParamsRequestCondition#combine(ParamsRequestCondition)}.
* <li>Request headers are combined as per {@link HeadersRequestCondition#combine(HeadersRequestCondition)}.
* <li>Consumes are combined as per {@link ConsumesRequestCondition#combine(ConsumesRequestCondition)}.
* </ul>
* @param methodKey the key to combine with
* @return a new request mapping info containing conditions from both keys
* Sets a custom request condition.
*/
public RequestMappingInfo combine(RequestMappingInfo methodKey) {
PatternsRequestCondition patterns = this.patternsCondition.combine(methodKey.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(methodKey.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(methodKey.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(methodKey.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(methodKey.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(methodKey.producesCondition);
public void setCustomCondition(CustomRequestCondition customCondition) {
this.customCondition = customCondition;
}
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces);
/**
* Combines "this" request mapping info (i.e. the current instance) with another request mapping info instance.
* <p>Example: combine type- and method-level request mappings.
* @return a new request mapping info instance; never {@code null}
*/
public RequestMappingInfo combine(RequestMappingInfo other) {
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
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);
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom);
}
/**
* Returns a new {@code RequestMappingInfo} with conditions relevant to the current request.
* For example the list of URL path patterns is trimmed to contain the patterns that match the URL.
* @param request the current request
* @return a new request mapping info that contains all matching attributes, or {@code null} if not all conditions match
* Checks if all conditions in this request mapping info match the provided request and returns
* a potentially new request mapping info with conditions tailored to the current request.
* <p>For example the returned instance may contain the subset of URL patterns that match to
* 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 getMatchingRequestMapping(HttpServletRequest request) {
RequestMethodsRequestCondition matchingMethod = methodsCondition.getMatchingCondition(request);
ParamsRequestCondition matchingParams = paramsCondition.getMatchingCondition(request);
HeadersRequestCondition matchingHeaders = headersCondition.getMatchingCondition(request);
ConsumesRequestCondition matchingConsumes = consumesCondition.getMatchingCondition(request);
ProducesRequestCondition matchingProduces = producesCondition.getMatchingCondition(request);
if (matchingMethod == null || matchingParams == null || matchingHeaders == null ||
matchingConsumes == null || matchingProduces == null) {
public RequestMappingInfo getMatchingRequestMappingInfo(HttpServletRequest request) {
RequestMethodsRequestCondition methods = methodsCondition.getMatchingCondition(request);
ParamsRequestCondition params = paramsCondition.getMatchingCondition(request);
HeadersRequestCondition headers = headersCondition.getMatchingCondition(request);
ConsumesRequestCondition consumes = consumesCondition.getMatchingCondition(request);
ProducesRequestCondition produces = producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
return null;
}
PatternsRequestCondition matchingPatterns = patternsCondition.getMatchingCondition(request);
if (matchingPatterns != null) {
return new RequestMappingInfo(matchingPatterns, matchingMethod,
matchingParams, matchingHeaders, matchingConsumes,
matchingProduces);
PatternsRequestCondition patterns = patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
CustomRequestCondition custom = customCondition.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom);
}
/**
* Compares "this" info (i.e. the current instance) with another info in the context of a request.
* <p>Note: it is assumed both instances have been obtained via
* {@link #getMatchingRequestMappingInfo(HttpServletRequest)} to ensure they have conditions with
* content relevant to current request.
*/
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
int result = patternsCondition.compareTo(other.getPatternsCondition(), request);
if (result != 0) {
return result;
}
result = paramsCondition.compareTo(other.getParamsCondition(), request);
if (result != 0) {
return result;
}
result = headersCondition.compareTo(other.getHeadersCondition(), request);
if (result != 0) {
return result;
}
result = consumesCondition.compareTo(other.getConsumesCondition(), request);
if (result != 0) {
return result;
}
result = producesCondition.compareTo(other.getProducesCondition(), request);
if (result != 0) {
return result;
}
result = methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
}
result = customCondition.compareTo(other.customCondition, request);
if (result != 0) {
return result;
}
return 0;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
......@@ -184,7 +249,8 @@ public final class RequestMappingInfo {
this.paramsCondition.equals(other.paramsCondition) &&
this.headersCondition.equals(other.headersCondition) &&
this.consumesCondition.equals(other.consumesCondition) &&
this.producesCondition.equals(other.producesCondition));
this.producesCondition.equals(other.producesCondition) &&
this.customCondition.equals(other.customCondition));
}
return false;
}
......@@ -199,6 +265,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();
hash = result;
}
return result;
......@@ -213,6 +280,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('}');
return builder.toString();
}
......
......@@ -27,7 +27,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
......@@ -53,34 +52,47 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
while (mappings.size() > 1) {
RequestMappingInfo mapping = mappings.remove(0);
for (RequestMappingInfo otherMapping : mappings) {
// further validate mapping conditions
// TODO: further validate mapping conditions
}
}
}
/**
* Get the URL paths associated with this {@link RequestMappingInfo}.
*/
@Override
protected Set<String> getMappingPaths(RequestMappingInfo mapping) {
return mapping.getPatternsCondition().getPatterns();
Set<String> paths = new HashSet<String>();
for (String pattern : mapping.getPatternsCondition().getPatterns()) {
if (!getPathMatcher().isPattern(pattern)) {
paths.add(pattern);
}
}
return paths;
}
/**
* Returns a new {@link RequestMappingInfo} with attributes matching to the current request or {@code null}.
* 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.
*
* @see RequestMappingInfo#getMatchingRequestMapping(String, HttpServletRequest, PathMatcher)
* @returns a RequestMappingInfo instance in case of a match; or {@code null} in case of no match.
*/
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo mapping,
String lookupPath,
HttpServletRequest request) {
return mapping.getMatchingRequestMapping(request);
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo mapping, HttpServletRequest request) {
return mapping.getMatchingRequestMappingInfo(request);
}
/**
* Returns a {@link Comparator} that can be used to sort and select the best matching {@link RequestMappingInfo}.
* Returns a {@link Comparator} for sorting {@link RequestMappingInfo} in the context of the given request.
*/
@Override
protected Comparator<RequestMappingInfo> getMappingComparator(String lookupPath, HttpServletRequest request) {
return new RequestMappingInfoComparator(request);
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
return new Comparator<RequestMappingInfo>() {
public int compare(RequestMappingInfo info, RequestMappingInfo otherInfo) {
return info.compareTo(otherInfo, request);
}
};
}
/**
......@@ -106,7 +118,12 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
/**
* Iterates all {@link RequestMappingInfo}s looking for mappings that match by URL but not by HTTP method.
*
* @throws HttpRequestMethodNotSupportedException if there are matches by URL but not by HTTP method
* @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
* @throws HttpMediaTypeNotAcceptableException
* if there are matches by URL but the producible media types don't match the 'Accept' header
*/
@Override
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
......@@ -116,8 +133,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
Set<MediaType> consumableMediaTypes = new HashSet<MediaType>();
Set<MediaType> producibleMediaTypes = new HashSet<MediaType>();
for (RequestMappingInfo info : requestMappingInfos) {
for (String pattern : info.getPatternsCondition().getPatterns()) {
if (getPathMatcher().match(pattern, lookupPath)) {
if (info.getPatternsCondition().getMatchingCondition(request) != null) {
if (info.getMethodsCondition().getMatchingCondition(request) == null) {
for (RequestMethod method : info.getMethodsCondition().getMethods()) {
allowedMethods.add(method.name());
......@@ -131,7 +147,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
}
}
}
if (!allowedMethods.isEmpty()) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
}
......@@ -150,45 +165,4 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
}
/**
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context
* of a specific request. For example only a subset of URL patterns may apply to the current request.
*/
private class RequestMappingInfoComparator implements Comparator<RequestMappingInfo> {
private final HttpServletRequest request;
public RequestMappingInfoComparator(HttpServletRequest request) {
this.request = request;
}
public int compare(RequestMappingInfo mapping, RequestMappingInfo otherMapping) {
int result = mapping.getPatternsCondition().compareTo(otherMapping.getPatternsCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getParamsCondition().compareTo(otherMapping.getParamsCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getHeadersCondition().compareTo(otherMapping.getHeadersCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getConsumesCondition().compareTo(otherMapping.getConsumesCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getProducesCondition().compareTo(otherMapping.getProducesCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getMethodsCondition().compareTo(otherMapping.getMethodsCondition(), request);
if (result != 0) {
return result;
}
return 0;
}
}
}
......@@ -37,7 +37,7 @@ import org.springframework.web.servlet.mvc.method.condition.RequestMethodsReques
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1.0
* @since 3.1
*/
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
......@@ -62,20 +62,18 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (annotation != null) {
RequestMappingInfo methodMapping = createFromRequestMapping(annotation);
RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnot != null) {
RequestMappingInfo typeMapping = createFromRequestMapping(typeAnnot);
return typeMapping.combine(methodMapping);
}
else {
return methodMapping;
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation == null) {
return null;
}
RequestMappingInfo methodInfo = createFromRequestMapping(methodAnnotation);
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
RequestMappingInfo typeInfo = createFromRequestMapping(typeAnnotation);
return typeInfo.combine(methodInfo);
}
else {
return null;
return methodInfo;
}
}
......
......@@ -25,11 +25,11 @@ import java.util.Iterator;
* @author Rossen Stoyanchev
* @since 3.1
*/
abstract class RequestConditionSupport<This extends RequestConditionSupport<This>> implements RequestCondition<This> {
abstract class AbstractRequestCondition<T extends AbstractRequestCondition<T>> implements RequestCondition<T> {
/**
* Returns the individual expressions a request condition is composed of such as
* URL patterns, HTTP request methods, parameter expressions, etc.
* Returns the discrete expressions a request condition is composed of such as URL patterns,
* HTTP request methods, parameter expressions, etc.
*/
protected abstract Collection<?> getContent();
......@@ -39,7 +39,7 @@ abstract class RequestConditionSupport<This extends RequestConditionSupport<This
return true;
}
if (o != null && getClass().equals(o.getClass())) {
RequestConditionSupport<?> other = (RequestConditionSupport<?>) o;
AbstractRequestCondition<?> other = (AbstractRequestCondition<?>) o;
return getContent().equals(other.getContent());
}
return false;
......@@ -57,12 +57,7 @@ abstract class RequestConditionSupport<This extends RequestConditionSupport<This
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
if (isLogicalConjunction()) {
builder.append(" && ");
}
else {
builder.append(" || ");
}
builder.append(getToStringInfix());
}
}
builder.append("]");
......@@ -73,6 +68,6 @@ abstract class RequestConditionSupport<This extends RequestConditionSupport<This
* Returns {@code true} if the individual expressions of the condition are combined via logical
* conjunction (" && "); or {@code false} otherwise.
*/
protected abstract boolean isLogicalConjunction();
protected abstract String getToStringInfix();
}
......@@ -44,13 +44,13 @@ import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondit
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ConsumesRequestCondition extends RequestConditionSupport<ConsumesRequestCondition> {
public class ConsumesRequestCondition extends AbstractRequestCondition<ConsumesRequestCondition> {
private final List<ConsumeMediaTypeExpression> expressions;
/**
* Creates a {@link ConsumesRequestCondition} with the given consumable media type expressions.
* @param consumes the expressions to parse; if 0 the condition matches to every request
* @param consumes the expressions to parse; if 0, the condition matches to every request
*/
public ConsumesRequestCondition(String... consumes) {
this(consumes, null);
......@@ -60,8 +60,8 @@ public class ConsumesRequestCondition extends RequestConditionSupport<ConsumesRe
* Creates a {@link ConsumesRequestCondition} with the given header and consumes expressions.
* In addition to consume expressions, {@code "Content-Type"} header expressions are extracted
* and treated as consumable media type expressions.
* @param consumes the consumes expressions to parse; 0 matches to all requests
* @param headers the header expression to parse; 0 matches to all requests
* @param consumes the consumes expressions to parse; if 0, the condition matches to all requests
* @param headers the header expression to parse; if 0, the condition matches to all requests
*/
public ConsumesRequestCondition(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
......@@ -119,12 +119,12 @@ public class ConsumesRequestCondition extends RequestConditionSupport<ConsumesRe
}
@Override
protected boolean isLogicalConjunction() {
return false;
protected String getToStringInfix() {
return " || ";
}
/**
* Returns the "other" instance as long as it contains any expressions; or "this" instance otherwise.
* Returns the "other" instance provided it contains expressions; returns "this" instance otherwise.
* In other words "other" takes precedence over "this" as long as it has expressions.
* <p>Example: method-level "consumes" overrides type-level "consumes" condition.
*/
......@@ -133,13 +133,13 @@ public class ConsumesRequestCondition extends RequestConditionSupport<ConsumesRe
}
/**
* Checks if any of the consumable media type expressions match the given request and returns an instance that
* is guaranteed to contain matching media type expressions only.
* Checks if any of the consumable media type expressions match the given request and returns an
* instance that is guaranteed to contain matching media type expressions only.
*
* @param request the current request
*
* @return the same instance if the condition contains no expressions;
* or a new condition with matching expressions; or {@code null} if no expressions match.
* or a new condition with matching expressions only; or {@code null} if no expressions match.
*/
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
......
/*
* 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.condition;
import java.util.Collection;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
/**
* Wraps and delegates operations to a custom {@link RequestCondition} whose type is not known and even its
* presence is not guaranteed ahead of time. The main purpose of this class is to ensure a type-safe and
* null-safe way of combining and comparing custom request conditions.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class CustomRequestCondition extends AbstractRequestCondition<CustomRequestCondition> {
@SuppressWarnings("rawtypes")
private final RequestCondition customCondition;
/**
* Creates a {@link CustomRequestCondition} that wraps the given {@link RequestCondition} instance.
* @param requestCondition the custom request condition to wrap
*/
public CustomRequestCondition(RequestCondition<?> requestCondition) {
this.customCondition = requestCondition;
}
/**
* Creates an empty {@link CustomRequestCondition}.
*/
public CustomRequestCondition() {
this(null);
}
public RequestCondition<?> getRequestCondition() {
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);
}
}
}
......@@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HeadersRequestCondition extends RequestConditionSupport<HeadersRequestCondition> {
public class HeadersRequestCondition extends AbstractRequestCondition<HeadersRequestCondition> {
private final Set<HeaderExpression> expressions;
......@@ -50,7 +50,7 @@ public class HeadersRequestCondition extends RequestConditionSupport<HeadersRequ
* Those should be converted and used as "produces" and "consumes" conditions instead.
* See the constructors for {@link ProducesRequestCondition} and {@link ConsumesRequestCondition}.
*
* @param headers 0 or more header expressions; if 0 the condition will match to every request.
* @param headers 0 or more header expressions; if 0, the condition will match to every request.
*/
public HeadersRequestCondition(String... headers) {
this(parseExpressions(headers));
......@@ -80,8 +80,8 @@ public class HeadersRequestCondition extends RequestConditionSupport<HeadersRequ
}
@Override
protected boolean isLogicalConjunction() {
return true;
protected String getToStringInfix() {
return " && ";
}
/**
......
......@@ -36,14 +36,14 @@ import org.springframework.web.util.WebUtils;
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ParamsRequestCondition extends RequestConditionSupport<ParamsRequestCondition> {
public class ParamsRequestCondition extends AbstractRequestCondition<ParamsRequestCondition> {
private final Set<ParamExpression> expressions;
/**
* Create a {@link ParamsRequestCondition} with the given param expressions.
*
* @param params 0 or more param expressions; if 0 the condition will match to every request.
* @param params 0 or more param expressions; if 0, the condition will match to every request.
*/
public ParamsRequestCondition(String... params) {
this(parseExpressions(params));
......@@ -69,8 +69,8 @@ public class ParamsRequestCondition extends RequestConditionSupport<ParamsReques
}
@Override
protected boolean isLogicalConjunction() {
return true;
protected String getToStringInfix() {
return " && ";
}
/**
......
......@@ -41,7 +41,7 @@ import org.springframework.web.util.UrlPathHelper;
* @author Rossen Stoyanchev
* @since 3.1
*/
public class PatternsRequestCondition extends RequestConditionSupport<PatternsRequestCondition> {
public class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
private final Set<String> patterns;
......@@ -62,7 +62,7 @@ public class PatternsRequestCondition extends RequestConditionSupport<PatternsRe
* Creates a new {@link PatternsRequestCondition} with the given URL patterns.
* Each pattern that is not empty and does not start with "/" is prepended with "/".
*
* @param patterns the URL patterns to use; if 0 the condition will match to every request.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
* @param pathMatcher a {@link PathMatcher} for pattern path matching
*/
......@@ -107,8 +107,8 @@ public class PatternsRequestCondition extends RequestConditionSupport<PatternsRe
}
@Override
protected boolean isLogicalConjunction() {
return false;
protected String getToStringInfix() {
return " || ";
}
/**
......
......@@ -44,7 +44,7 @@ import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondit
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ProducesRequestCondition extends RequestConditionSupport<ProducesRequestCondition> {
public class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
private final List<ProduceMediaTypeExpression> expressions;
......@@ -60,8 +60,8 @@ public class ProducesRequestCondition extends RequestConditionSupport<ProducesRe
* Creates a {@link ProducesRequestCondition} with the given header and produces expressions.
* In addition to produces expressions, {@code "Accept"} header expressions are extracted and treated as
* producible media type expressions.
* @param produces the produces expressions to parse
* @param headers the header expression to parse
* @param produces the produces expressions to parse; if 0, the condition matches to all requests
* @param headers the header expression to parse; if 0, the condition matches to all requests
*/
public ProducesRequestCondition(String[] produces, String[] headers) {
this(parseExpressions(produces, headers));
......@@ -119,8 +119,8 @@ public class ProducesRequestCondition extends RequestConditionSupport<ProducesRe
}
@Override
protected boolean isLogicalConjunction() {
return false;
protected String getToStringInfix() {
return " || ";
}
/**
......
......@@ -24,11 +24,13 @@ import javax.servlet.http.HttpServletRequest;
* <p>Request conditions can be combined (e.g. type + method-level conditions), matched to a request,
* or compared to each other to determine if one matches the request better.
*
* @param <T> The type of objects that this RequestCondition can be compared to and combined with.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
*/
public interface RequestCondition<This extends RequestCondition<This>> {
public interface RequestCondition<T> {
/**
* Defines the rules for combining "this" condition (i.e. the current instance) with another condition.
......@@ -36,7 +38,7 @@ public interface RequestCondition<This extends RequestCondition<This>> {
*
* @returns a request condition instance that is the result of combining the two condition instances.
*/
This combine(This other);
T combine(T other);
/**
* Checks if this condition matches the provided request and returns a potentially new request condition
......@@ -45,13 +47,13 @@ public interface RequestCondition<This extends RequestCondition<This>> {
*
* @return a condition instance in case of a match; or {@code null} if there is no match.
*/
This getMatchingCondition(HttpServletRequest request);
T getMatchingCondition(HttpServletRequest request);
/**
* Compares "this" condition (i.e. the current instance) with another condition in the context of a request.
* <p>Note: it is assumed instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* <p>Note: it is assumed both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* to ensure they have content relevant to current request only.
*/
int compareTo(This other, HttpServletRequest request);
int compareTo(T other, HttpServletRequest request);
}
......@@ -36,13 +36,13 @@ import org.springframework.web.bind.annotation.RequestMethod;
* @author Rossen Stoyanchev
* @since 3.1
*/
public class RequestMethodsRequestCondition extends RequestConditionSupport<RequestMethodsRequestCondition> {
public class RequestMethodsRequestCondition extends AbstractRequestCondition<RequestMethodsRequestCondition> {
private final Set<RequestMethod> methods;
/**
* Create a {@link RequestMethodsRequestCondition} with the given {@link RequestMethod}s.
* @param requestMethods 0 or more HTTP request methods; if 0 the condition will match to every request.
* Create a {@link RequestMethodsRequestCondition} with the given request methods.
* @param requestMethods 0 or more HTTP request methods; if, 0 the condition will match to every request.
*/
public RequestMethodsRequestCondition(RequestMethod... requestMethods) {
this(asList(requestMethods));
......@@ -72,8 +72,8 @@ public class RequestMethodsRequestCondition extends RequestConditionSupport<Requ
}
@Override
protected boolean isLogicalConjunction() {
return false;
protected String getToStringInfix() {
return " || ";
}
/**
......
......@@ -31,6 +31,7 @@ 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;
/**
* Test for {@link AbstractHandlerMethodMapping}.
......@@ -89,10 +90,13 @@ public class HandlerMethodMappingTests {
private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping<String> {
private UrlPathHelper pathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
@Override
protected String getMatchingMapping(String pattern, String lookupPath, HttpServletRequest request) {
protected String getMatchingMapping(String pattern, HttpServletRequest request) {
String lookupPath = pathHelper.getLookupPathForRequest(request);
return pathMatcher.match(pattern, lookupPath) ? pattern : null;
}
......@@ -103,7 +107,8 @@ public class HandlerMethodMappingTests {
}
@Override
protected Comparator<String> getMappingComparator(String lookupPath, HttpServletRequest request) {
protected Comparator<String> getMappingComparator(HttpServletRequest request) {
String lookupPath = pathHelper.getLookupPathForRequest(request);
return pathMatcher.getPatternComparator(lookupPath);
}
......
......@@ -34,7 +34,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
import org.springframework.web.util.UrlPathHelper;
/**
* Test fixture with {@link RequestMappingHandlerMapping} testing its {@link RequestMappingInfo} comparator.
......@@ -57,8 +56,7 @@ public class RequestMappingInfoComparatorTests {
@Test
public void moreSpecificPatternWins() {
request.setRequestURI("/foo");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(lookupPath, request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(request);
RequestMappingInfo key1 = new RequestMappingInfo(new String[]{"/fo*"});
RequestMappingInfo key2 = new RequestMappingInfo(new String[]{"/foo"});
......@@ -68,8 +66,7 @@ public class RequestMappingInfoComparatorTests {
@Test
public void equalPatterns() {
request.setRequestURI("/foo");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(lookupPath, request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(request);
RequestMappingInfo key1 = new RequestMappingInfo(new String[]{"/foo*"});
RequestMappingInfo key2 = new RequestMappingInfo(new String[]{"/foo*"});
......@@ -79,20 +76,19 @@ public class RequestMappingInfoComparatorTests {
@Test
public void greaterNumberOfMatchingPatternsWins() throws Exception {
request.setRequestURI("/foo.html");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key1 = new RequestMappingInfo(new String[]{"/foo", "*.jpeg"});
RequestMappingInfo key2 = new RequestMappingInfo(new String[]{"/foo", "*.html"});
RequestMappingInfo match1 = handlerMapping.getMatchingMapping(key1, lookupPath, request);
RequestMappingInfo match2 = handlerMapping.getMatchingMapping(key2, lookupPath, request);
RequestMappingInfo match1 = handlerMapping.getMatchingMapping(key1, request);
RequestMappingInfo match2 = handlerMapping.getMatchingMapping(key2, request);
List<RequestMappingInfo> matches = asList(match1, match2);
Collections.sort(matches, handlerMapping.getMappingComparator(lookupPath, request));
Collections.sort(matches, handlerMapping.getMappingComparator(request));
assertSame(match2.getPatternsCondition(), matches.get(0).getPatternsCondition());
}
@Test
public void oneMethodWinsOverNone() {
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator("", request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(request);
RequestMappingInfo key1 = new RequestMappingInfo(null);
RequestMappingInfo key2 = new RequestMappingInfo(null, new RequestMethod[] {RequestMethod.GET});
......@@ -108,7 +104,7 @@ public class RequestMappingInfoComparatorTests {
new ParamsRequestCondition("foo"), null, null, null);
List<RequestMappingInfo> list = asList(empty, oneMethod, oneMethodOneParam);
Collections.shuffle(list);
Collections.sort(list, handlerMapping.getMappingComparator("", request));
Collections.sort(list, handlerMapping.getMappingComparator(request));
assertEquals(oneMethodOneParam, list.get(0));
assertEquals(oneMethod, list.get(1));
......@@ -122,7 +118,7 @@ public class RequestMappingInfoComparatorTests {
RequestMappingInfo none = new RequestMappingInfo(null);
request.addHeader("Accept", "application/xml, text/html");
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator("", request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(request);
int result = comparator.compare(html, xml);
assertTrue("Invalid comparison result: " + result, result > 0);
......@@ -134,14 +130,14 @@ public class RequestMappingInfoComparatorTests {
request = new MockHttpServletRequest();
request.addHeader("Accept", "application/xml, text/*");
comparator = handlerMapping.getMappingComparator("", request);
comparator = handlerMapping.getMappingComparator(request);
assertTrue(comparator.compare(html, xml) > 0);
assertTrue(comparator.compare(xml, html) < 0);
request = new MockHttpServletRequest();
request.addHeader("Accept", "application/pdf");
comparator = handlerMapping.getMappingComparator("", request);
comparator = handlerMapping.getMappingComparator(request);
assertTrue(comparator.compare(html, xml) == 0);
assertTrue(comparator.compare(xml, html) == 0);
......@@ -149,7 +145,7 @@ public class RequestMappingInfoComparatorTests {
// See SPR-7000
request = new MockHttpServletRequest();
request.addHeader("Accept", "text/html;q=0.9,application/xml");
comparator = handlerMapping.getMappingComparator("", request);
comparator = handlerMapping.getMappingComparator(request);
assertTrue(comparator.compare(html, xml) > 0);
assertTrue(comparator.compare(xml, html) < 0);
......
......@@ -87,34 +87,34 @@ public class RequestMappingInfoTests {
@Test
public void matchPatternsToRequest() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestMappingInfo match = createFromPatterns("/foo").getMatchingRequestMapping(request);
RequestMappingInfo match = createFromPatterns("/foo").getMatchingRequestMappingInfo(request);
assertNotNull(match);
request = new MockHttpServletRequest("GET", "/foo/bar");
match = createFromPatterns("/foo/*").getMatchingRequestMapping(request);
match = createFromPatterns("/foo/*").getMatchingRequestMappingInfo(request);
assertNotNull("Pattern match", match);
request = new MockHttpServletRequest("GET", "/foo.html");
match = createFromPatterns("/foo").getMatchingRequestMapping(request);
match = createFromPatterns("/foo").getMatchingRequestMappingInfo(request);
assertNotNull("Implicit match by extension", match);
assertEquals("Contains matched pattern", "/foo.*", match.getPatternsCondition().getPatterns().iterator().next());
request = new MockHttpServletRequest("GET", "/foo/");
match = createFromPatterns("/foo").getMatchingRequestMapping(request);
match = createFromPatterns("/foo").getMatchingRequestMappingInfo(request);
assertNotNull("Implicit match by trailing slash", match);
assertEquals("Contains matched pattern", "/foo/", match.getPatternsCondition().getPatterns().iterator().next());
request = new MockHttpServletRequest("GET", "/foo.html");
match = createFromPatterns("/foo.jpg").getMatchingRequestMapping(request);
match = createFromPatterns("/foo.jpg").getMatchingRequestMappingInfo(request);
assertNull("Implicit match ignored if pattern has extension", match);
request = new MockHttpServletRequest("GET", "/foo.html");
match = createFromPatterns("/foo.jpg").getMatchingRequestMapping(request);
match = createFromPatterns("/foo.jpg").getMatchingRequestMappingInfo(request);
assertNull("Implicit match ignored on pattern with trailing slash", match);
}
......@@ -124,17 +124,17 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestMappingInfo key = createFromPatterns("/foo");
RequestMappingInfo match = createFromPatterns("/foo").getMatchingRequestMapping(request);
RequestMappingInfo match = createFromPatterns("/foo").getMatchingRequestMappingInfo(request);
assertNotNull("No method matches any method", match);
key = new RequestMappingInfo(new String[]{"/foo"}, GET);
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
assertNotNull("Exact match", match);
key = new RequestMappingInfo(new String[]{"/foo"}, POST);
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
assertNull("No match", match);
}
......@@ -144,13 +144,13 @@ public class RequestMappingInfoTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestMappingInfo key = new RequestMappingInfo(new String[] {"/foo*", "/bar"}, GET, POST);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
RequestMappingInfo match = key.getMatchingRequestMappingInfo(request);
RequestMappingInfo expected = new RequestMappingInfo(new String[] {"/foo*"}, GET);
assertEquals("Matching RequestKey contains matched patterns and methods only", expected, match);
key = createFromPatterns("/**", "/foo*", "/foo");
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
expected = createFromPatterns("/foo", "/foo*", "/**");
assertEquals("Matched patterns are sorted with best match at the top", expected, match);
......@@ -165,14 +165,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo=bar"), null, null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
RequestMappingInfo match = key.getMatchingRequestMappingInfo(request);
assertNotNull(match);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo!=bar"), null, null, null);
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
assertNull(match);
}
......@@ -186,14 +186,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo=bar"), null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
RequestMappingInfo match = key.getMatchingRequestMappingInfo(request);
assertNotNull(match);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo!=bar"), null, null);
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
assertNull(match);
}
......@@ -207,14 +207,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("text/plain"), null);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
RequestMappingInfo match = key.getMatchingRequestMappingInfo(request);
assertNotNull(match);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("application/xml"), null);
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
assertNull(match);
}
......@@ -228,14 +228,14 @@ public class RequestMappingInfoTests {
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("text/plain"));
RequestMappingInfo match = key.getMatchingRequestMapping(request);
RequestMappingInfo match = key.getMatchingRequestMappingInfo(request);
assertNotNull(match);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("application/xml"));
match = key.getMatchingRequestMapping(request);
match = key.getMatchingRequestMappingInfo(request);
assertNull(match);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册