提交 96b0752d 编写于 作者: R Rossen Stoyanchev

SPR-7812 RequestCondition refactoring with the possibility for custom request conditions in mind.

上级 3a332e55
......@@ -16,31 +16,19 @@
package org.springframework.web.servlet.mvc.method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
/**
* Contains a set of conditions to match to a given request such as URL patterns, HTTP methods, request parameters
* and headers.
*
* <p>Two {@link RequestMappingInfo}s can be combined resulting in a new {@link RequestMappingInfo} with conditions
* from both. A {@link RequestMappingInfo} can also match itself to an HTTP request resulting in a new
* {@link RequestMappingInfo} with the subset of conditions relevant to the request.
* Contains request mapping conditions to be matched to a given request.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
......@@ -48,7 +36,7 @@ import org.springframework.web.servlet.mvc.method.condition.RequestMethodsReques
*/
public final class RequestMappingInfo {
private final Set<String> patterns;
private final PatternsRequestCondition patternsCondition;
private final RequestMethodsRequestCondition methodsCondition;
......@@ -63,24 +51,15 @@ public final class RequestMappingInfo {
private int hash;
/**
* Creates a new {@code RequestMappingInfo} instance with the given URL patterns and HTTP methods.
*
* <p>Package protected for testing purposes.
*/
RequestMappingInfo(Collection<String> patterns, RequestMethod[] methods) {
this(patterns, RequestConditionFactory.parseMethods(methods), null, null, null, null);
}
/**
* Creates a new {@code RequestMappingInfo} instance with a full set of conditions.
* Creates a new {@code RequestMappingInfo} instance.
*/
public RequestMappingInfo(Collection<String> patterns,
RequestMethodsRequestCondition methodsCondition,
ParamsRequestCondition paramsCondition,
HeadersRequestCondition headersCondition,
ConsumesRequestCondition consumesCondition,
ProducesRequestCondition producesCondition) {
this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns));
public RequestMappingInfo(PatternsRequestCondition patternsCondition,
RequestMethodsRequestCondition methodsCondition,
ParamsRequestCondition paramsCondition,
HeadersRequestCondition headersCondition,
ConsumesRequestCondition consumesCondition,
ProducesRequestCondition producesCondition) {
this.patternsCondition = patternsCondition != null ? patternsCondition : new PatternsRequestCondition();
this.methodsCondition = methodsCondition != null ? methodsCondition : new RequestMethodsRequestCondition();
this.paramsCondition = paramsCondition != null ? paramsCondition : new ParamsRequestCondition();
this.headersCondition = headersCondition != null ? headersCondition : new HeadersRequestCondition();
......@@ -88,67 +67,52 @@ public final class RequestMappingInfo {
this.producesCondition = producesCondition != null ? producesCondition : new ProducesRequestCondition();
}
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
if (patterns == null) {
return Collections.emptySet();
}
Set<String> result = new LinkedHashSet<String>(patterns.size());
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
private static <T> Set<T> asUnmodifiableSet(Collection<T> collection) {
if (collection == null) {
return Collections.emptySet();
}
Set<T> result = new LinkedHashSet<T>(collection);
return Collections.unmodifiableSet(result);
/**
* Package protected, used for testing.
*/
RequestMappingInfo(String[] patterns, RequestMethod... methods) {
this(new PatternsRequestCondition(patterns), new RequestMethodsRequestCondition(methods), null, null, null, null);
}
/**
* Returns the patterns of this request mapping info.
*/
public Set<String> getPatterns() {
return patterns;
public PatternsRequestCondition getPatternsCondition() {
return patternsCondition;
}
/**
* Returns the request method conditions of this request mapping info.
* Returns the request method condition of this request mapping info.
*/
public RequestMethodsRequestCondition getMethods() {
public RequestMethodsRequestCondition getMethodsCondition() {
return methodsCondition;
}
/**
* Returns the request parameters conditions of this request mapping info.
* Returns the request parameters condition of this request mapping info.
*/
public ParamsRequestCondition getParams() {
public ParamsRequestCondition getParamsCondition() {
return paramsCondition;
}
/**
* Returns the request headers conditions of this request mapping info.
* Returns the request headers condition of this request mapping info.
*/
public HeadersRequestCondition getHeaders() {
public HeadersRequestCondition getHeadersCondition() {
return headersCondition;
}
/**
* Returns the request consumes conditions of this request mapping info.
* Returns the request consumes condition of this request mapping info.
*/
public ConsumesRequestCondition getConsumes() {
public ConsumesRequestCondition getConsumesCondition() {
return consumesCondition;
}
/**
* Returns the request produces conditions of this request mapping info.
* Returns the request produces condition of this request mapping info.
*/
public ProducesRequestCondition getProduces() {
public ProducesRequestCondition getProducesCondition() {
return producesCondition;
}
......@@ -167,110 +131,44 @@ public final class RequestMappingInfo {
* <li>Consumes are combined as per {@link ConsumesRequestCondition#combine(ConsumesRequestCondition)}.
* </ul>
* @param methodKey the key to combine with
* @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns
* @return a new request mapping info containing conditions from both keys
*/
public RequestMappingInfo combine(RequestMappingInfo methodKey, PathMatcher pathMatcher) {
Set<String> patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
public RequestMappingInfo combine(RequestMappingInfo methodKey) {
PatternsRequestCondition patterns = this.patternsCondition.combine(methodKey.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(methodKey.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(methodKey.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(methodKey.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(methodKey.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(methodKey.producesCondition);
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces);
}
private static Set<String> combinePatterns(Collection<String> typePatterns,
Collection<String> methodPatterns,
PathMatcher pathMatcher) {
Set<String> result = new LinkedHashSet<String>();
if (!typePatterns.isEmpty() && !methodPatterns.isEmpty()) {
for (String pattern1 : typePatterns) {
for (String pattern2 : methodPatterns) {
result.add(pathMatcher.combine(pattern1, pattern2));
}
}
}
else if (!typePatterns.isEmpty()) {
result.addAll(typePatterns);
}
else if (!methodPatterns.isEmpty()) {
result.addAll(methodPatterns);
}
else {
result.add("");
}
return result;
}
/**
* Returns a new {@code RequestMappingInfo} that contains all conditions of this key that are relevant to the request.
* <ul>
* <li>The list of URL path patterns is trimmed to contain the patterns that match the URL with matching patterns
* sorted via {@link PathMatcher#getPatternComparator(String)}.
* <li>The list of HTTP methods is trimmed to contain only the method of the request.
* <li>Request parameter and request header conditions are included in full.
* <li>The list of consumes conditions is trimmed and sorted to match the request "Content-Type" header.
* </ul>
* @param lookupPath mapping lookup path within the current servlet mapping if applicable
* Returns a new {@code RequestMappingInfo} with conditions relevant to the current request.
* For example the list of URL path patterns is trimmed to contain the patterns that match the URL.
* @param request the current request
* @param pathMatcher to check for matching patterns
* @return a new request mapping info that contains all matching attributes, or {@code null} if not all conditions match
*/
public RequestMappingInfo getMatchingRequestMapping(String lookupPath, HttpServletRequest request, PathMatcher pathMatcher) {
RequestMethodsRequestCondition matchingMethodCondition = methodsCondition.getMatchingCondition(request);
ParamsRequestCondition matchingParamsCondition = paramsCondition.getMatchingCondition(request);
HeadersRequestCondition matchingHeadersCondition = headersCondition.getMatchingCondition(request);
ConsumesRequestCondition matchingConsumesCondition = consumesCondition.getMatchingCondition(request);
ProducesRequestCondition matchingProducesCondition = producesCondition.getMatchingCondition(request);
if (matchingMethodCondition == null || matchingParamsCondition == null || matchingHeadersCondition == null ||
matchingConsumesCondition == null || matchingProducesCondition == null) {
public RequestMappingInfo getMatchingRequestMapping(HttpServletRequest request) {
RequestMethodsRequestCondition matchingMethod = methodsCondition.getMatchingCondition(request);
ParamsRequestCondition matchingParams = paramsCondition.getMatchingCondition(request);
HeadersRequestCondition matchingHeaders = headersCondition.getMatchingCondition(request);
ConsumesRequestCondition matchingConsumes = consumesCondition.getMatchingCondition(request);
ProducesRequestCondition matchingProduces = producesCondition.getMatchingCondition(request);
if (matchingMethod == null || matchingParams == null || matchingHeaders == null ||
matchingConsumes == null || matchingProduces == null) {
return null;
}
else {
List<String> matchingPatterns = getMatchingPatterns(lookupPath, pathMatcher);
if (!matchingPatterns.isEmpty()) {
return new RequestMappingInfo(matchingPatterns, matchingMethodCondition, matchingParamsCondition,
matchingHeadersCondition, matchingConsumesCondition, matchingProducesCondition);
}
else {
return null;
}
PatternsRequestCondition matchingPatterns = patternsCondition.getMatchingCondition(request);
if (matchingPatterns != null) {
return new RequestMappingInfo(matchingPatterns, matchingMethod,
matchingParams, matchingHeaders, matchingConsumes,
matchingProduces);
}
}
private List<String> getMatchingPatterns(String lookupPath, PathMatcher pathMatcher) {
List<String> matchingPatterns = new ArrayList<String>();
for (String pattern : this.patterns) {
String matchingPattern = getMatchingPattern(pattern, lookupPath, pathMatcher);
if (matchingPattern != null) {
matchingPatterns.add(matchingPattern);
}
}
Collections.sort(matchingPatterns, pathMatcher.getPatternComparator(lookupPath));
return matchingPatterns;
}
private String getMatchingPattern(String pattern, String lookupPath, PathMatcher pathMatcher) {
if (pattern.equals(lookupPath)) {
return pattern;
}
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
if (pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
boolean endsWithSlash = pattern.endsWith("/");
if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
return pattern +"/";
}
return null;
}
......@@ -281,7 +179,7 @@ public final class RequestMappingInfo {
}
if (obj != null && obj instanceof RequestMappingInfo) {
RequestMappingInfo other = (RequestMappingInfo) obj;
return (this.patterns.equals(other.patterns) &&
return (this.patternsCondition.equals(other.patternsCondition) &&
this.methodsCondition.equals(other.methodsCondition) &&
this.paramsCondition.equals(other.paramsCondition) &&
this.headersCondition.equals(other.headersCondition) &&
......@@ -295,7 +193,7 @@ public final class RequestMappingInfo {
public int hashCode() {
int result = hash;
if (result == 0) {
result = patterns.hashCode();
result = patternsCondition.hashCode();
result = 31 * result + methodsCondition.hashCode();
result = 31 * result + paramsCondition.hashCode();
result = 31 * result + headersCondition.hashCode();
......@@ -309,7 +207,7 @@ public final class RequestMappingInfo {
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
builder.append(patterns);
builder.append(patternsCondition);
builder.append(",methods=").append(methodsCondition);
builder.append(",params=").append(paramsCondition);
builder.append(",headers=").append(headersCondition);
......
......@@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -61,7 +60,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
@Override
protected Set<String> getMappingPaths(RequestMappingInfo mapping) {
return mapping.getPatterns();
return mapping.getPatternsCondition().getPatterns();
}
/**
......@@ -73,7 +72,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo mapping,
String lookupPath,
HttpServletRequest request) {
return mapping.getMatchingRequestMapping(lookupPath, request, getPathMatcher());
return mapping.getMatchingRequestMapping(request);
}
/**
......@@ -81,7 +80,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
*/
@Override
protected Comparator<RequestMappingInfo> getMappingComparator(String lookupPath, HttpServletRequest request) {
return new RequestMappingInfoComparator(lookupPath, request);
return new RequestMappingInfoComparator(request);
}
/**
......@@ -94,12 +93,12 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);
String pattern = info.getPatterns().iterator().next();
String pattern = info.getPatternsCondition().getPatterns().iterator().next();
Map<String, String> uriTemplateVariables = getPathMatcher().extractUriTemplateVariables(pattern, lookupPath);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
if (!info.getProduces().isEmpty()) {
Set<MediaType> mediaTypes = info.getProduces().getMediaTypes();
if (!info.getProducesCondition().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
......@@ -117,18 +116,18 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
Set<MediaType> consumableMediaTypes = new HashSet<MediaType>();
Set<MediaType> producibleMediaTypes = new HashSet<MediaType>();
for (RequestMappingInfo info : requestMappingInfos) {
for (String pattern : info.getPatterns()) {
for (String pattern : info.getPatternsCondition().getPatterns()) {
if (getPathMatcher().match(pattern, lookupPath)) {
if (!info.getMethods().match(request)) {
for (RequestMethod method : info.getMethods().getMethods()) {
if (info.getMethodsCondition().getMatchingCondition(request) == null) {
for (RequestMethod method : info.getMethodsCondition().getMethods()) {
allowedMethods.add(method.name());
}
}
if (!info.getConsumes().match(request)) {
consumableMediaTypes.addAll(info.getConsumes().getMediaTypes());
if (info.getConsumesCondition().getMatchingCondition(request) == null) {
consumableMediaTypes.addAll(info.getConsumesCondition().getMediaTypes());
}
if (!info.getProduces().match(request)) {
producibleMediaTypes.addAll(info.getProduces().getMediaTypes());
if (info.getProducesCondition().getMatchingCondition(request) == null) {
producibleMediaTypes.addAll(info.getProducesCondition().getMediaTypes());
}
}
}
......@@ -152,77 +151,44 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
/**
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context of a specific
* request. For example not all {@link RequestMappingInfo} patterns may apply to the current request. Therefore an
* HttpServletRequest is required as input.
*
* <p>Furthermore, the following assumptions are made about the input RequestMappings: <ul><li>Each RequestMappingInfo
* has been fully matched to the request <li>The RequestMappingInfo contains matched patterns only <li>Patterns are
* ordered with the best matching pattern at the top </ul>
*
* @see RequestMappingInfoHandlerMapping#getMatchingMapping(RequestMappingInfo, String, HttpServletRequest)
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context
* of a specific request. For example only a subset of URL patterns may apply to the current request.
*/
private class RequestMappingInfoComparator implements Comparator<RequestMappingInfo> {
private Comparator<String> patternComparator;
private List<MediaType> requestAcceptHeader;
private final HttpServletRequest request;
public RequestMappingInfoComparator(String lookupPath, HttpServletRequest request) {
this.patternComparator = getPathMatcher().getPatternComparator(lookupPath);
String acceptHeader = request.getHeader("Accept");
this.requestAcceptHeader = MediaType.parseMediaTypes(acceptHeader);
MediaType.sortByQualityValue(this.requestAcceptHeader);
public RequestMappingInfoComparator(HttpServletRequest request) {
this.request = request;
}
public int compare(RequestMappingInfo mapping, RequestMappingInfo otherMapping) {
int result = comparePatterns(mapping.getPatterns(), otherMapping.getPatterns());
int result = mapping.getPatternsCondition().compareTo(otherMapping.getPatternsCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getParams().compareTo(otherMapping.getParams());
result = mapping.getParamsCondition().compareTo(otherMapping.getParamsCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getHeaders().compareTo(otherMapping.getHeaders());
result = mapping.getHeadersCondition().compareTo(otherMapping.getHeadersCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getConsumes().compareTo(otherMapping.getConsumes());
result = mapping.getConsumesCondition().compareTo(otherMapping.getConsumesCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getProduces().compareTo(otherMapping.getProduces(), this.requestAcceptHeader);
result = mapping.getProducesCondition().compareTo(otherMapping.getProducesCondition(), request);
if (result != 0) {
return result;
}
result = mapping.getMethods().compareTo(otherMapping.getMethods());
result = mapping.getMethodsCondition().compareTo(otherMapping.getMethodsCondition(), request);
if (result != 0) {
return result;
}
return 0;
}
private int comparePatterns(Set<String> patterns, Set<String> otherPatterns) {
Iterator<String> iterator = patterns.iterator();
Iterator<String> iteratorOther = otherPatterns.iterator();
while (iterator.hasNext() && iteratorOther.hasNext()) {
int result = patternComparator.compare(iterator.next(), iteratorOther.next());
if (result != 0) {
return result;
}
}
if (iterator.hasNext()) {
return -1;
}
else if (iteratorOther.hasNext()) {
return 1;
}
else {
return 0;
}
}
}
}
......@@ -17,7 +17,6 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
......@@ -25,7 +24,12 @@ import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
/**
* A sub-class of {@link RequestMappingInfoHandlerMapping} that prepares {@link RequestMappingInfo}s
......@@ -64,7 +68,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
RequestMapping typeAnnot = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnot != null) {
RequestMappingInfo typeMapping = createFromRequestMapping(typeAnnot);
return typeMapping.combine(methodMapping, getPathMatcher());
return typeMapping.combine(methodMapping);
}
else {
return methodMapping;
......@@ -75,13 +79,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
}
private static RequestMappingInfo createFromRequestMapping(RequestMapping annotation) {
return new RequestMappingInfo(Arrays.asList(annotation.value()),
RequestConditionFactory.parseMethods(annotation.method()),
RequestConditionFactory.parseParams(annotation.params()),
RequestConditionFactory.parseHeaders(annotation.headers()),
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()),
RequestConditionFactory.parseProduces(annotation.produces(), annotation.headers()));
private RequestMappingInfo createFromRequestMapping(RequestMapping annotation) {
return new RequestMappingInfo(
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher()),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers()));
}
}
......@@ -19,14 +19,15 @@ package org.springframework.web.servlet.mvc.method.condition;
import javax.servlet.http.HttpServletRequest;
/**
* A condition that supports simple "name=value" style expressions as documented in {@link
* org.springframework.web.bind.annotation.RequestMapping#params()} and {@link org.springframework.web.bind.annotation.RequestMapping#headers()}.
* Supports "name=value" style expressions as described in:
* {@link org.springframework.web.bind.annotation.RequestMapping#params()} and
* {@link org.springframework.web.bind.annotation.RequestMapping#headers()}.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
*/
abstract class AbstractNameValueCondition<T> implements RequestCondition {
abstract class AbstractNameValueExpression<T> {
protected final String name;
......@@ -34,7 +35,7 @@ abstract class AbstractNameValueCondition<T> implements RequestCondition {
protected final boolean isNegated;
AbstractNameValueCondition(String expression) {
AbstractNameValueExpression(String expression) {
int separator = expression.indexOf('=');
if (separator == -1) {
this.isNegated = expression.startsWith("!");
......@@ -65,6 +66,20 @@ abstract class AbstractNameValueCondition<T> implements RequestCondition {
protected abstract boolean matchValue(HttpServletRequest request);
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof AbstractNameValueExpression) {
AbstractNameValueExpression<?> other = (AbstractNameValueExpression<?>) obj;
return ((this.name.equalsIgnoreCase(other.name)) &&
(this.value != null ? this.value.equals(other.value) : other.value == null) &&
this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
int result = name.hashCode();
......
......@@ -16,140 +16,194 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition.HeaderExpression;
/**
* Represents a collection of consumes conditions, typically obtained from {@link org.springframework.web.bind.annotation.RequestMapping#consumes()
* &#64;RequestMapping.consumes()}.
*
* 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.
*
* @author Arjen Poutsma
* @see RequestConditionFactory#parseConsumes(String...)
* @see RequestConditionFactory#parseConsumes(String[], String[])
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ConsumesRequestCondition
extends MediaTypesRequestCondition<ConsumesRequestCondition.ConsumeRequestCondition>
implements Comparable<ConsumesRequestCondition> {
public class ConsumesRequestCondition extends RequestConditionSupport<ConsumesRequestCondition> {
ConsumesRequestCondition(Collection<ConsumeRequestCondition> conditions) {
super(conditions);
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
*/
public ConsumesRequestCondition(String... consumes) {
this(consumes, null);
}
ConsumesRequestCondition(String... consumes) {
this(parseConditions(Arrays.asList(consumes)));
/**
* Creates a {@link ConsumesRequestCondition} with the given header and consumes expressions.
* In addition to consume expressions, {@code "Content-Type"} header expressions are extracted
* and treated as consumable media type expressions.
* @param consumes the consumes expressions to parse; 0 matches to all requests
* @param headers the header expression to parse; 0 matches to all requests
*/
public ConsumesRequestCondition(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
}
private static Set<ConsumeRequestCondition> parseConditions(List<String> consumes) {
Set<ConsumeRequestCondition> conditions = new LinkedHashSet<ConsumeRequestCondition>(consumes.size());
for (String consume : consumes) {
conditions.add(new ConsumeRequestCondition(consume));
/**
* Private constructor.
*/
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<ConsumeMediaTypeExpression>(expressions);
Collections.sort(this.expressions);
}
private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name)) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ConsumeMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
return conditions;
if (consumes != null) {
for (String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
}
return result;
}
/**
* Returns the consumable media types contained in all expressions of this condition.
*/
public Set<MediaType> getMediaTypes() {
Set<MediaType> result = new LinkedHashSet<MediaType>();
for (ConsumeMediaTypeExpression expression : expressions) {
result.add(expression.getMediaType());
}
return result;
}
/**
* Creates a default set of consumes request conditions.
* Returns true if this condition contains 0 consumable media type expressions.
*/
public ConsumesRequestCondition() {
this(Collections.<ConsumeRequestCondition>emptySet());
public boolean isEmpty() {
return expressions.isEmpty();
}
@Override
protected Collection<ConsumeMediaTypeExpression> getContent() {
return expressions;
}
@Override
protected boolean isLogicalConjunction() {
return false;
}
/**
* Returns a new {@code RequestCondition} that contains all conditions of this key that match the request.
*
* @param request the request
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
* Returns the "other" instance as long as it contains any expressions; or "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.
*/
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.
*
* @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.
*/
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ConsumeRequestCondition> matchingConditions = new LinkedHashSet<ConsumeRequestCondition>(getConditions());
for (Iterator<ConsumeRequestCondition> iterator = matchingConditions.iterator(); iterator.hasNext();) {
ConsumeRequestCondition condition = iterator.next();
if (!condition.match(request)) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(expressions);
for (Iterator<ConsumeMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ConsumeMediaTypeExpression expression = iterator.next();
if (!expression.match(request)) {
iterator.remove();
}
}
if (matchingConditions.isEmpty()) {
return null;
}
else {
return new ConsumesRequestCondition(matchingConditions);
}
return (result.isEmpty()) ? null : new ConsumesRequestCondition(result);
}
/**
* Combines this collection of request condition with another. Returns {@code other}, unless it is empty.
*
* @param other the condition to combine with
* 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
* </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.
*/
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
return !other.isEmpty() ? other : this;
}
public int compareTo(ConsumesRequestCondition other) {
MediaTypeRequestCondition thisMostSpecificCondition = this.getMostSpecificCondition();
MediaTypeRequestCondition otherMostSpecificCondition = other.getMostSpecificCondition();
if (thisMostSpecificCondition == null && otherMostSpecificCondition == null) {
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
if (expressions.isEmpty() && other.expressions.isEmpty()) {
return 0;
}
else if (thisMostSpecificCondition == null) {
else if (expressions.isEmpty()) {
return 1;
}
else if (otherMostSpecificCondition == null) {
else if (other.expressions.isEmpty()) {
return -1;
}
else {
return thisMostSpecificCondition.compareTo(otherMostSpecificCondition);
}
}
private MediaTypeRequestCondition getMostSpecificCondition() {
if (!isEmpty()) {
return getSortedConditions().get(0);
}
else {
return null;
return expressions.get(0).compareTo(other.expressions.get(0));
}
}
static class ConsumeRequestCondition extends MediaTypesRequestCondition.MediaTypeRequestCondition {
/**
* Parsing and request matching logic for consumable media type expressions.
* @see RequestMapping#consumes()
*/
static class ConsumeMediaTypeExpression extends MediaTypeExpression {
ConsumeRequestCondition(String expression) {
ConsumeMediaTypeExpression(String expression) {
super(expression);
}
ConsumeRequestCondition(MediaType mediaType, boolean negated) {
ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
@Override
protected boolean match(HttpServletRequest request, MediaType mediaType) {
MediaType contentType = getContentType(request);
return mediaType.includes(contentType);
}
private MediaType getContentType(HttpServletRequest request) {
if (StringUtils.hasLength(request.getContentType())) {
return MediaType.parseMediaType(request.getContentType());
}
else {
return MediaType.APPLICATION_OCTET_STREAM;
}
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM ;
return mediaType.includes(contentType);
}
}
}
......@@ -16,79 +16,117 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Represents a collection of header request conditions, typically obtained from {@link
* org.springframework.web.bind.annotation.RequestMapping#headers() @RequestMapping.headers()}.
*
* A logical conjunction (' && ') request condition that matches a request against a set of header expressions.
*
* <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}.
*
* @author Arjen Poutsma
* @see RequestConditionFactory#parseHeaders(String...)
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HeadersRequestCondition
extends LogicalConjunctionRequestCondition<HeadersRequestCondition.HeaderRequestCondition>
implements Comparable<HeadersRequestCondition> {
public class HeadersRequestCondition extends RequestConditionSupport<HeadersRequestCondition> {
private final Set<HeaderExpression> expressions;
HeadersRequestCondition(Collection<HeaderRequestCondition> conditions) {
super(conditions);
/**
* 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.
*/
public HeadersRequestCondition(String... headers) {
this(parseExpressions(headers));
}
private HeadersRequestCondition(Collection<HeaderExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<HeaderExpression>(conditions));
}
private static Collection<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<HeaderExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Accept".equalsIgnoreCase(expr.name) || "Content-Type".equalsIgnoreCase(expr.name)) {
continue;
}
expressions.add(expr);
}
}
return expressions;
}
HeadersRequestCondition(String... headers) {
this(parseConditions(Arrays.asList(headers)));
@Override
protected Collection<HeaderExpression> getContent() {
return expressions;
}
private static Set<HeaderRequestCondition> parseConditions(Collection<String> params) {
Set<HeaderRequestCondition> conditions = new LinkedHashSet<HeaderRequestCondition>(params.size());
for (String param : params) {
conditions.add(new HeaderRequestCondition(param));
}
return conditions;
@Override
protected boolean isLogicalConjunction() {
return true;
}
/**
* Creates an empty set of header request conditions.
* Returns a new instance with the union of the header expressions from "this" and the "other" instance.
*/
public HeadersRequestCondition() {
this(Collections.<HeaderRequestCondition>emptySet());
public HeadersRequestCondition combine(HeadersRequestCondition other) {
Set<HeaderExpression> set = new LinkedHashSet<HeaderExpression>(this.expressions);
set.addAll(other.expressions);
return new HeadersRequestCondition(set);
}
/**
* Returns a new {@code RequestCondition} that contains all conditions that match the request.
*
* @param request the request
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
* Returns "this" instance if the request matches to all header expressions; or {@code null} otherwise.
*/
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
return match(request) ? this : null;
for (HeaderExpression expression : expressions) {
if (!expression.match(request)) {
return null;
}
}
return this;
}
/**
* Combines this collection of request condition with another by combining all header request conditions into a
* logical AND.
*
* @param other the condition to combine with
* Returns:
* <ul>
* <li>0 if the two conditions have the same number of header expressions
* <li>Less than 1 if "this" instance has more header expressions
* <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.
*/
public HeadersRequestCondition combine(HeadersRequestCondition other) {
Set<HeaderRequestCondition> conditions = new LinkedHashSet<HeaderRequestCondition>(getConditions());
conditions.addAll(other.getConditions());
return new HeadersRequestCondition(conditions);
}
public int compareTo(HeadersRequestCondition other) {
return other.getConditions().size() - this.getConditions().size();
public int compareTo(HeadersRequestCondition other, HttpServletRequest request) {
return other.expressions.size() - this.expressions.size();
}
static class HeaderRequestCondition extends AbstractNameValueCondition<String> {
/**
* Parsing and request matching logic for header expressions.
* @see RequestMapping#headers()
*/
static class HeaderExpression extends AbstractNameValueExpression<String> {
public HeaderRequestCondition(String expression) {
public HeaderExpression(String expression) {
super(expression);
}
......@@ -107,20 +145,6 @@ public class HeadersRequestCondition
return value.equals(request.getHeader(name));
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof HeaderRequestCondition) {
HeaderRequestCondition other = (HeaderRequestCondition) obj;
return ((this.name.equalsIgnoreCase(other.name)) &&
(this.value != null ? this.value.equals(other.value) : other.value == null) &&
this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
int result = name.toLowerCase().hashCode();
......@@ -128,7 +152,5 @@ public class HeadersRequestCondition
result = 31 * result + (isNegated ? 1 : 0);
return result;
}
}
}
}
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Collection;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
/**
* {@link RequestCondition} implementation that represents a logical AND (i.e. &&).
*
* @author Arjen Poutsma
* @since 3.1
*/
class LogicalConjunctionRequestCondition<T extends RequestCondition> extends RequestConditionComposite<T> {
LogicalConjunctionRequestCondition(Collection<T> conditions) {
super(conditions);
}
public boolean match(HttpServletRequest request) {
Set<T> conditions = getConditions();
if (isEmpty()) {
return true;
}
for (T condition : conditions) {
if (!condition.match(request)) {
return false;
}
}
return true;
}
@Override
protected String getToStringInfix() {
return " && ";
}
}
......@@ -16,36 +16,81 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* {@link RequestCondition} implementation that represents a logical OR (i.e. ||).
*
* Supports media type expressions as described in:
* {@link RequestMapping#consumes()} and {@link RequestMapping#produces()}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
class LogicalDisjunctionRequestCondition<T extends RequestCondition> extends RequestConditionComposite<T> {
abstract class MediaTypeExpression implements Comparable<MediaTypeExpression> {
private final MediaType mediaType;
private final boolean isNegated;
MediaTypeExpression(String expression) {
if (expression.startsWith("!")) {
isNegated = true;
expression = expression.substring(1);
}
else {
isNegated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
LogicalDisjunctionRequestCondition(Collection<T> conditions) {
super(conditions);
MediaTypeExpression(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
isNegated = negated;
}
public boolean match(HttpServletRequest request) {
if (isEmpty()) {
boolean match = match(request, this.mediaType);
return !isNegated ? match : !match;
}
protected abstract boolean match(HttpServletRequest request, MediaType mediaType);
MediaType getMediaType() {
return mediaType;
}
public int compareTo(MediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
for (RequestCondition condition : getConditions()) {
if (condition.match(request)) {
return true;
}
if (obj != null && getClass().equals(obj.getClass())) {
MediaTypeExpression other = (MediaTypeExpression) obj;
return (this.mediaType.equals(other.mediaType)) && (this.isNegated == other.isNegated);
}
return false;
}
@Override
protected String getToStringInfix() {
return " || ";
public int hashCode() {
return mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isNegated) {
builder.append('!');
}
builder.append(mediaType.toString());
return builder.toString();
}
}
}
\ No newline at end of file
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.condition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
/**
* @author Arjen Poutsma
*/
class MediaTypesRequestCondition<T extends MediaTypesRequestCondition.MediaTypeRequestCondition>
extends LogicalDisjunctionRequestCondition<T> {
private final List<T> sortedConditions;
public MediaTypesRequestCondition(Collection<T> conditions) {
super(conditions);
sortedConditions = new ArrayList<T>(conditions);
Collections.sort(sortedConditions);
}
protected List<T> getSortedConditions() {
return sortedConditions;
}
/**
* Returns all {@link MediaType}s contained in this condition.
*/
public Set<MediaType> getMediaTypes() {
Set<MediaType> result = new LinkedHashSet<MediaType>();
for (MediaTypeRequestCondition condition : getConditions()) {
result.add(condition.getMediaType());
}
return result;
}
/**
* @author Arjen Poutsma
*/
protected abstract static class MediaTypeRequestCondition
implements RequestCondition, Comparable<MediaTypeRequestCondition> {
private final MediaType mediaType;
private final boolean isNegated;
MediaTypeRequestCondition(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
isNegated = negated;
}
MediaTypeRequestCondition(String expression) {
if (expression.startsWith("!")) {
isNegated = true;
expression = expression.substring(1);
}
else {
isNegated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
public boolean match(HttpServletRequest request) {
boolean match = match(request, this.mediaType);
return !isNegated ? match : !match;
}
protected abstract boolean match(HttpServletRequest request, MediaType mediaType);
MediaType getMediaType() {
return mediaType;
}
public int compareTo(MediaTypeRequestCondition other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && getClass().equals(obj.getClass())) {
MediaTypeRequestCondition other = (MediaTypeRequestCondition) obj;
return (this.mediaType.equals(other.mediaType)) && (this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
return mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isNegated) {
builder.append('!');
}
builder.append(mediaType.toString());
return builder.toString();
}
}
}
......@@ -16,80 +16,106 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.util.WebUtils;
/**
* Represents a collection of parameter request conditions, typically obtained from {@link
* org.springframework.web.bind.annotation.RequestMapping#params() @RequestMapping.params()}.
*
* 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.
*
* @author Arjen Poutsma
* @see RequestConditionFactory#parseParams(String...)
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ParamsRequestCondition
extends LogicalConjunctionRequestCondition<ParamsRequestCondition.ParamRequestCondition>
implements Comparable<ParamsRequestCondition> {
public class ParamsRequestCondition extends RequestConditionSupport<ParamsRequestCondition> {
private ParamsRequestCondition(Collection<ParamRequestCondition> conditions) {
super(conditions);
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.
*/
public ParamsRequestCondition(String... params) {
this(parseExpressions(params));
}
ParamsRequestCondition(String... params) {
this(parseConditions(Arrays.asList(params)));
private ParamsRequestCondition(Collection<ParamExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<ParamExpression>(conditions));
}
private static Set<ParamRequestCondition> parseConditions(List<String> params) {
Set<ParamRequestCondition> conditions = new LinkedHashSet<ParamRequestCondition>(params.size());
for (String param : params) {
conditions.add(new ParamRequestCondition(param));
private static Collection<ParamExpression> parseExpressions(String... params) {
Set<ParamExpression> expressions = new LinkedHashSet<ParamExpression>();
if (params != null) {
for (String header : params) {
expressions.add(new ParamExpression(header));
}
}
return conditions;
return expressions;
}
@Override
protected Collection<ParamExpression> getContent() {
return expressions;
}
@Override
protected boolean isLogicalConjunction() {
return true;
}
/**
* Creates an empty set of parameter request conditions.
* Returns a new instance with the union of the param expressions from "this" and the "other" instance.
*/
public ParamsRequestCondition() {
this(Collections.<ParamRequestCondition>emptySet());
public ParamsRequestCondition combine(ParamsRequestCondition other) {
Set<ParamExpression> set = new LinkedHashSet<ParamExpression>(this.expressions);
set.addAll(other.expressions);
return new ParamsRequestCondition(set);
}
/**
* Returns a new {@code RequestCondition} that contains all conditions that match the request.
*
* @param request the request
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
* Returns "this" instance if the request matches to all parameter expressions; or {@code null} otherwise.
*/
public ParamsRequestCondition getMatchingCondition(HttpServletRequest request) {
return match(request) ? this : null;
for (ParamExpression expression : expressions) {
if (!expression.match(request)) {
return null;
}
}
return this;
}
/**
* Combines this collection of request condition with another by combining all parameter request conditions into a
* logical AND.
*
* @param other the condition to combine with
* Returns:
* <ul>
* <li>0 if the two conditions have the same number of parameter expressions
* <li>Less than 1 if "this" instance has more parameter expressions
* <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.
*/
public ParamsRequestCondition combine(ParamsRequestCondition other) {
Set<ParamRequestCondition> conditions = new LinkedHashSet<ParamRequestCondition>(getConditions());
conditions.addAll(other.getConditions());
return new ParamsRequestCondition(conditions);
}
public int compareTo(ParamsRequestCondition other) {
return other.getConditions().size() - this.getConditions().size();
public int compareTo(ParamsRequestCondition other, HttpServletRequest request) {
return other.expressions.size() - this.expressions.size();
}
static class ParamRequestCondition extends AbstractNameValueCondition<String> {
/**
* Parsing and request matching logic for parameter expressions.
* @see RequestMapping#params()
*/
static class ParamExpression extends AbstractNameValueExpression<String> {
ParamRequestCondition(String expression) {
ParamExpression(String expression) {
super(expression);
}
......@@ -107,20 +133,6 @@ public class ParamsRequestCondition
protected boolean matchValue(HttpServletRequest request) {
return value.equals(request.getParameter(name));
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof ParamRequestCondition) {
ParamRequestCondition other = (ParamRequestCondition) obj;
return ((this.name.equals(other.name)) &&
(this.value != null ? this.value.equals(other.value) : other.value == null) &&
this.isNegated == other.isNegated);
}
return false;
}
}
}
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.condition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
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.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class PatternsRequestCondition extends RequestConditionSupport<PatternsRequestCondition> {
private final Set<String> patterns;
private final UrlPathHelper urlPathHelper;
private final PathMatcher pathMatcher;
/**
* 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, new UrlPathHelper(), new AntPathMatcher());
}
/**
* Creates a new {@link PatternsRequestCondition} with the given URL patterns.
* Each pattern that is not empty and does not start with "/" is prepended with "/".
*
* @param patterns the URL patterns to use; if 0 the condition will match to every request.
* @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
* @param pathMatcher a {@link PathMatcher} for pattern path matching
*/
public PatternsRequestCondition(String[] patterns, UrlPathHelper urlPathHelper, PathMatcher pathMatcher) {
this(asList(patterns), urlPathHelper, pathMatcher);
}
private static List<String> asList(String... patterns) {
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
}
/**
* Private constructor.
*/
private PatternsRequestCondition(Collection<String> patterns, UrlPathHelper urlPathHelper, PathMatcher pathMatcher) {
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
this.urlPathHelper = urlPathHelper;
this.pathMatcher = pathMatcher;
}
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
if (patterns == null) {
return Collections.emptySet();
}
Set<String> result = new LinkedHashSet<String>(patterns.size());
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
public Set<String> getPatterns() {
return patterns;
}
@Override
protected Collection<String> getContent() {
return patterns;
}
@Override
protected boolean isLogicalConjunction() {
return false;
}
/**
* Returns a new instance with URL patterns from the current instance ("this") and
* the "other" instance as follows:
* <ul>
* <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 "".
* </ul>
*/
public PatternsRequestCondition combine(PatternsRequestCondition other) {
Set<String> result = new LinkedHashSet<String>();
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
for (String pattern1 : this.patterns) {
for (String pattern2 : other.patterns) {
result.add(pathMatcher.combine(pattern1, pattern2));
}
}
}
else if (!this.patterns.isEmpty()) {
result.addAll(this.patterns);
}
else if (!other.patterns.isEmpty()) {
result.addAll(other.patterns);
}
else {
result.add("");
}
return new PatternsRequestCondition(result, urlPathHelper, pathMatcher);
}
/**
* 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 "/"
* </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.
*/
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
if (patterns.isEmpty()) {
return this;
}
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
List<String> matches = new ArrayList<String>();
for (String pattern : patterns) {
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches.add(match);
}
}
Collections.sort(matches, pathMatcher.getPatternComparator(lookupPath));
return matches.isEmpty() ? null : new PatternsRequestCondition(matches, urlPathHelper, pathMatcher);
}
private String getMatchingPattern(String pattern, String lookupPath) {
if (pattern.equals(lookupPath)) {
return pattern;
}
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
if (pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
boolean endsWithSlash = pattern.endsWith("/");
if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
return pattern +"/";
}
return null;
}
/**
* 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.
*
* <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);
Comparator<String> patternComparator = pathMatcher.getPatternComparator(lookupPath);
Iterator<String> iterator = patterns.iterator();
Iterator<String> iteratorOther = other.patterns.iterator();
while (iterator.hasNext() && iteratorOther.hasNext()) {
int result = patternComparator.compare(iterator.next(), iteratorOther.next());
if (result != 0) {
return result;
}
}
if (iterator.hasNext()) {
return -1;
}
else if (iteratorOther.hasNext()) {
return 1;
}
else {
return 0;
}
}
}
......@@ -16,99 +16,170 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition.HeaderExpression;
/**
* Represents a collection of produces conditions, typically obtained from {@link
* org.springframework.web.bind.annotation.RequestMapping#produces() &#64;RequestMapping.produces()}.
*
* 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 with 0 producible media type expressions, it matches to every request.
*
* <p>This request condition is also capable of parsing header expressions specifically selecting 'Accept' header
* expressions and converting them to prodicuble media type expressions.
*
* @author Arjen Poutsma
* @see RequestConditionFactory#parseProduces(String...)
* @see RequestConditionFactory#parseProduces(String[], String[])
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ProducesRequestCondition
extends MediaTypesRequestCondition<ProducesRequestCondition.ProduceRequestCondition> {
public class ProducesRequestCondition extends RequestConditionSupport<ProducesRequestCondition> {
private final List<ProduceMediaTypeExpression> expressions;
ProducesRequestCondition(Collection<ProduceRequestCondition> conditions) {
super(conditions);
/**
* 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
*/
public ProducesRequestCondition(String... produces) {
this(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
* @param headers the header expression to parse
*/
public ProducesRequestCondition(String[] produces, String[] headers) {
this(parseExpressions(produces, headers));
}
/**
* A private constructor.
*/
private ProducesRequestCondition(Collection<ProduceMediaTypeExpression> expressions) {
this.expressions = new ArrayList<ProduceMediaTypeExpression>(expressions);
Collections.sort(this.expressions);
}
ProducesRequestCondition(String... consumes) {
this(parseConditions(Arrays.asList(consumes)));
private static Set<ProduceMediaTypeExpression> parseExpressions(String[] produces, String[] headers) {
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Accept".equalsIgnoreCase(expr.name)) {
for( MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ProduceMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
if (produces != null) {
for (String produce : produces) {
result.add(new ProduceMediaTypeExpression(produce));
}
}
return result;
}
private static Set<ProduceRequestCondition> parseConditions(List<String> consumes) {
Set<ProduceRequestCondition> conditions = new LinkedHashSet<ProduceRequestCondition>(consumes.size());
for (String consume : consumes) {
conditions.add(new ProduceRequestCondition(consume));
/**
* Returns the producible media types contained in all expressions of this condition.
*/
public Set<MediaType> getMediaTypes() {
Set<MediaType> result = new LinkedHashSet<MediaType>();
for (ProduceMediaTypeExpression expression : expressions) {
result.add(expression.getMediaType());
}
return conditions;
return result;
}
/**
* Creates an empty set of consumes request conditions.
* Returns true if this condition contains no producible media type expressions.
*/
public ProducesRequestCondition() {
this(Collections.<ProduceRequestCondition>emptySet());
public boolean isEmpty() {
return expressions.isEmpty();
}
@Override
protected Collection<ProduceMediaTypeExpression> getContent() {
return expressions;
}
@Override
protected boolean isLogicalConjunction() {
return false;
}
/**
* 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.
*/
public ProducesRequestCondition combine(ProducesRequestCondition other) {
return !other.expressions.isEmpty() ? other : this;
}
/**
* Returns a new {@code RequestCondition} that contains all conditions of this key that match the request.
*
* @param request the request
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
* 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.
*
* @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.
*/
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ProduceRequestCondition> matchingConditions = new LinkedHashSet<ProduceRequestCondition>(getConditions());
for (Iterator<ProduceRequestCondition> iterator = matchingConditions.iterator(); iterator.hasNext();) {
ProduceRequestCondition condition = iterator.next();
if (!condition.match(request)) {
Set<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>(expressions);
for (Iterator<ProduceMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ProduceMediaTypeExpression expression = iterator.next();
if (!expression.match(request)) {
iterator.remove();
}
}
if (matchingConditions.isEmpty()) {
return null;
}
else {
return new ProducesRequestCondition(matchingConditions);
}
return (result.isEmpty()) ? null : new ProducesRequestCondition(result);
}
/**
* Combines this collection of request condition with another. Returns {@code other}, unless it has the default
* value (i.e. {@code &#42;/&#42;}).
*
* @param other the condition to combine with
* 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>
*
* <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 ProducesRequestCondition combine(ProducesRequestCondition other) {
return !other.isEmpty() ? other : this;
}
public int compareTo(ProducesRequestCondition other, List<MediaType> acceptedMediaTypes) {
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) {
int thisIndex = this.indexOfMediaType(acceptedMediaType);
int otherIndex = other.indexOfMediaType(acceptedMediaType);
if (thisIndex != otherIndex) {
return otherIndex - thisIndex;
} else if (thisIndex != -1 && otherIndex != -1) {
ProduceRequestCondition thisCondition = this.getSortedConditions().get(thisIndex);
ProduceRequestCondition otherCondition = other.getSortedConditions().get(otherIndex);
int result = thisCondition.compareTo(otherCondition);
ProduceMediaTypeExpression thisExpr = this.expressions.get(thisIndex);
ProduceMediaTypeExpression otherExpr = other.expressions.get(otherIndex);
int result = thisExpr.compareTo(otherExpr);
if (result != 0) {
return result;
}
......@@ -118,29 +189,31 @@ public class ProducesRequestCondition
}
private int indexOfMediaType(MediaType mediaType) {
List<ProduceRequestCondition> sortedConditions = getSortedConditions();
for (int i = 0; i < sortedConditions.size(); i++) {
ProduceRequestCondition condition = sortedConditions.get(i);
if (mediaType.includes(condition.getMediaType())) {
for (int i = 0; i < expressions.size(); i++) {
if (mediaType.includes(expressions.get(i).getMediaType())) {
return i;
}
}
return -1;
}
static class ProduceRequestCondition extends MediaTypesRequestCondition.MediaTypeRequestCondition {
/**
* Parsing and request matching logic for producible media type expressions.
* @see RequestMapping#produces()
*/
static class ProduceMediaTypeExpression extends MediaTypeExpression {
ProduceRequestCondition(MediaType mediaType, boolean negated) {
ProduceMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
ProduceRequestCondition(String expression) {
ProduceMediaTypeExpression(String expression) {
super(expression);
}
@Override
protected boolean match(HttpServletRequest request, MediaType mediaType) {
List<MediaType> acceptedMediaTypes = getAccept(request);
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (mediaType.isCompatibleWith(acceptedMediaType)) {
return true;
......@@ -149,7 +222,7 @@ public class ProducesRequestCondition
return false;
}
private List<MediaType> getAccept(HttpServletRequest request) {
private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) {
String acceptHeader = request.getHeader("Accept");
if (StringUtils.hasLength(acceptHeader)) {
return MediaType.parseMediaTypes(acceptHeader);
......@@ -159,4 +232,5 @@ public class ProducesRequestCondition
}
}
}
}
......@@ -19,23 +19,39 @@ package org.springframework.web.servlet.mvc.method.condition;
import javax.servlet.http.HttpServletRequest;
/**
* Defines the contract for conditions that must be met given an incoming request.
*
* <p>Implementations of this interface are created by the {@link RequestConditionFactory}.
*
* The contract for request conditions.
*
* <p>Request conditions can be combined (e.g. type + method-level conditions), matched to a request,
* or compared to each other to determine if one matches the request better.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @see RequestConditionFactory
* @since 3.1
*/
public interface RequestCondition {
public interface RequestCondition<This extends RequestCondition<This>> {
/**
* Indicates whether this condition matches against the given servlet request.
*
* @param request the request
* @return {@code true} if this condition matches the request; {@code false} otherwise
* Defines the rules for combining "this" condition (i.e. the current instance) with another condition.
* <p>Example: combine type- and method-level request mapping conditions.
*
* @returns a request condition instance that is the result of combining the two condition instances.
*/
boolean match(HttpServletRequest request);
This combine(This other);
/**
* Checks if this condition matches the provided 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.
*/
This getMatchingCondition(HttpServletRequest request);
/**
* Compares "this" condition (i.e. the current instance) with another condition in the context of a request.
* <p>Note: it is assumed instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
* to ensure they have content relevant to current request only.
*/
int compareTo(This other, HttpServletRequest request);
}
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.condition;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Factory for {@link RequestCondition} objects.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class RequestConditionFactory {
private static final String CONTENT_TYPE_HEADER = "Content-Type";
private static final String ACCEPT_HEADER = "Accept";
/**
* Parses the given request methods, and returns them as a single request condition.
*
* @param methods the methods
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#method()
*/
public static RequestMethodsRequestCondition parseMethods(RequestMethod... methods) {
return methods != null ? new RequestMethodsRequestCondition(methods) : new RequestMethodsRequestCondition();
}
/**
* Parses the given parameters, and returns them as a single request condition.
*
* @param params the parameters
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#params()
*/
public static ParamsRequestCondition parseParams(String... params) {
return params != null ? new ParamsRequestCondition(params) : new ParamsRequestCondition();
}
/**
* Parses the given headers, and returns them as a single request condition.
*
* @param headers the headers
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#headers()
*/
public static HeadersRequestCondition parseHeaders(String... headers) {
if (headers == null) {
return new HeadersRequestCondition();
}
HeadersRequestCondition headersCondition = new HeadersRequestCondition(headers);
// filter out Accept and Content-Type headers, they are dealt with by produces and consumes respectively
Set<HeadersRequestCondition.HeaderRequestCondition> filteredConditions =
new LinkedHashSet<HeadersRequestCondition.HeaderRequestCondition>(headersCondition.getConditions());
for (Iterator<HeadersRequestCondition.HeaderRequestCondition> iterator = filteredConditions.iterator();
iterator.hasNext();) {
HeadersRequestCondition.HeaderRequestCondition headerCondition = iterator.next();
if (ACCEPT_HEADER.equalsIgnoreCase(headerCondition.name) ||
CONTENT_TYPE_HEADER.equalsIgnoreCase(headerCondition.name)) {
iterator.remove();
}
}
return new HeadersRequestCondition(filteredConditions);
}
/**
* Parses the given consumes, and returns them as a single request condition.
*
* @param consumes the consumes
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#consumes()
*/
public static ConsumesRequestCondition parseConsumes(String... consumes) {
return new ConsumesRequestCondition(consumes);
}
/**
* Parses the given consumes and {@code Content-Type} headers, and returns them as a single request condition. <p>Only
* {@code Content-Type} headers will be used, all other headers will be ignored.
*
* @param consumes the consumes
* @param headers the headers
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#consumes()
*/
public static ConsumesRequestCondition parseConsumes(String[] consumes, String[] headers) {
List<ConsumesRequestCondition.ConsumeRequestCondition> allConditions = parseContentTypeHeaders(headers);
// ignore the default consumes() value if any content-type headers have been set
boolean headersHasContentType = !allConditions.isEmpty();
boolean consumesHasDefaultValue = consumes.length == 1 && consumes[0].equals("*/*");
if (!headersHasContentType || !consumesHasDefaultValue) {
for (String consume : consumes) {
allConditions.add(new ConsumesRequestCondition.ConsumeRequestCondition(consume));
}
}
return new ConsumesRequestCondition(allConditions);
}
private static List<ConsumesRequestCondition.ConsumeRequestCondition> parseContentTypeHeaders(String[] headers) {
List<ConsumesRequestCondition.ConsumeRequestCondition> conditions =
new ArrayList<ConsumesRequestCondition.ConsumeRequestCondition>();
HeadersRequestCondition headersCondition = new HeadersRequestCondition(headers);
for (HeadersRequestCondition.HeaderRequestCondition headerCondition : headersCondition.getConditions()) {
if (CONTENT_TYPE_HEADER.equalsIgnoreCase(headerCondition.name)) {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerCondition.value);
for (MediaType mediaType : mediaTypes) {
conditions.add(new ConsumesRequestCondition.ConsumeRequestCondition(mediaType,
headerCondition.isNegated));
}
}
}
return conditions;
}
/**
* Parses the given produces, and returns them as a single request condition.
*
* @param produces the produces
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#produces()
*/
public static ProducesRequestCondition parseProduces(String... produces) {
return new ProducesRequestCondition(produces);
}
/**
* Parses the given produces and {@code Accept} headers, and returns them as a single request condition. <p>Only {@code
* Accept} headers will be used, all other headers will be ignored.
*
* @param produces the consumes
* @param headers the headers
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#produces()
*/
public static ProducesRequestCondition parseProduces(String[] produces, String[] headers) {
List<ProducesRequestCondition.ProduceRequestCondition> allConditions = parseAcceptHeaders(headers);
// ignore the default consumes() value if any accept headers have been set
boolean headersHasAccept = !allConditions.isEmpty();
boolean producesHasDefaultValue = produces.length == 1 && produces[0].equals("*/*");
if (!headersHasAccept || !producesHasDefaultValue) {
for (String produce : produces) {
allConditions.add(new ProducesRequestCondition.ProduceRequestCondition(produce));
}
}
return new ProducesRequestCondition(allConditions);
}
private static List<ProducesRequestCondition.ProduceRequestCondition> parseAcceptHeaders(String[] headers) {
List<ProducesRequestCondition.ProduceRequestCondition> allConditions =
new ArrayList<ProducesRequestCondition.ProduceRequestCondition>();
HeadersRequestCondition headersCondition = new HeadersRequestCondition(headers);
for (HeadersRequestCondition.HeaderRequestCondition headerCondition : headersCondition.getConditions()) {
if (ACCEPT_HEADER.equalsIgnoreCase(headerCondition.name)) {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerCondition.value);
for (MediaType mediaType : mediaTypes) {
allConditions.add(new ProducesRequestCondition.ProduceRequestCondition(mediaType,
headerCondition.isNegated));
}
}
}
return allConditions;
}
}
......@@ -17,64 +17,62 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Abstract base class for {@link RequestCondition} implementations that wrap other request conditions.
*
* @author Arjen Poutsma
* Base class for {@link RequestCondition}s.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
abstract class RequestConditionComposite<T extends RequestCondition> implements RequestCondition {
private final Set<T> conditions;
protected RequestConditionComposite(Collection<T> conditions) {
this.conditions = Collections.unmodifiableSet(new LinkedHashSet<T>(conditions));
}
protected Set<T> getConditions() {
return conditions;
}
public boolean isEmpty() {
return conditions.isEmpty();
}
abstract class RequestConditionSupport<This extends RequestConditionSupport<This>> implements RequestCondition<This> {
/**
* Returns the individual expressions a request condition is composed of such as
* URL patterns, HTTP request methods, parameter expressions, etc.
*/
protected abstract Collection<?> getContent();
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && getClass().equals(o.getClass())) {
RequestConditionComposite other = (RequestConditionComposite) o;
return this.conditions.equals(other.conditions);
RequestConditionSupport<?> other = (RequestConditionSupport<?>) o;
return getContent().equals(other.getContent());
}
return false;
}
@Override
public int hashCode() {
return conditions.hashCode();
return getContent().hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
String infix = getToStringInfix();
for (Iterator<T> iterator = conditions.iterator(); iterator.hasNext();) {
RequestCondition condition = iterator.next();
builder.append(condition.toString());
for (Iterator<?> iterator = getContent().iterator(); iterator.hasNext();) {
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
builder.append(infix);
if (isLogicalConjunction()) {
builder.append(" && ");
}
else {
builder.append(" || ");
}
}
}
builder.append("]");
return builder.toString();
}
protected abstract String getToStringInfix();
/**
* Returns {@code true} if the individual expressions of the condition are combined via logical
* conjunction (" && "); or {@code false} otherwise.
*/
protected abstract boolean isLogicalConjunction();
}
......@@ -22,132 +22,102 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Represents a collection of {@link RequestMethod} conditions, typically obtained from {@link
* org.springframework.web.bind.annotation.RequestMapping#method() @RequestMapping.methods()}.
*
* 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
* @see RequestConditionFactory#parseMethods(RequestMethod...)
* @author Rossen Stoyanchev
* @since 3.1
*/
public class RequestMethodsRequestCondition
extends LogicalDisjunctionRequestCondition<RequestMethodsRequestCondition.RequestMethodRequestCondition>
implements Comparable<RequestMethodsRequestCondition> {
public class RequestMethodsRequestCondition extends RequestConditionSupport<RequestMethodsRequestCondition> {
private RequestMethodsRequestCondition(Collection<RequestMethodRequestCondition> conditions) {
super(conditions);
}
private final Set<RequestMethod> methods;
RequestMethodsRequestCondition(RequestMethod... methods) {
this(parseConditions(Arrays.asList(methods)));
/**
* Create a {@link RequestMethodsRequestCondition} with the given {@link RequestMethod}s.
* @param requestMethods 0 or more HTTP request methods; if 0 the condition will match to every request.
*/
public RequestMethodsRequestCondition(RequestMethod... requestMethods) {
this(asList(requestMethods));
}
private static Set<RequestMethodRequestCondition> parseConditions(List<RequestMethod> methods) {
Set<RequestMethodRequestCondition> conditions =
new LinkedHashSet<RequestMethodRequestCondition>(methods.size());
for (RequestMethod method : methods) {
conditions.add(new RequestMethodRequestCondition(method));
}
return conditions;
private static List<RequestMethod> asList(RequestMethod... requestMethods) {
return requestMethods != null ? Arrays.asList(requestMethods) : Collections.<RequestMethod>emptyList();
}
/**
* Creates an empty set of method request conditions.
* Private constructor.
*/
public RequestMethodsRequestCondition() {
this(Collections.<RequestMethodRequestCondition>emptySet());
}
private RequestMethodsRequestCondition(Collection<RequestMethod> requestMethods) {
this.methods = Collections.unmodifiableSet(new LinkedHashSet<RequestMethod>(requestMethods));
}
/**
* Returns all {@link RequestMethod}s contained in this condition.
*/
public Set<RequestMethod> getMethods() {
Set<RequestMethod> result = new LinkedHashSet<RequestMethod>();
for (RequestMethodRequestCondition condition : getConditions()) {
result.add(condition.getMethod());
}
return result;
return methods;
}
public int compareTo(RequestMethodsRequestCondition other) {
return other.getConditions().size() - this.getConditions().size();
@Override
protected Collection<RequestMethod> getContent() {
return methods;
}
@Override
protected boolean isLogicalConjunction() {
return false;
}
/**
* Returns a new {@code RequestMethodsRequestCondition} that contains all conditions that match the request.
*
* @param request the request
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
* 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);
set.addAll(other.methods);
return new RequestMethodsRequestCondition(set);
}
/**
* Checks if any of the HTTP request methods match the given request and returns an instance that
* contain the matching request method.
* @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.
*/
public RequestMethodsRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
if (methods.isEmpty()) {
return this;
}
else {
if (match(request)) {
return new RequestMethodsRequestCondition(RequestMethod.valueOf(request.getMethod()));
}
else {
return null;
RequestMethod incomingRequestMethod = RequestMethod.valueOf(request.getMethod());
for (RequestMethod method : methods) {
if (method.equals(incomingRequestMethod)) {
return new RequestMethodsRequestCondition(method);
}
}
return null;
}
/**
* Combines this collection of request method conditions with another by combining all methods into a logical OR.
*
* @param other the condition to combine with
* Returns:
* <ul>
* <li>0 if the two conditions contain the same number of HTTP request methods.
* <li>Less than 1 if "this" instance has an HTTP request method but "other" doesn't.
* <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.
*/
public RequestMethodsRequestCondition combine(RequestMethodsRequestCondition other) {
Set<RequestMethodRequestCondition> conditions =
new LinkedHashSet<RequestMethodRequestCondition>(getConditions());
conditions.addAll(other.getConditions());
return new RequestMethodsRequestCondition(conditions);
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
return other.methods.size() - this.methods.size();
}
static class RequestMethodRequestCondition implements RequestCondition {
private final RequestMethod method;
RequestMethodRequestCondition(RequestMethod method) {
this.method = method;
}
RequestMethod getMethod() {
return method;
}
public boolean match(HttpServletRequest request) {
RequestMethod method = RequestMethod.valueOf(request.getMethod());
return this.method.equals(method);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof RequestMethodRequestCondition) {
RequestMethodRequestCondition other = (RequestMethodRequestCondition) obj;
return this.method.equals(other.method);
}
return false;
}
@Override
public int hashCode() {
return method.hashCode();
}
@Override
public String toString() {
return method.toString();
}
}
}
......@@ -16,6 +16,11 @@
package org.springframework.web.servlet.mvc.method;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
......@@ -23,17 +28,14 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
import org.springframework.web.util.UrlPathHelper;
import static java.util.Arrays.*;
import static org.junit.Assert.*;
/**
* Test fixture with {@link RequestMappingHandlerMapping} testing its {@link RequestMappingInfo} comparator.
*
......@@ -57,8 +59,8 @@ public class RequestMappingInfoComparatorTests {
request.setRequestURI("/foo");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(lookupPath, request);
RequestMappingInfo key1 = new RequestMappingInfo(asList("/fo*"), null);
RequestMappingInfo key2 = new RequestMappingInfo(asList("/foo"), null);
RequestMappingInfo key1 = new RequestMappingInfo(new String[]{"/fo*"});
RequestMappingInfo key2 = new RequestMappingInfo(new String[]{"/foo"});
assertEquals(1, comparator.compare(key1, key2));
}
......@@ -68,8 +70,8 @@ public class RequestMappingInfoComparatorTests {
request.setRequestURI("/foo");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator(lookupPath, request);
RequestMappingInfo key1 = new RequestMappingInfo(asList("/foo*"), null);
RequestMappingInfo key2 = new RequestMappingInfo(asList("/foo*"), null);
RequestMappingInfo key1 = new RequestMappingInfo(new String[]{"/foo*"});
RequestMappingInfo key2 = new RequestMappingInfo(new String[]{"/foo*"});
assertEquals(0, comparator.compare(key1, key2));
}
......@@ -78,20 +80,20 @@ public class RequestMappingInfoComparatorTests {
public void greaterNumberOfMatchingPatternsWins() throws Exception {
request.setRequestURI("/foo.html");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key1 = new RequestMappingInfo(asList("/foo", "*.jpeg"), null);
RequestMappingInfo key2 = new RequestMappingInfo(asList("/foo", "*.html"), null);
RequestMappingInfo key1 = new RequestMappingInfo(new String[]{"/foo", "*.jpeg"});
RequestMappingInfo key2 = new RequestMappingInfo(new String[]{"/foo", "*.html"});
RequestMappingInfo match1 = handlerMapping.getMatchingMapping(key1, lookupPath, request);
RequestMappingInfo match2 = handlerMapping.getMatchingMapping(key2, lookupPath, request);
List<RequestMappingInfo> matches = asList(match1, match2);
Collections.sort(matches, handlerMapping.getMappingComparator(lookupPath, request));
assertSame(match2.getPatterns(), matches.get(0).getPatterns());
assertSame(match2.getPatternsCondition(), matches.get(0).getPatternsCondition());
}
@Test
public void oneMethodWinsOverNone() {
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator("", request);
RequestMappingInfo key1 = new RequestMappingInfo(null, null);
RequestMappingInfo key1 = new RequestMappingInfo(null);
RequestMappingInfo key2 = new RequestMappingInfo(null, new RequestMethod[] {RequestMethod.GET});
assertEquals(1, comparator.compare(key1, key2));
......@@ -99,10 +101,11 @@ public class RequestMappingInfoComparatorTests {
@Test
public void methodsAndParams() {
RequestMappingInfo empty = new RequestMappingInfo(null, null);
RequestMappingInfo empty = new RequestMappingInfo(null);
RequestMappingInfo oneMethod = new RequestMappingInfo(null, new RequestMethod[] {RequestMethod.GET});
RequestMappingInfo oneMethodOneParam =
new RequestMappingInfo(null, RequestConditionFactory.parseMethods(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null, null, null);
new RequestMappingInfo(null, new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo"), null, null, null);
List<RequestMappingInfo> list = asList(empty, oneMethod, oneMethodOneParam);
Collections.shuffle(list);
Collections.sort(list, handlerMapping.getMappingComparator("", request));
......@@ -114,9 +117,9 @@ public class RequestMappingInfoComparatorTests {
@Test
public void produces() {
RequestMappingInfo html = new RequestMappingInfo(null, null, null, null, null, RequestConditionFactory.parseProduces("text/html"));
RequestMappingInfo xml = new RequestMappingInfo(null, null, null, null, null, RequestConditionFactory.parseProduces("application/xml"));
RequestMappingInfo none = new RequestMappingInfo(null, null);
RequestMappingInfo html = new RequestMappingInfo(null, null, null, null, null, new ProducesRequestCondition("text/html"));
RequestMappingInfo xml = new RequestMappingInfo(null, null, null, null, null, new ProducesRequestCondition("application/xml"));
RequestMappingInfo none = new RequestMappingInfo(null);
request.addHeader("Accept", "application/xml, text/html");
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator("", request);
......
......@@ -24,7 +24,6 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import org.junit.Before;
......@@ -42,9 +41,13 @@ import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
import org.springframework.web.util.UrlPathHelper;
/**
......@@ -129,7 +132,7 @@ public class RequestMappingInfoHandlerMappingTests {
@Test
public void uriTemplateVariables() {
RequestMappingInfo key = new RequestMappingInfo(Arrays.asList("/{path1}/{path2}"), null);
RequestMappingInfo key = new RequestMappingInfo(new String[] {"/{path1}/{path2}"});
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
......@@ -197,12 +200,13 @@ public class RequestMappingInfoHandlerMappingTests {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
return new RequestMappingInfo(Arrays.asList(annotation.value()),
RequestConditionFactory.parseMethods(annotation.method()),
RequestConditionFactory.parseParams(annotation.params()),
RequestConditionFactory.parseHeaders(annotation.headers()),
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()),
RequestConditionFactory.parseProduces(annotation.produces(), annotation.headers()));
return new RequestMappingInfo(
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher()),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers()));
}
}
......
......@@ -16,20 +16,19 @@
package org.springframework.web.servlet.mvc.method;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
import org.springframework.web.util.UrlPathHelper;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static org.junit.Assert.*;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
/**
* Test fixture for {@link RequestMappingInfo} tests.
......@@ -41,8 +40,8 @@ public class RequestMappingInfoTests {
@Test
public void equals() {
RequestMappingInfo key1 = new RequestMappingInfo(singleton("/foo"), methods(GET));
RequestMappingInfo key2 = new RequestMappingInfo(singleton("/foo"), methods(GET));
RequestMappingInfo key1 = new RequestMappingInfo(new String[] {"/foo"}, GET);
RequestMappingInfo key2 = new RequestMappingInfo(new String[] {"/foo"}, GET);
assertEquals(key1, key2);
assertEquals(key1.hashCode(), key2.hashCode());
......@@ -50,8 +49,8 @@ public class RequestMappingInfoTests {
@Test
public void equalsPrependSlash() {
RequestMappingInfo key1 = new RequestMappingInfo(singleton("/foo"), methods(GET));
RequestMappingInfo key2 = new RequestMappingInfo(singleton("foo"), methods(GET));
RequestMappingInfo key1 = new RequestMappingInfo(new String[] {"/foo"}, GET);
RequestMappingInfo key2 = new RequestMappingInfo(new String[] {"foo"}, GET);
assertEquals(key1, key2);
assertEquals(key1.hashCode(), key2.hashCode());
......@@ -59,213 +58,190 @@ public class RequestMappingInfoTests {
@Test
public void combinePatterns() {
AntPathMatcher pathMatcher = new AntPathMatcher();
RequestMappingInfo key1 = createKeyFromPatterns("/t1", "/t2");
RequestMappingInfo key2 = createKeyFromPatterns("/m1", "/m2");
RequestMappingInfo key3 = createKeyFromPatterns("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2");
assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns());
key1 = createKeyFromPatterns("/t1");
key2 = createKeyFromPatterns();
key3 = createKeyFromPatterns("/t1");
assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns());
key1 = createKeyFromPatterns();
key2 = createKeyFromPatterns("/m1");
key3 = createKeyFromPatterns("/m1");
assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns());
key1 = createKeyFromPatterns();
key2 = createKeyFromPatterns();
key3 = createKeyFromPatterns("");
assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns());
key1 = createKeyFromPatterns("/t1");
key2 = createKeyFromPatterns("");
key3 = createKeyFromPatterns("/t1");
assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns());
RequestMappingInfo key1 = createFromPatterns("/t1", "/t2");
RequestMappingInfo key2 = createFromPatterns("/m1", "/m2");
RequestMappingInfo key3 = createFromPatterns("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2");
assertEquals(key3.getPatternsCondition(), key1.combine(key2).getPatternsCondition());
key1 = createFromPatterns("/t1");
key2 = createFromPatterns();
key3 = createFromPatterns("/t1");
assertEquals(key3.getPatternsCondition(), key1.combine(key2).getPatternsCondition());
key1 = createFromPatterns();
key2 = createFromPatterns("/m1");
key3 = createFromPatterns("/m1");
assertEquals(key3.getPatternsCondition(), key1.combine(key2).getPatternsCondition());
key1 = createFromPatterns();
key2 = createFromPatterns();
key3 = createFromPatterns("");
assertEquals(key3.getPatternsCondition(), key1.combine(key2).getPatternsCondition());
key1 = createFromPatterns("/t1");
key2 = createFromPatterns("");
key3 = createFromPatterns("/t1");
assertEquals(key3.getPatternsCondition(), key1.combine(key2).getPatternsCondition());
}
@Test
public void matchPatternsToRequest() {
UrlPathHelper pathHelper = new UrlPathHelper();
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null);
RequestMappingInfo match =
key.getMatchingRequestMapping(pathHelper.getLookupPathForRequest(request), request, pathMatcher);
RequestMappingInfo match = createFromPatterns("/foo").getMatchingRequestMapping(request);
assertNotNull(match);
request = new MockHttpServletRequest("GET", "/foo/bar");
key = new RequestMappingInfo(singleton("/foo/*"), null);
match = key.getMatchingRequestMapping(pathHelper.getLookupPathForRequest(request), request, pathMatcher);
match = createFromPatterns("/foo/*").getMatchingRequestMapping(request);
assertNotNull("Pattern match", match);
request = new MockHttpServletRequest("GET", "/foo.html");
key = new RequestMappingInfo(singleton("/foo"), null);
match = key.getMatchingRequestMapping(pathHelper.getLookupPathForRequest(request), request, pathMatcher);
match = createFromPatterns("/foo").getMatchingRequestMapping(request);
assertNotNull("Implicit match by extension", match);
assertEquals("Contains matched pattern", "/foo.*", match.getPatterns().iterator().next());
assertEquals("Contains matched pattern", "/foo.*", match.getPatternsCondition().getPatterns().iterator().next());
request = new MockHttpServletRequest("GET", "/foo/");
key = new RequestMappingInfo(singleton("/foo"), null);
match = key.getMatchingRequestMapping(pathHelper.getLookupPathForRequest(request), request, pathMatcher);
match = createFromPatterns("/foo").getMatchingRequestMapping(request);
assertNotNull("Implicit match by trailing slash", match);
assertEquals("Contains matched pattern", "/foo/", match.getPatterns().iterator().next());
assertEquals("Contains matched pattern", "/foo/", match.getPatternsCondition().getPatterns().iterator().next());
request = new MockHttpServletRequest("GET", "/foo.html");
key = new RequestMappingInfo(singleton("/foo.jpg"), null);
match = key.getMatchingRequestMapping(pathHelper.getLookupPathForRequest(request), request, pathMatcher);
match = createFromPatterns("/foo.jpg").getMatchingRequestMapping(request);
assertNull("Implicit match ignored if pattern has extension", match);
request = new MockHttpServletRequest("GET", "/foo.html");
key = new RequestMappingInfo(singleton("/foo.jpg"), null);
match = key.getMatchingRequestMapping(pathHelper.getLookupPathForRequest(request), request, pathMatcher);
match = createFromPatterns("/foo.jpg").getMatchingRequestMapping(request);
assertNull("Implicit match ignored on pattern with trailing slash", match);
}
@Test
public void matchRequestMethods() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
RequestMappingInfo key = createFromPatterns("/foo");
RequestMappingInfo match = createFromPatterns("/foo").getMatchingRequestMapping(request);
assertNotNull("No method matches any method", match);
key = new RequestMappingInfo(singleton("/foo"), methods(GET));
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
key = new RequestMappingInfo(new String[]{"/foo"}, GET);
match = key.getMatchingRequestMapping(request);
assertNotNull("Exact match", match);
key = new RequestMappingInfo(singleton("/foo"), methods(POST));
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
key = new RequestMappingInfo(new String[]{"/foo"}, POST);
match = key.getMatchingRequestMapping(request);
assertNull("No match", match);
}
@Test
public void matchingKeyContent() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key = new RequestMappingInfo(asList("/foo*", "/bar"), methods(GET, POST));
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
RequestMappingInfo expected = new RequestMappingInfo(singleton("/foo*"), methods(GET));
RequestMappingInfo key = new RequestMappingInfo(new String[] {"/foo*", "/bar"}, GET, POST);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
RequestMappingInfo expected = new RequestMappingInfo(new String[] {"/foo*"}, GET);
assertEquals("Matching RequestKey contains matched patterns and methods only", expected, match);
key = new RequestMappingInfo(asList("/**", "/foo*", "/foo"), null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
expected = new RequestMappingInfo(asList("/foo", "/foo*", "/**"), null);
key = createFromPatterns("/**", "/foo*", "/foo");
match = key.getMatchingRequestMapping(request);
expected = createFromPatterns("/foo", "/foo*", "/**");
assertEquals("Matched patterns are sorted with best match at the top", expected, match);
}
@Test
public void paramsCondition() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("foo", "bar");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key =
new RequestMappingInfo(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null,
null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo=bar"), null, null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null,
null, null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo!=bar"), null, null, null);
match = key.getMatchingRequestMapping(request);
assertNull(match);
}
@Test
public void headersCondition() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("foo", "bar");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key =
new RequestMappingInfo(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"),
null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo=bar"), null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"),
null, null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo!=bar"), null, null);
match = key.getMatchingRequestMapping(request);
assertNull(match);
}
@Test
public void consumesCondition() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setContentType("text/plain");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null, null, null,
RequestConditionFactory.parseConsumes("text/plain"), null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
RequestMappingInfo key =
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("text/plain"), null);
RequestMappingInfo match = key.getMatchingRequestMapping(request);
assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, null, null,
RequestConditionFactory.parseConsumes("application/xml"), null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("application/xml"), null);
match = key.getMatchingRequestMapping(request);
assertNull(match);
}
@Test
public void producesCondition() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("Accept", "text/plain");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null, null, null,
null, RequestConditionFactory.parseProduces("text/plain"));
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
RequestMappingInfo key =
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("text/plain"));
RequestMappingInfo match = key.getMatchingRequestMapping(request);
assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, null, null, null,
RequestConditionFactory.parseProduces("application/xml"));
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
key = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("application/xml"));
match = key.getMatchingRequestMapping(request);
assertNull(match);
}
private RequestMappingInfo createKeyFromPatterns(String... patterns) {
return new RequestMappingInfo(asList(patterns), null);
}
private RequestMethod[] methods(RequestMethod... methods) {
if (methods != null) {
return methods;
}
else {
return new RequestMethod[0];
}
private RequestMappingInfo createFromPatterns(String... patterns) {
return new RequestMappingInfo(patterns);
}
}
\ No newline at end of file
......@@ -16,13 +16,17 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import java.util.Collection;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.Assert.*;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition.ConsumeMediaTypeExpression;
/**
* @author Arjen Poutsma
......@@ -31,75 +35,79 @@ public class ConsumesRequestConditionTests {
@Test
public void consumesMatch() {
RequestCondition condition = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("text/plain");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void negatedConsumesMatch() {
RequestCondition condition = new ConsumesRequestCondition("!text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("!text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("text/plain");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void consumesWildcardMatch() {
RequestCondition condition = new ConsumesRequestCondition("text/*");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/*");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("text/plain");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void consumesMultipleMatch() {
RequestCondition condition = new ConsumesRequestCondition("text/plain", "application/xml");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain", "application/xml");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("text/plain");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void consumesSingleNoMatch() {
RequestCondition condition = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("application/xml");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void compareToSingle() {
MockHttpServletRequest request = new MockHttpServletRequest();
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition("text/*");
int result = condition1.compareTo(condition2);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void compareToMultiple() {
MockHttpServletRequest request = new MockHttpServletRequest();
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("*/*", "text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition("text/*", "text/plain;q=0.7");
int result = condition1.compareTo(condition2);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
}
......@@ -126,28 +134,11 @@ public class ConsumesRequestConditionTests {
public void parseConsumesAndHeaders() {
String[] consumes = new String[] {"text/plain"};
String[] headers = new String[]{"foo=bar", "content-type=application/xml,application/pdf"};
ConsumesRequestCondition condition = RequestConditionFactory.parseConsumes(consumes, headers);
ConsumesRequestCondition condition = new ConsumesRequestCondition(consumes, headers);
assertConditions(condition, "text/plain", "application/xml", "application/pdf");
}
@Test
public void parseConsumesDefault() {
String[] consumes = new String[] {"*/*"};
String[] headers = new String[0];
ConsumesRequestCondition condition = RequestConditionFactory.parseConsumes(consumes, headers);
assertConditions(condition, "*/*");
}
@Test
public void parseConsumesDefaultAndHeaders() {
String[] consumes = new String[] {"*/*"};
String[] headers = new String[]{"foo=bar", "content-type=text/plain"};
ConsumesRequestCondition condition = RequestConditionFactory.parseConsumes(consumes, headers);
assertConditions(condition, "text/plain");
}
@Test
public void getMatchingCondition() {
MockHttpServletRequest request = new MockHttpServletRequest();
......@@ -165,12 +156,12 @@ public class ConsumesRequestConditionTests {
}
private void assertConditions(ConsumesRequestCondition condition, String... expected) {
Set<ConsumesRequestCondition.ConsumeRequestCondition> conditions = condition.getConditions();
assertEquals("Invalid amount of conditions", conditions.size(), expected.length);
Collection<ConsumeMediaTypeExpression> expressions = condition.getContent();
assertEquals("Invalid amount of conditions", expressions.size(), expected.length);
for (String s : expected) {
boolean found = false;
for (ConsumesRequestCondition.ConsumeRequestCondition requestCondition : conditions) {
String conditionMediaType = requestCondition.getMediaType().toString();
for (ConsumeMediaTypeExpression expr : expressions) {
String conditionMediaType = expr.getMediaType().toString();
if (conditionMediaType.equals(s)) {
found = true;
break;
......@@ -181,12 +172,6 @@ public class ConsumesRequestConditionTests {
fail("Condition [" + s + "] not found");
}
}
}
}
......@@ -16,13 +16,17 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Set;
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.assertTrue;
import org.junit.Test;
import java.util.Collection;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.Assert.*;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition.HeaderExpression;
/**
* @author Arjen Poutsma
......@@ -40,81 +44,83 @@ public class HeadersRequestConditionTests {
@Test
public void headerPresent() {
RequestCondition condition = new HeadersRequestCondition("accept");
HeadersRequestCondition condition = new HeadersRequestCondition("accept");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void headerPresentNoMatch() {
RequestCondition condition = new HeadersRequestCondition("foo");
HeadersRequestCondition condition = new HeadersRequestCondition("foo");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("bar", "");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void headerNotPresent() {
RequestCondition condition = new HeadersRequestCondition("!accept");
HeadersRequestCondition condition = new HeadersRequestCondition("!accept");
MockHttpServletRequest request = new MockHttpServletRequest();
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void headerValueMatch() {
RequestCondition condition = new HeadersRequestCondition("foo=bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "bar");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void headerValueNoMatch() {
RequestCondition condition = new HeadersRequestCondition("foo=bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "bazz");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void headerCaseSensitiveValueMatch() {
RequestCondition condition = new HeadersRequestCondition("foo=Bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo=Bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "bar");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void headerValueMatchNegated() {
RequestCondition condition = new HeadersRequestCondition("foo!=bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo!=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "baz");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void compareTo() {
MockHttpServletRequest request = new MockHttpServletRequest();
HeadersRequestCondition condition1 = new HeadersRequestCondition("foo", "bar", "baz");
HeadersRequestCondition condition2 = new HeadersRequestCondition("foo", "bar");
int result = condition1.compareTo(condition2);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
}
......@@ -125,7 +131,7 @@ public class HeadersRequestConditionTests {
HeadersRequestCondition condition2 = new HeadersRequestCondition("foo=baz");
HeadersRequestCondition result = condition1.combine(condition2);
Set<HeadersRequestCondition.HeaderRequestCondition> conditions = result.getConditions();
Collection<HeaderExpression> conditions = result.getContent();
assertEquals(2, conditions.size());
}
......
......@@ -16,13 +16,17 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Set;
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.assertTrue;
import org.junit.Test;
import java.util.Collection;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.Assert.*;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition.ParamExpression;
/**
* @author Arjen Poutsma
......@@ -41,62 +45,64 @@ public class ParamsRequestConditionTests {
@Test
public void paramPresent() {
RequestCondition condition = new ParamsRequestCondition("foo");
ParamsRequestCondition condition = new ParamsRequestCondition("foo");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("foo", "");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void paramPresentNoMatch() {
RequestCondition condition = new ParamsRequestCondition("foo");
ParamsRequestCondition condition = new ParamsRequestCondition("foo");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("bar", "");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void paramNotPresent() {
RequestCondition condition = new ParamsRequestCondition("!foo");
ParamsRequestCondition condition = new ParamsRequestCondition("!foo");
MockHttpServletRequest request = new MockHttpServletRequest();
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void paramValueMatch() {
RequestCondition condition = new ParamsRequestCondition("foo=bar");
ParamsRequestCondition condition = new ParamsRequestCondition("foo=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("foo", "bar");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void paramValueNoMatch() {
RequestCondition condition = new ParamsRequestCondition("foo=bar");
ParamsRequestCondition condition = new ParamsRequestCondition("foo=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("foo", "bazz");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void compareTo() {
MockHttpServletRequest request = new MockHttpServletRequest();
ParamsRequestCondition condition1 = new ParamsRequestCondition("foo", "bar", "baz");
ParamsRequestCondition condition2 = new ParamsRequestCondition("foo", "bar");
int result = condition1.compareTo(condition2);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
}
......@@ -106,7 +112,7 @@ public class ParamsRequestConditionTests {
ParamsRequestCondition condition2 = new ParamsRequestCondition("foo=baz");
ParamsRequestCondition result = condition1.combine(condition2);
Set<ParamsRequestCondition.ParamRequestCondition> conditions = result.getConditions();
Collection<ParamExpression> conditions = result.getContent();
assertEquals(2, conditions.size());
}
......
......@@ -16,17 +16,17 @@
package org.springframework.web.servlet.mvc.method.condition;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import java.util.Collection;
import org.springframework.http.MediaType;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.Assert.*;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition.ProduceMediaTypeExpression;
/**
* @author Arjen Poutsma
......@@ -35,65 +35,66 @@ public class ProducesRequestConditionTests {
@Test
public void consumesMatch() {
RequestCondition condition = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void negatedConsumesMatch() {
RequestCondition condition = new ProducesRequestCondition("!text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void consumesWildcardMatch() {
RequestCondition condition = new ProducesRequestCondition("text/*");
ProducesRequestCondition condition = new ProducesRequestCondition("text/*");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void consumesMultipleMatch() {
RequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void consumesSingleNoMatch() {
RequestCondition condition = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "application/xml");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void compareToSingle() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*");
List<MediaType> accept = Collections.singletonList(MediaType.TEXT_PLAIN);
int result = condition1.compareTo(condition2, accept);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, accept);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
}
......@@ -102,23 +103,25 @@ public class ProducesRequestConditionTests {
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
List<MediaType> accept = Collections.singletonList(MediaType.TEXT_PLAIN);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
int result = condition1.compareTo(condition2, accept);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, accept);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
condition1 = new ProducesRequestCondition("*/*");
condition2 = new ProducesRequestCondition("text/*");
accept = Collections.singletonList(new MediaType("text", "*"));
request = new MockHttpServletRequest();
request.addHeader("Accept", "text/*");
result = condition1.compareTo(condition2, accept);
result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result > 0);
result = condition2.compareTo(condition1, accept);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result < 0);
}
......@@ -127,20 +130,24 @@ public class ProducesRequestConditionTests {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
List<MediaType> accept = Arrays.asList(MediaType.TEXT_PLAIN, MediaType.APPLICATION_XML);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
request.addHeader("Accept", "application/xml");
int result = condition1.compareTo(condition2, accept);
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, accept);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
accept = Arrays.asList(MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN);
request = new MockHttpServletRequest();
request.addHeader("Accept", "application/xml");
request.addHeader("Accept", "text/plain");
result = condition1.compareTo(condition2, accept);
result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result > 0);
result = condition2.compareTo(condition1, accept);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result < 0);
}
......@@ -166,28 +173,11 @@ public class ProducesRequestConditionTests {
public void parseConsumesAndHeaders() {
String[] consumes = new String[] {"text/plain"};
String[] headers = new String[]{"foo=bar", "accept=application/xml,application/pdf"};
ProducesRequestCondition condition = RequestConditionFactory.parseProduces(consumes, headers);
ProducesRequestCondition condition = new ProducesRequestCondition(consumes, headers);
assertConditions(condition, "text/plain", "application/xml", "application/pdf");
}
@Test
public void parseConsumesDefault() {
String[] consumes = new String[] {"*/*"};
String[] headers = new String[0];
ProducesRequestCondition condition = RequestConditionFactory.parseProduces(consumes, headers);
assertConditions(condition, "*/*");
}
@Test
public void parseConsumesDefaultAndHeaders() {
String[] consumes = new String[] {"*/*"};
String[] headers = new String[]{"foo=bar", "accept=text/plain"};
ProducesRequestCondition condition = RequestConditionFactory.parseProduces(consumes, headers);
assertConditions(condition, "text/plain");
}
@Test
public void getMatchingCondition() {
MockHttpServletRequest request = new MockHttpServletRequest();
......@@ -205,12 +195,12 @@ public class ProducesRequestConditionTests {
}
private void assertConditions(ProducesRequestCondition condition, String... expected) {
Set<ProducesRequestCondition.ProduceRequestCondition> conditions = condition.getConditions();
assertEquals("Invalid amount of conditions", conditions.size(), expected.length);
Collection<ProduceMediaTypeExpression> expressions = condition.getContent();
assertEquals("Invalid amount of conditions", expressions.size(), expected.length);
for (String s : expected) {
boolean found = false;
for (ProducesRequestCondition.ProduceRequestCondition requestCondition : conditions) {
String conditionMediaType = requestCondition.getMediaType().toString();
for (ProduceMediaTypeExpression expr : expressions) {
String conditionMediaType = expr.getMediaType().toString();
if (conditionMediaType.equals(s)) {
found = true;
break;
......
......@@ -30,29 +30,29 @@ public class RequestMethodsRequestConditionTests {
@Test
public void methodMatch() {
RequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
@Test
public void methodNoMatch() {
RequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET);
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo");
assertFalse(condition.match(request));
assertNull(condition.getMatchingCondition(request));
}
@Test
public void multipleMethodsMatch() {
RequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
assertTrue(condition.match(request));
assertNotNull(condition.getMatchingCondition(request));
}
......@@ -62,16 +62,18 @@ public class RequestMethodsRequestConditionTests {
RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(RequestMethod.POST);
RequestMethodsRequestCondition condition3 = new RequestMethodsRequestCondition();
int result = condition1.compareTo(condition2);
MockHttpServletRequest request = new MockHttpServletRequest();
int result = condition1.compareTo(condition2, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1);
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
result = condition2.compareTo(condition3);
result = condition2.compareTo(condition3, request);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition1.compareTo(condition1);
result = condition1.compareTo(condition1, request);
assertEquals("Invalid comparison result ", 0, result);
}
......@@ -81,7 +83,7 @@ public class RequestMethodsRequestConditionTests {
RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(RequestMethod.POST);
RequestMethodsRequestCondition result = condition1.combine(condition2);
assertEquals(2, result.getConditions().size());
assertEquals(2, result.getContent().size());
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册