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}