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