/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.method.annotation; import java.beans.PropertyEditor; import java.util.Collection; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Part; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.util.StringUtils; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.UriComponentsContributor; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.multipart.support.MultipartResolutionDelegate; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.WebUtils; /** * Resolves method arguments annotated with @{@link RequestParam}, arguments of * type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver} * abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction * with Servlet 3.0 multipart requests. This resolver can also be created in default * resolution mode in which simple types (int, long, etc.) not annotated with * {@link RequestParam @RequestParam} are also treated as request parameters with * the parameter name derived from the argument name. * * <p>If the method parameter type is {@link Map}, the name specified in the * annotation is used to resolve the request parameter String value. The value is * then converted to a {@link Map} via type conversion assuming a suitable * {@link Converter} or {@link PropertyEditor} has been registered. * Or if a request parameter name is not specified the * {@link RequestParamMapMethodArgumentResolver} is used instead to provide * access to all request parameters in the form of a map. * * <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved request * header values that don't yet match the method parameter type. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Brian Clozel * @since 3.1 * @see RequestParamMapMethodArgumentResolver */ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor { private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); private final boolean useDefaultResolution; /** * @param useDefaultResolution in default resolution mode a method argument * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, * is treated as a request parameter even if it isn't annotated, the * request parameter name is derived from the method parameter name. */ public RequestParamMethodArgumentResolver(boolean useDefaultResolution) { this.useDefaultResolution = useDefaultResolution; } /** * @param beanFactory a bean factory used for resolving ${...} placeholder * and #{...} SpEL expressions in default values, or {@code null} if default * values are not expected to contain expressions * @param useDefaultResolution in default resolution mode a method argument * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, * is treated as a request parameter even if it isn't annotated, the * request parameter name is derived from the method parameter name. */ public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) { super(beanFactory); this.useDefaultResolution = useDefaultResolution; } /** * Supports the following: * <ul> * <li>@RequestParam-annotated method arguments. * This excludes {@link Map} params where the annotation doesn't * specify a name. See {@link RequestParamMapMethodArgumentResolver} * instead for such params. * <li>Arguments of type {@link MultipartFile} * unless annotated with @{@link RequestPart}. * <li>Arguments of type {@code javax.servlet.http.Part} * unless annotated with @{@link RequestPart}. * <li>In default resolution mode, simple type arguments * even if not with @{@link RequestParam}. * </ul> */ @Override public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(RequestParam.class)) { if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { String paramName = parameter.getParameterAnnotation(RequestParam.class).name(); return StringUtils.hasText(paramName); } else { return true; } } else { if (parameter.hasParameterAnnotation(RequestPart.class)) { return false; } parameter = parameter.nestedIfOptional(); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { return true; } else if (this.useDefaultResolution) { return BeanUtils.isSimpleProperty(parameter.getNestedParameterType()); } else { return false; } } } @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestParam ann = parameter.getParameterAnnotation(RequestParam.class); return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo()); } @Override protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } Object arg = null; if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0) : files); } } if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; } @Override protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (MultipartResolutionDelegate.isMultipartArgument(parameter)) { if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { throw new MultipartException("Current request is not a multipart request"); } else { throw new MissingServletRequestPartException(name); } } else { throw new MissingServletRequestParameterException(name, parameter.getNestedParameterType().getSimpleName()); } } @Override public void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) { Class<?> paramType = parameter.getNestedParameterType(); if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType || Part.class == paramType) { return; } RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); String name = (requestParam == null || StringUtils.isEmpty(requestParam.name()) ? parameter.getParameterName() : requestParam.name()); if (value == null) { if (requestParam != null) { if (!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)) { return; } } builder.queryParam(name); } else if (value instanceof Collection) { for (Object element : (Collection<?>) value) { element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element); builder.queryParam(name, element); } } else { builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value)); } } protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) { if (value == null) { return null; } else if (value instanceof String) { return (String) value; } else if (cs != null) { return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR); } else { return value.toString(); } } private static class RequestParamNamedValueInfo extends NamedValueInfo { public RequestParamNamedValueInfo() { super("", false, ValueConstants.DEFAULT_NONE); } public RequestParamNamedValueInfo(RequestParam annotation) { super(annotation.name(), annotation.required(), annotation.defaultValue()); } } }