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}