001/* 002 * Copyright 2002-2018 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; 022 023import javax.servlet.http.HttpServletRequest; 024 025import org.springframework.core.Conventions; 026import org.springframework.core.MethodParameter; 027import org.springframework.core.annotation.AnnotatedElementUtils; 028import org.springframework.http.converter.HttpMessageConverter; 029import org.springframework.http.converter.HttpMessageNotReadableException; 030import org.springframework.http.converter.HttpMessageNotWritableException; 031import org.springframework.http.server.ServletServerHttpRequest; 032import org.springframework.http.server.ServletServerHttpResponse; 033import org.springframework.lang.Nullable; 034import org.springframework.util.Assert; 035import org.springframework.validation.BindingResult; 036import org.springframework.web.HttpMediaTypeNotAcceptableException; 037import org.springframework.web.HttpMediaTypeNotSupportedException; 038import org.springframework.web.accept.ContentNegotiationManager; 039import org.springframework.web.bind.MethodArgumentNotValidException; 040import org.springframework.web.bind.WebDataBinder; 041import org.springframework.web.bind.annotation.RequestBody; 042import org.springframework.web.bind.annotation.ResponseBody; 043import org.springframework.web.bind.support.WebDataBinderFactory; 044import org.springframework.web.context.request.NativeWebRequest; 045import org.springframework.web.method.support.ModelAndViewContainer; 046import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; 047 048/** 049 * Resolves method arguments annotated with {@code @RequestBody} and handles return 050 * values from methods annotated with {@code @ResponseBody} by reading and writing 051 * to the body of the request or response with an {@link HttpMessageConverter}. 052 * 053 * <p>An {@code @RequestBody} method argument is also validated if it is annotated 054 * with {@code @javax.validation.Valid}. In case of validation failure, 055 * {@link MethodArgumentNotValidException} is raised and results in an HTTP 400 056 * response status code if {@link DefaultHandlerExceptionResolver} is configured. 057 * 058 * @author Arjen Poutsma 059 * @author Rossen Stoyanchev 060 * @author Juergen Hoeller 061 * @since 3.1 062 */ 063public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { 064 065 /** 066 * Basic constructor with converters only. Suitable for resolving 067 * {@code @RequestBody}. For handling {@code @ResponseBody} consider also 068 * providing a {@code ContentNegotiationManager}. 069 */ 070 public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) { 071 super(converters); 072 } 073 074 /** 075 * Basic constructor with converters and {@code ContentNegotiationManager}. 076 * Suitable for resolving {@code @RequestBody} and handling 077 * {@code @ResponseBody} without {@code Request~} or 078 * {@code ResponseBodyAdvice}. 079 */ 080 public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, 081 @Nullable ContentNegotiationManager manager) { 082 083 super(converters, manager); 084 } 085 086 /** 087 * Complete constructor for resolving {@code @RequestBody} method arguments. 088 * For handling {@code @ResponseBody} consider also providing a 089 * {@code ContentNegotiationManager}. 090 * @since 4.2 091 */ 092 public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, 093 @Nullable List<Object> requestResponseBodyAdvice) { 094 095 super(converters, null, requestResponseBodyAdvice); 096 } 097 098 /** 099 * Complete constructor for resolving {@code @RequestBody} and handling 100 * {@code @ResponseBody}. 101 */ 102 public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters, 103 @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) { 104 105 super(converters, manager, requestResponseBodyAdvice); 106 } 107 108 109 @Override 110 public boolean supportsParameter(MethodParameter parameter) { 111 return parameter.hasParameterAnnotation(RequestBody.class); 112 } 113 114 @Override 115 public boolean supportsReturnType(MethodParameter returnType) { 116 return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || 117 returnType.hasMethodAnnotation(ResponseBody.class)); 118 } 119 120 /** 121 * Throws MethodArgumentNotValidException if validation fails. 122 * @throws HttpMessageNotReadableException if {@link RequestBody#required()} 123 * is {@code true} and there is no body content or if there is no suitable 124 * converter to read the content with. 125 */ 126 @Override 127 public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, 128 NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { 129 130 parameter = parameter.nestedIfOptional(); 131 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); 132 String name = Conventions.getVariableNameForParameter(parameter); 133 134 if (binderFactory != null) { 135 WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); 136 if (arg != null) { 137 validateIfApplicable(binder, parameter); 138 if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { 139 throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); 140 } 141 } 142 if (mavContainer != null) { 143 mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); 144 } 145 } 146 147 return adaptArgumentIfNecessary(arg, parameter); 148 } 149 150 @Override 151 protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, 152 Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { 153 154 HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); 155 Assert.state(servletRequest != null, "No HttpServletRequest"); 156 ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); 157 158 Object arg = readWithMessageConverters(inputMessage, parameter, paramType); 159 if (arg == null && checkRequired(parameter)) { 160 throw new HttpMessageNotReadableException("Required request body is missing: " + 161 parameter.getExecutable().toGenericString(), inputMessage); 162 } 163 return arg; 164 } 165 166 protected boolean checkRequired(MethodParameter parameter) { 167 RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class); 168 return (requestBody != null && requestBody.required() && !parameter.isOptional()); 169 } 170 171 @Override 172 public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, 173 ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 174 throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { 175 176 mavContainer.setRequestHandled(true); 177 ServletServerHttpRequest inputMessage = createInputMessage(webRequest); 178 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); 179 180 // Try even with null return value. ResponseBodyAdvice could get involved. 181 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); 182 } 183 184}