001/*
002 * Copyright 2002-2017 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.Collection;
020import java.util.Collections;
021
022import javax.servlet.http.HttpServletRequest;
023
024import org.springframework.lang.Nullable;
025
026/**
027 * A holder for a {@link RequestCondition} useful when the type of the request
028 * condition is not known ahead of time, e.g. custom condition. Since this
029 * class is also an implementation of {@code RequestCondition}, effectively it
030 * decorates the held request condition and allows it to be combined and compared
031 * with other request conditions in a type and null safe way.
032 *
033 * <p>When two {@code RequestConditionHolder} instances are combined or compared
034 * with each other, it is expected the conditions they hold are of the same type.
035 * If they are not, a {@link ClassCastException} is raised.
036 *
037 * @author Rossen Stoyanchev
038 * @since 3.1
039 */
040public final class RequestConditionHolder extends AbstractRequestCondition<RequestConditionHolder> {
041
042        @Nullable
043        private final RequestCondition<Object> condition;
044
045
046        /**
047         * Create a new holder to wrap the given request condition.
048         * @param requestCondition the condition to hold, may be {@code null}
049         */
050        @SuppressWarnings("unchecked")
051        public RequestConditionHolder(@Nullable RequestCondition<?> requestCondition) {
052                this.condition = (RequestCondition<Object>) requestCondition;
053        }
054
055
056        /**
057         * Return the held request condition, or {@code null} if not holding one.
058         */
059        @Nullable
060        public RequestCondition<?> getCondition() {
061                return this.condition;
062        }
063
064        @Override
065        protected Collection<?> getContent() {
066                return (this.condition != null ? Collections.singleton(this.condition) : Collections.emptyList());
067        }
068
069        @Override
070        protected String getToStringInfix() {
071                return " ";
072        }
073
074        /**
075         * Combine the request conditions held by the two RequestConditionHolder
076         * instances after making sure the conditions are of the same type.
077         * Or if one holder is empty, the other holder is returned.
078         */
079        @Override
080        public RequestConditionHolder combine(RequestConditionHolder other) {
081                if (this.condition == null && other.condition == null) {
082                        return this;
083                }
084                else if (this.condition == null) {
085                        return other;
086                }
087                else if (other.condition == null) {
088                        return this;
089                }
090                else {
091                        assertEqualConditionTypes(this.condition, other.condition);
092                        RequestCondition<?> combined = (RequestCondition<?>) this.condition.combine(other.condition);
093                        return new RequestConditionHolder(combined);
094                }
095        }
096
097        /**
098         * Ensure the held request conditions are of the same type.
099         */
100        private void assertEqualConditionTypes(RequestCondition<?> thisCondition, RequestCondition<?> otherCondition) {
101                Class<?> clazz = thisCondition.getClass();
102                Class<?> otherClazz = otherCondition.getClass();
103                if (!clazz.equals(otherClazz)) {
104                        throw new ClassCastException("Incompatible request conditions: " + clazz + " and " + otherClazz);
105                }
106        }
107
108        /**
109         * Get the matching condition for the held request condition wrap it in a
110         * new RequestConditionHolder instance. Or otherwise if this is an empty
111         * holder, return the same holder instance.
112         */
113        @Override
114        @Nullable
115        public RequestConditionHolder getMatchingCondition(HttpServletRequest request) {
116                if (this.condition == null) {
117                        return this;
118                }
119                RequestCondition<?> match = (RequestCondition<?>) this.condition.getMatchingCondition(request);
120                return (match != null ? new RequestConditionHolder(match) : null);
121        }
122
123        /**
124         * Compare the request conditions held by the two RequestConditionHolder
125         * instances after making sure the conditions are of the same type.
126         * Or if one holder is empty, the other holder is preferred.
127         */
128        @Override
129        public int compareTo(RequestConditionHolder other, HttpServletRequest request) {
130                if (this.condition == null && other.condition == null) {
131                        return 0;
132                }
133                else if (this.condition == null) {
134                        return 1;
135                }
136                else if (other.condition == null) {
137                        return -1;
138                }
139                else {
140                        assertEqualConditionTypes(this.condition, other.condition);
141                        return this.condition.compareTo(other.condition, request);
142                }
143        }
144
145}