SPR-7353 - Added equivalent of JAX-RS @Produces to Spring MVC

This commit is contained in:
Arjen Poutsma 2011-05-13 09:43:45 +00:00
parent a557878a6f
commit bb2cc8457f
11 changed files with 702 additions and 131 deletions

View File

@ -25,7 +25,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
@ -141,7 +140,9 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
RequestConditionFactory.parseMethods(annotation.method()), RequestConditionFactory.parseMethods(annotation.method()),
RequestConditionFactory.parseParams(annotation.params()), RequestConditionFactory.parseParams(annotation.params()),
RequestConditionFactory.parseHeaders(annotation.headers()), RequestConditionFactory.parseHeaders(annotation.headers()),
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers())); RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()),
RequestConditionFactory.parseProduces(annotation.produces(), annotation.headers())
);
} }
@Override @Override
@ -257,13 +258,10 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
if (result != 0) { if (result != 0) {
return result; return result;
} }
/* result = mapping.getProduces().compareTo(otherMapping.getProduces(), this.requestAcceptHeader);
TODO: fix
result = compareAcceptHeaders(mapping.getAcceptHeaderMediaTypes(), otherMapping.getAcceptHeaderMediaTypes());
if (result != 0) { if (result != 0) {
return result; return result;
} }
*/
result = mapping.getMethods().compareTo(otherMapping.getMethods()); result = mapping.getMethods().compareTo(otherMapping.getMethods());
if (result != 0) { if (result != 0) {
return result; return result;

View File

@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition; import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition; import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition; import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory; import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
@ -57,6 +58,8 @@ public final class RequestMappingInfo {
private final ConsumesRequestCondition consumesCondition; private final ConsumesRequestCondition consumesCondition;
private final ProducesRequestCondition producesCondition;
private int hash; private int hash;
/** /**
@ -65,7 +68,7 @@ public final class RequestMappingInfo {
* <p>Package protected for testing purposes. * <p>Package protected for testing purposes.
*/ */
RequestMappingInfo(Collection<String> patterns, RequestMethod[] methods) { RequestMappingInfo(Collection<String> patterns, RequestMethod[] methods) {
this(patterns, RequestConditionFactory.parseMethods(methods), null, null, null); this(patterns, RequestConditionFactory.parseMethods(methods), null, null, null, null);
} }
/** /**
@ -75,12 +78,14 @@ public final class RequestMappingInfo {
RequestMethodsRequestCondition methodsCondition, RequestMethodsRequestCondition methodsCondition,
ParamsRequestCondition paramsCondition, ParamsRequestCondition paramsCondition,
HeadersRequestCondition headersCondition, HeadersRequestCondition headersCondition,
ConsumesRequestCondition consumesCondition) { ConsumesRequestCondition consumesCondition,
ProducesRequestCondition producesCondition) {
this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns)); this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns));
this.methodsCondition = methodsCondition != null ? methodsCondition : new RequestMethodsRequestCondition(); this.methodsCondition = methodsCondition != null ? methodsCondition : new RequestMethodsRequestCondition();
this.paramsCondition = paramsCondition != null ? paramsCondition : new ParamsRequestCondition(); this.paramsCondition = paramsCondition != null ? paramsCondition : new ParamsRequestCondition();
this.headersCondition = headersCondition != null ? headersCondition : new HeadersRequestCondition(); this.headersCondition = headersCondition != null ? headersCondition : new HeadersRequestCondition();
this.consumesCondition = consumesCondition != null ? consumesCondition : new ConsumesRequestCondition(); this.consumesCondition = consumesCondition != null ? consumesCondition : new ConsumesRequestCondition();
this.producesCondition = producesCondition != null ? producesCondition : new ProducesRequestCondition();
} }
private static Set<String> prependLeadingSlash(Collection<String> patterns) { private static Set<String> prependLeadingSlash(Collection<String> patterns) {
@ -106,40 +111,47 @@ public final class RequestMappingInfo {
} }
/** /**
* Returns the patterns of this request key. * Returns the patterns of this request mapping info.
*/ */
public Set<String> getPatterns() { public Set<String> getPatterns() {
return patterns; return patterns;
} }
/** /**
* Returns the request method conditions of this request key. * Returns the request method conditions of this request mapping info.
*/ */
public RequestMethodsRequestCondition getMethods() { public RequestMethodsRequestCondition getMethods() {
return methodsCondition; return methodsCondition;
} }
/** /**
* Returns the request parameters conditions of this request key. * Returns the request parameters conditions of this request mapping info.
*/ */
public ParamsRequestCondition getParams() { public ParamsRequestCondition getParams() {
return paramsCondition; return paramsCondition;
} }
/** /**
* Returns the request headers conditions of this request key. * Returns the request headers conditions of this request mapping info.
*/ */
public HeadersRequestCondition getHeaders() { public HeadersRequestCondition getHeaders() {
return headersCondition; return headersCondition;
} }
/** /**
* Returns the request consumes conditions of this request key. * Returns the request consumes conditions of this request mapping info.
*/ */
public ConsumesRequestCondition getConsumes() { public ConsumesRequestCondition getConsumes() {
return consumesCondition; return consumesCondition;
} }
/**
* Returns the request produces conditions of this request mapping info.
*/
public ProducesRequestCondition getProduces() {
return producesCondition;
}
/** /**
* Combines this {@code RequestMappingInfo} with another as follows: * Combines this {@code RequestMappingInfo} with another as follows:
* <ul> * <ul>
@ -156,7 +168,7 @@ public final class RequestMappingInfo {
* </ul> * </ul>
* @param methodKey the key to combine with * @param methodKey the key to combine with
* @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns * @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns
* @return a new request key containing conditions from both keys * @return a new request mapping info containing conditions from both keys
*/ */
public RequestMappingInfo combine(RequestMappingInfo methodKey, PathMatcher pathMatcher) { public RequestMappingInfo combine(RequestMappingInfo methodKey, PathMatcher pathMatcher) {
Set<String> patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher); Set<String> patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
@ -164,8 +176,9 @@ public final class RequestMappingInfo {
ParamsRequestCondition params = this.paramsCondition.combine(methodKey.paramsCondition); ParamsRequestCondition params = this.paramsCondition.combine(methodKey.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(methodKey.headersCondition); HeadersRequestCondition headers = this.headersCondition.combine(methodKey.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(methodKey.consumesCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(methodKey.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(methodKey.producesCondition);
return new RequestMappingInfo(patterns, methods, params, headers, consumes); return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces);
} }
private static Set<String> combinePatterns(Collection<String> typePatterns, private static Set<String> combinePatterns(Collection<String> typePatterns,
@ -203,23 +216,24 @@ public final class RequestMappingInfo {
* @param lookupPath mapping lookup path within the current servlet mapping if applicable * @param lookupPath mapping lookup path within the current servlet mapping if applicable
* @param request the current request * @param request the current request
* @param pathMatcher to check for matching patterns * @param pathMatcher to check for matching patterns
* @return a new request key that contains all matching attributes, or {@code null} if not all conditions match * @return a new request mapping info that contains all matching attributes, or {@code null} if not all conditions match
*/ */
public RequestMappingInfo getMatchingRequestMapping(String lookupPath, HttpServletRequest request, PathMatcher pathMatcher) { public RequestMappingInfo getMatchingRequestMapping(String lookupPath, HttpServletRequest request, PathMatcher pathMatcher) {
RequestMethodsRequestCondition matchingMethodCondition = methodsCondition.getMatchingCondition(request); RequestMethodsRequestCondition matchingMethodCondition = methodsCondition.getMatchingCondition(request);
ParamsRequestCondition matchingParamsCondition = paramsCondition.getMatchingCondition(request); ParamsRequestCondition matchingParamsCondition = paramsCondition.getMatchingCondition(request);
HeadersRequestCondition matchingHeadersCondition = headersCondition.getMatchingCondition(request); HeadersRequestCondition matchingHeadersCondition = headersCondition.getMatchingCondition(request);
ConsumesRequestCondition matchingConsumesCondition = consumesCondition.getMatchingCondition(request); ConsumesRequestCondition matchingConsumesCondition = consumesCondition.getMatchingCondition(request);
ProducesRequestCondition matchingProducesCondition = producesCondition.getMatchingCondition(request);
if (matchingMethodCondition == null || matchingParamsCondition == null || matchingHeadersCondition == null || if (matchingMethodCondition == null || matchingParamsCondition == null || matchingHeadersCondition == null ||
matchingConsumesCondition == null) { matchingConsumesCondition == null || matchingProducesCondition == null) {
return null; return null;
} }
else { else {
List<String> matchingPatterns = getMatchingPatterns(lookupPath, pathMatcher); List<String> matchingPatterns = getMatchingPatterns(lookupPath, pathMatcher);
if (!matchingPatterns.isEmpty()) { if (!matchingPatterns.isEmpty()) {
return new RequestMappingInfo(matchingPatterns, matchingMethodCondition, matchingParamsCondition, return new RequestMappingInfo(matchingPatterns, matchingMethodCondition, matchingParamsCondition,
matchingHeadersCondition, matchingConsumesCondition); matchingHeadersCondition, matchingConsumesCondition, matchingProducesCondition);
} }
else { else {
return null; return null;
@ -271,7 +285,8 @@ public final class RequestMappingInfo {
this.methodsCondition.equals(other.methodsCondition) && this.methodsCondition.equals(other.methodsCondition) &&
this.paramsCondition.equals(other.paramsCondition) && this.paramsCondition.equals(other.paramsCondition) &&
this.headersCondition.equals(other.headersCondition) && this.headersCondition.equals(other.headersCondition) &&
this.consumesCondition.equals(other.consumesCondition)); this.consumesCondition.equals(other.consumesCondition) &&
this.producesCondition.equals(other.producesCondition));
} }
return false; return false;
} }
@ -285,6 +300,7 @@ public final class RequestMappingInfo {
result = 31 * result + paramsCondition.hashCode(); result = 31 * result + paramsCondition.hashCode();
result = 31 * result + headersCondition.hashCode(); result = 31 * result + headersCondition.hashCode();
result = 31 * result + consumesCondition.hashCode(); result = 31 * result + consumesCondition.hashCode();
result = 31 * result + producesCondition.hashCode();
hash = result; hash = result;
} }
return result; return result;
@ -298,6 +314,7 @@ public final class RequestMappingInfo {
builder.append(",params=").append(paramsCondition); builder.append(",params=").append(paramsCondition);
builder.append(",headers=").append(headersCondition); builder.append(",headers=").append(headersCondition);
builder.append(",consumes=").append(consumesCondition); builder.append(",consumes=").append(consumesCondition);
builder.append(",produces=").append(producesCondition);
builder.append('}'); builder.append('}');
return builder.toString(); return builder.toString();
} }

View File

@ -16,7 +16,6 @@
package org.springframework.web.servlet.mvc.method.condition; package org.springframework.web.servlet.mvc.method.condition;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -27,33 +26,23 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* Represents a collection of consumes conditions, typically obtained from {@link * Represents a collection of consumes conditions, typically obtained from {@link org.springframework.web.bind.annotation.RequestMapping#consumes()
* org.springframework.web.bind.annotation.RequestMapping#consumes() @RequestMapping.consumes()}. * &#64;RequestMapping.consumes()}.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @see RequestConditionFactory#parseHeaders(String...) * @see RequestConditionFactory#parseConsumes(String...)
* @see RequestConditionFactory#parseConsumes(String[], String[])
* @since 3.1 * @since 3.1
*/ */
public class ConsumesRequestCondition public class ConsumesRequestCondition
extends LogicalDisjunctionRequestCondition<ConsumesRequestCondition.ConsumeRequestCondition> extends MediaTypesRequestCondition<ConsumesRequestCondition.ConsumeRequestCondition>
implements Comparable<ConsumesRequestCondition> { implements Comparable<ConsumesRequestCondition> {
private final ConsumeRequestCondition mostSpecificCondition;
ConsumesRequestCondition(Collection<ConsumeRequestCondition> conditions) { ConsumesRequestCondition(Collection<ConsumeRequestCondition> conditions) {
super(conditions); super(conditions);
Assert.notEmpty(conditions, "'conditions' must not be empty");
mostSpecificCondition = getMostSpecificCondition();
}
private ConsumeRequestCondition getMostSpecificCondition() {
List<ConsumeRequestCondition> conditions = new ArrayList<ConsumeRequestCondition>(getConditions());
Collections.sort(conditions);
return conditions.get(0);
} }
ConsumesRequestCondition(String... consumes) { ConsumesRequestCondition(String... consumes) {
@ -72,7 +61,7 @@ public class ConsumesRequestCondition
} }
/** /**
* Creates an default set of consumes request conditions. * Creates a default set of consumes request conditions.
*/ */
public ConsumesRequestCondition() { public ConsumesRequestCondition() {
this(Collections.singleton(new ConsumeRequestCondition(MediaType.ALL, false))); this(Collections.singleton(new ConsumeRequestCondition(MediaType.ALL, false)));
@ -101,8 +90,8 @@ public class ConsumesRequestCondition
} }
/** /**
* Combines this collection of request condition with another. Returns {@code other}, unless it has the default * Combines this collection of request condition with another. Returns {@code other}, unless it has the default value
* value (i.e. <code>&#42;/&#42;</code>). * (i.e. <code>&#42;/&#42;</code>).
* *
* @param other the condition to combine with * @param other the condition to combine with
*/ */
@ -122,79 +111,32 @@ public class ConsumesRequestCondition
} }
public int compareTo(ConsumesRequestCondition other) { public int compareTo(ConsumesRequestCondition other) {
return this.mostSpecificCondition.compareTo(other.mostSpecificCondition); return this.getMostSpecificCondition().compareTo(other.getMostSpecificCondition());
} }
private static MediaType getContentType(HttpServletRequest request) { static class ConsumeRequestCondition extends MediaTypesRequestCondition.MediaTypeRequestCondition {
if (StringUtils.hasLength(request.getContentType())) {
return MediaType.parseMediaType(request.getContentType());
}
else {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
static class ConsumeRequestCondition implements RequestCondition, Comparable<ConsumeRequestCondition> {
private final MediaType mediaType;
private final boolean isNegated;
ConsumeRequestCondition(String expression) { ConsumeRequestCondition(String expression) {
if (expression.startsWith("!")) { super(expression);
isNegated = true; }
expression = expression.substring(1);
ConsumeRequestCondition(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
@Override
protected boolean match(HttpServletRequest request, MediaType mediaType) {
MediaType contentType = getContentType(request);
return mediaType.includes(contentType);
}
private MediaType getContentType(HttpServletRequest request) {
if (StringUtils.hasLength(request.getContentType())) {
return MediaType.parseMediaType(request.getContentType());
} }
else { else {
isNegated = false; return MediaType.APPLICATION_OCTET_STREAM;
} }
this.mediaType = MediaType.parseMediaType(expression);
}
ConsumeRequestCondition(MediaType mediaType, boolean isNegated) {
this.mediaType = mediaType;
this.isNegated = isNegated;
}
public boolean match(HttpServletRequest request) {
MediaType contentType = getContentType(request);
boolean match = this.mediaType.includes(contentType);
return !isNegated ? match : !match;
}
public int compareTo(ConsumeRequestCondition other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.mediaType, other.mediaType);
}
MediaType getMediaType() {
return mediaType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof ConsumeRequestCondition) {
ConsumeRequestCondition other = (ConsumeRequestCondition) obj;
return (this.mediaType.equals(other.mediaType)) && (this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
return mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isNegated) {
builder.append('!');
}
builder.append(mediaType.toString());
return builder.toString();
} }
} }

View File

@ -0,0 +1,125 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.condition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
*/
class MediaTypesRequestCondition<T extends MediaTypesRequestCondition.MediaTypeRequestCondition>
extends LogicalDisjunctionRequestCondition<T> {
private final List<T> sortedConditions;
public MediaTypesRequestCondition(Collection<T> conditions) {
super(conditions);
Assert.notEmpty(conditions, "'conditions' must not be empty");
sortedConditions = new ArrayList<T>(conditions);
Collections.sort(sortedConditions);
}
private MediaTypeRequestCondition getMostSpecificCondition(Collection<T> conditions) {
List<MediaTypeRequestCondition> conditionList = new ArrayList<MediaTypeRequestCondition>(conditions);
Collections.sort(conditionList);
return conditionList.get(0);
}
protected MediaTypeRequestCondition getMostSpecificCondition() {
return sortedConditions.get(0);
}
protected List<T> getSortedConditions() {
return sortedConditions;
}
/**
* @author Arjen Poutsma
*/
protected abstract static class MediaTypeRequestCondition
implements RequestCondition, Comparable<MediaTypeRequestCondition> {
private final MediaType mediaType;
private final boolean isNegated;
MediaTypeRequestCondition(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
isNegated = negated;
}
MediaTypeRequestCondition(String expression) {
if (expression.startsWith("!")) {
isNegated = true;
expression = expression.substring(1);
}
else {
isNegated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
public boolean match(HttpServletRequest request) {
boolean match = match(request, this.mediaType);
return !isNegated ? match : !match;
}
protected abstract boolean match(HttpServletRequest request, MediaType mediaType);
MediaType getMediaType() {
return mediaType;
}
public int compareTo(MediaTypeRequestCondition other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && getClass().equals(obj.getClass())) {
MediaTypeRequestCondition other = (MediaTypeRequestCondition) obj;
return (this.mediaType.equals(other.mediaType)) && (this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
return mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isNegated) {
builder.append('!');
}
builder.append(mediaType.toString());
return builder.toString();
}
}
}

View File

@ -0,0 +1,173 @@
/*
* 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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
/**
* Represents a collection of produces conditions, typically obtained from {@link
* org.springframework.web.bind.annotation.RequestMapping#produces() &#64;RequestMapping.produces()}.
*
* @author Arjen Poutsma
* @see RequestConditionFactory#parseProduces(String...)
* @see RequestConditionFactory#parseProduces(String[], String[])
* @since 3.1
*/
public class ProducesRequestCondition
extends MediaTypesRequestCondition<ProducesRequestCondition.ProduceRequestCondition> {
ProducesRequestCondition(Collection<ProduceRequestCondition> conditions) {
super(conditions);
}
ProducesRequestCondition(String... consumes) {
this(parseConditions(Arrays.asList(consumes)));
}
private static Set<ProduceRequestCondition> parseConditions(List<String> consumes) {
if (consumes.isEmpty()) {
consumes = Collections.singletonList("*/*");
}
Set<ProduceRequestCondition> conditions = new LinkedHashSet<ProduceRequestCondition>(consumes.size());
for (String consume : consumes) {
conditions.add(new ProduceRequestCondition(consume));
}
return conditions;
}
/**
* Creates an default set of consumes request conditions.
*/
public ProducesRequestCondition() {
this(Collections.singleton(new ProduceRequestCondition(MediaType.ALL, false)));
}
/**
* Returns a new {@code RequestCondition} that contains all conditions of this key that match the request.
*
* @param request the request
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
*/
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
Set<ProduceRequestCondition> matchingConditions = new LinkedHashSet<ProduceRequestCondition>(getConditions());
for (Iterator<ProduceRequestCondition> iterator = matchingConditions.iterator(); iterator.hasNext();) {
ProduceRequestCondition condition = iterator.next();
if (!condition.match(request)) {
iterator.remove();
}
}
if (matchingConditions.isEmpty()) {
return null;
}
else {
return new ProducesRequestCondition(matchingConditions);
}
}
/**
* Combines this collection of request condition with another. Returns {@code other}, unless it has the default
* value (i.e. {@code &#42;/&#42;}).
*
* @param other the condition to combine with
*/
public ProducesRequestCondition combine(ProducesRequestCondition other) {
return !other.hasDefaultValue() ? other : this;
}
private boolean hasDefaultValue() {
Set<ProduceRequestCondition> conditions = getConditions();
if (conditions.size() == 1) {
ProduceRequestCondition condition = conditions.iterator().next();
return condition.getMediaType().equals(MediaType.ALL);
}
else {
return false;
}
}
public int compareTo(ProducesRequestCondition other, List<MediaType> acceptedMediaTypes) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
int thisIndex = this.indexOfMediaType(acceptedMediaType);
int otherIndex = other.indexOfMediaType(acceptedMediaType);
if (thisIndex != otherIndex) {
return otherIndex - thisIndex;
} else if (thisIndex != -1 && otherIndex != -1) {
ProduceRequestCondition thisCondition = this.getSortedConditions().get(thisIndex);
ProduceRequestCondition otherCondition = other.getSortedConditions().get(otherIndex);
int result = thisCondition.compareTo(otherCondition);
if (result != 0) {
return result;
}
}
}
return 0;
}
private int indexOfMediaType(MediaType mediaType) {
List<ProduceRequestCondition> sortedConditions = getSortedConditions();
for (int i = 0; i < sortedConditions.size(); i++) {
ProduceRequestCondition condition = sortedConditions.get(i);
if (mediaType.includes(condition.getMediaType())) {
return i;
}
}
return -1;
}
static class ProduceRequestCondition extends MediaTypesRequestCondition.MediaTypeRequestCondition {
ProduceRequestCondition(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
ProduceRequestCondition(String expression) {
super(expression);
}
@Override
protected boolean match(HttpServletRequest request, MediaType mediaType) {
List<MediaType> acceptedMediaTypes = getAccept(request);
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (mediaType.isCompatibleWith(acceptedMediaType)) {
return true;
}
}
return false;
}
private List<MediaType> getAccept(HttpServletRequest request) {
String acceptHeader = request.getHeader("Accept");
if (StringUtils.hasLength(acceptHeader)) {
return MediaType.parseMediaTypes(acceptHeader);
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
}
}

View File

@ -99,6 +99,15 @@ public abstract class RequestConditionFactory {
return new ConsumesRequestCondition(consumes); return new ConsumesRequestCondition(consumes);
} }
/**
* Parses the given consumes and {@code Content-Type} headers, and returns them as a single request condition. <p>Only
* {@code Content-Type} headers will be used, all other headers will be ignored.
*
* @param consumes the consumes
* @param headers the headers
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#consumes()
*/
public static ConsumesRequestCondition parseConsumes(String[] consumes, String[] headers) { public static ConsumesRequestCondition parseConsumes(String[] consumes, String[] headers) {
List<ConsumesRequestCondition.ConsumeRequestCondition> allConditions = parseContentTypeHeaders(headers); List<ConsumesRequestCondition.ConsumeRequestCondition> allConditions = parseContentTypeHeaders(headers);
@ -115,14 +124,65 @@ public abstract class RequestConditionFactory {
} }
private static List<ConsumesRequestCondition.ConsumeRequestCondition> parseContentTypeHeaders(String[] headers) { private static List<ConsumesRequestCondition.ConsumeRequestCondition> parseContentTypeHeaders(String[] headers) {
List<ConsumesRequestCondition.ConsumeRequestCondition> allConditions = List<ConsumesRequestCondition.ConsumeRequestCondition> conditions =
new ArrayList<ConsumesRequestCondition.ConsumeRequestCondition>(); new ArrayList<ConsumesRequestCondition.ConsumeRequestCondition>();
HeadersRequestCondition headersCondition = new HeadersRequestCondition(headers); HeadersRequestCondition headersCondition = new HeadersRequestCondition(headers);
for (HeadersRequestCondition.HeaderRequestCondition headerCondition : headersCondition.getConditions()) { for (HeadersRequestCondition.HeaderRequestCondition headerCondition : headersCondition.getConditions()) {
if (CONTENT_TYPE_HEADER.equalsIgnoreCase(headerCondition.name)) { if (CONTENT_TYPE_HEADER.equalsIgnoreCase(headerCondition.name)) {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerCondition.value); List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerCondition.value);
for (MediaType mediaType : mediaTypes) { for (MediaType mediaType : mediaTypes) {
allConditions.add(new ConsumesRequestCondition.ConsumeRequestCondition(mediaType, conditions.add(new ConsumesRequestCondition.ConsumeRequestCondition(mediaType,
headerCondition.isNegated));
}
}
}
return conditions;
}
/**
* Parses the given produces, and returns them as a single request condition.
*
* @param produces the produces
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#produces()
*/
public static ProducesRequestCondition parseProduces(String... produces) {
return new ProducesRequestCondition(produces);
}
/**
* Parses the given produces and {@code Accept} headers, and returns them as a single request condition. <p>Only {@code
* Accept} headers will be used, all other headers will be ignored.
*
* @param produces the consumes
* @param headers the headers
* @return the request condition
* @see org.springframework.web.bind.annotation.RequestMapping#produces()
*/
public static ProducesRequestCondition parseProduces(String[] produces, String[] headers) {
List<ProducesRequestCondition.ProduceRequestCondition> allConditions = parseAcceptHeaders(headers);
// ignore the default consumes() value if any accept headers have been set
boolean headersHasAccept = !allConditions.isEmpty();
boolean producesHasDefaultValue = produces.length == 1 && produces[0].equals("*/*");
if (!headersHasAccept || !producesHasDefaultValue) {
for (String produce : produces) {
allConditions.add(new ProducesRequestCondition.ProduceRequestCondition(produce));
}
}
return new ProducesRequestCondition(allConditions);
}
private static List<ProducesRequestCondition.ProduceRequestCondition> parseAcceptHeaders(String[] headers) {
List<ProducesRequestCondition.ProduceRequestCondition> allConditions =
new ArrayList<ProducesRequestCondition.ProduceRequestCondition>();
HeadersRequestCondition headersCondition = new HeadersRequestCondition(headers);
for (HeadersRequestCondition.HeaderRequestCondition headerCondition : headersCondition.getConditions()) {
if (ACCEPT_HEADER.equalsIgnoreCase(headerCondition.name)) {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerCondition.value);
for (MediaType mediaType : mediaTypes) {
allConditions.add(new ProducesRequestCondition.ProduceRequestCondition(mediaType,
headerCondition.isNegated)); headerCondition.isNegated));
} }
} }
@ -130,4 +190,5 @@ public abstract class RequestConditionFactory {
return allConditions; return allConditions;
} }
} }

View File

@ -21,7 +21,6 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
@ -100,7 +99,7 @@ public class RequestMappingInfoComparatorTests {
RequestMappingInfo empty = new RequestMappingInfo(null, null); RequestMappingInfo empty = new RequestMappingInfo(null, null);
RequestMappingInfo oneMethod = new RequestMappingInfo(null, new RequestMethod[] {RequestMethod.GET}); RequestMappingInfo oneMethod = new RequestMappingInfo(null, new RequestMethod[] {RequestMethod.GET});
RequestMappingInfo oneMethodOneParam = RequestMappingInfo oneMethodOneParam =
new RequestMappingInfo(null, RequestConditionFactory.parseMethods(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null, null); new RequestMappingInfo(null, RequestConditionFactory.parseMethods(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null, null, null);
List<RequestMappingInfo> list = asList(empty, oneMethod, oneMethodOneParam); List<RequestMappingInfo> list = asList(empty, oneMethod, oneMethodOneParam);
Collections.shuffle(list); Collections.shuffle(list);
Collections.sort(list, handlerMapping.getMappingComparator("", request)); Collections.sort(list, handlerMapping.getMappingComparator("", request));
@ -111,16 +110,16 @@ public class RequestMappingInfoComparatorTests {
} }
@Test @Test
@Ignore // TODO : remove ignore public void produces() {
public void acceptHeaders() { RequestMappingInfo html = new RequestMappingInfo(null, null, null, null, null, RequestConditionFactory.parseProduces("text/html"));
RequestMappingInfo html = new RequestMappingInfo(null, null, null, RequestConditionFactory.parseHeaders("accept=text/html"), null); RequestMappingInfo xml = new RequestMappingInfo(null, null, null, null, null, RequestConditionFactory.parseProduces("application/xml"));
RequestMappingInfo xml = new RequestMappingInfo(null, null, null, RequestConditionFactory.parseHeaders("accept=application/xml"), null);
RequestMappingInfo none = new RequestMappingInfo(null, null); RequestMappingInfo none = new RequestMappingInfo(null, null);
request.addHeader("Accept", "application/xml, text/html"); request.addHeader("Accept", "application/xml, text/html");
Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator("", request); Comparator<RequestMappingInfo> comparator = handlerMapping.getMappingComparator("", request);
assertTrue(comparator.compare(html, xml) > 0); int result = comparator.compare(html, xml);
assertTrue("Invalid comparison result: " + result, result > 0);
assertTrue(comparator.compare(xml, html) < 0); assertTrue(comparator.compare(xml, html) < 0);
assertTrue(comparator.compare(xml, none) < 0); assertTrue(comparator.compare(xml, none) < 0);
assertTrue(comparator.compare(none, xml) > 0); assertTrue(comparator.compare(none, xml) > 0);

View File

@ -181,13 +181,13 @@ public class RequestMappingInfoTests {
RequestMappingInfo key = RequestMappingInfo key =
new RequestMappingInfo(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null, new RequestMappingInfo(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null,
null); null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher); RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNotNull(match); assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null, key = new RequestMappingInfo(singleton("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null,
null); null, null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher); match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNull(match); assertNull(match);
@ -202,13 +202,13 @@ public class RequestMappingInfoTests {
RequestMappingInfo key = RequestMappingInfo key =
new RequestMappingInfo(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"), new RequestMappingInfo(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"),
null); null, null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher); RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNotNull(match); assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"), key = new RequestMappingInfo(singleton("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"),
null); null, null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher); match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNull(match); assertNull(match);
@ -222,13 +222,33 @@ public class RequestMappingInfoTests {
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request); String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null, null, null, RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null, null, null,
RequestConditionFactory.parseConsumes("text/plain")); RequestConditionFactory.parseConsumes("text/plain"), null);
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher); RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNotNull(match); assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, null, null, key = new RequestMappingInfo(singleton("/foo"), null, null, null,
RequestConditionFactory.parseConsumes("application/xml")); RequestConditionFactory.parseConsumes("application/xml"), null);
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNull(match);
}
@Test
public void producesCondition() {
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("Accept", "text/plain");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
RequestMappingInfo key = new RequestMappingInfo(singleton("/foo"), null, null, null,
null, RequestConditionFactory.parseProduces("text/plain"));
RequestMappingInfo match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNotNull(match);
key = new RequestMappingInfo(singleton("/foo"), null, null, null, null,
RequestConditionFactory.parseProduces("application/xml"));
match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher); match = key.getMatchingRequestMapping(lookupPath, request, pathMatcher);
assertNull(match); assertNull(match);

View File

@ -16,14 +16,6 @@
package org.springframework.web.servlet.mvc.method.annotation; package org.springframework.web.servlet.mvc.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.beans.PropertyEditorSupport; import java.beans.PropertyEditorSupport;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
@ -48,7 +40,6 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -60,8 +51,8 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor; import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor;
@ -142,6 +133,8 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebA
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver;
import static org.junit.Assert.*;
/** /**
* The origin of this test fixture is {@link ServletHandlerMethodTests} with tests in this class adapted to run * The origin of this test fixture is {@link ServletHandlerMethodTests} with tests in this class adapted to run
* against the HandlerMethod infrastructure rather than against the DefaultAnnotationHandlerMapping, the * against the HandlerMethod infrastructure rather than against the DefaultAnnotationHandlerMapping, the
@ -1033,8 +1026,6 @@ public class ServletHandlerMethodTests {
assertEquals("non-pdf", response.getContentAsString()); assertEquals("non-pdf", response.getContentAsString());
} }
// TODO: uncomment ignore
@Ignore
@Test @Test
public void acceptHeaders() throws ServletException, IOException { public void acceptHeaders() throws ServletException, IOException {
initDispatcherServlet(AcceptHeadersController.class, null); initDispatcherServlet(AcceptHeadersController.class, null);

View File

@ -0,0 +1,232 @@
/*
* 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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
public class ProducesRequestConditionTests {
@Test
public void consumesMatch() {
RequestCondition condition = new ProducesRequestCondition("text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertTrue(condition.match(request));
}
@Test
public void negatedConsumesMatch() {
RequestCondition condition = new ProducesRequestCondition("!text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertFalse(condition.match(request));
}
@Test
public void consumesWildcardMatch() {
RequestCondition condition = new ProducesRequestCondition("text/*");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertTrue(condition.match(request));
}
@Test
public void consumesMultipleMatch() {
RequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
assertTrue(condition.match(request));
}
@Test
public void consumesSingleNoMatch() {
RequestCondition condition = new ProducesRequestCondition("text/plain");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "application/xml");
assertFalse(condition.match(request));
}
@Test
public void compareToSingle() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*");
List<MediaType> accept = Collections.singletonList(MediaType.TEXT_PLAIN);
int result = condition1.compareTo(condition2, accept);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, accept);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void compareToMultiple() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
List<MediaType> accept = Collections.singletonList(MediaType.TEXT_PLAIN);
int result = condition1.compareTo(condition2, accept);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, accept);
assertTrue("Invalid comparison result: " + result, result > 0);
condition1 = new ProducesRequestCondition("*/*");
condition2 = new ProducesRequestCondition("text/*");
accept = Collections.singletonList(new MediaType("text", "*"));
result = condition1.compareTo(condition2, accept);
assertTrue("Invalid comparison result: " + result, result > 0);
result = condition2.compareTo(condition1, accept);
assertTrue("Invalid comparison result: " + result, result < 0);
}
@Test
public void compareToMultipleAccept() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
List<MediaType> accept = Arrays.asList(MediaType.TEXT_PLAIN, MediaType.APPLICATION_XML);
int result = condition1.compareTo(condition2, accept);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, accept);
assertTrue("Invalid comparison result: " + result, result > 0);
accept = Arrays.asList(MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN);
result = condition1.compareTo(condition2, accept);
assertTrue("Invalid comparison result: " + result, result > 0);
result = condition2.compareTo(condition1, accept);
assertTrue("Invalid comparison result: " + result, result < 0);
}
@Test
public void combine() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/xml");
ProducesRequestCondition result = condition1.combine(condition2);
assertEquals(condition2, result);
}
@Test
public void combineWithDefault() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("*/*");
ProducesRequestCondition result = condition1.combine(condition2);
assertEquals(condition1, result);
}
@Test
public void parseConsumesAndHeaders() {
String[] consumes = new String[] {"text/plain"};
String[] headers = new String[]{"foo=bar", "accept=application/xml,application/pdf"};
ProducesRequestCondition condition = RequestConditionFactory.parseProduces(consumes, headers);
assertConditions(condition, "text/plain", "application/xml", "application/pdf");
}
@Test
public void parseConsumesDefault() {
String[] consumes = new String[] {"*/*"};
String[] headers = new String[0];
ProducesRequestCondition condition = RequestConditionFactory.parseProduces(consumes, headers);
assertConditions(condition, "*/*");
}
@Test
public void parseConsumesDefaultAndHeaders() {
String[] consumes = new String[] {"*/*"};
String[] headers = new String[]{"foo=bar", "accept=text/plain"};
ProducesRequestCondition condition = RequestConditionFactory.parseProduces(consumes, headers);
assertConditions(condition, "text/plain");
}
@Test
public void getMatchingCondition() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
ProducesRequestCondition result = condition.getMatchingCondition(request);
assertConditions(result, "text/plain");
condition = new ProducesRequestCondition("application/xml");
result = condition.getMatchingCondition(request);
assertNull(result);
}
private void assertConditions(ProducesRequestCondition condition, String... expected) {
Set<ProducesRequestCondition.ProduceRequestCondition> conditions = condition.getConditions();
assertEquals("Invalid amount of conditions", conditions.size(), expected.length);
for (String s : expected) {
boolean found = false;
for (ProducesRequestCondition.ProduceRequestCondition requestCondition : conditions) {
String conditionMediaType = requestCondition.getMediaType().toString();
if (conditionMediaType.equals(s)) {
found = true;
break;
}
}
if (!found) {
fail("Condition [" + s + "] not found");
}
}
}
}

View File

@ -311,4 +311,17 @@ public @interface RequestMapping {
*/ */
String[] consumes() default "*/*"; String[] consumes() default "*/*";
/**
* The producible media types of the mapped request, narrowing the primary mapping.
* <p>The format is a sequence of media types ("text/plain", "application/*),
* with a request only mapped if the {@code Accept} matches one of these media types.
* Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
* all requests with a {@code Accept} other than "text/plain".
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings override
* this consumes restriction.
* @see org.springframework.http.MediaType
*/
String[] produces() default "*/*";
} }