SPR-8536 When two methods match a request for any content type (i.e. Accept=*/* or no Accept header), choose the one that doesn't define any 'Accept' media types by default. This addresses an important use case where methods serving browsers typically don't specify Accept media types.
This commit is contained in:
parent
8f23c66512
commit
3c7e44ada4
|
|
@ -20,7 +20,8 @@ import java.util.Collection;
|
|||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Base class for {@link RequestCondition}s.
|
||||
* A base class for {@link RequestCondition} types providing implementations of
|
||||
* {@link #equals(Object)}, {@link #hashCode()}, and {@link #toString()}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
|
|
|
|||
|
|
@ -32,13 +32,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition to match requests against consumable media type expressions.
|
||||
*
|
||||
* <p>For details on the syntax of the expressions see {@link RequestMapping#consumes()}. If the condition is
|
||||
* created with 0 consumable media type expressions, it matches to every request.
|
||||
*
|
||||
* <p>This request condition is also capable of parsing header expressions specifically selecting 'Content-Type'
|
||||
* header expressions and converting them to consumable media type expressions.
|
||||
* A logical disjunction (' || ') request condition to match a request's 'Content-Type'
|
||||
* header to a list of media type expressions. Two kinds of media type expressions are
|
||||
* supported, which are described in {@link RequestMapping#consumes()} and
|
||||
* {@link RequestMapping#headers()} where the header name is 'Content-Type'.
|
||||
* Regardless of which syntax is used, the semantics are the same.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -49,26 +47,27 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
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
|
||||
* Creates a new instance from 0 or more "consumes" expressions.
|
||||
* @param consumes expressions with the syntax described in {@link RequestMapping#consumes()}
|
||||
* if 0 expressions are provided, the condition will match to every request
|
||||
*/
|
||||
public ConsumesRequestCondition(String... consumes) {
|
||||
this(consumes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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; if 0, the condition matches to all requests
|
||||
* @param headers the header expression to parse; if 0, the condition matches to all requests
|
||||
* Creates a new instance with "consumes" and "header" expressions. "Header" expressions
|
||||
* where the header name is not 'Content-Type' or have no header value defined are ignored.
|
||||
* If 0 expressions are provided in total, the condition will match to every request
|
||||
* @param consumes expressions with the syntax described in {@link RequestMapping#consumes()}
|
||||
* @param headers expressions with the syntax described in {@link RequestMapping#headers()}
|
||||
*/
|
||||
public ConsumesRequestCondition(String[] consumes, String[] headers) {
|
||||
this(parseExpressions(consumes, headers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
* Private constructor accepting parsed media type expressions.
|
||||
*/
|
||||
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
|
||||
this.expressions = new ArrayList<ConsumeMediaTypeExpression>(expressions);
|
||||
|
|
@ -96,7 +95,7 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the consumable media types contained in all expressions of this condition.
|
||||
* Returns the media types for this condition.
|
||||
*/
|
||||
public Set<MediaType> getMediaTypes() {
|
||||
Set<MediaType> result = new LinkedHashSet<MediaType>();
|
||||
|
|
@ -107,7 +106,7 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if this condition contains 0 consumable media type expressions.
|
||||
* Whether the condition has any media type expressions.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return expressions.isEmpty();
|
||||
|
|
@ -124,22 +123,25 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "consumes"
|
||||
* overrides a type-level "consumes" condition.
|
||||
*/
|
||||
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
|
||||
return !other.expressions.isEmpty() ? other : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 contained media type expressions match the given
|
||||
* request 'Accept' header and returns an instance that is guaranteed to
|
||||
* contain matching expressions only. The match is performed via
|
||||
* {@link MediaType#includes(MediaType)}.
|
||||
*
|
||||
* @param request the current request
|
||||
*
|
||||
* @return the same instance if the condition contains no expressions;
|
||||
* or a new condition with matching expressions only; 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()) {
|
||||
|
|
@ -159,12 +161,13 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
* Returns:
|
||||
* <ul>
|
||||
* <li>0 if the two conditions have the same number of expressions
|
||||
* <li>Less than 1 if "this" has more in number or more specific consumable media type expressions
|
||||
* <li>Greater than 1 if "other" has more in number or more specific consumable media type expressions
|
||||
* <li>Less than 1 if "this" has more or more specific media type expressions
|
||||
* <li>Greater than 1 if "other" has more or more specific media type expressions
|
||||
* </ul>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* and each instance contains the matching consumable media type expression only or is otherwise empty.
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
|
||||
* the matching consumable media type expression only or is otherwise empty.
|
||||
*/
|
||||
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
|
||||
if (expressions.isEmpty() && other.expressions.isEmpty()) {
|
||||
|
|
@ -182,8 +185,7 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
/**
|
||||
* Parsing and request matching logic for consumable media type expressions.
|
||||
* @see RequestMapping#consumes()
|
||||
* Parses and matches a single media type expression to a request's 'Content-Type' header.
|
||||
*/
|
||||
static class ConsumeMediaTypeExpression extends MediaTypeExpression {
|
||||
|
||||
|
|
@ -196,13 +198,13 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(HttpServletRequest request, MediaType mediaType) {
|
||||
protected boolean matchMediaType(HttpServletRequest request) {
|
||||
|
||||
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
|
||||
MediaType.parseMediaType(request.getContentType()) :
|
||||
MediaType.APPLICATION_OCTET_STREAM ;
|
||||
|
||||
return mediaType.includes(contentType);
|
||||
return getMediaType().includes(contentType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,14 +26,12 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* A logical conjunction (' && ') request condition that matches a request against a set of header expressions.
|
||||
* A logical conjunction (' && ') request condition that matches a request against
|
||||
* a set of header expressions with syntax defined in {@link RequestMapping#headers()}.
|
||||
*
|
||||
* <p>For details on the syntax of the expressions see {@link RequestMapping#headers()}. If the condition is
|
||||
* created with 0 header expressions, it will match to every request.
|
||||
*
|
||||
* <p>Note: when parsing header expressions, {@code "Accept"} and {@code "Content-Type"} header expressions
|
||||
* are filtered out. Those should be converted and used as "produces" and "consumes" conditions instead.
|
||||
* See the constructors for {@link ProducesRequestCondition} and {@link ConsumesRequestCondition}.
|
||||
* <p>Expressions passed to the constructor with header names 'Accept' or
|
||||
* 'Content-Type' are ignored. See {@link ConsumesRequestCondition} and
|
||||
* {@link ProducesRequestCondition} for those.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -44,13 +42,11 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
|
|||
private final Set<HeaderExpression> expressions;
|
||||
|
||||
/**
|
||||
* Create a {@link HeadersRequestCondition} with the given header expressions.
|
||||
*
|
||||
* <p>Note: {@code "Accept"} and {@code "Content-Type"} header expressions are filtered out.
|
||||
* 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.
|
||||
* Create a new instance from the given header expressions. Expressions with
|
||||
* header names 'Accept' or 'Content-Type' are ignored. See {@link ConsumesRequestCondition}
|
||||
* and {@link ProducesRequestCondition} for those.
|
||||
* @param headers media type expressions with syntax defined in {@link RequestMapping#headers()};
|
||||
* if 0, the condition will match to every request.
|
||||
*/
|
||||
public HeadersRequestCondition(String... headers) {
|
||||
this(parseExpressions(headers));
|
||||
|
|
@ -85,7 +81,8 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance with the union of the header expressions from "this" and the "other" instance.
|
||||
* Returns a new instance with the union of the header expressions
|
||||
* from "this" and the "other" instance.
|
||||
*/
|
||||
public HeadersRequestCondition combine(HeadersRequestCondition other) {
|
||||
Set<HeaderExpression> set = new LinkedHashSet<HeaderExpression>(this.expressions);
|
||||
|
|
@ -94,7 +91,8 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns "this" instance if the request matches to all header expressions; or {@code null} otherwise.
|
||||
* Returns "this" instance if the request matches all expressions;
|
||||
* or {@code null} otherwise.
|
||||
*/
|
||||
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
for (HeaderExpression expression : expressions) {
|
||||
|
|
@ -113,16 +111,16 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
|
|||
* <li>Greater than 1 if the "other" instance has more header expressions
|
||||
* </ul>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* and each instance contains the matching header expression only or is otherwise empty.
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||
* contains the matching header expression only or is otherwise empty.
|
||||
*/
|
||||
public int compareTo(HeadersRequestCondition other, HttpServletRequest request) {
|
||||
return other.expressions.size() - this.expressions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing and request matching logic for header expressions.
|
||||
* @see RequestMapping#headers()
|
||||
* Parses and matches a single header expression to a request.
|
||||
*/
|
||||
static class HeaderExpression extends AbstractNameValueExpression<String> {
|
||||
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@ abstract class MediaTypeExpression implements Comparable<MediaTypeExpression> {
|
|||
isNegated = negated;
|
||||
}
|
||||
|
||||
public boolean match(HttpServletRequest request) {
|
||||
boolean match = match(request, this.mediaType);
|
||||
public final boolean match(HttpServletRequest request) {
|
||||
boolean match = matchMediaType(request);
|
||||
return !isNegated ? match : !match;
|
||||
}
|
||||
|
||||
protected abstract boolean match(HttpServletRequest request, MediaType mediaType);
|
||||
protected abstract boolean matchMediaType(HttpServletRequest request);
|
||||
|
||||
MediaType getMediaType() {
|
||||
return mediaType;
|
||||
|
|
|
|||
|
|
@ -27,10 +27,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* A logical conjunction (' && ') request condition that matches a request against a set parameter expressions.
|
||||
*
|
||||
* <p>For details on the syntax of the expressions see {@link RequestMapping#params()}. If the condition is
|
||||
* created with 0 parameter expressions, it will match to every request.
|
||||
* A logical conjunction (' && ') request condition that matches a request against
|
||||
* a set parameter expressions with syntax defined in {@link RequestMapping#params()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -41,9 +39,9 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
|
|||
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.
|
||||
* Create a new instance from the given param expressions.
|
||||
* @param params expressions with syntax defined in {@link RequestMapping#params()};
|
||||
* if 0, the condition will match to every request.
|
||||
*/
|
||||
public ParamsRequestCondition(String... params) {
|
||||
this(parseExpressions(params));
|
||||
|
|
@ -74,7 +72,8 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance with the union of the param expressions from "this" and the "other" instance.
|
||||
* Returns a new instance with the union of the param expressions
|
||||
* from "this" and the "other" instance.
|
||||
*/
|
||||
public ParamsRequestCondition combine(ParamsRequestCondition other) {
|
||||
Set<ParamExpression> set = new LinkedHashSet<ParamExpression>(this.expressions);
|
||||
|
|
@ -83,7 +82,8 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns "this" instance if the request matches to all parameter expressions; or {@code null} otherwise.
|
||||
* Returns "this" instance if the request matches all param expressions;
|
||||
* or {@code null} otherwise.
|
||||
*/
|
||||
public ParamsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
for (ParamExpression expression : expressions) {
|
||||
|
|
@ -102,16 +102,16 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
|
|||
* <li>Greater than 1 if the "other" instance has more parameter expressions
|
||||
* </ul>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* and each instance contains the matching parameter expressions only or is otherwise empty.
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||
* contains the matching parameter expressions only or is otherwise empty.
|
||||
*/
|
||||
public int compareTo(ParamsRequestCondition other, HttpServletRequest request) {
|
||||
return other.expressions.size() - this.expressions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing and request matching logic for parameter expressions.
|
||||
* @see RequestMapping#params()
|
||||
* Parses and matches a single param expression to a request.
|
||||
*/
|
||||
static class ParamExpression extends AbstractNameValueExpression<String> {
|
||||
|
||||
|
|
|
|||
|
|
@ -34,9 +34,8 @@ import org.springframework.util.StringUtils;
|
|||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition that matches a request against a set of URL path patterns.
|
||||
*
|
||||
* <p>See Javadoc on individual methods for details on how URL patterns are matched, combined, and compared.
|
||||
* A logical disjunction (' || ') request condition that matches a request
|
||||
* against a set of URL path patterns.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
|
|
@ -52,9 +51,17 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
private final boolean useSuffixPatternMatch;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PatternsRequestCondition} with the given URL patterns.
|
||||
* Each pattern that is not empty and does not start with "/" is prepended with "/".
|
||||
*
|
||||
* Creates a new instance with the given URL patterns.
|
||||
* Each pattern that is not empty and does not start with "/" is pre-pended with "/".
|
||||
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
|
||||
*/
|
||||
public PatternsRequestCondition(String... patterns) {
|
||||
this(asList(patterns), null, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given URL patterns.
|
||||
* Each pattern that is not empty and does not start with "/" is pre-pended with "/".
|
||||
* @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
|
||||
|
|
@ -68,20 +75,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 0 or more URL patterns; if 0 the condition will match to every request.
|
||||
*/
|
||||
public PatternsRequestCondition(String... patterns) {
|
||||
this(patterns, null, null, true);
|
||||
}
|
||||
|
||||
private static List<String> asList(String... patterns) {
|
||||
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
* Private constructor accepting a collection of patterns.
|
||||
*/
|
||||
private PatternsRequestCondition(Collection<String> patterns,
|
||||
UrlPathHelper urlPathHelper,
|
||||
|
|
@ -93,6 +87,10 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
||||
}
|
||||
|
||||
private static List<String> asList(String... patterns) {
|
||||
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
|
||||
}
|
||||
|
||||
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
|
||||
if (patterns == null) {
|
||||
return Collections.emptySet();
|
||||
|
|
@ -128,7 +126,7 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
* <li>If there are patterns in both instances, combine the patterns in "this" with
|
||||
* the patterns in "other" using {@link PathMatcher#combine(String, String)}.
|
||||
* <li>If only one instance has patterns, use them.
|
||||
* <li>If neither instance has patterns, use "".
|
||||
* <li>If neither instance has patterns, use an empty String (i.e. "").
|
||||
* </ul>
|
||||
*/
|
||||
public PatternsRequestCondition combine(PatternsRequestCondition other) {
|
||||
|
|
@ -153,21 +151,23 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the patterns match the given request and returns an instance that is guaranteed
|
||||
* to contain matching patterns, sorted via {@link PathMatcher#getPatternComparator(String)}.
|
||||
* Checks if any of the patterns match the given request and returns an instance
|
||||
* that is guaranteed to contain matching patterns, sorted via
|
||||
* {@link PathMatcher#getPatternComparator(String)}.
|
||||
*
|
||||
* <p>A matching pattern is obtained by making checks in the following order:
|
||||
* <ul>
|
||||
* <li>Direct match
|
||||
* <li>A pattern match with ".*" appended assuming the pattern already doesn't contain "."
|
||||
* <li>A pattern match
|
||||
* <li>A pattern match with "/" appended assuming the patterns already end with "/"
|
||||
* <li>Pattern match with ".*" appended if the pattern doesn't already contain a "."
|
||||
* <li>Pattern match
|
||||
* <li>Pattern match with "/" appended if the pattern doesn't already end in "/"
|
||||
* </ul>
|
||||
*
|
||||
* @param request the current request
|
||||
*
|
||||
* @return the same instance if the condition contains no patterns;
|
||||
* or a new condition with sorted matching patterns; or {@code null} if no patterns match.
|
||||
* or a new condition with sorted matching patterns;
|
||||
* or {@code null} if no patterns match.
|
||||
*/
|
||||
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
if (patterns.isEmpty()) {
|
||||
|
|
@ -207,13 +207,16 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
|||
}
|
||||
|
||||
/**
|
||||
* Compare the two conditions and return 0 if they match equally to the request, less than one if "this"
|
||||
* matches the request more, and greater than one if "other" matches the request more. Patterns are
|
||||
* compared one at a time, from top to bottom via {@link PathMatcher#getPatternComparator(String)}.
|
||||
* If all compared patterns match equally, but one instance has more patterns, it is a closer match.
|
||||
* Compare the two conditions based on the URL patterns they contain.
|
||||
* Patterns are compared one at a time, from top to bottom via
|
||||
* {@link PathMatcher#getPatternComparator(String)}. If all compared
|
||||
* patterns match equally, but one instance has more patterns, it is
|
||||
* considered a closer match.
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* to ensure they contain only patterns that match the request and are sorted with the best matches on top.
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} to ensure they
|
||||
* contain only patterns that match the request and are sorted with
|
||||
* the best matches on top.
|
||||
*/
|
||||
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
|
||||
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package org.springframework.web.servlet.mvc.condition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -33,14 +32,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition to match requests against producible
|
||||
* media type expressions.
|
||||
*
|
||||
* <p>For details on the syntax of the expressions see {@link RequestMapping#consumes()}.
|
||||
* If the condition is created without media type expressions, it matches to every request.
|
||||
*
|
||||
* <p>This request condition is also capable of parsing header expressions by selecting
|
||||
* 'Accept' header expressions and converting them to prodicuble media type expressions.
|
||||
* A logical disjunction (' || ') request condition to match a request's 'Accept' header
|
||||
* to a list of media type expressions. Two kinds of media type expressions are
|
||||
* supported, which are described in {@link RequestMapping#produces()} and
|
||||
* {@link RequestMapping#headers()} where the header name is 'Accept'.
|
||||
* Regardless of which syntax is used, the semantics are the same.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -51,26 +47,27 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
private final List<ProduceMediaTypeExpression> expressions;
|
||||
|
||||
/**
|
||||
* Creates a {@link ProducesRequestCondition} with the given producible media type expressions.
|
||||
* @param produces the expressions to parse; if 0 the condition matches to every request
|
||||
* Creates a new instance from 0 or more "produces" expressions.
|
||||
* @param produces expressions with the syntax described in {@link RequestMapping#produces()}
|
||||
* if 0 expressions are provided, the condition matches to every request
|
||||
*/
|
||||
public ProducesRequestCondition(String... produces) {
|
||||
this(produces, null);
|
||||
this(parseExpressions(produces, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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; if 0, the condition matches to all requests
|
||||
* @param headers the header expression to parse; if 0, the condition matches to all requests
|
||||
* Creates a new instance with "produces" and "header" expressions. "Header" expressions
|
||||
* where the header name is not 'Accept' or have no header value defined are ignored.
|
||||
* If 0 expressions are provided in total, the condition matches to every request
|
||||
* @param produces expressions with the syntax described in {@link RequestMapping#produces()}
|
||||
* @param headers expressions with the syntax described in {@link RequestMapping#headers()}
|
||||
*/
|
||||
public ProducesRequestCondition(String[] produces, String[] headers) {
|
||||
this(parseExpressions(produces, headers));
|
||||
}
|
||||
|
||||
/**
|
||||
* A private constructor.
|
||||
* Private constructor accepting parsed media type expressions.
|
||||
*/
|
||||
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions) {
|
||||
this.expressions = new ArrayList<ProduceMediaTypeExpression>(expressions);
|
||||
|
|
@ -98,7 +95,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the producible media types contained in all expressions of this condition.
|
||||
* Returns the media types for this condition.
|
||||
*/
|
||||
public Set<MediaType> getMediaTypes() {
|
||||
Set<MediaType> result = new LinkedHashSet<MediaType>();
|
||||
|
|
@ -109,7 +106,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if this condition contains no producible media type expressions.
|
||||
* Whether the condition has any media type expressions.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return expressions.isEmpty();
|
||||
|
|
@ -126,22 +123,25 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the "other" instance if "other" as long as it contains any expressions; or "this" instance otherwise.
|
||||
* In other words "other" takes precedence over "this" as long as it contains any expressions.
|
||||
* <p>Example: method-level "produces" overrides type-level "produces" condition.
|
||||
* Returns the "other" instance if it has any expressions; returns "this"
|
||||
* instance otherwise. Practically that means a method-level "produces"
|
||||
* overrides a type-level "produces" condition.
|
||||
*/
|
||||
public ProducesRequestCondition combine(ProducesRequestCondition other) {
|
||||
return !other.expressions.isEmpty() ? other : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the producible 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 contained media type expressions match the given
|
||||
* request 'Content-Type' header and returns an instance that is guaranteed
|
||||
* to contain matching expressions only. The match is performed via
|
||||
* {@link MediaType#isCompatibleWith(MediaType)}.
|
||||
*
|
||||
* @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.
|
||||
* @return the same instance if there are no expressions;
|
||||
* or a new condition with matching expressions;
|
||||
* or {@code null} if no expressions match.
|
||||
*/
|
||||
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
if (isEmpty()) {
|
||||
|
|
@ -158,22 +158,38 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns:
|
||||
* <ul>
|
||||
* <li>0 if the two conditions have the same number of expressions
|
||||
* <li>Less than 1 if "this" has more in number or more specific producible media type expressions
|
||||
* <li>Greater than 1 if "other" has more in number or more specific producible media type expressions
|
||||
* </ul>
|
||||
* Compares this and another Produces condition as follows:
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* and each instance contains the matching producible media type expression only or is otherwise empty.
|
||||
* <ol>
|
||||
* <li>Sorts the request 'Accept' header media types by quality value via
|
||||
* {@link MediaType#sortByQualityValue(List)} and iterates over the sorted types.
|
||||
* <li>Compares the sorted request media types against the media types of each
|
||||
* Produces condition via {@link MediaType#includes(MediaType)}.
|
||||
* <li>A "produces" condition with a matching media type listed earlier wins.
|
||||
* <li>If both conditions have a matching media type at the same index, the
|
||||
* media types are further compared by specificity and quality.
|
||||
* </ol>
|
||||
*
|
||||
* <p>If a request media type is {@link MediaType#ALL} or if there is no 'Accept'
|
||||
* header, and therefore both conditions match, preference is given to one
|
||||
* Produces condition if it is empty and the other one is not.
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||
* contains the matching producible media type expression only or
|
||||
* is otherwise empty.
|
||||
*/
|
||||
public int compareTo(ProducesRequestCondition other, HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
List<MediaType> acceptedMediaTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||
MediaType.sortByQualityValue(acceptedMediaTypes);
|
||||
|
||||
|
||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||
if (acceptedMediaType.equals(MediaType.ALL)) {
|
||||
if (isOneEmptyButNotBoth(other)) {
|
||||
return this.isEmpty() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
int thisIndex = this.indexOfMediaType(acceptedMediaType);
|
||||
int otherIndex = other.indexOfMediaType(acceptedMediaType);
|
||||
if (thisIndex != otherIndex) {
|
||||
|
|
@ -187,6 +203,13 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptedMediaTypes.isEmpty()) {
|
||||
if (isOneEmptyButNotBoth(other)) {
|
||||
return this.isEmpty() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -199,12 +222,15 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
return -1;
|
||||
}
|
||||
|
||||
private boolean isOneEmptyButNotBoth(ProducesRequestCondition other) {
|
||||
return ((this.isEmpty() || other.isEmpty()) && (this.expressions.size() != other.expressions.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing and request matching logic for producible media type expressions.
|
||||
* @see RequestMapping#produces()
|
||||
* Parses and matches a single media type expression to a request's 'Accept' header.
|
||||
*/
|
||||
static class ProduceMediaTypeExpression extends MediaTypeExpression {
|
||||
|
||||
|
||||
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
|
||||
super(mediaType, negated);
|
||||
}
|
||||
|
|
@ -214,10 +240,10 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(HttpServletRequest request, MediaType mediaType) {
|
||||
protected boolean matchMediaType(HttpServletRequest request) {
|
||||
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
|
||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||
if (mediaType.isCompatibleWith(acceptedMediaType)) {
|
||||
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
/**
|
||||
* The contract for request conditions.
|
||||
*
|
||||
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to a request via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)}, and compared to each other via
|
||||
* {@link #compareTo(Object, HttpServletRequest)} to determine which matches a request more closely.
|
||||
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to
|
||||
* a request via {@link #getMatchingCondition(HttpServletRequest)}, and compared
|
||||
* to each other via {@link #compareTo(Object, HttpServletRequest)} to determine
|
||||
* which matches a request more closely.
|
||||
*
|
||||
* @param <T> The type of objects that this RequestCondition can be compared to and combined with.
|
||||
* @param <T> The type of objects that this RequestCondition can be combined
|
||||
* with compared to.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Arjen Poutsma
|
||||
|
|
@ -36,27 +38,33 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
public interface RequestCondition<T> {
|
||||
|
||||
/**
|
||||
* Defines the rules for combining this condition (i.e. the current instance) with another condition.
|
||||
* For example combining type- and method-level {@link RequestMapping} conditions.
|
||||
* Defines the rules for combining this condition (i.e. the current instance)
|
||||
* with another condition. For example combining type- and method-level
|
||||
* {@link RequestMapping} conditions.
|
||||
*
|
||||
* @param other the condition to combine with.
|
||||
* @returns a request condition instance that is the result of combining the two condition instances.
|
||||
* @returns a request condition instance that is the result of combining
|
||||
* the two condition instances.
|
||||
*/
|
||||
T combine(T other);
|
||||
|
||||
/**
|
||||
* Checks if this condition matches the given request and returns a potentially new request condition
|
||||
* with content tailored to the current request. For example a condition with URL patterns might return
|
||||
* a new condition that contains matching patterns sorted with best matching patterns on top.
|
||||
* Checks if this condition matches the given request and returns a
|
||||
* potentially new request condition with content tailored to the
|
||||
* current request. For example a condition with URL patterns might
|
||||
* return a new condition that contains matching patterns sorted
|
||||
* with best matching patterns on top.
|
||||
*
|
||||
* @return a condition instance in case of a match; or {@code null} if there is no match.
|
||||
* @return a condition instance in case of a match;
|
||||
* or {@code null} if there is no match.
|
||||
*/
|
||||
T getMatchingCondition(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Compares this condition to another condition in the context of a specific request. This method assumes
|
||||
* both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)} to ensure they
|
||||
* have content relevant to current request only.
|
||||
* Compares this condition to another condition in the context of
|
||||
* a specific request. This method assumes both instances have
|
||||
* been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* to ensure they have content relevant to current request only.
|
||||
*/
|
||||
int compareTo(T other, HttpServletRequest request);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition that matches a request against a set of {@link RequestMethod}s.
|
||||
*
|
||||
* <p>If the condition is created with 0 HTTP request methods, it matches to every request.
|
||||
* A logical disjunction (' || ') request condition that matches a request
|
||||
* against a set of {@link RequestMethod}s.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -41,8 +40,9 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
|
|||
private final Set<RequestMethod> methods;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Create a new instance 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));
|
||||
|
|
@ -77,7 +77,8 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance with a union of the HTTP request methods from "this" and the "other" instance.
|
||||
* Returns a new instance with a union of the HTTP request methods
|
||||
* from "this" and the "other" instance.
|
||||
*/
|
||||
public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) {
|
||||
Set<RequestMethod> set = new LinkedHashSet<RequestMethod>(this.methods);
|
||||
|
|
@ -86,11 +87,12 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the HTTP request methods match the given request and returns an instance that
|
||||
* contain the matching request method.
|
||||
* Checks if any of the HTTP request methods match the given request and returns
|
||||
* an instance that contains the matching request method only.
|
||||
* @param request the current request
|
||||
* @return the same instance if the condition contains no request method;
|
||||
* or a new condition with the matching request method; or {@code null} if no request methods match.
|
||||
* or a new condition with the matching request method;
|
||||
* or {@code null} if no request methods match.
|
||||
*/
|
||||
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||
if (methods.isEmpty()) {
|
||||
|
|
@ -113,8 +115,9 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
|
|||
* <li>Greater than 1 "other" has an HTTP request method but "this" doesn't.
|
||||
* </ul>
|
||||
*
|
||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||
* and therefore each instance contains the matching HTTP request method only or is otherwise empty.
|
||||
* <p>It is assumed that both instances have been obtained via
|
||||
* {@link #getMatchingCondition(HttpServletRequest)} and therefore each instance
|
||||
* contains the matching HTTP request method only or is otherwise empty.
|
||||
*/
|
||||
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
|
||||
return other.methods.size() - this.methods.size();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
|
@ -39,6 +47,7 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
|
@ -51,7 +60,6 @@ import javax.validation.constraints.NotNull;
|
|||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
|
|
@ -133,8 +141,6 @@ import org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMap
|
|||
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
|
|
@ -1869,6 +1875,40 @@ public class ServletAnnotationControllerTests {
|
|||
servlet.service(request, response);
|
||||
assertEquals(405, response.getStatus());
|
||||
}
|
||||
|
||||
// SPR-8536
|
||||
|
||||
@Test
|
||||
public void testHeadersCondition() throws Exception {
|
||||
initServlet(HeadersConditionController.class);
|
||||
|
||||
// No "Accept" header
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("home", response.getForwardedUrl());
|
||||
|
||||
// Accept "*/*"
|
||||
request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("Accept", "*/*");
|
||||
response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("home", response.getForwardedUrl());
|
||||
|
||||
// Accept "application/json"
|
||||
request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("Accept", "application/json");
|
||||
response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("application/json", response.getHeader("Content-Type"));
|
||||
assertEquals("homeJson", response.getContentAsString());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
|
@ -3151,5 +3191,20 @@ public class ServletAnnotationControllerTests {
|
|||
writer.write("handle2");
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class HeadersConditionController {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
public String home() {
|
||||
return "home";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET, headers="Accept=application/json")
|
||||
@ResponseBody
|
||||
public String homeJson() {
|
||||
return "homeJson";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void compareToSingle() {
|
||||
public void compareToWithSingleExpression() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addHeader("Accept", "text/plain");
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ public class ProducesRequestConditionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void compareToMultiple() {
|
||||
public void compareToMultipleExpressions() {
|
||||
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
|
||||
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
|
||||
|
||||
|
|
@ -147,22 +147,10 @@ public class ProducesRequestConditionTests {
|
|||
|
||||
result = condition2.compareTo(condition1, request);
|
||||
assertTrue("Invalid comparison result: " + result, result > 0);
|
||||
|
||||
condition1 = new ProducesRequestCondition("*/*");
|
||||
condition2 = new ProducesRequestCondition("text/*");
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
request.addHeader("Accept", "text/*");
|
||||
|
||||
result = condition1.compareTo(condition2, request);
|
||||
assertTrue("Invalid comparison result: " + result, result > 0);
|
||||
|
||||
result = condition2.compareTo(condition1, request);
|
||||
assertTrue("Invalid comparison result: " + result, result < 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareToMultipleAccept() {
|
||||
public void compareToMultipleExpressionsAndMultipeAcceptHeaderValues() {
|
||||
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
|
||||
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
|
||||
|
||||
|
|
@ -186,7 +174,36 @@ public class ProducesRequestConditionTests {
|
|||
result = condition2.compareTo(condition1, request);
|
||||
assertTrue("Invalid comparison result: " + result, result < 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void compareToEmptyCondition() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addHeader("Accept", "*/*");
|
||||
|
||||
ProducesRequestCondition condition1 = new ProducesRequestCondition();
|
||||
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
|
||||
|
||||
int result = condition1.compareTo(condition2, request);
|
||||
assertTrue("Invalid comparison result: " + result, result < 0);
|
||||
|
||||
result = condition2.compareTo(condition1, request);
|
||||
assertTrue("Invalid comparison result: " + result, result > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareToWithoutAcceptHeader() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
ProducesRequestCondition condition1 = new ProducesRequestCondition();
|
||||
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
|
||||
|
||||
int result = condition1.compareTo(condition2, request);
|
||||
assertTrue("Invalid comparison result: " + result, result < 0);
|
||||
|
||||
result = condition2.compareTo(condition1, request);
|
||||
assertTrue("Invalid comparison result: " + result, result > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void combine() {
|
||||
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
|
||||
|
|
|
|||
|
|
@ -1419,6 +1419,40 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
|||
assertEquals(405, response.getStatus());
|
||||
}
|
||||
|
||||
// SPR-8536
|
||||
|
||||
@Test
|
||||
public void testHeadersCondition() throws Exception {
|
||||
initServletWithControllers(HeadersConditionController.class);
|
||||
|
||||
// No "Accept" header
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("home", response.getForwardedUrl());
|
||||
|
||||
// Accept "*/*"
|
||||
request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("Accept", "*/*");
|
||||
response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("home", response.getForwardedUrl());
|
||||
|
||||
// Accept "application/json"
|
||||
request = new MockHttpServletRequest("GET", "/");
|
||||
request.addHeader("Accept", "application/json");
|
||||
response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals("application/json", response.getHeader("Content-Type"));
|
||||
assertEquals("homeJson", response.getContentAsString());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Controllers
|
||||
|
|
@ -2713,6 +2747,21 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
|||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class HeadersConditionController {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
public String home() {
|
||||
return "home";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET, headers="Accept=application/json")
|
||||
@ResponseBody
|
||||
public String homeJson() {
|
||||
return "homeJson";
|
||||
}
|
||||
}
|
||||
|
||||
// Test cases deleted from the original SevletAnnotationControllerTests:
|
||||
|
||||
// @Ignore("Controller interface => no method-level @RequestMapping annotation")
|
||||
|
|
|
|||
Loading…
Reference in New Issue