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}