SPR-7354 - Added equivalent of JAX-RS @Consumes to Spring MVC

This commit is contained in:
Arjen Poutsma
2011-04-08 09:26:17 +00:00
parent 381af43f91
commit bf6693dbc5
19 changed files with 1044 additions and 559 deletions

View File

@@ -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<RequestCondition> parseParams(String... params) {
if (params == null) {
return Collections.emptySet();
}
Set<RequestCondition> result = new LinkedHashSet<RequestCondition>(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<RequestCondition> parseHeaders(String... headers) {
if (headers == null) {
return Collections.emptySet();
}
Set<RequestCondition> result = new LinkedHashSet<RequestCondition>(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
* <code>@RequestMapping.params()</code> and <code>@RequestMapping.headers()</code>.
*/
private static abstract class AbstractNameValueCondition<T> 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<String> {
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<String> {
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<List<MediaType>> {
public MediaTypeHeaderNameValueCondition(String expression) {
super(expression);
}
@Override
protected List<MediaType> 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<MediaType> 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;
}
}
}

View File

@@ -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<RequestMethod> methods;
private final Set<RequestCondition> paramConditions;
private final RequestCondition paramsCondition;
private final Set<RequestCondition> headerConditions;
private final RequestCondition headersCondition;
private final RequestCondition consumesCondition;
private int hash;
RequestKey(Collection<String> patterns, Collection<RequestMethod> 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<String> patterns,
Collection<RequestMethod> methods,
Collection<RequestCondition> paramConditions,
Collection<RequestCondition> 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<String> prependLeadingSlash(Collection<String> 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<RequestCondition> getParams() {
return paramConditions;
public RequestCondition getParams() {
return paramsCondition;
}
/**
* Returns the request headers of this request key.
*/
public Set<RequestCondition> 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<String> patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
Set<RequestMethod> methods = union(this.methods, methodKey.methods);
Set<RequestCondition> params = union(this.paramConditions, methodKey.paramConditions);
Set<RequestCondition> headers = union(this.headerConditions, methodKey.headerConditions);
public RequestKey combine(RequestKey other, PathMatcher pathMatcher) {
Set<String> patterns = combinePatterns(this.patterns, other.patterns, pathMatcher);
Set<RequestMethod> 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<String> combinePatterns(Collection<String> typePatterns,
@@ -205,7 +216,7 @@ public final class RequestKey {
List<String> matchingPatterns = getMatchingPatterns(request, pathMatcher, urlPathHelper);
if (!matchingPatterns.isEmpty()) {
Set<RequestMethod> 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();
}

View File

@@ -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;
}

View File

@@ -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<T> 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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<String> {
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;
}
}

View File

@@ -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<RequestCondition> 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 " && ";
}
}

View File

@@ -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<RequestCondition> 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);
}

View File

@@ -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<List<MediaType>> {
public MediaTypeHeaderRequestCondition(String expression) {
super(expression);
}
@Override
protected List<MediaType> 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<MediaType> 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;
}
}

View File

@@ -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<String> {
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;
}
}

View File

@@ -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}.
*
* <p>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();
}

View File

@@ -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<RequestCondition> conditions;
public RequestConditionComposite(List<RequestCondition> 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<RequestCondition> 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();
}

View File

@@ -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<RequestCondition> filteredConditions = new ArrayList<RequestCondition>(Arrays.asList(conditions));
for (Iterator<RequestCondition> 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<RequestCondition> filteredConditions = new ArrayList<RequestCondition>(Arrays.asList(conditions));
for (Iterator<RequestCondition> 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
//
}