Skip to content

Commit bb2cc84

Browse files
committed
SPR-7353 - Added equivalent of JAX-RS @produces to Spring MVC
1 parent a557878 commit bb2cc84

File tree

11 files changed

+696
-125
lines changed

11 files changed

+696
-125
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Set;
28-
2928
import javax.servlet.http.HttpServletRequest;
3029

3130
import org.springframework.core.annotation.AnnotationUtils;
@@ -141,7 +140,9 @@ private static RequestMappingInfo createFromRequestMapping(RequestMapping annota
141140
RequestConditionFactory.parseMethods(annotation.method()),
142141
RequestConditionFactory.parseParams(annotation.params()),
143142
RequestConditionFactory.parseHeaders(annotation.headers()),
144-
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()));
143+
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()),
144+
RequestConditionFactory.parseProduces(annotation.produces(), annotation.headers())
145+
);
145146
}
146147

147148
@Override
@@ -257,13 +258,10 @@ public int compare(RequestMappingInfo mapping, RequestMappingInfo otherMapping)
257258
if (result != 0) {
258259
return result;
259260
}
260-
/*
261-
TODO: fix
262-
result = compareAcceptHeaders(mapping.getAcceptHeaderMediaTypes(), otherMapping.getAcceptHeaderMediaTypes());
261+
result = mapping.getProduces().compareTo(otherMapping.getProduces(), this.requestAcceptHeader);
263262
if (result != 0) {
264263
return result;
265264
}
266-
*/
267265
result = mapping.getMethods().compareTo(otherMapping.getMethods());
268266
if (result != 0) {
269267
return result;

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingInfo.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.web.servlet.mvc.method.condition.ConsumesRequestCondition;
3131
import org.springframework.web.servlet.mvc.method.condition.HeadersRequestCondition;
3232
import org.springframework.web.servlet.mvc.method.condition.ParamsRequestCondition;
33+
import org.springframework.web.servlet.mvc.method.condition.ProducesRequestCondition;
3334
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
3435
import org.springframework.web.servlet.mvc.method.condition.RequestMethodsRequestCondition;
3536

@@ -57,6 +58,8 @@ public final class RequestMappingInfo {
5758

5859
private final ConsumesRequestCondition consumesCondition;
5960

61+
private final ProducesRequestCondition producesCondition;
62+
6063
private int hash;
6164

6265
/**
@@ -65,7 +68,7 @@ public final class RequestMappingInfo {
6568
* <p>Package protected for testing purposes.
6669
*/
6770
RequestMappingInfo(Collection<String> patterns, RequestMethod[] methods) {
68-
this(patterns, RequestConditionFactory.parseMethods(methods), null, null, null);
71+
this(patterns, RequestConditionFactory.parseMethods(methods), null, null, null, null);
6972
}
7073

7174
/**
@@ -75,12 +78,14 @@ public RequestMappingInfo(Collection<String> patterns,
7578
RequestMethodsRequestCondition methodsCondition,
7679
ParamsRequestCondition paramsCondition,
7780
HeadersRequestCondition headersCondition,
78-
ConsumesRequestCondition consumesCondition) {
81+
ConsumesRequestCondition consumesCondition,
82+
ProducesRequestCondition producesCondition) {
7983
this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns));
8084
this.methodsCondition = methodsCondition != null ? methodsCondition : new RequestMethodsRequestCondition();
8185
this.paramsCondition = paramsCondition != null ? paramsCondition : new ParamsRequestCondition();
8286
this.headersCondition = headersCondition != null ? headersCondition : new HeadersRequestCondition();
8387
this.consumesCondition = consumesCondition != null ? consumesCondition : new ConsumesRequestCondition();
88+
this.producesCondition = producesCondition != null ? producesCondition : new ProducesRequestCondition();
8489
}
8590

8691
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
@@ -106,40 +111,47 @@ private static <T> Set<T> asUnmodifiableSet(Collection<T> collection) {
106111
}
107112

108113
/**
109-
* Returns the patterns of this request key.
114+
* Returns the patterns of this request mapping info.
110115
*/
111116
public Set<String> getPatterns() {
112117
return patterns;
113118
}
114119

115120
/**
116-
* Returns the request method conditions of this request key.
121+
* Returns the request method conditions of this request mapping info.
117122
*/
118123
public RequestMethodsRequestCondition getMethods() {
119124
return methodsCondition;
120125
}
121126

122127
/**
123-
* Returns the request parameters conditions of this request key.
128+
* Returns the request parameters conditions of this request mapping info.
124129
*/
125130
public ParamsRequestCondition getParams() {
126131
return paramsCondition;
127132
}
128133

129134
/**
130-
* Returns the request headers conditions of this request key.
135+
* Returns the request headers conditions of this request mapping info.
131136
*/
132137
public HeadersRequestCondition getHeaders() {
133138
return headersCondition;
134139
}
135140

136141
/**
137-
* Returns the request consumes conditions of this request key.
142+
* Returns the request consumes conditions of this request mapping info.
138143
*/
139144
public ConsumesRequestCondition getConsumes() {
140145
return consumesCondition;
141146
}
142147

148+
/**
149+
* Returns the request produces conditions of this request mapping info.
150+
*/
151+
public ProducesRequestCondition getProduces() {
152+
return producesCondition;
153+
}
154+
143155
/**
144156
* Combines this {@code RequestMappingInfo} with another as follows:
145157
* <ul>
@@ -156,16 +168,17 @@ public ConsumesRequestCondition getConsumes() {
156168
* </ul>
157169
* @param methodKey the key to combine with
158170
* @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns
159-
* @return a new request key containing conditions from both keys
171+
* @return a new request mapping info containing conditions from both keys
160172
*/
161173
public RequestMappingInfo combine(RequestMappingInfo methodKey, PathMatcher pathMatcher) {
162174
Set<String> patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
163175
RequestMethodsRequestCondition methods = this.methodsCondition.combine(methodKey.methodsCondition);
164176
ParamsRequestCondition params = this.paramsCondition.combine(methodKey.paramsCondition);
165177
HeadersRequestCondition headers = this.headersCondition.combine(methodKey.headersCondition);
166178
ConsumesRequestCondition consumes = this.consumesCondition.combine(methodKey.consumesCondition);
179+
ProducesRequestCondition produces = this.producesCondition.combine(methodKey.producesCondition);
167180

168-
return new RequestMappingInfo(patterns, methods, params, headers, consumes);
181+
return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces);
169182
}
170183

171184
private static Set<String> combinePatterns(Collection<String> typePatterns,
@@ -203,23 +216,24 @@ else if (!methodPatterns.isEmpty()) {
203216
* @param lookupPath mapping lookup path within the current servlet mapping if applicable
204217
* @param request the current request
205218
* @param pathMatcher to check for matching patterns
206-
* @return a new request key that contains all matching attributes, or {@code null} if not all conditions match
219+
* @return a new request mapping info that contains all matching attributes, or {@code null} if not all conditions match
207220
*/
208221
public RequestMappingInfo getMatchingRequestMapping(String lookupPath, HttpServletRequest request, PathMatcher pathMatcher) {
209222
RequestMethodsRequestCondition matchingMethodCondition = methodsCondition.getMatchingCondition(request);
210223
ParamsRequestCondition matchingParamsCondition = paramsCondition.getMatchingCondition(request);
211224
HeadersRequestCondition matchingHeadersCondition = headersCondition.getMatchingCondition(request);
212225
ConsumesRequestCondition matchingConsumesCondition = consumesCondition.getMatchingCondition(request);
226+
ProducesRequestCondition matchingProducesCondition = producesCondition.getMatchingCondition(request);
213227

214228
if (matchingMethodCondition == null || matchingParamsCondition == null || matchingHeadersCondition == null ||
215-
matchingConsumesCondition == null) {
229+
matchingConsumesCondition == null || matchingProducesCondition == null) {
216230
return null;
217231
}
218232
else {
219233
List<String> matchingPatterns = getMatchingPatterns(lookupPath, pathMatcher);
220234
if (!matchingPatterns.isEmpty()) {
221235
return new RequestMappingInfo(matchingPatterns, matchingMethodCondition, matchingParamsCondition,
222-
matchingHeadersCondition, matchingConsumesCondition);
236+
matchingHeadersCondition, matchingConsumesCondition, matchingProducesCondition);
223237
}
224238
else {
225239
return null;
@@ -271,7 +285,8 @@ public boolean equals(Object obj) {
271285
this.methodsCondition.equals(other.methodsCondition) &&
272286
this.paramsCondition.equals(other.paramsCondition) &&
273287
this.headersCondition.equals(other.headersCondition) &&
274-
this.consumesCondition.equals(other.consumesCondition));
288+
this.consumesCondition.equals(other.consumesCondition) &&
289+
this.producesCondition.equals(other.producesCondition));
275290
}
276291
return false;
277292
}
@@ -285,6 +300,7 @@ public int hashCode() {
285300
result = 31 * result + paramsCondition.hashCode();
286301
result = 31 * result + headersCondition.hashCode();
287302
result = 31 * result + consumesCondition.hashCode();
303+
result = 31 * result + producesCondition.hashCode();
288304
hash = result;
289305
}
290306
return result;
@@ -298,6 +314,7 @@ public String toString() {
298314
builder.append(",params=").append(paramsCondition);
299315
builder.append(",headers=").append(headersCondition);
300316
builder.append(",consumes=").append(consumesCondition);
317+
builder.append(",produces=").append(producesCondition);
301318
builder.append('}');
302319
return builder.toString();
303320
}

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java

Lines changed: 21 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.web.servlet.mvc.method.condition;
1818

19-
import java.util.ArrayList;
2019
import java.util.Arrays;
2120
import java.util.Collection;
2221
import java.util.Collections;
@@ -27,33 +26,23 @@
2726
import javax.servlet.http.HttpServletRequest;
2827

2928
import org.springframework.http.MediaType;
30-
import org.springframework.util.Assert;
3129
import org.springframework.util.StringUtils;
3230

3331
/**
34-
* Represents a collection of consumes conditions, typically obtained from {@link
35-
* org.springframework.web.bind.annotation.RequestMapping#consumes() @RequestMapping.consumes()}.
32+
* Represents a collection of consumes conditions, typically obtained from {@link org.springframework.web.bind.annotation.RequestMapping#consumes()
33+
* &#64;RequestMapping.consumes()}.
3634
*
3735
* @author Arjen Poutsma
38-
* @see RequestConditionFactory#parseHeaders(String...)
36+
* @see RequestConditionFactory#parseConsumes(String...)
37+
* @see RequestConditionFactory#parseConsumes(String[], String[])
3938
* @since 3.1
4039
*/
4140
public class ConsumesRequestCondition
42-
extends LogicalDisjunctionRequestCondition<ConsumesRequestCondition.ConsumeRequestCondition>
41+
extends MediaTypesRequestCondition<ConsumesRequestCondition.ConsumeRequestCondition>
4342
implements Comparable<ConsumesRequestCondition> {
4443

45-
private final ConsumeRequestCondition mostSpecificCondition;
46-
4744
ConsumesRequestCondition(Collection<ConsumeRequestCondition> conditions) {
4845
super(conditions);
49-
Assert.notEmpty(conditions, "'conditions' must not be empty");
50-
mostSpecificCondition = getMostSpecificCondition();
51-
}
52-
53-
private ConsumeRequestCondition getMostSpecificCondition() {
54-
List<ConsumeRequestCondition> conditions = new ArrayList<ConsumeRequestCondition>(getConditions());
55-
Collections.sort(conditions);
56-
return conditions.get(0);
5746
}
5847

5948
ConsumesRequestCondition(String... consumes) {
@@ -72,7 +61,7 @@ private static Set<ConsumeRequestCondition> parseConditions(List<String> consume
7261
}
7362

7463
/**
75-
* Creates an default set of consumes request conditions.
64+
* Creates a default set of consumes request conditions.
7665
*/
7766
public ConsumesRequestCondition() {
7867
this(Collections.singleton(new ConsumeRequestCondition(MediaType.ALL, false)));
@@ -101,8 +90,8 @@ public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request)
10190
}
10291

10392
/**
104-
* Combines this collection of request condition with another. Returns {@code other}, unless it has the default
105-
* value (i.e. <code>&#42;/&#42;</code>).
93+
* Combines this collection of request condition with another. Returns {@code other}, unless it has the default value
94+
* (i.e. <code>&#42;/&#42;</code>).
10695
*
10796
* @param other the condition to combine with
10897
*/
@@ -122,79 +111,32 @@ private boolean hasDefaultValue() {
122111
}
123112

124113
public int compareTo(ConsumesRequestCondition other) {
125-
return this.mostSpecificCondition.compareTo(other.mostSpecificCondition);
126-
}
127-
128-
private static MediaType getContentType(HttpServletRequest request) {
129-
if (StringUtils.hasLength(request.getContentType())) {
130-
return MediaType.parseMediaType(request.getContentType());
131-
}
132-
else {
133-
return MediaType.APPLICATION_OCTET_STREAM;
134-
}
114+
return this.getMostSpecificCondition().compareTo(other.getMostSpecificCondition());
135115
}
136116

137-
static class ConsumeRequestCondition implements RequestCondition, Comparable<ConsumeRequestCondition> {
138-
139-
private final MediaType mediaType;
140-
141-
private final boolean isNegated;
117+
static class ConsumeRequestCondition extends MediaTypesRequestCondition.MediaTypeRequestCondition {
142118

143119
ConsumeRequestCondition(String expression) {
144-
if (expression.startsWith("!")) {
145-
isNegated = true;
146-
expression = expression.substring(1);
147-
}
148-
else {
149-
isNegated = false;
150-
}
151-
this.mediaType = MediaType.parseMediaType(expression);
120+
super(expression);
152121
}
153122

154-
ConsumeRequestCondition(MediaType mediaType, boolean isNegated) {
155-
this.mediaType = mediaType;
156-
this.isNegated = isNegated;
123+
ConsumeRequestCondition(MediaType mediaType, boolean negated) {
124+
super(mediaType, negated);
157125
}
158126

159-
public boolean match(HttpServletRequest request) {
127+
@Override
128+
protected boolean match(HttpServletRequest request, MediaType mediaType) {
160129
MediaType contentType = getContentType(request);
161-
boolean match = this.mediaType.includes(contentType);
162-
return !isNegated ? match : !match;
130+
return mediaType.includes(contentType);
163131
}
164132

165-
public int compareTo(ConsumeRequestCondition other) {
166-
return MediaType.SPECIFICITY_COMPARATOR.compare(this.mediaType, other.mediaType);
167-
}
168-
169-
MediaType getMediaType() {
170-
return mediaType;
171-
}
172-
173-
@Override
174-
public boolean equals(Object obj) {
175-
if (this == obj) {
176-
return true;
177-
}
178-
if (obj != null && obj instanceof ConsumeRequestCondition) {
179-
ConsumeRequestCondition other = (ConsumeRequestCondition) obj;
180-
return (this.mediaType.equals(other.mediaType)) && (this.isNegated == other.isNegated);
133+
private MediaType getContentType(HttpServletRequest request) {
134+
if (StringUtils.hasLength(request.getContentType())) {
135+
return MediaType.parseMediaType(request.getContentType());
181136
}
182-
return false;
183-
}
184-
185-
@Override
186-
public int hashCode() {
187-
return mediaType.hashCode();
188-
}
189-
190-
@Override
191-
public String toString() {
192-
StringBuilder builder = new StringBuilder();
193-
if (isNegated) {
194-
builder.append('!');
137+
else {
138+
return MediaType.APPLICATION_OCTET_STREAM;
195139
}
196-
builder.append(mediaType.toString());
197-
return builder.toString();
198140
}
199141

200142
}

0 commit comments

Comments
 (0)