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