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}