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}