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.reactive.result.method.annotation;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022
023import org.springframework.beans.factory.config.ConfigurableBeanFactory;
024import org.springframework.core.MethodParameter;
025import org.springframework.core.ReactiveAdapterRegistry;
026import org.springframework.lang.Nullable;
027import org.springframework.util.Assert;
028import org.springframework.util.CollectionUtils;
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.reactive.HandlerMapping;
034import org.springframework.web.server.ServerErrorException;
035import org.springframework.web.server.ServerWebExchange;
036import org.springframework.web.server.ServerWebInputException;
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 * @since 5.0.1
048 * @see MatrixVariableMapMethodArgumentResolver
049 */
050public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver {
051
052        public MatrixVariableMethodArgumentResolver(
053                        @Nullable ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) {
054
055                super(factory, registry);
056        }
057
058
059        @Override
060        public boolean supportsParameter(MethodParameter parameter) {
061                return checkAnnotatedParamNoReactiveWrapper(parameter, MatrixVariable.class,
062                                (ann, type) -> !Map.class.isAssignableFrom(type) || StringUtils.hasText(ann.name()));
063        }
064
065
066        @Override
067        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
068                MatrixVariable ann = parameter.getParameterAnnotation(MatrixVariable.class);
069                Assert.state(ann != null, "No MatrixVariable annotation");
070                return new MatrixVariableNamedValueInfo(ann);
071        }
072
073        @Nullable
074        @Override
075        protected Object resolveNamedValue(String name, MethodParameter param, ServerWebExchange exchange) {
076                Map<String, MultiValueMap<String, String>> pathParameters =
077                                exchange.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
078                if (CollectionUtils.isEmpty(pathParameters)) {
079                        return null;
080                }
081
082                MatrixVariable ann = param.getParameterAnnotation(MatrixVariable.class);
083                Assert.state(ann != null, "No MatrixVariable annotation");
084                String pathVar = ann.pathVar();
085                List<String> paramValues = null;
086
087                if (!pathVar.equals(ValueConstants.DEFAULT_NONE)) {
088                        if (pathParameters.containsKey(pathVar)) {
089                                paramValues = pathParameters.get(pathVar).get(name);
090                        }
091                }
092                else {
093                        boolean found = false;
094                        paramValues = new ArrayList<>();
095                        for (MultiValueMap<String, String> params : pathParameters.values()) {
096                                if (params.containsKey(name)) {
097                                        if (found) {
098                                                String paramType = param.getNestedParameterType().getName();
099                                                throw new ServerErrorException(
100                                                                "Found more than one match for URI path parameter '" + name +
101                                                                "' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate.",
102                                                                param, null);
103                                        }
104                                        paramValues.addAll(params.get(name));
105                                        found = true;
106                                }
107                        }
108                }
109
110                if (CollectionUtils.isEmpty(paramValues)) {
111                        return null;
112                }
113                else if (paramValues.size() == 1) {
114                        return paramValues.get(0);
115                }
116                else {
117                        return paramValues;
118                }
119        }
120
121        @Override
122        protected void handleMissingValue(String name, MethodParameter parameter) throws ServerWebInputException {
123                String paramInfo = parameter.getNestedParameterType().getSimpleName();
124                throw new ServerWebInputException("Missing matrix variable '" + name + "' " +
125                                "for method parameter of type " + paramInfo, parameter);
126        }
127
128
129        private static final class MatrixVariableNamedValueInfo extends NamedValueInfo {
130
131                private MatrixVariableNamedValueInfo(MatrixVariable annotation) {
132                        super(annotation.name(), annotation.required(), annotation.defaultValue());
133                }
134        }
135
136}