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