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