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}