001/* 002 * Copyright 2002-2020 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.condition; 018 019import java.util.Collection; 020import java.util.Collections; 021import java.util.LinkedHashSet; 022import java.util.Set; 023 024import org.springframework.util.ObjectUtils; 025import org.springframework.web.bind.annotation.RequestMapping; 026import org.springframework.web.server.ServerWebExchange; 027 028/** 029 * A logical conjunction (' && ') request condition that matches a request against 030 * a set parameter expressions with syntax defined in {@link RequestMapping#params()}. 031 * 032 * @author Rossen Stoyanchev 033 * @since 5.0 034 */ 035public final class ParamsRequestCondition extends AbstractRequestCondition<ParamsRequestCondition> { 036 037 private final Set<ParamExpression> expressions; 038 039 040 /** 041 * Create a new instance from the given param expressions. 042 * @param params expressions with syntax defined in {@link RequestMapping#params()}; 043 * if 0, the condition will match to every request. 044 */ 045 public ParamsRequestCondition(String... params) { 046 this.expressions = parseExpressions(params); 047 } 048 049 private static Set<ParamExpression> parseExpressions(String... params) { 050 if (ObjectUtils.isEmpty(params)) { 051 return Collections.emptySet(); 052 } 053 Set<ParamExpression> result = new LinkedHashSet<>(params.length); 054 for (String param : params) { 055 result.add(new ParamExpression(param)); 056 } 057 return result; 058 } 059 060 private ParamsRequestCondition(Set<ParamExpression> conditions) { 061 this.expressions = conditions; 062 } 063 064 065 /** 066 * Return the contained request parameter expressions. 067 */ 068 public Set<NameValueExpression<String>> getExpressions() { 069 return new LinkedHashSet<>(this.expressions); 070 } 071 072 @Override 073 protected Collection<ParamExpression> getContent() { 074 return this.expressions; 075 } 076 077 @Override 078 protected String getToStringInfix() { 079 return " && "; 080 } 081 082 /** 083 * Returns a new instance with the union of the param expressions 084 * from "this" and the "other" instance. 085 */ 086 @Override 087 public ParamsRequestCondition combine(ParamsRequestCondition other) { 088 if (isEmpty() && other.isEmpty()) { 089 return this; 090 } 091 else if (other.isEmpty()) { 092 return this; 093 } 094 else if (isEmpty()) { 095 return other; 096 } 097 Set<ParamExpression> set = new LinkedHashSet<>(this.expressions); 098 set.addAll(other.expressions); 099 return new ParamsRequestCondition(set); 100 } 101 102 /** 103 * Returns "this" instance if the request matches all param expressions; 104 * or {@code null} otherwise. 105 */ 106 @Override 107 public ParamsRequestCondition getMatchingCondition(ServerWebExchange exchange) { 108 for (ParamExpression expression : this.expressions) { 109 if (!expression.match(exchange)) { 110 return null; 111 } 112 } 113 return this; 114 } 115 116 /** 117 * Compare to another condition based on parameter expressions. A condition 118 * is considered to be a more specific match, if it has: 119 * <ol> 120 * <li>A greater number of expressions. 121 * <li>A greater number of non-negated expressions with a concrete value. 122 * </ol> 123 * <p>It is assumed that both instances have been obtained via 124 * {@link #getMatchingCondition(ServerWebExchange)} and each instance 125 * contains the matching parameter expressions only or is otherwise empty. 126 */ 127 @Override 128 public int compareTo(ParamsRequestCondition other, ServerWebExchange exchange) { 129 int result = other.expressions.size() - this.expressions.size(); 130 if (result != 0) { 131 return result; 132 } 133 return (int) (getValueMatchCount(other.expressions) - getValueMatchCount(this.expressions)); 134 } 135 136 private long getValueMatchCount(Set<ParamExpression> expressions) { 137 long count = 0; 138 for (ParamExpression e : expressions) { 139 if (e.getValue() != null && !e.isNegated()) { 140 count++; 141 } 142 } 143 return count; 144 } 145 146 147 /** 148 * Parses and matches a single param expression to a request. 149 */ 150 static class ParamExpression extends AbstractNameValueExpression<String> { 151 152 ParamExpression(String expression) { 153 super(expression); 154 } 155 156 @Override 157 protected boolean isCaseSensitiveName() { 158 return true; 159 } 160 161 @Override 162 protected String parseValue(String valueExpression) { 163 return valueExpression; 164 } 165 166 @Override 167 protected boolean matchName(ServerWebExchange exchange) { 168 return exchange.getRequest().getQueryParams().containsKey(this.name); 169 } 170 171 @Override 172 protected boolean matchValue(ServerWebExchange exchange) { 173 return (this.value != null && 174 this.value.equals(exchange.getRequest().getQueryParams().getFirst(this.name))); 175 } 176 } 177 178}