View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.servlet.mvc.method.annotation;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.PushbackInputStream;
22  import java.lang.reflect.Type;
23  import java.util.List;
24  import javax.servlet.http.HttpServletRequest;
25  
26  import org.springframework.core.Conventions;
27  import org.springframework.core.MethodParameter;
28  import org.springframework.core.annotation.AnnotationUtils;
29  import org.springframework.http.HttpInputMessage;
30  import org.springframework.http.converter.HttpMessageConverter;
31  import org.springframework.http.converter.HttpMessageNotReadableException;
32  import org.springframework.http.server.ServletServerHttpRequest;
33  import org.springframework.validation.BindingResult;
34  import org.springframework.web.HttpMediaTypeNotAcceptableException;
35  import org.springframework.web.HttpMediaTypeNotSupportedException;
36  import org.springframework.web.accept.ContentNegotiationManager;
37  import org.springframework.web.bind.MethodArgumentNotValidException;
38  import org.springframework.web.bind.WebDataBinder;
39  import org.springframework.web.bind.annotation.RequestBody;
40  import org.springframework.web.bind.annotation.ResponseBody;
41  import org.springframework.web.bind.support.WebDataBinderFactory;
42  import org.springframework.web.context.request.NativeWebRequest;
43  import org.springframework.web.method.support.ModelAndViewContainer;
44  import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
45  
46  /**
47   * Resolves method arguments annotated with {@code @RequestBody} and handles return
48   * values from methods annotated with {@code @ResponseBody} by reading and writing
49   * to the body of the request or response with an {@link HttpMessageConverter}.
50   *
51   * <p>An {@code @RequestBody} method argument is also validated if it is annotated
52   * with {@code @javax.validation.Valid}. In case of validation failure,
53   * {@link MethodArgumentNotValidException} is raised and results in an HTTP 400
54   * response status code if {@link DefaultHandlerExceptionResolver} is configured.
55   *
56   * @author Arjen Poutsma
57   * @author Rossen Stoyanchev
58   * @since 3.1
59   */
60  public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
61  
62  	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters) {
63  		super(messageConverters);
64  	}
65  
66  	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
67  			ContentNegotiationManager contentNegotiationManager) {
68  
69  		super(messageConverters, contentNegotiationManager);
70  	}
71  
72  	public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
73  			ContentNegotiationManager contentNegotiationManager, List<Object> responseBodyAdvice) {
74  
75  		super(messageConverters, contentNegotiationManager, responseBodyAdvice);
76  	}
77  
78  
79  	@Override
80  	public boolean supportsParameter(MethodParameter parameter) {
81  		return parameter.hasParameterAnnotation(RequestBody.class);
82  	}
83  
84  	@Override
85  	public boolean supportsReturnType(MethodParameter returnType) {
86  		return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
87  				returnType.getMethodAnnotation(ResponseBody.class) != null);
88  	}
89  
90  	/**
91  	 * Throws MethodArgumentNotValidException if validation fails.
92  	 * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
93  	 * is {@code true} and there is no body content or if there is no suitable
94  	 * converter to read the content with.
95  	 */
96  	@Override
97  	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
98  			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
99  
100 		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
101 		String name = Conventions.getVariableNameForParameter(parameter);
102 		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
103 		if (arg != null) {
104 			validateIfApplicable(binder, parameter);
105 			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
106 				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
107 			}
108 		}
109 		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
110 		return arg;
111 	}
112 
113 	@Override
114 	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam,
115 			Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
116 
117 		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
118 		HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
119 
120 		InputStream inputStream = inputMessage.getBody();
121 		if (inputStream == null) {
122 			return handleEmptyBody(methodParam);
123 		}
124 		else if (inputStream.markSupported()) {
125 			inputStream.mark(1);
126 			if (inputStream.read() == -1) {
127 				return handleEmptyBody(methodParam);
128 			}
129 			inputStream.reset();
130 		}
131 		else {
132 			final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
133 			int b = pushbackInputStream.read();
134 			if (b == -1) {
135 				return handleEmptyBody(methodParam);
136 			}
137 			else {
138 				pushbackInputStream.unread(b);
139 			}
140 			inputMessage = new ServletServerHttpRequest(servletRequest) {
141 				@Override
142 				public InputStream getBody() {
143 					// Form POST should not get here
144 					return pushbackInputStream;
145 				}
146 			};
147 		}
148 
149 		return super.readWithMessageConverters(inputMessage, methodParam, paramType);
150 	}
151 
152 	private Object handleEmptyBody(MethodParameter param) {
153 		if (param.getParameterAnnotation(RequestBody.class).required()) {
154 			throw new HttpMessageNotReadableException("Required request body content is missing: " + param);
155 		}
156 		return null;
157 	}
158 
159 	@Override
160 	public void handleReturnValue(Object returnValue, MethodParameter returnType,
161 			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
162 			throws IOException, HttpMediaTypeNotAcceptableException {
163 
164 		mavContainer.setRequestHandled(true);
165 
166 		// Try even with null return value. ResponseBodyAdvice could get involved.
167 		writeWithMessageConverters(returnValue, returnType, webRequest);
168 	}
169 
170 }