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}