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