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.servlet.mvc.method.annotation; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022 023import org.springframework.core.MethodParameter; 024import org.springframework.lang.Nullable; 025import org.springframework.util.Assert; 026import org.springframework.util.CollectionUtils; 027import org.springframework.util.MultiValueMap; 028import org.springframework.util.StringUtils; 029import org.springframework.web.bind.MissingMatrixVariableException; 030import org.springframework.web.bind.ServletRequestBindingException; 031import org.springframework.web.bind.annotation.MatrixVariable; 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.servlet.HandlerMapping; 037 038/** 039 * Resolves arguments annotated with {@link MatrixVariable @MatrixVariable}. 040 * 041 * <p>If the method parameter is of type {@link Map} it will by resolved by 042 * {@link MatrixVariableMapMethodArgumentResolver} instead unless the annotation 043 * specifies a name in which case it is considered to be a single attribute of 044 * type map (vs multiple attributes collected in a map). 045 * 046 * @author Rossen Stoyanchev 047 * @author Sam Brannen 048 * @since 3.2 049 */ 050public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver { 051 052 public MatrixVariableMethodArgumentResolver() { 053 super(null); 054 } 055 056 057 @Override 058 public boolean supportsParameter(MethodParameter parameter) { 059 if (!parameter.hasParameterAnnotation(MatrixVariable.class)) { 060 return false; 061 } 062 if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { 063 MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class); 064 return (matrixVariable != null && StringUtils.hasText(matrixVariable.name())); 065 } 066 return true; 067 } 068 069 @Override 070 protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { 071 MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class); 072 Assert.state(ann != null, "No MatrixVariable annotation"); 073 return new MatrixVariableNamedValueInfo(ann); 074 } 075 076 @Override 077 @SuppressWarnings("unchecked") 078 @Nullable 079 protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { 080 Map<String, MultiValueMap<String, String>> pathParameters = (Map<String, MultiValueMap<String, String>>) 081 request.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); 082 if (CollectionUtils.isEmpty(pathParameters)) { 083 return null; 084 } 085 086 MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class); 087 Assert.state(ann != null, "No MatrixVariable annotation"); 088 String pathVar = ann.pathVar(); 089 List<String> paramValues = null; 090 091 if (!pathVar.equals(ValueConstants.DEFAULT_NONE)) { 092 if (pathParameters.containsKey(pathVar)) { 093 paramValues = pathParameters.get(pathVar).get(name); 094 } 095 } 096 else { 097 boolean found = false; 098 paramValues = new ArrayList<>(); 099 for (MultiValueMap<String, String> params : pathParameters.values()) { 100 if (params.containsKey(name)) { 101 if (found) { 102 String paramType = parameter.getNestedParameterType().getName(); 103 throw new ServletRequestBindingException( 104 "Found more than one match for URI path parameter '" + name + 105 "' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate."); 106 } 107 paramValues.addAll(params.get(name)); 108 found = true; 109 } 110 } 111 } 112 113 if (CollectionUtils.isEmpty(paramValues)) { 114 return null; 115 } 116 else if (paramValues.size() == 1) { 117 return paramValues.get(0); 118 } 119 else { 120 return paramValues; 121 } 122 } 123 124 @Override 125 protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException { 126 throw new MissingMatrixVariableException(name, parameter); 127 } 128 129 130 private static final class MatrixVariableNamedValueInfo extends NamedValueInfo { 131 132 private MatrixVariableNamedValueInfo(MatrixVariable annotation) { 133 super(annotation.name(), annotation.required(), annotation.defaultValue()); 134 } 135 } 136 137}