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.servlet.mvc.condition; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.List; 023 024import javax.servlet.http.HttpServletRequest; 025 026import org.springframework.lang.Nullable; 027import org.springframework.util.Assert; 028import org.springframework.util.ObjectUtils; 029 030/** 031 * Implements the {@link RequestCondition} contract by delegating to multiple 032 * {@code RequestCondition} types and using a logical conjunction (' && ') to 033 * ensure all conditions match a given request. 034 * 035 * <p>When {@code CompositeRequestCondition} instances are combined or compared 036 * they are expected to (a) contain the same number of conditions and (b) that 037 * conditions in the respective index are of the same type. It is acceptable to 038 * provide {@code null} conditions or no conditions at all to the constructor. 039 * 040 * @author Rossen Stoyanchev 041 * @since 3.2 042 */ 043public class CompositeRequestCondition extends AbstractRequestCondition<CompositeRequestCondition> { 044 045 private final RequestConditionHolder[] requestConditions; 046 047 048 /** 049 * Create an instance with 0 or more {@code RequestCondition} types. It is 050 * important to create {@code CompositeRequestCondition} instances with the 051 * same number of conditions so they may be compared and combined. 052 * It is acceptable to provide {@code null} conditions. 053 */ 054 public CompositeRequestCondition(RequestCondition<?>... requestConditions) { 055 this.requestConditions = wrap(requestConditions); 056 } 057 058 private CompositeRequestCondition(RequestConditionHolder[] requestConditions) { 059 this.requestConditions = requestConditions; 060 } 061 062 063 private RequestConditionHolder[] wrap(RequestCondition<?>... rawConditions) { 064 RequestConditionHolder[] wrappedConditions = new RequestConditionHolder[rawConditions.length]; 065 for (int i = 0; i < rawConditions.length; i++) { 066 wrappedConditions[i] = new RequestConditionHolder(rawConditions[i]); 067 } 068 return wrappedConditions; 069 } 070 071 /** 072 * Whether this instance contains 0 conditions or not. 073 */ 074 @Override 075 public boolean isEmpty() { 076 return ObjectUtils.isEmpty(this.requestConditions); 077 } 078 079 /** 080 * Return the underlying conditions (possibly empty but never {@code null}). 081 */ 082 public List<RequestCondition<?>> getConditions() { 083 return unwrap(); 084 } 085 086 private List<RequestCondition<?>> unwrap() { 087 List<RequestCondition<?>> result = new ArrayList<>(); 088 for (RequestConditionHolder holder : this.requestConditions) { 089 result.add(holder.getCondition()); 090 } 091 return result; 092 } 093 094 @Override 095 protected Collection<?> getContent() { 096 return (!isEmpty() ? getConditions() : Collections.emptyList()); 097 } 098 099 @Override 100 protected String getToStringInfix() { 101 return " && "; 102 } 103 104 private int getLength() { 105 return this.requestConditions.length; 106 } 107 108 /** 109 * If one instance is empty, return the other. 110 * If both instances have conditions, combine the individual conditions 111 * after ensuring they are of the same type and number. 112 */ 113 @Override 114 public CompositeRequestCondition combine(CompositeRequestCondition other) { 115 if (isEmpty() && other.isEmpty()) { 116 return this; 117 } 118 else if (other.isEmpty()) { 119 return this; 120 } 121 else if (isEmpty()) { 122 return other; 123 } 124 else { 125 assertNumberOfConditions(other); 126 RequestConditionHolder[] combinedConditions = new RequestConditionHolder[getLength()]; 127 for (int i = 0; i < getLength(); i++) { 128 combinedConditions[i] = this.requestConditions[i].combine(other.requestConditions[i]); 129 } 130 return new CompositeRequestCondition(combinedConditions); 131 } 132 } 133 134 private void assertNumberOfConditions(CompositeRequestCondition other) { 135 Assert.isTrue(getLength() == other.getLength(), 136 "Cannot combine CompositeRequestConditions with a different number of conditions. " + 137 ObjectUtils.nullSafeToString(this.requestConditions) + " and " + 138 ObjectUtils.nullSafeToString(other.requestConditions)); 139 } 140 141 /** 142 * Delegate to <em>all</em> contained conditions to match the request and return the 143 * resulting "matching" condition instances. 144 * <p>An empty {@code CompositeRequestCondition} matches to all requests. 145 */ 146 @Override 147 @Nullable 148 public CompositeRequestCondition getMatchingCondition(HttpServletRequest request) { 149 if (isEmpty()) { 150 return this; 151 } 152 RequestConditionHolder[] matchingConditions = new RequestConditionHolder[getLength()]; 153 for (int i = 0; i < getLength(); i++) { 154 matchingConditions[i] = this.requestConditions[i].getMatchingCondition(request); 155 if (matchingConditions[i] == null) { 156 return null; 157 } 158 } 159 return new CompositeRequestCondition(matchingConditions); 160 } 161 162 /** 163 * If one instance is empty, the other "wins". If both instances have 164 * conditions, compare them in the order in which they were provided. 165 */ 166 @Override 167 public int compareTo(CompositeRequestCondition other, HttpServletRequest request) { 168 if (isEmpty() && other.isEmpty()) { 169 return 0; 170 } 171 else if (isEmpty()) { 172 return 1; 173 } 174 else if (other.isEmpty()) { 175 return -1; 176 } 177 else { 178 assertNumberOfConditions(other); 179 for (int i = 0; i < getLength(); i++) { 180 int result = this.requestConditions[i].compareTo(other.requestConditions[i], request); 181 if (result != 0) { 182 return result; 183 } 184 } 185 return 0; 186 } 187 } 188 189}