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.method.annotation;
018
019import java.beans.PropertyEditor;
020import java.util.Collection;
021import java.util.List;
022import java.util.Map;
023import javax.servlet.http.HttpServletRequest;
024
025import org.springframework.beans.BeanUtils;
026import org.springframework.beans.factory.config.ConfigurableBeanFactory;
027import org.springframework.core.MethodParameter;
028import org.springframework.core.convert.ConversionService;
029import org.springframework.core.convert.TypeDescriptor;
030import org.springframework.core.convert.converter.Converter;
031import org.springframework.util.StringUtils;
032import org.springframework.web.bind.MissingServletRequestParameterException;
033import org.springframework.web.bind.WebDataBinder;
034import org.springframework.web.bind.annotation.RequestParam;
035import org.springframework.web.bind.annotation.RequestPart;
036import org.springframework.web.bind.annotation.ValueConstants;
037import org.springframework.web.context.request.NativeWebRequest;
038import org.springframework.web.method.support.UriComponentsContributor;
039import org.springframework.web.multipart.MultipartException;
040import org.springframework.web.multipart.MultipartFile;
041import org.springframework.web.multipart.MultipartHttpServletRequest;
042import org.springframework.web.multipart.MultipartResolver;
043import org.springframework.web.multipart.support.MissingServletRequestPartException;
044import org.springframework.web.multipart.support.MultipartResolutionDelegate;
045import org.springframework.web.util.UriComponentsBuilder;
046import org.springframework.web.util.WebUtils;
047
048/**
049 * Resolves method arguments annotated with @{@link RequestParam}, arguments of
050 * type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver}
051 * abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction
052 * with Servlet 3.0 multipart requests. This resolver can also be created in default
053 * resolution mode in which simple types (int, long, etc.) not annotated with
054 * {@link RequestParam @RequestParam} are also treated as request parameters with
055 * the parameter name derived from the argument name.
056 *
057 * <p>If the method parameter type is {@link Map}, the name specified in the
058 * annotation is used to resolve the request parameter String value. The value is
059 * then converted to a {@link Map} via type conversion assuming a suitable
060 * {@link Converter} or {@link PropertyEditor} has been registered.
061 * Or if a request parameter name is not specified the
062 * {@link RequestParamMapMethodArgumentResolver} is used instead to provide
063 * access to all request parameters in the form of a map.
064 *
065 * <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved request
066 * header values that don't yet match the method parameter type.
067 *
068 * @author Arjen Poutsma
069 * @author Rossen Stoyanchev
070 * @author Brian Clozel
071 * @since 3.1
072 * @see RequestParamMapMethodArgumentResolver
073 */
074public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
075                implements UriComponentsContributor {
076
077        private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
078
079        private final boolean useDefaultResolution;
080
081
082        /**
083         * Create a new {@link RequestParamMethodArgumentResolver} instance.
084         * @param useDefaultResolution in default resolution mode a method argument
085         * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty},
086         * is treated as a request parameter even if it isn't annotated, the
087         * request parameter name is derived from the method parameter name.
088         */
089        public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
090                this.useDefaultResolution = useDefaultResolution;
091        }
092
093        /**
094         * Create a new {@link RequestParamMethodArgumentResolver} instance.
095         * @param beanFactory a bean factory used for resolving  ${...} placeholder
096         * and #{...} SpEL expressions in default values, or {@code null} if default
097         * values are not expected to contain expressions
098         * @param useDefaultResolution in default resolution mode a method argument
099         * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty},
100         * is treated as a request parameter even if it isn't annotated, the
101         * request parameter name is derived from the method parameter name.
102         */
103        public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
104                super(beanFactory);
105                this.useDefaultResolution = useDefaultResolution;
106        }
107
108
109        /**
110         * Supports the following:
111         * <ul>
112         * <li>@RequestParam-annotated method arguments.
113         * This excludes {@link Map} params where the annotation does not specify a name.
114         * See {@link RequestParamMapMethodArgumentResolver} instead for such params.
115         * <li>Arguments of type {@link MultipartFile} unless annotated with @{@link RequestPart}.
116         * <li>Arguments of type {@code Part} unless annotated with @{@link RequestPart}.
117         * <li>In default resolution mode, simple type arguments even if not with @{@link RequestParam}.
118         * </ul>
119         */
120        @Override
121        public boolean supportsParameter(MethodParameter parameter) {
122                if (parameter.hasParameterAnnotation(RequestParam.class)) {
123                        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
124                                String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
125                                return StringUtils.hasText(paramName);
126                        }
127                        else {
128                                return true;
129                        }
130                }
131                else {
132                        if (parameter.hasParameterAnnotation(RequestPart.class)) {
133                                return false;
134                        }
135                        parameter = parameter.nestedIfOptional();
136                        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
137                                return true;
138                        }
139                        else if (this.useDefaultResolution) {
140                                return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
141                        }
142                        else {
143                                return false;
144                        }
145                }
146        }
147
148        @Override
149        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
150                RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
151                return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
152        }
153
154        @Override
155        protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
156                HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
157                MultipartHttpServletRequest multipartRequest =
158                                WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
159
160                Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
161                if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
162                        return mpArg;
163                }
164
165                Object arg = null;
166                if (multipartRequest != null) {
167                        List<MultipartFile> files = multipartRequest.getFiles(name);
168                        if (!files.isEmpty()) {
169                                arg = (files.size() == 1 ? files.get(0) : files);
170                        }
171                }
172                if (arg == null) {
173                        String[] paramValues = request.getParameterValues(name);
174                        if (paramValues != null) {
175                                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
176                        }
177                }
178                return arg;
179        }
180
181        @Override
182        protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
183                        throws Exception {
184
185                HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
186                if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
187                        if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
188                                throw new MultipartException("Current request is not a multipart request");
189                        }
190                        else {
191                                throw new MissingServletRequestPartException(name);
192                        }
193                }
194                else {
195                        throw new MissingServletRequestParameterException(name,
196                                        parameter.getNestedParameterType().getSimpleName());
197                }
198        }
199
200        @Override
201        public void contributeMethodArgument(MethodParameter parameter, Object value,
202                        UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
203
204                Class<?> paramType = parameter.getNestedParameterType();
205                if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType ||
206                                "javax.servlet.http.Part".equals(paramType.getName())) {
207                        return;
208                }
209
210                RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
211                String name = (requestParam == null || StringUtils.isEmpty(requestParam.name()) ?
212                                parameter.getParameterName() : requestParam.name());
213
214                if (value == null) {
215                        if (requestParam != null) {
216                                if (!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)) {
217                                        return;
218                                }
219                        }
220                        builder.queryParam(name);
221                }
222                else if (value instanceof Collection) {
223                        for (Object element : (Collection<?>) value) {
224                                element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element);
225                                builder.queryParam(name, element);
226                        }
227                }
228                else {
229                        builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
230                }
231        }
232
233        protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
234                if (value == null) {
235                        return null;
236                }
237                else if (value instanceof String) {
238                        return (String) value;
239                }
240                else if (cs != null) {
241                        return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
242                }
243                else {
244                        return value.toString();
245                }
246        }
247
248
249        private static class RequestParamNamedValueInfo extends NamedValueInfo {
250
251                public RequestParamNamedValueInfo() {
252                        super("", false, ValueConstants.DEFAULT_NONE);
253                }
254
255                public RequestParamNamedValueInfo(RequestParam annotation) {
256                        super(annotation.name(), annotation.required(), annotation.defaultValue());
257                }
258        }
259
260}