diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactory.java deleted file mode 100644 index ef226b3d117..00000000000 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactory.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.servlet.mvc.method.annotation; - -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; -import org.springframework.web.util.WebUtils; - -/** - * Factory for request condition objects. - * - * @author Arjen Poutsma - * @author Rossen Stoyanchev - */ -public abstract class RequestConditionFactory { - - /** - * Parses the given parameters, and returns them as a set of request conditions. - * - * @param params the parameters - * @return the request conditions - * @see org.springframework.web.bind.annotation.RequestMapping#params() - */ - public static Set parseParams(String... params) { - if (params == null) { - return Collections.emptySet(); - } - Set result = new LinkedHashSet(params.length); - for (String expression : params) { - result.add(new ParamNameValueCondition(expression)); - } - return result; - } - - /** - * Parses the given headers, and returns them as a set of request conditions. - * - * @param headers the headers - * @return the request conditions - * @see org.springframework.web.bind.annotation.RequestMapping#headers() - */ - public static Set parseHeaders(String... headers) { - if (headers == null) { - return Collections.emptySet(); - } - Set result = new LinkedHashSet(headers.length); - for (String expression : headers) { - HeaderNameValueCondition header = new HeaderNameValueCondition(expression); - if (isMediaTypeHeader(header.name)) { - result.add(new MediaTypeHeaderNameValueCondition(expression)); - } - else { - result.add(header); - } - } - return result; - } - - private static boolean isMediaTypeHeader(String name) { - return "Accept".equalsIgnoreCase(name) || "Content-Type".equalsIgnoreCase(name); - } - - /** - * A condition that supports simple "name=value" style expressions as documented in - * @RequestMapping.params() and @RequestMapping.headers(). - */ - private static abstract class AbstractNameValueCondition implements RequestCondition { - - protected final String name; - - protected final T value; - - protected final boolean isNegated; - - protected AbstractNameValueCondition(String expression) { - int separator = expression.indexOf('='); - if (separator == -1) { - this.isNegated = expression.startsWith("!"); - this.name = isNegated ? expression.substring(1) : expression; - this.value = null; - } - else { - this.isNegated = (separator > 0) && (expression.charAt(separator - 1) == '!'); - this.name = isNegated ? expression.substring(0, separator - 1) : expression.substring(0, separator); - this.value = parseValue(expression.substring(separator + 1)); - } - } - - protected abstract T parseValue(String valueExpression); - - public final boolean match(HttpServletRequest request) { - boolean isMatch; - if (this.value != null) { - isMatch = matchValue(request); - } - else { - isMatch = matchName(request); - } - return isNegated ? !isMatch : isMatch; - } - - protected abstract boolean matchName(HttpServletRequest request); - - protected abstract boolean matchValue(HttpServletRequest request); - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - if (value != null) { - builder.append(name); - if (isNegated) { - builder.append('!'); - } - builder.append('='); - builder.append(value); - } - else { - if (isNegated) { - builder.append('!'); - } - builder.append(name); - } - return builder.toString(); - } - - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + (value != null ? value.hashCode() : 0); - result = 31 * result + (isNegated ? 1 : 0); - return result; - } - } - - /** - * Request parameter name-value condition. - */ - private static class ParamNameValueCondition extends AbstractNameValueCondition { - - private ParamNameValueCondition(String expression) { - super(expression); - } - - @Override - protected String parseValue(String valueExpression) { - return valueExpression; - } - - @Override - protected boolean matchName(HttpServletRequest request) { - return WebUtils.hasSubmitParameter(request, name); - } - - @Override - 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 ParamNameValueCondition) { - ParamNameValueCondition other = (ParamNameValueCondition) obj; - return ((this.name.equals(other.name)) && - (this.value != null ? this.value.equals(other.value) : other.value == null) && - this.isNegated == other.isNegated); - } - return false; - } - } - - /** - * Request header name-value condition. - */ - static class HeaderNameValueCondition extends AbstractNameValueCondition { - - public HeaderNameValueCondition(String expression) { - super(expression); - } - - @Override - protected String parseValue(String valueExpression) { - return valueExpression; - } - - @Override - protected boolean matchName(HttpServletRequest request) { - return request.getHeader(name) != null; - } - - @Override - final protected boolean matchValue(HttpServletRequest request) { - return value.equals(request.getHeader(name)); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj != null && obj instanceof HeaderNameValueCondition) { - HeaderNameValueCondition other = (HeaderNameValueCondition) obj; - return ((this.name.equalsIgnoreCase(other.name)) && - (this.value != null ? this.value.equals(other.value) : other.value == null) && - this.isNegated == other.isNegated); - } - return false; - } - - - } - - /** - * A RequestCondition that for headers that contain {@link org.springframework.http.MediaType MediaTypes}. - */ - private static class MediaTypeHeaderNameValueCondition extends AbstractNameValueCondition> { - - public MediaTypeHeaderNameValueCondition(String expression) { - super(expression); - } - - @Override - protected List parseValue(String valueExpression) { - return Collections.unmodifiableList(MediaType.parseMediaTypes(valueExpression)); - } - - @Override - protected boolean matchName(HttpServletRequest request) { - return request.getHeader(name) != null; - } - - @Override - protected boolean matchValue(HttpServletRequest request) { - List requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(name)); - - for (MediaType mediaType : this.value) { - for (MediaType requestMediaType : requestMediaTypes) { - if (mediaType.includes(requestMediaType)) { - return true; - } - } - } - return false; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj != null && obj instanceof MediaTypeHeaderNameValueCondition) { - MediaTypeHeaderNameValueCondition other = (MediaTypeHeaderNameValueCondition) obj; - return ((this.name.equalsIgnoreCase(other.name)) && - (this.value != null ? this.value.equals(other.value) : other.value == null) && - this.isNegated == other.isNegated); - } - return false; - } - - - } -} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java index 988c58ddff8..c6d358d02aa 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java @@ -23,12 +23,13 @@ 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.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.method.condition.RequestCondition; +import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; import org.springframework.web.util.UrlPathHelper; /** @@ -43,12 +44,18 @@ public final class RequestKey { private final Set methods; - private final Set paramConditions; + private final RequestCondition paramsCondition; - private final Set headerConditions; + private final RequestCondition headersCondition; + + private final RequestCondition consumesCondition; private int hash; + RequestKey(Collection patterns, Collection methods) { + this(patterns, methods, null, null, null); + } + /** * Creates a new {@code RequestKey} instance with the given parameters. * @@ -56,12 +63,14 @@ public final class RequestKey { */ RequestKey(Collection patterns, Collection methods, - Collection paramConditions, - Collection headerConditions) { + RequestCondition paramsCondition, + RequestCondition headersCondition, + RequestCondition consumesCondition) { this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns)); this.methods = asUnmodifiableSet(methods); - this.paramConditions = asUnmodifiableSet(paramConditions); - this.headerConditions = asUnmodifiableSet(headerConditions); + this.paramsCondition = paramsCondition != null ? paramsCondition : RequestConditionFactory.trueCondition(); + this.headersCondition = headersCondition != null ? headersCondition : RequestConditionFactory.trueCondition(); + this.consumesCondition = consumesCondition != null ? consumesCondition : RequestConditionFactory.trueCondition(); } private static Set prependLeadingSlash(Collection patterns) { @@ -95,7 +104,9 @@ public final class RequestKey { public static RequestKey createFromRequestMapping(RequestMapping annotation) { return new RequestKey(Arrays.asList(annotation.value()), Arrays.asList(annotation.method()), RequestConditionFactory.parseParams(annotation.params()), - RequestConditionFactory.parseHeaders(annotation.headers())); + RequestConditionFactory.parseHeaders(annotation.headers()), + RequestConditionFactory.parseConsumes(annotation.consumes()) + ); } /** @@ -109,7 +120,7 @@ public final class RequestKey { public static RequestKey createFromServletRequest(HttpServletRequest request, UrlPathHelper urlPathHelper) { String lookupPath = urlPathHelper.getLookupPathForRequest(request); RequestMethod method = RequestMethod.valueOf(request.getMethod()); - return new RequestKey(Arrays.asList(lookupPath), Arrays.asList(method), null, null); + return new RequestKey(Collections.singleton(lookupPath), Collections.singleton(method)); } /** @@ -129,32 +140,32 @@ public final class RequestKey { /** * Returns the request parameters of this request key. */ - public Set getParams() { - return paramConditions; + public RequestCondition getParams() { + return paramsCondition; } /** * Returns the request headers of this request key. */ - public Set getHeaders() { - return headerConditions; + public RequestCondition getHeaders() { + return headersCondition; } /** - * Creates a new {@code RequestKey} by combining it with another. The typical use case for this is combining type + * Combines this {@code RequestKey} with another. The typical use case for this is combining type * and method-level {@link RequestMapping @RequestMapping} annotations. * - * @param methodKey the method-level RequestKey + * @param other the method-level RequestKey * @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns * @return the combined request key */ - public RequestKey combine(RequestKey methodKey, PathMatcher pathMatcher) { - Set patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher); - Set methods = union(this.methods, methodKey.methods); - Set params = union(this.paramConditions, methodKey.paramConditions); - Set headers = union(this.headerConditions, methodKey.headerConditions); + public RequestKey combine(RequestKey other, PathMatcher pathMatcher) { + Set patterns = combinePatterns(this.patterns, other.patterns, pathMatcher); + Set methods = union(this.methods, other.methods); + RequestCondition params = RequestConditionFactory.and(this.paramsCondition, other.paramsCondition); + RequestCondition headers = RequestConditionFactory.and(this.headersCondition, other.headersCondition); - return new RequestKey(patterns, methods, params, headers); + return new RequestKey(patterns, methods, params, headers, null); } private static Set combinePatterns(Collection typePatterns, @@ -205,7 +216,7 @@ public final class RequestKey { List matchingPatterns = getMatchingPatterns(request, pathMatcher, urlPathHelper); if (!matchingPatterns.isEmpty()) { Set matchingMethods = getMatchingMethods(request); - return new RequestKey(matchingPatterns, matchingMethods, this.paramConditions, this.headerConditions); + return new RequestKey(matchingPatterns, matchingMethods, this.paramsCondition, this.headersCondition, null); } else { return null; @@ -245,11 +256,11 @@ public final class RequestKey { } private boolean checkParams(HttpServletRequest request) { - return checkConditions(paramConditions, request); + return paramsCondition.match(request); } private boolean checkHeaders(HttpServletRequest request) { - return checkConditions(headerConditions, request); + return headersCondition.match(request); } private String getMatchingPattern(String pattern, String lookupPath, PathMatcher pathMatcher) { @@ -284,8 +295,8 @@ public final class RequestKey { if (obj != null && obj instanceof RequestKey) { RequestKey other = (RequestKey) obj; return (this.patterns.equals(other.patterns) && this.methods.equals(other.methods) && - this.paramConditions.equals(other.paramConditions) && - this.headerConditions.equals(other.headerConditions)); + this.paramsCondition.equals(other.paramsCondition) && + this.headersCondition.equals(other.headersCondition)); } return false; } @@ -296,8 +307,8 @@ public final class RequestKey { if (result == 0) { result = patterns.hashCode(); result = 31 * result + methods.hashCode(); - result = 31 * result + paramConditions.hashCode(); - result = 31 * result + headerConditions.hashCode(); + result = 31 * result + paramsCondition.hashCode(); + result = 31 * result + headersCondition.hashCode(); hash = result; } return result; @@ -311,14 +322,9 @@ public final class RequestKey { builder.append(','); builder.append(methods); } - if (!headerConditions.isEmpty()) { - builder.append(','); - builder.append(headerConditions); - } - if (!paramConditions.isEmpty()) { - builder.append(','); - builder.append(paramConditions); - } + builder.append(",params=").append(paramsCondition.toString()); + builder.append(",headers=").append(headersCondition.toString()); + builder.append(",consumes=").append(consumesCondition.toString()); builder.append('}'); return builder.toString(); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java index 8e6a5d89e4f..51f68acda62 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java @@ -23,7 +23,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; - import javax.servlet.http.HttpServletRequest; import org.springframework.core.annotation.AnnotationUtils; @@ -253,11 +252,11 @@ public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMap if (result != 0) { return result; } - result = otherKey.getParams().size() - key.getParams().size(); + result = otherKey.getParams().weight() - key.getParams().weight(); if (result != 0) { return result; } - result = otherKey.getHeaders().size() - key.getHeaders().size(); + result = otherKey.getHeaders().weight() - key.getHeaders().weight(); if (result != 0) { return result; } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/AbstractNameValueCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/AbstractNameValueCondition.java new file mode 100644 index 00000000000..58ed4faec4e --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/AbstractNameValueCondition.java @@ -0,0 +1,100 @@ +/* + * 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 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()}. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @since 3.1 + */ +abstract class AbstractNameValueCondition implements RequestCondition { + + protected final String name; + + protected final T value; + + protected final boolean isNegated; + + AbstractNameValueCondition(String expression) { + int separator = expression.indexOf('='); + if (separator == -1) { + this.isNegated = expression.startsWith("!"); + this.name = isNegated ? expression.substring(1) : expression; + this.value = null; + } + else { + this.isNegated = (separator > 0) && (expression.charAt(separator - 1) == '!'); + this.name = isNegated ? expression.substring(0, separator - 1) : expression.substring(0, separator); + this.value = parseValue(expression.substring(separator + 1)); + } + } + + protected abstract T parseValue(String valueExpression); + + public final boolean match(HttpServletRequest request) { + boolean isMatch; + if (this.value != null) { + isMatch = matchValue(request); + } + else { + isMatch = matchName(request); + } + return isNegated ? !isMatch : isMatch; + } + + protected abstract boolean matchName(HttpServletRequest request); + + protected abstract boolean matchValue(HttpServletRequest request); + + public int weight() { + return 1; + } + + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (isNegated ? 1 : 0); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (value != null) { + builder.append(name); + if (isNegated) { + builder.append('!'); + } + builder.append('='); + builder.append(value); + } + else { + if (isNegated) { + builder.append('!'); + } + builder.append(name); + } + return builder.toString(); + } +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java new file mode 100644 index 00000000000..76d0ba78db6 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java @@ -0,0 +1,47 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; + +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; + +/** +* @author Arjen Poutsma +*/ +class ConsumesRequestCondition implements RequestCondition { + + private final MediaType mediaType; + + ConsumesRequestCondition(String mediaType) { + this.mediaType = MediaType.parseMediaType(mediaType); + } + + public boolean match(HttpServletRequest request) { + String contentTypeString = request.getContentType(); + if (StringUtils.hasLength(contentTypeString)) { + MediaType contentType = MediaType.parseMediaType(contentTypeString); + return this.mediaType.includes(contentType); + } + return false; + } + + public int weight() { + return 1; + } +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/HeaderRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/HeaderRequestCondition.java new file mode 100644 index 00000000000..e8dc4461e06 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/HeaderRequestCondition.java @@ -0,0 +1,64 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; + +/** + * Request header name-value condition. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @see org.springframework.web.bind.annotation.RequestMapping#headers() + * @since 3.1 + */ +class HeaderRequestCondition extends AbstractNameValueCondition { + + public HeaderRequestCondition(String expression) { + super(expression); + } + + @Override + protected String parseValue(String valueExpression) { + return valueExpression; + } + + @Override + protected boolean matchName(HttpServletRequest request) { + return request.getHeader(name) != null; + } + + @Override + protected boolean matchValue(HttpServletRequest request) { + 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; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/LogicalConjunctionRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/LogicalConjunctionRequestCondition.java new file mode 100644 index 00000000000..c03818956f9 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/LogicalConjunctionRequestCondition.java @@ -0,0 +1,48 @@ +/* + * 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.List; +import javax.servlet.http.HttpServletRequest; + +/** + * {@link RequestCondition} implementation that represents a logical AND (i.e. &&). + * + * @author Arjen Poutsma + * @since 3.1 + */ +class LogicalConjunctionRequestCondition extends RequestConditionComposite { + + LogicalConjunctionRequestCondition(List conditions) { + super(conditions); + } + + public boolean match(HttpServletRequest request) { + for (RequestCondition condition : conditions) { + if (!condition.match(request)) { + return false; + } + } + return true; + } + + @Override + protected String getToStringInfix() { + return " && "; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/LogicalDisjunctionRequestCondition.java similarity index 51% rename from org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestCondition.java rename to org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/LogicalDisjunctionRequestCondition.java index 335066939a4..6c45f68ecc0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestCondition.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/LogicalDisjunctionRequestCondition.java @@ -14,16 +14,35 @@ * limitations under the License. */ -package org.springframework.web.servlet.mvc.method.annotation; +package org.springframework.web.servlet.mvc.method.condition; +import java.util.List; import javax.servlet.http.HttpServletRequest; /** - * A condition that can be matched to a ServletRequest. - * - * @author Rossen Stoyanchev + * {@link RequestCondition} implementation that represents a logical OR (i.e. ||). + * + * @author Arjen Poutsma + * @since 3.1 */ -interface RequestCondition { +class LogicalDisjunctionRequestCondition extends RequestConditionComposite { + + LogicalDisjunctionRequestCondition(List conditions) { + super(conditions); + } + + public boolean match(HttpServletRequest request) { + for (RequestCondition condition : conditions) { + if (condition.match(request)) { + return true; + } + } + return false; + } + + @Override + protected String getToStringInfix() { + return " || "; + } - boolean match(HttpServletRequest request); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/MediaTypeHeaderRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/MediaTypeHeaderRequestCondition.java new file mode 100644 index 00000000000..cec3cae3c6f --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/MediaTypeHeaderRequestCondition.java @@ -0,0 +1,73 @@ +/* + * 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.Collections; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.MediaType; + +/** + * A RequestCondition that for headers that contain {@link org.springframework.http.MediaType MediaTypes}. + */ +class MediaTypeHeaderRequestCondition extends AbstractNameValueCondition> { + + public MediaTypeHeaderRequestCondition(String expression) { + super(expression); + } + + @Override + protected List parseValue(String valueExpression) { + return Collections.unmodifiableList(MediaType.parseMediaTypes(valueExpression)); + } + + @Override + protected boolean matchName(HttpServletRequest request) { + return request.getHeader(name) != null; + } + + @Override + protected boolean matchValue(HttpServletRequest request) { + List requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(name)); + + for (MediaType mediaType : this.value) { + for (MediaType requestMediaType : requestMediaTypes) { + if (mediaType.includes(requestMediaType)) { + return true; + } + } + } + return false; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && obj instanceof MediaTypeHeaderRequestCondition) { + MediaTypeHeaderRequestCondition other = (MediaTypeHeaderRequestCondition) obj; + return ((this.name.equalsIgnoreCase(other.name)) && + (this.value != null ? this.value.equals(other.value) : other.value == null) && + this.isNegated == other.isNegated); + } + return false; + } + + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ParamRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ParamRequestCondition.java new file mode 100644 index 00000000000..c3d1761627f --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ParamRequestCondition.java @@ -0,0 +1,66 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; + +import org.springframework.web.util.WebUtils; + +/** + * Request parameter name-value condition. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @see org.springframework.web.bind.annotation.RequestMapping#params() + * @since 3.1 + */ +class ParamRequestCondition extends AbstractNameValueCondition { + + ParamRequestCondition(String expression) { + super(expression); + } + + @Override + protected String parseValue(String valueExpression) { + return valueExpression; + } + + @Override + protected boolean matchName(HttpServletRequest request) { + return WebUtils.hasSubmitParameter(request, name); + } + + @Override + 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; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java new file mode 100644 index 00000000000..9139d0c4e0b --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestCondition.java @@ -0,0 +1,49 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; + +/** + * Defines the contract for conditions that must be met before an incoming request matches a {@link + * org.springframework.web.servlet.mvc.method.annotation.RequestKey RequestKey}. + * + *

Implementations of this interface are created by the {@link RequestConditionFactory}. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @see RequestConditionFactory + * @since 3.1 + */ +public interface RequestCondition { + + /** + * 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 + */ + boolean match(HttpServletRequest request); + + /** + * Indicates the relative weight of this condition. More important conditions have a higher weight than ones that are + * less so. + * + * @return the weight of this condition + */ + int weight(); +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionComposite.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionComposite.java new file mode 100644 index 00000000000..3b0199f82ed --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionComposite.java @@ -0,0 +1,77 @@ +/* + * 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.Iterator; +import java.util.List; + +/** + * Abstract base class for {@link RequestCondition} implementations that wrap other request conditions. + * + * @author Arjen Poutsma + * @since 3.1 + */ +abstract class RequestConditionComposite implements RequestCondition { + + protected final List conditions; + + public RequestConditionComposite(List conditions) { + this.conditions = conditions; + } + + public int weight() { + int size = 0; + for (RequestCondition condition : conditions) { + size += condition.weight(); + } + return size; + } + + @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); + } + return false; + } + + @Override + public int hashCode() { + return conditions.hashCode(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("["); + String infix = getToStringInfix(); + for (Iterator iterator = conditions.iterator(); iterator.hasNext();) { + RequestCondition condition = iterator.next(); + builder.append(condition.toString()); + if (iterator.hasNext()) { + builder.append(infix); + } + } + builder.append("]"); + return builder.toString(); + } + + protected abstract String getToStringInfix(); +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionFactory.java new file mode 100644 index 00000000000..2b52e7db648 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionFactory.java @@ -0,0 +1,175 @@ +/* + * 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.Iterator; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.util.ObjectUtils; + +/** + * Factory for {@link RequestCondition} objects. + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @since 3.1 + */ +public abstract class RequestConditionFactory { + + private static final RequestCondition TRUE_CONDITION = new RequestCondition() { + public boolean match(HttpServletRequest request) { + return true; + } + + public int weight() { + return 0; + } + + @Override + public String toString() { + return "TRUE"; + } + }; + + private static final RequestCondition FALSE_CONDITION = new RequestCondition() { + public boolean match(HttpServletRequest request) { + return false; + } + + public int weight() { + return 0; + } + + + @Override + public String toString() { + return "FALSE"; + } + }; + + public static RequestCondition trueCondition() { + return TRUE_CONDITION; + } + + public static RequestCondition falseCondition() { + return FALSE_CONDITION; + } + + /** + * Combines the given conditions into a logical AND, i.e. the returned condition will return {@code true} for {@link + * RequestCondition#match(HttpServletRequest)} if all of the given conditions do so. + * + * @param conditions the conditions + * @return a condition that represents a logical AND + */ + public static RequestCondition and(RequestCondition... conditions) { + List filteredConditions = new ArrayList(Arrays.asList(conditions)); + for (Iterator iterator = filteredConditions.iterator(); iterator.hasNext();) { + RequestCondition condition = iterator.next(); + if (condition == TRUE_CONDITION) { + iterator.remove(); + } + } + return new LogicalConjunctionRequestCondition(filteredConditions); + } + + /** + * Combines the given conditions into a logical OR, i.e. the returned condition will return {@code true} for {@link + * RequestCondition#match(HttpServletRequest)} if any of the given conditions do so. + * + * @param conditions the conditions + * @return a condition that represents a logical OR + */ + public static RequestCondition or(RequestCondition... conditions) { + List filteredConditions = new ArrayList(Arrays.asList(conditions)); + for (Iterator iterator = filteredConditions.iterator(); iterator.hasNext();) { + RequestCondition condition = iterator.next(); + if (condition == TRUE_CONDITION) { + return trueCondition(); + } + else if (condition == FALSE_CONDITION) { + iterator.remove(); + } + } + return new LogicalDisjunctionRequestCondition(filteredConditions); + } + + /** + * Parses the given parameters, and returns them as a single request conditions. + * + * @param params the parameters + * @return the request condition + * @see org.springframework.web.bind.annotation.RequestMapping#params() + */ + public static RequestCondition parseParams(String... params) { + if (ObjectUtils.isEmpty(params)) { + return TRUE_CONDITION; + } + RequestCondition[] result = new RequestCondition[params.length]; + for (int i = 0; i < params.length; i++) { + result[i] = new ParamRequestCondition(params[i]); + } + return and(result); + } + + /** + * 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 RequestCondition parseHeaders(String... headers) { + if (ObjectUtils.isEmpty(headers)) { + return TRUE_CONDITION; + } + RequestCondition[] result = new RequestCondition[headers.length]; + for (int i = 0; i < headers.length; i++) { + HeaderRequestCondition header = new HeaderRequestCondition(headers[i]); + if (isMediaTypeHeader(header.name)) { + result[i] = new MediaTypeHeaderRequestCondition(headers[i]); + } + else { + result[i] = header; + } + } + return and(result); + } + + private static boolean isMediaTypeHeader(String name) { + return "Accept".equalsIgnoreCase(name) || "Content-Type".equalsIgnoreCase(name); + } + + public static RequestCondition parseConsumes(String... consumes) { + if (ObjectUtils.isEmpty(consumes)) { + return TRUE_CONDITION; + } + RequestCondition[] result = new RequestCondition[consumes.length]; + for (int i = 0; i < consumes.length; i++) { + result[i] = new ConsumesRequestCondition(consumes[i]); + } + return or(result); + } + + // + // Conditions + // + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactoryTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactoryTests.java deleted file mode 100644 index 93f35604672..00000000000 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactoryTests.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.servlet.mvc.method.annotation; - -import java.util.Set; - -import org.junit.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.servlet.mvc.method.annotation.RequestCondition; -import org.springframework.web.servlet.mvc.method.annotation.RequestConditionFactory; - -import static org.junit.Assert.*; - -/** - * @author Arjen Poutsma - */ -public class RequestConditionFactoryTests { - - @Test - public void paramEquals() { - assertEquals(getSingleParamCondition("foo"), getSingleParamCondition("foo")); - assertFalse(getSingleParamCondition("foo").equals(getSingleParamCondition("bar"))); - assertFalse(getSingleParamCondition("foo").equals(getSingleParamCondition("FOO"))); - assertEquals(getSingleParamCondition("foo=bar"), getSingleParamCondition("foo=bar")); - assertFalse(getSingleParamCondition("foo=bar").equals(getSingleParamCondition("FOO=bar"))); - } - - @Test - public void headerEquals() { - assertEquals(getSingleHeaderCondition("foo"), getSingleHeaderCondition("foo")); - assertEquals(getSingleHeaderCondition("foo"), getSingleHeaderCondition("FOO")); - assertFalse(getSingleHeaderCondition("foo").equals(getSingleHeaderCondition("bar"))); - assertEquals(getSingleHeaderCondition("foo=bar"), getSingleHeaderCondition("foo=bar")); - assertEquals(getSingleHeaderCondition("foo=bar"), getSingleHeaderCondition("FOO=bar")); - assertEquals(getSingleHeaderCondition("content-type=text/xml"), - getSingleHeaderCondition("Content-Type=TEXT/XML")); - } - - @Test - public void headerPresent() { - RequestCondition condition = getSingleHeaderCondition("accept"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Accept", ""); - - assertTrue(condition.match(request)); - } - - @Test - public void headerPresentNoMatch() { - RequestCondition condition = getSingleHeaderCondition("foo"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("bar", ""); - - assertFalse(condition.match(request)); - } - - @Test - public void headerNotPresent() { - RequestCondition condition = getSingleHeaderCondition("!accept"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - - assertTrue(condition.match(request)); - } - - @Test - public void headerValueMatch() { - RequestCondition condition = getSingleHeaderCondition("foo=bar"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("foo", "bar"); - - assertTrue(condition.match(request)); - } - - @Test - public void headerValueNoMatch() { - RequestCondition condition = getSingleHeaderCondition("foo=bar"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("foo", "bazz"); - - assertFalse(condition.match(request)); - } - - @Test - public void headerCaseSensitiveValueMatch() { - RequestCondition condition = getSingleHeaderCondition("foo=Bar"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("foo", "bar"); - - assertFalse(condition.match(request)); - } - - @Test - public void headerValueMatchNegated() { - RequestCondition condition = getSingleHeaderCondition("foo!=bar"); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("foo", "baz"); - - assertTrue(condition.match(request)); - } - - @Test - public void mediaTypeHeaderValueMatch() { - RequestCondition condition = getSingleHeaderCondition("accept=text/html"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Accept", "text/html"); - - assertTrue(condition.match(request)); - } - - @Test - public void mediaTypeHeaderValueMatchNegated() { - RequestCondition condition = getSingleHeaderCondition("accept!=text/html"); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Accept", "application/html"); - - assertTrue(condition.match(request)); - } - - private RequestCondition getSingleHeaderCondition(String expression) { - Set conditions = RequestConditionFactory.parseHeaders(expression); - assertEquals(1, conditions.size()); - return conditions.iterator().next(); - } - - private RequestCondition getSingleParamCondition(String expression) { - Set conditions = RequestConditionFactory.parseParams(expression); - assertEquals(1, conditions.size()); - return conditions.iterator().next(); - } - - -} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java index 914c0f3fbd5..8147319b3e3 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyComparatorTests.java @@ -16,11 +16,6 @@ package org.springframework.web.servlet.mvc.method.annotation; -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.util.Collections; import java.util.Comparator; import java.util.List; @@ -28,11 +23,13 @@ import java.util.List; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; + import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.mvc.method.annotation.RequestConditionFactory; -import org.springframework.web.servlet.mvc.method.annotation.RequestKey; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping; +import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; + +import static java.util.Arrays.*; +import static org.junit.Assert.*; /** * @author Arjen Poutsma @@ -54,8 +51,8 @@ public class RequestKeyComparatorTests { public void moreSpecificPatternWins() { request.setRequestURI("/foo"); Comparator comparator = handlerMapping.getKeyComparator(request); - RequestKey key1 = new RequestKey(asList("/fo*"), null, null, null); - RequestKey key2 = new RequestKey(asList("/foo"), null, null, null); + RequestKey key1 = new RequestKey(asList("/fo*"), null); + RequestKey key2 = new RequestKey(asList("/foo"), null); assertEquals(1, comparator.compare(key1, key2)); } @@ -64,8 +61,8 @@ public class RequestKeyComparatorTests { public void equalPatterns() { request.setRequestURI("/foo"); Comparator comparator = handlerMapping.getKeyComparator(request); - RequestKey key1 = new RequestKey(asList("/foo*"), null, null, null); - RequestKey key2 = new RequestKey(asList("/foo*"), null, null, null); + RequestKey key1 = new RequestKey(asList("/foo*"), null); + RequestKey key2 = new RequestKey(asList("/foo*"), null); assertEquals(0, comparator.compare(key1, key2)); } @@ -73,8 +70,8 @@ public class RequestKeyComparatorTests { @Test public void greaterNumberOfMatchingPatternsWins() throws Exception { request.setRequestURI("/foo.html"); - RequestKey key1 = new RequestKey(asList("/foo", "*.jpeg"), null, null, null); - RequestKey key2 = new RequestKey(asList("/foo", "*.html"), null, null, null); + RequestKey key1 = new RequestKey(asList("/foo", "*.jpeg"), null); + RequestKey key2 = new RequestKey(asList("/foo", "*.html"), null); RequestKey match1 = handlerMapping.getMatchingKey(key1, request); RequestKey match2 = handlerMapping.getMatchingKey(key2, request); List matches = asList(match1, match2); @@ -86,18 +83,18 @@ public class RequestKeyComparatorTests { @Test public void oneMethodWinsOverNone() { Comparator comparator = handlerMapping.getKeyComparator(request); - RequestKey key1 = new RequestKey(null, null, null, null); - RequestKey key2 = new RequestKey(null, asList(RequestMethod.GET), null, null); + RequestKey key1 = new RequestKey(null, null); + RequestKey key2 = new RequestKey(null, asList(RequestMethod.GET)); assertEquals(1, comparator.compare(key1, key2)); } @Test public void methodsAndParams() { - RequestKey empty = new RequestKey(null, null, null, null); - RequestKey oneMethod = new RequestKey(null, asList(RequestMethod.GET), null, null); + RequestKey empty = new RequestKey(null, null); + RequestKey oneMethod = new RequestKey(null, asList(RequestMethod.GET)); RequestKey oneMethodOneParam = - new RequestKey(null, asList(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null); + new RequestKey(null, asList(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null, null); List list = asList(empty, oneMethod, oneMethodOneParam); Collections.shuffle(list); Collections.sort(list, handlerMapping.getKeyComparator(request)); @@ -110,9 +107,9 @@ public class RequestKeyComparatorTests { @Test @Ignore // TODO : remove ignore public void acceptHeaders() { - RequestKey html = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=text/html")); - RequestKey xml = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=application/xml")); - RequestKey none = new RequestKey(null, null, null, null); + RequestKey html = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=text/html"), null); + RequestKey xml = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=application/xml"), null); + RequestKey none = new RequestKey(null, null); request.addHeader("Accept", "application/xml, text/html"); Comparator comparator = handlerMapping.getKeyComparator(request); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java index 3a7fb435d07..c44817ba504 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestKeyTests.java @@ -16,22 +16,20 @@ package org.springframework.web.servlet.mvc.method.annotation; -import static java.util.Arrays.asList; -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.annotation.RequestConditionFactory; -import org.springframework.web.servlet.mvc.method.annotation.RequestKey; +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.*; + /** * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -40,8 +38,8 @@ public class RequestKeyTests { @Test public void equals() { - RequestKey key1 = new RequestKey(asList("/foo"), asList(GET), null, null); - RequestKey key2 = new RequestKey(asList("/foo"), asList(GET), null, null); + RequestKey key1 = new RequestKey(singleton("/foo"), singleton(GET)); + RequestKey key2 = new RequestKey(singleton("/foo"), singleton(GET)); assertEquals(key1, key2); assertEquals(key1.hashCode(), key2.hashCode()); @@ -49,8 +47,8 @@ public class RequestKeyTests { @Test public void equalsPrependSlash() { - RequestKey key1 = new RequestKey(asList("/foo"), asList(GET), null, null); - RequestKey key2 = new RequestKey(asList("foo"), asList(GET), null, null); + RequestKey key1 = new RequestKey(singleton("/foo"), singleton(GET)); + RequestKey key2 = new RequestKey(singleton("foo"), singleton(GET)); assertEquals(key1, key2); assertEquals(key1.hashCode(), key2.hashCode()); @@ -63,22 +61,22 @@ public class RequestKeyTests { RequestKey key1 = createKeyFromPatterns("/t1", "/t2"); RequestKey key2 = createKeyFromPatterns("/m1", "/m2"); RequestKey key3 = createKeyFromPatterns("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"); - assertEquals(key3, key1.combine(key2, pathMatcher)); + assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns()); key1 = createKeyFromPatterns("/t1"); - key2 = createKeyFromPatterns(new String[] {}); + key2 = createKeyFromPatterns(); key3 = createKeyFromPatterns("/t1"); - assertEquals(key3, key1.combine(key2, pathMatcher)); + assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns()); - key1 = createKeyFromPatterns(new String[] {}); + key1 = createKeyFromPatterns(); key2 = createKeyFromPatterns("/m1"); key3 = createKeyFromPatterns("/m1"); - assertEquals(key3, key1.combine(key2, pathMatcher)); + assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns()); - key1 = createKeyFromPatterns(new String[] {}); - key2 = createKeyFromPatterns(new String[] {}); + key1 = createKeyFromPatterns(); + key2 = createKeyFromPatterns(); key3 = createKeyFromPatterns("/"); - assertEquals(key3, key1.combine(key2, pathMatcher)); + assertEquals(key3.getPatterns(), key1.combine(key2, pathMatcher).getPatterns()); } @@ -88,39 +86,39 @@ public class RequestKeyTests { PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - RequestKey key = new RequestKey(asList("/foo"), null, null, null); + RequestKey key = new RequestKey(singleton("/foo"), null); RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull(match); request = new MockHttpServletRequest("GET", "/foo/bar"); - key = new RequestKey(asList("/foo/*"), null, null, null); + key = new RequestKey(singleton("/foo/*"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull("Pattern match", match); request = new MockHttpServletRequest("GET", "/foo.html"); - key = new RequestKey(asList("/foo"), null, null, null); + key = new RequestKey(singleton("/foo"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull("Implicit match by extension", match); assertEquals("Contains matched pattern", "/foo.*", match.getPatterns().iterator().next()); request = new MockHttpServletRequest("GET", "/foo/"); - key = new RequestKey(asList("/foo"), null, null, null); + key = new RequestKey(singleton("/foo"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull("Implicit match by trailing slash", match); assertEquals("Contains matched pattern", "/foo/", match.getPatterns().iterator().next()); request = new MockHttpServletRequest("GET", "/foo.html"); - key = new RequestKey(asList("/foo.jpg"), null, null, null); + key = new RequestKey(singleton("/foo.jpg"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNull("Implicit match ignored if pattern has extension", match); request = new MockHttpServletRequest("GET", "/foo.html"); - key = new RequestKey(asList("/foo.jpg"), null, null, null); + key = new RequestKey(singleton("/foo.jpg"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNull("Implicit match ignored on pattern with trailing slash", match); @@ -132,17 +130,17 @@ public class RequestKeyTests { PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - RequestKey key = new RequestKey(asList("/foo"), null, null, null); + RequestKey key = new RequestKey(singleton("/foo"), null); RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull("No method matches any method", match); - key = new RequestKey(asList("/foo"), asList(GET), null, null); + key = new RequestKey(singleton("/foo"), singleton(GET)); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull("Exact match", match); - key = new RequestKey(asList("/foo"), asList(POST), null, null); + key = new RequestKey(singleton("/foo"), singleton(POST)); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNull("No match", match); @@ -154,15 +152,15 @@ public class RequestKeyTests { PathMatcher pathMatcher = new AntPathMatcher(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); - RequestKey key = new RequestKey(asList("/foo*", "/bar"), asList(GET, POST), null, null); + RequestKey key = new RequestKey(asList("/foo*", "/bar"), asList(GET, POST)); RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); - RequestKey expected = new RequestKey(asList("/foo*"), asList(GET), null, null); + RequestKey expected = new RequestKey(singleton("/foo*"), singleton(GET)); assertEquals("Matching RequestKey contains matched patterns and methods only", expected, match); - key = new RequestKey(asList("/**", "/foo*", "/foo"), null, null, null); + key = new RequestKey(asList("/**", "/foo*", "/foo"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); - expected = new RequestKey(asList("/foo", "/foo*", "/**"), null, null, null); + expected = new RequestKey(asList("/foo", "/foo*", "/**"), null); assertEquals("Matched patterns are sorted with best match at the top", expected, match); @@ -175,12 +173,12 @@ public class RequestKeyTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setParameter("foo", "bar"); - RequestKey key = new RequestKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null); + RequestKey key = new RequestKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null, null); RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull(match); - key = new RequestKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null); + key = new RequestKey(singleton("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null, null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNull(match); @@ -193,12 +191,12 @@ public class RequestKeyTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.addHeader("foo", "bar"); - RequestKey key = new RequestKey(asList("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar")); + RequestKey key = new RequestKey(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"), null); RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNotNull(match); - key = new RequestKey(asList("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar")); + key = new RequestKey(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"), null); match = key.getMatchingKey(request, pathMatcher, urlPathHelper); assertNull(match); @@ -208,11 +206,11 @@ public class RequestKeyTests { public void testCreateFromServletRequest() { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); RequestKey key = RequestKey.createFromServletRequest(request, new UrlPathHelper()); - assertEquals(new RequestKey(asList("/foo"), asList(RequestMethod.GET), null, null), key); + assertEquals(new RequestKey(singleton("/foo"), singleton(RequestMethod.GET), null, null, null), key); } private RequestKey createKeyFromPatterns(String... patterns) { - return new RequestKey(asList(patterns), null, null, null); + return new RequestKey(asList(patterns), null); } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java index 6c14c3f4b43..eccb29afe1c 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMappingTests.java @@ -16,20 +16,13 @@ package org.springframework.web.servlet.mvc.method.annotation; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; - import java.util.Arrays; import java.util.Map; - import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; + import org.springframework.context.support.StaticApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.stereotype.Controller; @@ -42,8 +35,8 @@ 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.annotation.RequestKey; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping; + +import static org.junit.Assert.*; /** * @author Arjen Poutsma @@ -96,7 +89,7 @@ public class RequestMappingHandlerMethodMappingTests { @Test public void uriTemplateVariables() { - RequestKey key = new RequestKey(Arrays.asList("/{path1}/{path2}"), null, null, null); + RequestKey key = new RequestKey(Arrays.asList("/{path1}/{path2}"), null); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2"); mapping.handleMatch(key, request); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionFactoryTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionFactoryTests.java new file mode 100644 index 00000000000..4930c56a7b0 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionFactoryTests.java @@ -0,0 +1,212 @@ +/* + * 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 org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.Assert.*; + +/** + * @author Arjen Poutsma + */ +public class RequestConditionFactoryTests { + + + @Test + public void andMatch() { + RequestCondition condition1 = RequestConditionFactory.trueCondition(); + RequestCondition condition2 = RequestConditionFactory.trueCondition(); + RequestCondition and = RequestConditionFactory.and(condition1, condition2); + assertTrue(and.match(new MockHttpServletRequest())); + } + + @Test + public void andNoMatch() { + RequestCondition condition1 = RequestConditionFactory.trueCondition(); + RequestCondition condition2 = RequestConditionFactory.falseCondition(); + RequestCondition and = RequestConditionFactory.and(condition1, condition2); + assertFalse(and.match(new MockHttpServletRequest())); + } + + @Test + public void orMatch() { + RequestCondition condition1 = RequestConditionFactory.trueCondition(); + RequestCondition condition2 = RequestConditionFactory.falseCondition(); + RequestCondition and = RequestConditionFactory.or(condition1, condition2); + assertTrue(and.match(new MockHttpServletRequest())); + } + + @Test + public void orNoMatch() { + RequestCondition condition1 = RequestConditionFactory.falseCondition(); + RequestCondition condition2 = RequestConditionFactory.falseCondition(); + RequestCondition and = RequestConditionFactory.and(condition1, condition2); + assertFalse(and.match(new MockHttpServletRequest())); + } + + @Test + public void paramEquals() { + assertEquals(RequestConditionFactory.parseParams("foo"), RequestConditionFactory.parseParams("foo")); + assertFalse(RequestConditionFactory.parseParams("foo").equals(RequestConditionFactory.parseParams("bar"))); + assertFalse(RequestConditionFactory.parseParams("foo").equals(RequestConditionFactory.parseParams("FOO"))); + assertEquals(RequestConditionFactory.parseParams("foo=bar"), RequestConditionFactory.parseParams("foo=bar")); + assertFalse( + RequestConditionFactory.parseParams("foo=bar").equals(RequestConditionFactory.parseParams("FOO=bar"))); + } + + @Test + public void headerEquals() { + assertEquals(RequestConditionFactory.parseHeaders("foo"), RequestConditionFactory.parseHeaders("foo")); + assertEquals(RequestConditionFactory.parseHeaders("foo"), RequestConditionFactory.parseHeaders("FOO")); + assertFalse(RequestConditionFactory.parseHeaders("foo").equals(RequestConditionFactory.parseHeaders("bar"))); + assertEquals(RequestConditionFactory.parseHeaders("foo=bar"), RequestConditionFactory.parseHeaders("foo=bar")); + assertEquals(RequestConditionFactory.parseHeaders("foo=bar"), RequestConditionFactory.parseHeaders("FOO=bar")); + assertEquals(RequestConditionFactory.parseHeaders("content-type=text/xml"), + RequestConditionFactory.parseHeaders("Content-Type=TEXT/XML")); + } + + @Test + public void headerPresent() { + RequestCondition condition = RequestConditionFactory.parseHeaders("accept"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Accept", ""); + + assertTrue(condition.match(request)); + } + + @Test + public void headerPresentNoMatch() { + RequestCondition condition = RequestConditionFactory.parseHeaders("foo"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("bar", ""); + + assertFalse(condition.match(request)); + } + + @Test + public void headerNotPresent() { + RequestCondition condition = RequestConditionFactory.parseHeaders("!accept"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + + assertTrue(condition.match(request)); + } + + @Test + public void headerValueMatch() { + RequestCondition condition = RequestConditionFactory.parseHeaders("foo=bar"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("foo", "bar"); + + assertTrue(condition.match(request)); + } + + @Test + public void headerValueNoMatch() { + RequestCondition condition = RequestConditionFactory.parseHeaders("foo=bar"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("foo", "bazz"); + + assertFalse(condition.match(request)); + } + + @Test + public void headerCaseSensitiveValueMatch() { + RequestCondition condition = RequestConditionFactory.parseHeaders("foo=Bar"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("foo", "bar"); + + assertFalse(condition.match(request)); + } + + @Test + public void headerValueMatchNegated() { + RequestCondition condition = RequestConditionFactory.parseHeaders("foo!=bar"); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("foo", "baz"); + + assertTrue(condition.match(request)); + } + + @Test + public void mediaTypeHeaderValueMatch() { + RequestCondition condition = RequestConditionFactory.parseHeaders("accept=text/html"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Accept", "text/html"); + + assertTrue(condition.match(request)); + } + + @Test + public void mediaTypeHeaderValueMatchNegated() { + RequestCondition condition = RequestConditionFactory.parseHeaders("accept!=text/html"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Accept", "application/html"); + + assertTrue(condition.match(request)); + } + + @Test + public void consumesMatch() { + RequestCondition condition = RequestConditionFactory.parseConsumes("text/plain"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContentType("text/plain"); + + assertTrue(condition.match(request)); + } + + @Test + public void consumesWildcardMatch() { + RequestCondition condition = RequestConditionFactory.parseConsumes("text/*"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContentType("text/plain"); + + assertTrue(condition.match(request)); + } + + @Test + public void consumesMultipleMatch() { + RequestCondition condition = RequestConditionFactory.parseConsumes("text/plain", "application/xml"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContentType("text/plain"); + + assertTrue(condition.match(request)); + } + + @Test + public void consumesSingleNoMatch() { + RequestCondition condition = RequestConditionFactory.parseConsumes("text/plain"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContentType("application/xml"); + + assertFalse(condition.match(request)); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index 3c376dd7c32..91ce9516a05 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * 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. @@ -297,4 +297,6 @@ public @interface RequestMapping { */ String[] headers() default {}; + String[] consumes() default "*/*"; + }