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}