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}