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.util.List; 020import javax.servlet.http.HttpServletRequest; 021 022import org.springframework.core.MethodParameter; 023import org.springframework.http.HttpInputMessage; 024import org.springframework.http.converter.HttpMessageConverter; 025import org.springframework.validation.BindingResult; 026import org.springframework.web.bind.MethodArgumentNotValidException; 027import org.springframework.web.bind.WebDataBinder; 028import org.springframework.web.bind.annotation.RequestBody; 029import org.springframework.web.bind.annotation.RequestParam; 030import org.springframework.web.bind.annotation.RequestPart; 031import org.springframework.web.bind.support.WebDataBinderFactory; 032import org.springframework.web.context.request.NativeWebRequest; 033import org.springframework.web.method.support.ModelAndViewContainer; 034import org.springframework.web.multipart.MultipartException; 035import org.springframework.web.multipart.MultipartFile; 036import org.springframework.web.multipart.MultipartResolver; 037import org.springframework.web.multipart.support.MissingServletRequestPartException; 038import org.springframework.web.multipart.support.MultipartResolutionDelegate; 039import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest; 040 041/** 042 * Resolves the following method arguments: 043 * <ul> 044 * <li>Annotated with @{@link RequestPart} 045 * <li>Of type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver} abstraction 046 * <li>Of type {@code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests 047 * </ul> 048 * 049 * <p>When a parameter is annotated with {@code @RequestPart}, the content of the part is 050 * passed through an {@link HttpMessageConverter} to resolve the method argument with the 051 * 'Content-Type' of the request part in mind. This is analogous to what @{@link RequestBody} 052 * does to resolve an argument based on the content of a regular request. 053 * 054 * <p>When a parameter is not annotated or the name of the part is not specified, 055 * it is derived from the name of the method argument. 056 * 057 * <p>Automatic validation may be applied if the argument is annotated with 058 * {@code @javax.validation.Valid}. In case of validation failure, a {@link MethodArgumentNotValidException} 059 * is raised and a 400 response status code returned if 060 * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} is configured. 061 * 062 * @author Rossen Stoyanchev 063 * @author Brian Clozel 064 * @author Juergen Hoeller 065 * @since 3.1 066 */ 067public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver { 068 069 /** 070 * Basic constructor with converters only. 071 */ 072 public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) { 073 super(messageConverters); 074 } 075 076 /** 077 * Constructor with converters and {@code Request~} and 078 * {@code ResponseBodyAdvice}. 079 */ 080 public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters, 081 List<Object> requestResponseBodyAdvice) { 082 083 super(messageConverters, requestResponseBodyAdvice); 084 } 085 086 087 /** 088 * Whether the given {@linkplain MethodParameter method parameter} is a multi-part 089 * supported. Supports the following: 090 * <ul> 091 * <li>annotated with {@code @RequestPart} 092 * <li>of type {@link MultipartFile} unless annotated with {@code @RequestParam} 093 * <li>of type {@code javax.servlet.http.Part} unless annotated with 094 * {@code @RequestParam} 095 * </ul> 096 */ 097 @Override 098 public boolean supportsParameter(MethodParameter parameter) { 099 if (parameter.hasParameterAnnotation(RequestPart.class)) { 100 return true; 101 } 102 else { 103 if (parameter.hasParameterAnnotation(RequestParam.class)) { 104 return false; 105 } 106 return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional()); 107 } 108 } 109 110 @Override 111 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 112 NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { 113 114 HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); 115 RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class); 116 boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional()); 117 118 String name = getPartName(parameter, requestPart); 119 parameter = parameter.nestedIfOptional(); 120 Object arg = null; 121 122 Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); 123 if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { 124 arg = mpArg; 125 } 126 else { 127 try { 128 HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name); 129 arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType()); 130 WebDataBinder binder = binderFactory.createBinder(request, arg, name); 131 if (arg != null) { 132 validateIfApplicable(binder, parameter); 133 if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { 134 throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); 135 } 136 } 137 mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); 138 } 139 catch (MissingServletRequestPartException ex) { 140 if (isRequired) { 141 throw ex; 142 } 143 } 144 catch (MultipartException ex) { 145 if (isRequired) { 146 throw ex; 147 } 148 } 149 } 150 151 if (arg == null && isRequired) { 152 if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { 153 throw new MultipartException("Current request is not a multipart request"); 154 } 155 else { 156 throw new MissingServletRequestPartException(name); 157 } 158 } 159 return adaptArgumentIfNecessary(arg, parameter); 160 } 161 162 private String getPartName(MethodParameter methodParam, RequestPart requestPart) { 163 String partName = (requestPart != null ? requestPart.name() : ""); 164 if (partName.isEmpty()) { 165 partName = methodParam.getParameterName(); 166 if (partName == null) { 167 throw new IllegalArgumentException("Request part name for argument type [" + 168 methodParam.getNestedParameterType().getName() + 169 "] not specified, and parameter name information not found in class file either."); 170 } 171 } 172 return partName; 173 } 174 175}