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.Collections; 020import java.util.List; 021import java.util.Map; 022 023import org.springframework.core.MethodParameter; 024import org.springframework.core.ResolvableType; 025import org.springframework.lang.Nullable; 026import org.springframework.util.Assert; 027import org.springframework.util.CollectionUtils; 028import org.springframework.util.LinkedMultiValueMap; 029import org.springframework.util.MultiValueMap; 030import org.springframework.util.StringUtils; 031import org.springframework.web.bind.annotation.MatrixVariable; 032import org.springframework.web.bind.annotation.ValueConstants; 033import org.springframework.web.bind.support.WebDataBinderFactory; 034import org.springframework.web.context.request.NativeWebRequest; 035import org.springframework.web.context.request.RequestAttributes; 036import org.springframework.web.method.support.HandlerMethodArgumentResolver; 037import org.springframework.web.method.support.ModelAndViewContainer; 038import org.springframework.web.servlet.HandlerMapping; 039 040/** 041 * Resolves arguments of type {@link Map} annotated with {@link MatrixVariable @MatrixVariable} 042 * where the annotation does not specify a name. In other words the purpose of this resolver 043 * is to provide access to multiple matrix variables, either all or associated with a specific 044 * path variable. 045 * 046 * <p>When a name is specified, an argument of type Map is considered to be a single attribute 047 * with a Map value, and is resolved by {@link MatrixVariableMethodArgumentResolver} instead. 048 * 049 * @author Rossen Stoyanchev 050 * @since 3.2 051 */ 052public class MatrixVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver { 053 054 @Override 055 public boolean supportsParameter(MethodParameter parameter) { 056 MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class); 057 return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) && 058 !StringUtils.hasText(matrixVariable.name())); 059 } 060 061 @Override 062 @Nullable 063 public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, 064 NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception { 065 066 @SuppressWarnings("unchecked") 067 Map<String, MultiValueMap<String, String>> matrixVariables = 068 (Map<String, MultiValueMap<String, String>>) request.getAttribute( 069 HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); 070 071 if (CollectionUtils.isEmpty(matrixVariables)) { 072 return Collections.emptyMap(); 073 } 074 075 MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); 076 MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class); 077 Assert.state(ann != null, "No MatrixVariable annotation"); 078 String pathVariable = ann.pathVar(); 079 080 if (!pathVariable.equals(ValueConstants.DEFAULT_NONE)) { 081 MultiValueMap<String, String> mapForPathVariable = matrixVariables.get(pathVariable); 082 if (mapForPathVariable == null) { 083 return Collections.emptyMap(); 084 } 085 map.putAll(mapForPathVariable); 086 } 087 else { 088 for (MultiValueMap<String, String> vars : matrixVariables.values()) { 089 vars.forEach((name, values) -> { 090 for (String value : values) { 091 map.add(name, value); 092 } 093 }); 094 } 095 } 096 097 return (isSingleValueMap(parameter) ? map.toSingleValueMap() : map); 098 } 099 100 private boolean isSingleValueMap(MethodParameter parameter) { 101 if (!MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) { 102 ResolvableType[] genericTypes = ResolvableType.forMethodParameter(parameter).getGenerics(); 103 if (genericTypes.length == 2) { 104 return !List.class.isAssignableFrom(genericTypes[1].toClass()); 105 } 106 } 107 return false; 108 } 109 110}