001/* 002 * Copyright 2002-2016 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.beans.PropertyEditor; 020import java.util.HashMap; 021import java.util.Map; 022 023import org.springframework.core.MethodParameter; 024import org.springframework.core.convert.ConversionService; 025import org.springframework.core.convert.TypeDescriptor; 026import org.springframework.core.convert.converter.Converter; 027import org.springframework.util.StringUtils; 028import org.springframework.web.bind.MissingPathVariableException; 029import org.springframework.web.bind.ServletRequestBindingException; 030import org.springframework.web.bind.WebDataBinder; 031import org.springframework.web.bind.annotation.PathVariable; 032import org.springframework.web.bind.annotation.ValueConstants; 033import org.springframework.web.context.request.NativeWebRequest; 034import org.springframework.web.context.request.RequestAttributes; 035import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver; 036import org.springframework.web.method.support.ModelAndViewContainer; 037import org.springframework.web.method.support.UriComponentsContributor; 038import org.springframework.web.servlet.HandlerMapping; 039import org.springframework.web.servlet.View; 040import org.springframework.web.util.UriComponentsBuilder; 041 042/** 043 * Resolves method arguments annotated with an @{@link PathVariable}. 044 * 045 * <p>An @{@link PathVariable} is a named value that gets resolved from a URI template variable. 046 * It is always required and does not have a default value to fall back on. See the base class 047 * {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver} 048 * for more information on how named values are processed. 049 * 050 * <p>If the method parameter type is {@link Map}, the name specified in the annotation is used 051 * to resolve the URI variable String value. The value is then converted to a {@link Map} via 052 * type conversion, assuming a suitable {@link Converter} or {@link PropertyEditor} has been 053 * registered. 054 * 055 * <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved path variable 056 * values that don't yet match the method parameter type. 057 * 058 * @author Rossen Stoyanchev 059 * @author Arjen Poutsma 060 * @author Juergen Hoeller 061 * @since 3.1 062 */ 063public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver 064 implements UriComponentsContributor { 065 066 private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); 067 068 069 @Override 070 public boolean supportsParameter(MethodParameter parameter) { 071 if (!parameter.hasParameterAnnotation(PathVariable.class)) { 072 return false; 073 } 074 if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { 075 String paramName = parameter.getParameterAnnotation(PathVariable.class).value(); 076 return StringUtils.hasText(paramName); 077 } 078 return true; 079 } 080 081 @Override 082 protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { 083 PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class); 084 return new PathVariableNamedValueInfo(annotation); 085 } 086 087 @Override 088 @SuppressWarnings("unchecked") 089 protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { 090 Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute( 091 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); 092 return (uriTemplateVars != null ? uriTemplateVars.get(name) : null); 093 } 094 095 @Override 096 protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException { 097 throw new MissingPathVariableException(name, parameter); 098 } 099 100 @Override 101 @SuppressWarnings("unchecked") 102 protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, 103 ModelAndViewContainer mavContainer, NativeWebRequest request) { 104 105 String key = View.PATH_VARIABLES; 106 int scope = RequestAttributes.SCOPE_REQUEST; 107 Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope); 108 if (pathVars == null) { 109 pathVars = new HashMap<String, Object>(); 110 request.setAttribute(key, pathVars, scope); 111 } 112 pathVars.put(name, arg); 113 } 114 115 @Override 116 public void contributeMethodArgument(MethodParameter parameter, Object value, 117 UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) { 118 119 if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { 120 return; 121 } 122 123 PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); 124 String name = (ann != null && !StringUtils.isEmpty(ann.value()) ? ann.value() : parameter.getParameterName()); 125 value = formatUriValue(conversionService, new TypeDescriptor(parameter.nestedIfOptional()), value); 126 uriVariables.put(name, value); 127 } 128 129 protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) { 130 if (value == null) { 131 return null; 132 } 133 else if (value instanceof String) { 134 return (String) value; 135 } 136 else if (cs != null) { 137 return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR); 138 } 139 else { 140 return value.toString(); 141 } 142 } 143 144 145 private static class PathVariableNamedValueInfo extends NamedValueInfo { 146 147 public PathVariableNamedValueInfo(PathVariable annotation) { 148 super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE); 149 } 150 } 151 152}