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;
|
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
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logical disjunction (' || ') request condition to match requests against 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
|
||||||
* <p>For details on the syntax of the expressions see {@link RequestMapping#consumes()}. If the condition is
|
* supported, which are described in {@link RequestMapping#consumes()} and
|
||||||
* created with 0 consumable media type expressions, it matches to every request.
|
* {@link RequestMapping#headers()} where the header name is 'Content-Type'.
|
||||||
*
|
* Regardless of which syntax is used, the semantics are the same.
|
||||||
* <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.
|
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
|
@ -49,26 +47,27 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
||||||
private final List<ConsumeMediaTypeExpression> expressions;
|
private final List<ConsumeMediaTypeExpression> expressions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ConsumesRequestCondition} with the given consumable media type expressions.
|
* Creates a new instance from 0 or more "consumes" expressions.
|
||||||
* @param consumes the expressions to parse; if 0, the condition matches to every request
|
* @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) {
|
public ConsumesRequestCondition(String... consumes) {
|
||||||
this(consumes, null);
|
this(consumes, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ConsumesRequestCondition} with the given header and consumes expressions.
|
* Creates a new instance with "consumes" and "header" expressions. "Header" expressions
|
||||||
* In addition to consume expressions, {@code "Content-Type"} header expressions are extracted
|
* where the header name is not 'Content-Type' or have no header value defined are ignored.
|
||||||
* and treated as consumable media type expressions.
|
* If 0 expressions are provided in total, the condition will match to every request
|
||||||
* @param consumes the consumes expressions to parse; if 0, the condition matches to all requests
|
* @param consumes expressions with the syntax described in {@link RequestMapping#consumes()}
|
||||||
* @param headers the header expression to parse; if 0, the condition matches to all requests
|
* @param headers expressions with the syntax described in {@link RequestMapping#headers()}
|
||||||
*/
|
*/
|
||||||
public ConsumesRequestCondition(String[] consumes, String[] headers) {
|
public ConsumesRequestCondition(String[] consumes, String[] headers) {
|
||||||
this(parseExpressions(consumes, headers));
|
this(parseExpressions(consumes, headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor.
|
* Private constructor accepting parsed media type expressions.
|
||||||
*/
|
*/
|
||||||
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
|
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
|
||||||
this.expressions = new ArrayList<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() {
|
public Set<MediaType> getMediaTypes() {
|
||||||
Set<MediaType> result = new LinkedHashSet<MediaType>();
|
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() {
|
public boolean isEmpty() {
|
||||||
return expressions.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.
|
* Returns the "other" instance if it has any expressions; returns "this"
|
||||||
* In other words "other" takes precedence over "this" as long as it has expressions.
|
* instance otherwise. Practically that means a method-level "consumes"
|
||||||
* <p>Example: method-level "consumes" overrides type-level "consumes" condition.
|
* overrides a type-level "consumes" condition.
|
||||||
*/
|
*/
|
||||||
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
|
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
|
||||||
return !other.expressions.isEmpty() ? other : this;
|
return !other.expressions.isEmpty() ? other : this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if any of the consumable media type expressions match the given request and returns an
|
* Checks if any of the contained media type expressions match the given
|
||||||
* instance that is guaranteed to contain matching media type expressions only.
|
* 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
|
* @param request the current request
|
||||||
*
|
*
|
||||||
* @return the same instance if the condition contains no expressions;
|
* @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) {
|
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
|
|
@ -159,12 +161,13 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
||||||
* Returns:
|
* Returns:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>0 if the two conditions have the same number of expressions
|
* <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>Less than 1 if "this" has more or more specific media type expressions
|
||||||
* <li>Greater than 1 if "other" has more in number or more specific consumable media type expressions
|
* <li>Greater than 1 if "other" has more or more specific media type expressions
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
* <p>It is assumed that both instances have been obtained via
|
||||||
* and each instance contains the matching consumable media type expression only or is otherwise empty.
|
* {@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) {
|
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
|
||||||
if (expressions.isEmpty() && other.expressions.isEmpty()) {
|
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.
|
* Parses and matches a single media type expression to a request's 'Content-Type' header.
|
||||||
* @see RequestMapping#consumes()
|
|
||||||
*/
|
*/
|
||||||
static class ConsumeMediaTypeExpression extends MediaTypeExpression {
|
static class ConsumeMediaTypeExpression extends MediaTypeExpression {
|
||||||
|
|
||||||
|
|
@ -196,13 +198,13 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition<Con
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean match(HttpServletRequest request, MediaType mediaType) {
|
protected boolean matchMediaType(HttpServletRequest request) {
|
||||||
|
|
||||||
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
|
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
|
||||||
MediaType.parseMediaType(request.getContentType()) :
|
MediaType.parseMediaType(request.getContentType()) :
|
||||||
MediaType.APPLICATION_OCTET_STREAM ;
|
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;
|
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
|
* <p>Expressions passed to the constructor with header names 'Accept' or
|
||||||
* created with 0 header expressions, it will match to every request.
|
* 'Content-Type' are ignored. See {@link ConsumesRequestCondition} and
|
||||||
*
|
* {@link ProducesRequestCondition} for those.
|
||||||
* <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}.
|
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
|
@ -44,13 +42,11 @@ public final class HeadersRequestCondition extends AbstractRequestCondition<Head
|
||||||
private final Set<HeaderExpression> expressions;
|
private final Set<HeaderExpression> expressions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link HeadersRequestCondition} with the given header expressions.
|
* Create a new instance from the given header expressions. Expressions with
|
||||||
*
|
* header names 'Accept' or 'Content-Type' are ignored. See {@link ConsumesRequestCondition}
|
||||||
* <p>Note: {@code "Accept"} and {@code "Content-Type"} header expressions are filtered out.
|
* and {@link ProducesRequestCondition} for those.
|
||||||
* Those should be converted and used as "produces" and "consumes" conditions instead.
|
* @param headers media type expressions with syntax defined in {@link RequestMapping#headers()};
|
||||||
* See the constructors for {@link ProducesRequestCondition} and {@link ConsumesRequestCondition}.
|
* 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) {
|
public HeadersRequestCondition(String... headers) {
|
||||||
this(parseExpressions(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) {
|
public HeadersRequestCondition combine(HeadersRequestCondition other) {
|
||||||
Set<HeaderExpression> set = new LinkedHashSet<HeaderExpression>(this.expressions);
|
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) {
|
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||||
for (HeaderExpression expression : expressions) {
|
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
|
* <li>Greater than 1 if the "other" instance has more header expressions
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
* <p>It is assumed that both instances have been obtained via
|
||||||
* and each instance contains the matching header expression only or is otherwise empty.
|
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||||
|
* contains the matching header expression only or is otherwise empty.
|
||||||
*/
|
*/
|
||||||
public int compareTo(HeadersRequestCondition other, HttpServletRequest request) {
|
public int compareTo(HeadersRequestCondition other, HttpServletRequest request) {
|
||||||
return other.expressions.size() - this.expressions.size();
|
return other.expressions.size() - this.expressions.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsing and request matching logic for header expressions.
|
* Parses and matches a single header expression to a request.
|
||||||
* @see RequestMapping#headers()
|
|
||||||
*/
|
*/
|
||||||
static class HeaderExpression extends AbstractNameValueExpression<String> {
|
static class HeaderExpression extends AbstractNameValueExpression<String> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,12 @@ abstract class MediaTypeExpression implements Comparable<MediaTypeExpression> {
|
||||||
isNegated = negated;
|
isNegated = negated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean match(HttpServletRequest request) {
|
public final boolean match(HttpServletRequest request) {
|
||||||
boolean match = match(request, this.mediaType);
|
boolean match = matchMediaType(request);
|
||||||
return !isNegated ? match : !match;
|
return !isNegated ? match : !match;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract boolean match(HttpServletRequest request, MediaType mediaType);
|
protected abstract boolean matchMediaType(HttpServletRequest request);
|
||||||
|
|
||||||
MediaType getMediaType() {
|
MediaType getMediaType() {
|
||||||
return mediaType;
|
return mediaType;
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logical conjunction (' && ') request condition that matches a request against a set parameter expressions.
|
* A logical conjunction (' && ') request condition that matches a request against
|
||||||
*
|
* a set parameter expressions with syntax defined in {@link RequestMapping#params()}.
|
||||||
* <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.
|
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
|
@ -41,9 +39,9 @@ public final class ParamsRequestCondition extends AbstractRequestCondition<Param
|
||||||
private final Set<ParamExpression> expressions;
|
private final Set<ParamExpression> expressions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link ParamsRequestCondition} with the given param expressions.
|
* Create a new instance from the given param expressions.
|
||||||
*
|
* @param params expressions with syntax defined in {@link RequestMapping#params()};
|
||||||
* @param params 0 or more param expressions; if 0, the condition will match to every request.
|
* if 0, the condition will match to every request.
|
||||||
*/
|
*/
|
||||||
public ParamsRequestCondition(String... params) {
|
public ParamsRequestCondition(String... params) {
|
||||||
this(parseExpressions(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) {
|
public ParamsRequestCondition combine(ParamsRequestCondition other) {
|
||||||
Set<ParamExpression> set = new LinkedHashSet<ParamExpression>(this.expressions);
|
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) {
|
public ParamsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||||
for (ParamExpression expression : expressions) {
|
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
|
* <li>Greater than 1 if the "other" instance has more parameter expressions
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
* <p>It is assumed that both instances have been obtained via
|
||||||
* and each instance contains the matching parameter expressions only or is otherwise empty.
|
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
|
||||||
|
* contains the matching parameter expressions only or is otherwise empty.
|
||||||
*/
|
*/
|
||||||
public int compareTo(ParamsRequestCondition other, HttpServletRequest request) {
|
public int compareTo(ParamsRequestCondition other, HttpServletRequest request) {
|
||||||
return other.expressions.size() - this.expressions.size();
|
return other.expressions.size() - this.expressions.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsing and request matching logic for parameter expressions.
|
* Parses and matches a single param expression to a request.
|
||||||
* @see RequestMapping#params()
|
|
||||||
*/
|
*/
|
||||||
static class ParamExpression extends AbstractNameValueExpression<String> {
|
static class ParamExpression extends AbstractNameValueExpression<String> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,8 @@ import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.util.UrlPathHelper;
|
import org.springframework.web.util.UrlPathHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logical disjunction (' || ') request condition that matches a request against a set of URL path patterns.
|
* 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.
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
|
|
@ -52,9 +51,17 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
private final boolean useSuffixPatternMatch;
|
private final boolean useSuffixPatternMatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link PatternsRequestCondition} with the given URL patterns.
|
* Creates a new instance with the given URL patterns.
|
||||||
* Each pattern that is not empty and does not start with "/" is prepended with "/".
|
* 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 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 urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
|
||||||
* @param pathMatcher a {@link PathMatcher} for pattern path matching
|
* @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.
|
* Private constructor accepting a collection of 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 PatternsRequestCondition(Collection<String> patterns,
|
private PatternsRequestCondition(Collection<String> patterns,
|
||||||
UrlPathHelper urlPathHelper,
|
UrlPathHelper urlPathHelper,
|
||||||
|
|
@ -93,6 +87,10 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
|
||||||
this.useSuffixPatternMatch = useSuffixPatternMatch;
|
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) {
|
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
|
||||||
if (patterns == null) {
|
if (patterns == null) {
|
||||||
return Collections.emptySet();
|
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
|
* <li>If there are patterns in both instances, combine the patterns in "this" with
|
||||||
* the patterns in "other" using {@link PathMatcher#combine(String, String)}.
|
* the patterns in "other" using {@link PathMatcher#combine(String, String)}.
|
||||||
* <li>If only one instance has patterns, use them.
|
* <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>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public PatternsRequestCondition combine(PatternsRequestCondition other) {
|
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
|
* Checks if any of the patterns match the given request and returns an instance
|
||||||
* to contain matching patterns, sorted via {@link PathMatcher#getPatternComparator(String)}.
|
* 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:
|
* <p>A matching pattern is obtained by making checks in the following order:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Direct match
|
* <li>Direct match
|
||||||
* <li>A pattern match with ".*" appended assuming the pattern already doesn't contain "."
|
* <li>Pattern match with ".*" appended if the pattern doesn't already contain a "."
|
||||||
* <li>A pattern match
|
* <li>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 end in "/"
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
*
|
*
|
||||||
* @return the same instance if the condition contains no patterns;
|
* @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) {
|
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||||
if (patterns.isEmpty()) {
|
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"
|
* Compare the two conditions based on the URL patterns they contain.
|
||||||
* matches the request more, and greater than one if "other" matches the request more. Patterns are
|
* Patterns are compared one at a time, from top to bottom via
|
||||||
* compared one at a time, from top to bottom via {@link PathMatcher#getPatternComparator(String)}.
|
* {@link PathMatcher#getPatternComparator(String)}. If all compared
|
||||||
* If all compared patterns match equally, but one instance has more patterns, it is a closer match.
|
* 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)}
|
* <p>It is assumed that both instances have been obtained via
|
||||||
* to ensure they contain only patterns that match the request and are sorted with the best matches on top.
|
* {@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) {
|
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
|
||||||
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
|
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.web.servlet.mvc.condition;
|
package org.springframework.web.servlet.mvc.condition;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
@ -33,14 +32,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logical disjunction (' || ') request condition to match requests against producible
|
* A logical disjunction (' || ') request condition to match a request's 'Accept' header
|
||||||
* media type expressions.
|
* to a list of media type expressions. Two kinds of media type expressions are
|
||||||
*
|
* supported, which are described in {@link RequestMapping#produces()} and
|
||||||
* <p>For details on the syntax of the expressions see {@link RequestMapping#consumes()}.
|
* {@link RequestMapping#headers()} where the header name is 'Accept'.
|
||||||
* If the condition is created without media type expressions, it matches to every request.
|
* Regardless of which syntax is used, the semantics are the same.
|
||||||
*
|
|
||||||
* <p>This request condition is also capable of parsing header expressions by selecting
|
|
||||||
* 'Accept' header expressions and converting them to prodicuble media type expressions.
|
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
|
@ -51,26 +47,27 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
||||||
private final List<ProduceMediaTypeExpression> expressions;
|
private final List<ProduceMediaTypeExpression> expressions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ProducesRequestCondition} with the given producible media type expressions.
|
* Creates a new instance from 0 or more "produces" expressions.
|
||||||
* @param produces the expressions to parse; if 0 the condition matches to every request
|
* @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) {
|
public ProducesRequestCondition(String... produces) {
|
||||||
this(produces, null);
|
this(parseExpressions(produces, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ProducesRequestCondition} with the given header and produces expressions.
|
* Creates a new instance with "produces" and "header" expressions. "Header" expressions
|
||||||
* In addition to produces expressions, {@code "Accept"} header expressions are extracted and treated as
|
* where the header name is not 'Accept' or have no header value defined are ignored.
|
||||||
* producible media type expressions.
|
* If 0 expressions are provided in total, the condition matches to every request
|
||||||
* @param produces the produces expressions to parse; if 0, the condition matches to all requests
|
* @param produces expressions with the syntax described in {@link RequestMapping#produces()}
|
||||||
* @param headers the header expression to parse; if 0, the condition matches to all requests
|
* @param headers expressions with the syntax described in {@link RequestMapping#headers()}
|
||||||
*/
|
*/
|
||||||
public ProducesRequestCondition(String[] produces, String[] headers) {
|
public ProducesRequestCondition(String[] produces, String[] headers) {
|
||||||
this(parseExpressions(produces, headers));
|
this(parseExpressions(produces, headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A private constructor.
|
* Private constructor accepting parsed media type expressions.
|
||||||
*/
|
*/
|
||||||
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions) {
|
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions) {
|
||||||
this.expressions = new ArrayList<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() {
|
public Set<MediaType> getMediaTypes() {
|
||||||
Set<MediaType> result = new LinkedHashSet<MediaType>();
|
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() {
|
public boolean isEmpty() {
|
||||||
return expressions.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.
|
* Returns the "other" instance if it has any expressions; returns "this"
|
||||||
* In other words "other" takes precedence over "this" as long as it contains any expressions.
|
* instance otherwise. Practically that means a method-level "produces"
|
||||||
* <p>Example: method-level "produces" overrides type-level "produces" condition.
|
* overrides a type-level "produces" condition.
|
||||||
*/
|
*/
|
||||||
public ProducesRequestCondition combine(ProducesRequestCondition other) {
|
public ProducesRequestCondition combine(ProducesRequestCondition other) {
|
||||||
return !other.expressions.isEmpty() ? other : this;
|
return !other.expressions.isEmpty() ? other : this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if any of the producible media type expressions match the given request and returns an instance that
|
* Checks if any of the contained media type expressions match the given
|
||||||
* is guaranteed to contain matching media type expressions only.
|
* 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
|
* @param request the current request
|
||||||
*
|
*
|
||||||
* @return the same instance if the condition contains no expressions;
|
* @return the same instance if there are no expressions;
|
||||||
* or a new condition with matching expressions; or {@code null} if no expressions match.
|
* or a new condition with matching expressions;
|
||||||
|
* or {@code null} if no expressions match.
|
||||||
*/
|
*/
|
||||||
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
|
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||||
if (isEmpty()) {
|
if (isEmpty()) {
|
||||||
|
|
@ -158,15 +158,26 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns:
|
* Compares this and another Produces condition as follows:
|
||||||
* <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>
|
|
||||||
*
|
*
|
||||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
* <ol>
|
||||||
* and each instance contains the matching producible media type expression only or is otherwise empty.
|
* <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) {
|
public int compareTo(ProducesRequestCondition other, HttpServletRequest request) {
|
||||||
String acceptHeader = request.getHeader("Accept");
|
String acceptHeader = request.getHeader("Accept");
|
||||||
|
|
@ -174,6 +185,11 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
||||||
MediaType.sortByQualityValue(acceptedMediaTypes);
|
MediaType.sortByQualityValue(acceptedMediaTypes);
|
||||||
|
|
||||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||||
|
if (acceptedMediaType.equals(MediaType.ALL)) {
|
||||||
|
if (isOneEmptyButNotBoth(other)) {
|
||||||
|
return this.isEmpty() ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
int thisIndex = this.indexOfMediaType(acceptedMediaType);
|
int thisIndex = this.indexOfMediaType(acceptedMediaType);
|
||||||
int otherIndex = other.indexOfMediaType(acceptedMediaType);
|
int otherIndex = other.indexOfMediaType(acceptedMediaType);
|
||||||
if (thisIndex != otherIndex) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,9 +222,12 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
||||||
return -1;
|
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.
|
* Parses and matches a single media type expression to a request's 'Accept' header.
|
||||||
* @see RequestMapping#produces()
|
|
||||||
*/
|
*/
|
||||||
static class ProduceMediaTypeExpression extends MediaTypeExpression {
|
static class ProduceMediaTypeExpression extends MediaTypeExpression {
|
||||||
|
|
||||||
|
|
@ -214,10 +240,10 @@ public final class ProducesRequestCondition extends AbstractRequestCondition<Pro
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean match(HttpServletRequest request, MediaType mediaType) {
|
protected boolean matchMediaType(HttpServletRequest request) {
|
||||||
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
|
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
|
||||||
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
for (MediaType acceptedMediaType : acceptedMediaTypes) {
|
||||||
if (mediaType.isCompatibleWith(acceptedMediaType)) {
|
if (getMediaType().isCompatibleWith(acceptedMediaType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
/**
|
/**
|
||||||
* The contract for request conditions.
|
* The contract for request conditions.
|
||||||
*
|
*
|
||||||
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to a request via
|
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to
|
||||||
* {@link #getMatchingCondition(HttpServletRequest)}, and compared to each other via
|
* a request via {@link #getMatchingCondition(HttpServletRequest)}, and compared
|
||||||
* {@link #compareTo(Object, HttpServletRequest)} to determine which matches a request more closely.
|
* 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 Rossen Stoyanchev
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
|
|
@ -36,27 +38,33 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
public interface RequestCondition<T> {
|
public interface RequestCondition<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the rules for combining this condition (i.e. the current instance) with another condition.
|
* Defines the rules for combining this condition (i.e. the current instance)
|
||||||
* For example combining type- and method-level {@link RequestMapping} conditions.
|
* with another condition. For example combining type- and method-level
|
||||||
|
* {@link RequestMapping} conditions.
|
||||||
*
|
*
|
||||||
* @param other the condition to combine with.
|
* @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);
|
T combine(T other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if this condition matches the given request and returns a potentially new request condition
|
* Checks if this condition matches the given request and returns a
|
||||||
* with content tailored to the current request. For example a condition with URL patterns might return
|
* potentially new request condition with content tailored to the
|
||||||
* a new condition that contains matching patterns sorted with best matching patterns on top.
|
* 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);
|
T getMatchingCondition(HttpServletRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares this condition to another condition in the context of a specific request. This method assumes
|
* Compares this condition to another condition in the context of
|
||||||
* both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)} to ensure they
|
* a specific request. This method assumes both instances have
|
||||||
* have content relevant to current request only.
|
* been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
||||||
|
* to ensure they have content relevant to current request only.
|
||||||
*/
|
*/
|
||||||
int compareTo(T other, HttpServletRequest request);
|
int compareTo(T other, HttpServletRequest request);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,8 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logical disjunction (' || ') request condition that matches a request against a set of {@link RequestMethod}s.
|
* 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.
|
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
|
@ -41,8 +40,9 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
|
||||||
private final Set<RequestMethod> methods;
|
private final Set<RequestMethod> methods;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link RequestMethodsRequestCondition} with the given request methods.
|
* 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.
|
* @param requestMethods 0 or more HTTP request methods;
|
||||||
|
* if, 0 the condition will match to every request.
|
||||||
*/
|
*/
|
||||||
public RequestMethodsRequestCondition(RequestMethod... requestMethods) {
|
public RequestMethodsRequestCondition(RequestMethod... requestMethods) {
|
||||||
this(asList(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) {
|
public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) {
|
||||||
Set<RequestMethod> set = new LinkedHashSet<RequestMethod>(this.methods);
|
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
|
* Checks if any of the HTTP request methods match the given request and returns
|
||||||
* contain the matching request method.
|
* an instance that contains the matching request method only.
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @return the same instance if the condition contains no request method;
|
* @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) {
|
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
|
||||||
if (methods.isEmpty()) {
|
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.
|
* <li>Greater than 1 "other" has an HTTP request method but "this" doesn't.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
|
* <p>It is assumed that both instances have been obtained via
|
||||||
* and therefore each instance contains the matching HTTP request method only or is otherwise empty.
|
* {@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) {
|
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
|
||||||
return other.methods.size() - this.methods.size();
|
return other.methods.size() - this.methods.size();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,14 @@
|
||||||
|
|
||||||
package org.springframework.web.servlet.mvc.annotation;
|
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.beans.PropertyEditorSupport;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -39,6 +47,7 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
|
@ -51,7 +60,6 @@ import javax.validation.constraints.NotNull;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||||
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
|
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
|
||||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
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.servlet.view.InternalResourceViewResolver;
|
||||||
import org.springframework.web.util.NestedServletException;
|
import org.springframework.web.util.NestedServletException;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
|
@ -1870,6 +1876,40 @@ public class ServletAnnotationControllerTests {
|
||||||
assertEquals(405, response.getStatus());
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Controllers
|
* Controllers
|
||||||
|
|
@ -3152,4 +3192,19 @@ public class ServletAnnotationControllerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void compareToSingle() {
|
public void compareToWithSingleExpression() {
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.addHeader("Accept", "text/plain");
|
request.addHeader("Accept", "text/plain");
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ public class ProducesRequestConditionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compareToMultiple() {
|
public void compareToMultipleExpressions() {
|
||||||
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
|
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
|
||||||
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
|
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
|
||||||
|
|
||||||
|
|
@ -147,22 +147,10 @@ public class ProducesRequestConditionTests {
|
||||||
|
|
||||||
result = condition2.compareTo(condition1, request);
|
result = condition2.compareTo(condition1, request);
|
||||||
assertTrue("Invalid comparison result: " + result, result > 0);
|
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
|
@Test
|
||||||
public void compareToMultipleAccept() {
|
public void compareToMultipleExpressionsAndMultipeAcceptHeaderValues() {
|
||||||
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
|
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
|
||||||
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
|
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
|
||||||
|
|
||||||
|
|
@ -187,6 +175,35 @@ public class ProducesRequestConditionTests {
|
||||||
assertTrue("Invalid comparison result: " + result, result < 0);
|
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
|
@Test
|
||||||
public void combine() {
|
public void combine() {
|
||||||
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
|
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
|
||||||
|
|
|
||||||
|
|
@ -1419,6 +1419,40 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
assertEquals(405, response.getStatus());
|
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
|
* 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:
|
// Test cases deleted from the original SevletAnnotationControllerTests:
|
||||||
|
|
||||||
// @Ignore("Controller interface => no method-level @RequestMapping annotation")
|
// @Ignore("Controller interface => no method-level @RequestMapping annotation")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue