Skip to content

Commit ad2e0d4

Browse files
committed
SPR-7353 - @responsebody and returned HttpEntity now respect @RequestMapping.produces()
1 parent 57c757a commit ad2e0d4

File tree

12 files changed

+395
-218
lines changed

12 files changed

+395
-218
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.util.Map;
2323
import java.util.Set;
2424
import java.util.concurrent.ConcurrentHashMap;
25-
2625
import javax.servlet.http.HttpServletRequest;
2726
import javax.servlet.http.HttpServletResponse;
2827
import javax.servlet.http.HttpSession;
@@ -575,7 +574,7 @@ private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod h
575574
/**
576575
* MethodFilter that matches {@link InitBinder @InitBinder} methods.
577576
*/
578-
public static MethodFilter INIT_BINDER_METHODS = new MethodFilter() {
577+
public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() {
579578

580579
public boolean matches(Method method) {
581580
return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
@@ -585,7 +584,7 @@ public boolean matches(Method method) {
585584
/**
586585
* MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.
587586
*/
588-
public static MethodFilter MODEL_ATTRIBUTE_METHODS = new MethodFilter() {
587+
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = new MethodFilter() {
589588

590589
public boolean matches(Method method) {
591590
return ((AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) &&

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

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

3031
import org.springframework.core.annotation.AnnotationUtils;
@@ -33,6 +34,9 @@
3334
import org.springframework.util.AntPathMatcher;
3435
import org.springframework.util.Assert;
3536
import org.springframework.util.PathMatcher;
37+
import org.springframework.util.StringUtils;
38+
import org.springframework.web.HttpMediaTypeNotAcceptableException;
39+
import org.springframework.web.HttpMediaTypeNotSupportedException;
3640
import org.springframework.web.HttpRequestMethodNotSupportedException;
3741
import org.springframework.web.bind.annotation.RequestMapping;
3842
import org.springframework.web.bind.annotation.RequestMethod;
@@ -46,9 +50,9 @@
4650
import org.springframework.web.servlet.mvc.method.condition.RequestConditionFactory;
4751

4852
/**
49-
* An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo}s for the registration and
50-
* the lookup of {@link HandlerMethod}s.
51-
*
53+
* An {@link AbstractHandlerMethodMapping} variant that uses {@link RequestMappingInfo}s for the registration and the
54+
* lookup of {@link HandlerMethod}s.
55+
*
5256
* @author Arjen Poutsma
5357
* @author Rossen Stoyanchev
5458
* @since 3.1.0
@@ -84,10 +88,10 @@ protected void initInterceptors() {
8488
this.mappedInterceptors = MappedInterceptors.createFromDeclaredBeans(getApplicationContext());
8589
}
8690
}
87-
91+
8892
/**
89-
* {@inheritDoc}
90-
* The handler determination in this method is made based on the presence of a type-level {@link Controller} annotation.
93+
* {@inheritDoc} The handler determination in this method is made based on the presence of a type-level {@link
94+
* Controller} annotation.
9195
*/
9296
@Override
9397
protected boolean isHandler(Class<?> beanType) {
@@ -106,9 +110,8 @@ protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod>
106110
}
107111

108112
/**
109-
* Provides a {@link RequestMappingInfo} for the given method.
110-
* <p>Only {@link RequestMapping @RequestMapping}-annotated methods are considered.
111-
* Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their
113+
* Provides a {@link RequestMappingInfo} for the given method. <p>Only {@link RequestMapping @RequestMapping}-annotated
114+
* methods are considered. Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their
112115
* attributes combined with method-level {@link RequestMapping @RequestMapping} attributes.
113116
*
114117
* @param method the method to create a mapping for
@@ -137,25 +140,27 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
137140

138141
private static RequestMappingInfo createFromRequestMapping(RequestMapping annotation) {
139142
return new RequestMappingInfo(Arrays.asList(annotation.value()),
140-
RequestConditionFactory.parseMethods(annotation.method()),
141-
RequestConditionFactory.parseParams(annotation.params()),
142-
RequestConditionFactory.parseHeaders(annotation.headers()),
143-
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()),
144-
RequestConditionFactory.parseProduces(annotation.produces(), annotation.headers())
145-
);
143+
RequestConditionFactory.parseMethods(annotation.method()),
144+
RequestConditionFactory.parseParams(annotation.params()),
145+
RequestConditionFactory.parseHeaders(annotation.headers()),
146+
RequestConditionFactory.parseConsumes(annotation.consumes(), annotation.headers()),
147+
RequestConditionFactory.parseProduces(annotation.produces(), annotation.headers()));
146148
}
147-
149+
148150
@Override
149151
protected Set<String> getMappingPaths(RequestMappingInfo mapping) {
150152
return mapping.getPatterns();
151153
}
152154

153155
/**
154156
* Returns a new {@link RequestMappingInfo} with attributes matching to the current request or {@code null}.
157+
*
155158
* @see RequestMappingInfo#getMatchingRequestMapping(String, HttpServletRequest, PathMatcher)
156159
*/
157160
@Override
158-
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo mapping, String lookupPath, HttpServletRequest request) {
161+
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo mapping,
162+
String lookupPath,
163+
HttpServletRequest request) {
159164
return mapping.getMatchingRequestMapping(lookupPath, request, pathMatcher);
160165
}
161166

@@ -177,26 +182,43 @@ protected void handleMatch(RequestMappingInfo mapping, String lookupPath, HttpSe
177182

178183
/**
179184
* Iterates all {@link RequestMappingInfo}s looking for mappings that match by URL but not by HTTP method.
180-
* @exception HttpRequestMethodNotSupportedException if there are matches by URL but not by HTTP method
185+
*
186+
* @throws HttpRequestMethodNotSupportedException if there are matches by URL but not by HTTP method
181187
*/
182188
@Override
183-
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos, String lookupPath, HttpServletRequest request)
184-
throws HttpRequestMethodNotSupportedException {
189+
protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos,
190+
String lookupPath,
191+
HttpServletRequest request) throws ServletException {
185192
Set<String> allowedMethods = new HashSet<String>(6);
193+
Set<MediaType> consumableMediaTypes = new HashSet<MediaType>();
194+
Set<MediaType> producibleMediaTypes = new HashSet<MediaType>();
186195
for (RequestMappingInfo info : requestMappingInfos) {
187-
for (String pattern : info.getPatterns()) {
188-
if (pathMatcher.match(pattern, lookupPath)) {
189-
for (RequestMethod method : info.getMethods().getMethods()) {
190-
allowedMethods.add(method.name());
191-
}
196+
if (!info.getMethods().match(request)) {
197+
for (RequestMethod method : info.getMethods().getMethods()) {
198+
allowedMethods.add(method.name());
192199
}
193200
}
201+
if (!info.getConsumes().match(request)) {
202+
consumableMediaTypes.addAll(info.getConsumes().getMediaTypes());
203+
}
204+
if (!info.getProduces().match(request)) {
205+
producibleMediaTypes.addAll(info.getProduces().getMediaTypes());
206+
}
194207
}
195208
if (!allowedMethods.isEmpty()) {
196-
throw new HttpRequestMethodNotSupportedException(request.getMethod(),
197-
allowedMethods.toArray(new String[allowedMethods.size()]));
198-
199-
} else {
209+
throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
210+
}
211+
else if (!consumableMediaTypes.isEmpty()) {
212+
MediaType contentType = null;
213+
if (StringUtils.hasLength(request.getContentType())) {
214+
contentType = MediaType.parseMediaType(request.getContentType());
215+
}
216+
throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<MediaType>(consumableMediaTypes));
217+
}
218+
else if (!producibleMediaTypes.isEmpty()) {
219+
throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(producibleMediaTypes));
220+
}
221+
else {
200222
return null;
201223
}
202224
}
@@ -218,13 +240,13 @@ protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpSer
218240
}
219241

220242
/**
221-
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context of a
222-
* specific request. For example not all {@link RequestMappingInfo} patterns may apply to the current request.
223-
* Therefore an HttpServletRequest is required as input.
243+
* A comparator for {@link RequestMappingInfo}s. Effective comparison can only be done in the context of a specific
244+
* request. For example not all {@link RequestMappingInfo} patterns may apply to the current request. Therefore an
245+
* HttpServletRequest is required as input.
224246
*
225-
* <p>Furthermore, the following assumptions are made about the input RequestMappings:
226-
* <ul><li>Each RequestMappingInfo has been fully matched to the request <li>The RequestMappingInfo contains
227-
* matched patterns only <li>Patterns are ordered with the best matching pattern at the top </ul>
247+
* <p>Furthermore, the following assumptions are made about the input RequestMappings: <ul><li>Each RequestMappingInfo
248+
* has been fully matched to the request <li>The RequestMappingInfo contains matched patterns only <li>Patterns are
249+
* ordered with the best matching pattern at the top </ul>
228250
*
229251
* @see RequestMappingHandlerMapping#getMatchingMapping(RequestMappingInfo, String, HttpServletRequest)
230252
*/
@@ -289,26 +311,6 @@ else if (iteratorOther.hasNext()) {
289311
}
290312
}
291313

292-
private int compareAcceptHeaders(List<MediaType> accept, List<MediaType> otherAccept) {
293-
for (MediaType requestAccept : this.requestAcceptHeader) {
294-
int pos1 = indexOfIncluded(requestAccept, accept);
295-
int pos2 = indexOfIncluded(requestAccept, otherAccept);
296-
if (pos1 != pos2) {
297-
return pos2 - pos1;
298-
}
299-
}
300-
return 0;
301-
}
302-
303-
private int indexOfIncluded(MediaType requestAccept, List<MediaType> accept) {
304-
for (int i = 0; i < accept.size(); i++) {
305-
if (requestAccept.includes(accept.get(i))) {
306-
return i;
307-
}
308-
}
309-
return -1;
310-
}
311-
312314
}
313315

314316
}

0 commit comments

Comments
 (0)