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.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Set; 025import java.util.SortedSet; 026import java.util.TreeSet; 027 028import org.springframework.http.server.PathContainer; 029import org.springframework.lang.Nullable; 030import org.springframework.util.ObjectUtils; 031import org.springframework.web.server.ServerWebExchange; 032import org.springframework.web.util.pattern.PathPattern; 033import org.springframework.web.util.pattern.PathPatternParser; 034 035/** 036 * A logical disjunction (' || ') request condition that matches a request 037 * against a set of URL path patterns. 038 * 039 * @author Rossen Stoyanchev 040 * @author Brian Clozel 041 * @since 5.0 042 */ 043public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> { 044 045 private static final SortedSet<PathPattern> EMPTY_PATH_PATTERN = 046 new TreeSet<>(Collections.singleton(PathPatternParser.defaultInstance.parse(""))); 047 048 049 private final SortedSet<PathPattern> patterns; 050 051 052 /** 053 * Creates a new instance with the given URL patterns. 054 * @param patterns 0 or more URL patterns; if 0 the condition will match to every request. 055 */ 056 public PatternsRequestCondition(PathPattern... patterns) { 057 this(ObjectUtils.isEmpty(patterns) ? Collections.emptyList() : Arrays.asList(patterns)); 058 } 059 060 /** 061 * Creates a new instance with the given URL patterns. 062 */ 063 public PatternsRequestCondition(List<PathPattern> patterns) { 064 this.patterns = (patterns.isEmpty() ? EMPTY_PATH_PATTERN : new TreeSet<>(patterns)); 065 } 066 067 private PatternsRequestCondition(SortedSet<PathPattern> patterns) { 068 this.patterns = patterns; 069 } 070 071 072 public Set<PathPattern> getPatterns() { 073 return this.patterns; 074 } 075 076 @Override 077 protected Collection<PathPattern> getContent() { 078 return this.patterns; 079 } 080 081 @Override 082 protected String getToStringInfix() { 083 return " || "; 084 } 085 086 /** 087 * Returns a new instance with URL patterns from the current instance ("this") and 088 * the "other" instance as follows: 089 * <ul> 090 * <li>If there are patterns in both instances, combine the patterns in "this" with 091 * the patterns in "other" using {@link PathPattern#combine(PathPattern)}. 092 * <li>If only one instance has patterns, use them. 093 * <li>If neither instance has patterns, use an empty String (i.e. ""). 094 * </ul> 095 */ 096 @Override 097 public PatternsRequestCondition combine(PatternsRequestCondition other) { 098 if (isEmptyPathPattern() && other.isEmptyPathPattern()) { 099 return this; 100 } 101 else if (other.isEmptyPathPattern()) { 102 return this; 103 } 104 else if (isEmptyPathPattern()) { 105 return other; 106 } 107 else { 108 SortedSet<PathPattern> combined = new TreeSet<>(); 109 for (PathPattern pattern1 : this.patterns) { 110 for (PathPattern pattern2 : other.patterns) { 111 combined.add(pattern1.combine(pattern2)); 112 } 113 } 114 return new PatternsRequestCondition(combined); 115 } 116 } 117 118 private boolean isEmptyPathPattern() { 119 return this.patterns == EMPTY_PATH_PATTERN; 120 } 121 122 /** 123 * Checks if any of the patterns match the given request and returns an instance 124 * that is guaranteed to contain matching patterns, sorted. 125 * @param exchange the current exchange 126 * @return the same instance if the condition contains no patterns; 127 * or a new condition with sorted matching patterns; 128 * or {@code null} if no patterns match. 129 */ 130 @Override 131 @Nullable 132 public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) { 133 SortedSet<PathPattern> matches = getMatchingPatterns(exchange); 134 return (matches != null ? new PatternsRequestCondition(matches) : null); 135 } 136 137 @Nullable 138 private SortedSet<PathPattern> getMatchingPatterns(ServerWebExchange exchange) { 139 PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication(); 140 TreeSet<PathPattern> result = null; 141 for (PathPattern pattern : this.patterns) { 142 if (pattern.matches(lookupPath)) { 143 result = (result != null ? result : new TreeSet<>()); 144 result.add(pattern); 145 } 146 } 147 return result; 148 } 149 150 /** 151 * Compare the two conditions based on the URL patterns they contain. 152 * Patterns are compared one at a time, from top to bottom. If all compared 153 * patterns match equally, but one instance has more patterns, it is 154 * considered a closer match. 155 * <p>It is assumed that both instances have been obtained via 156 * {@link #getMatchingCondition(ServerWebExchange)} to ensure they 157 * contain only patterns that match the request and are sorted with 158 * the best matches on top. 159 */ 160 @Override 161 public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) { 162 Iterator<PathPattern> iterator = this.patterns.iterator(); 163 Iterator<PathPattern> iteratorOther = other.getPatterns().iterator(); 164 while (iterator.hasNext() && iteratorOther.hasNext()) { 165 int result = PathPattern.SPECIFICITY_COMPARATOR.compare(iterator.next(), iteratorOther.next()); 166 if (result != 0) { 167 return result; 168 } 169 } 170 if (iterator.hasNext()) { 171 return -1; 172 } 173 else if (iteratorOther.hasNext()) { 174 return 1; 175 } 176 else { 177 return 0; 178 } 179 } 180 181}