001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.mvc.method.annotation;
018
019import java.io.IOException;
020import java.lang.reflect.Type;
021import java.util.List;
022import javax.servlet.http.HttpServletRequest;
023
024import org.springframework.core.Conventions;
025import org.springframework.core.MethodParameter;
026import org.springframework.core.annotation.AnnotatedElementUtils;
027import org.springframework.http.converter.HttpMessageConverter;
028import org.springframework.http.converter.HttpMessageNotReadableException;
029import org.springframework.http.converter.HttpMessageNotWritableException;
030import org.springframework.http.server.ServletServerHttpRequest;
031import org.springframework.http.server.ServletServerHttpResponse;
032import org.springframework.validation.BindingResult;
033import org.springframework.web.HttpMediaTypeNotAcceptableException;
034import org.springframework.web.HttpMediaTypeNotSupportedException;
035import org.springframework.web.accept.ContentNegotiationManager;
036import org.springframework.web.bind.MethodArgumentNotValidException;
037import org.springframework.web.bind.WebDataBinder;
038import org.springframework.web.bind.annotation.RequestBody;
039import org.springframework.web.bind.annotation.ResponseBody;
040import org.springframework.web.bind.support.WebDataBinderFactory;
041import org.springframework.web.context.request.NativeWebRequest;
042import org.springframework.web.method.support.ModelAndViewContainer;
043import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
044
045/**
046 * Resolves method arguments annotated with {@code @RequestBody} and handles return
047 * values from methods annotated with {@code @ResponseBody} by reading and writing
048 * to the body of the request or response with an {@link HttpMessageConverter}.
049 *
050 * <p>An {@code @RequestBody} method argument is also validated if it is annotated
051 * with {@code @javax.validation.Valid}. In case of validation failure,
052 * {@link MethodArgumentNotValidException} is raised and results in an HTTP 400
053 * response status code if {@link DefaultHandlerExceptionResolver} is configured.
054 *
055 * @author Arjen Poutsma
056 * @author Rossen Stoyanchev
057 * @author Juergen Hoeller
058 * @since 3.1
059 */
060public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
061
062        /**
063         * Basic constructor with converters only. Suitable for resolving
064         * {@code @RequestBody}. For handling {@code @ResponseBody} consider also
065         * providing a {@code ContentNegotiationManager}.
066         */
067        public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
068                super(converters);
069        }
070
071        /**
072         * Basic constructor with converters and {@code ContentNegotiationManager}.
073         * Suitable for resolving {@code @RequestBody} and handling
074         * {@code @ResponseBody} without {@code Request~} or
075         * {@code ResponseBodyAdvice}.
076         */
077        public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
078                        ContentNegotiationManager manager) {
079
080                super(converters, manager);
081        }
082
083        /**
084         * Complete constructor for resolving {@code @RequestBody} method arguments.
085         * For handling {@code @ResponseBody} consider also providing a
086         * {@code ContentNegotiationManager}.
087         * @since 4.2
088         */
089        public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
090                        List<Object> requestResponseBodyAdvice) {
091
092                super(converters, null, requestResponseBodyAdvice);
093        }
094
095        /**
096         * Complete constructor for resolving {@code @RequestBody} and handling
097         * {@code @ResponseBody}.
098         */
099        public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
100                        ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
101
102                super(converters, manager, requestResponseBodyAdvice);
103        }
104
105
106        @Override
107        public boolean supportsParameter(MethodParameter parameter) {
108                return parameter.hasParameterAnnotation(RequestBody.class);
109        }
110
111        @Override
112        public boolean supportsReturnType(MethodParameter returnType) {
113                return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
114                                returnType.hasMethodAnnotation(ResponseBody.class));
115        }
116
117        /**
118         * Throws MethodArgumentNotValidException if validation fails.
119         * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
120         * is {@code true} and there is no body content or if there is no suitable
121         * converter to read the content with.
122         */
123        @Override
124        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
125                        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
126
127                parameter = parameter.nestedIfOptional();
128                Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
129                String name = Conventions.getVariableNameForParameter(parameter);
130
131                WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
132                if (arg != null) {
133                        validateIfApplicable(binder, parameter);
134                        if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
135                                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
136                        }
137                }
138                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
139
140                return adaptArgumentIfNecessary(arg, parameter);
141        }
142
143        @Override
144        protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
145                        Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
146
147                HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
148                ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
149
150                Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
151                if (arg == null) {
152                        if (checkRequired(parameter)) {
153                                throw new HttpMessageNotReadableException("Required request body is missing: " +
154                                                parameter.getMethod().toGenericString());
155                        }
156                }
157                return arg;
158        }
159
160        protected boolean checkRequired(MethodParameter parameter) {
161                return (parameter.getParameterAnnotation(RequestBody.class).required() && !parameter.isOptional());
162        }
163
164        @Override
165        public void handleReturnValue(Object returnValue, MethodParameter returnType,
166                        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
167                        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
168
169                mavContainer.setRequestHandled(true);
170                ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
171                ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
172
173                // Try even with null return value. ResponseBodyAdvice could get involved.
174                writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
175        }
176
177}