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;
021import java.util.LinkedHashSet;
022import java.util.Set;
023import javax.servlet.http.HttpServletRequest;
024
025import org.springframework.util.ObjectUtils;
026import org.springframework.web.bind.annotation.RequestMapping;
027import org.springframework.web.util.WebUtils;
028
029/**
030 * A logical conjunction (' && ') request condition that matches a request against
031 * a set parameter expressions with syntax defined in {@link RequestMapping#params()}.
032 *
033 * @author Arjen Poutsma
034 * @author Rossen Stoyanchev
035 * @since 3.1
036 */
037public final class ParamsRequestCondition extends AbstractRequestCondition<ParamsRequestCondition> {
038
039        private final Set<ParamExpression> expressions;
040
041
042        /**
043         * Create a new instance from the given param expressions.
044         * @param params expressions with syntax defined in {@link RequestMapping#params()};
045         *      if 0, the condition will match to every request.
046         */
047        public ParamsRequestCondition(String... params) {
048                this(parseExpressions(params));
049        }
050
051        private ParamsRequestCondition(Collection<ParamExpression> conditions) {
052                this.expressions = Collections.unmodifiableSet(new LinkedHashSet<ParamExpression>(conditions));
053        }
054
055
056        private static Collection<ParamExpression> parseExpressions(String... params) {
057                Set<ParamExpression> expressions = new LinkedHashSet<ParamExpression>();
058                if (params != null) {
059                        for (String param : params) {
060                                expressions.add(new ParamExpression(param));
061                        }
062                }
063                return expressions;
064        }
065
066
067        /**
068         * Return the contained request parameter expressions.
069         */
070        public Set<NameValueExpression<String>> getExpressions() {
071                return new LinkedHashSet<NameValueExpression<String>>(this.expressions);
072        }
073
074        @Override
075        protected Collection<ParamExpression> getContent() {
076                return this.expressions;
077        }
078
079        @Override
080        protected String getToStringInfix() {
081                return " && ";
082        }
083
084        /**
085         * Returns a new instance with the union of the param expressions
086         * from "this" and the "other" instance.
087         */
088        @Override
089        public ParamsRequestCondition combine(ParamsRequestCondition other) {
090                Set<ParamExpression> set = new LinkedHashSet<ParamExpression>(this.expressions);
091                set.addAll(other.expressions);
092                return new ParamsRequestCondition(set);
093        }
094
095        /**
096         * Returns "this" instance if the request matches all param expressions;
097         * or {@code null} otherwise.
098         */
099        @Override
100        public ParamsRequestCondition getMatchingCondition(HttpServletRequest request) {
101                for (ParamExpression expression : expressions) {
102                        if (!expression.match(request)) {
103                                return null;
104                        }
105                }
106                return this;
107        }
108
109        /**
110         * Returns:
111         * <ul>
112         * <li>0 if the two conditions have the same number of parameter expressions
113         * <li>Less than 0 if "this" instance has more parameter expressions
114         * <li>Greater than 0 if the "other" instance has more parameter expressions
115         * </ul>
116         * <p>It is assumed that both instances have been obtained via
117         * {@link #getMatchingCondition(HttpServletRequest)} and each instance
118         * contains the matching parameter expressions only or is otherwise empty.
119         */
120        @Override
121        public int compareTo(ParamsRequestCondition other, HttpServletRequest request) {
122                return (other.expressions.size() - this.expressions.size());
123        }
124
125
126        /**
127         * Parses and matches a single param expression to a request.
128         */
129        static class ParamExpression extends AbstractNameValueExpression<String> {
130
131                ParamExpression(String expression) {
132                        super(expression);
133                }
134
135                @Override
136                protected boolean isCaseSensitiveName() {
137                        return true;
138                }
139
140                @Override
141                protected String parseValue(String valueExpression) {
142                        return valueExpression;
143                }
144
145                @Override
146                protected boolean matchName(HttpServletRequest request) {
147                        return WebUtils.hasSubmitParameter(request, this.name);
148                }
149
150                @Override
151                protected boolean matchValue(HttpServletRequest request) {
152                        return ObjectUtils.nullSafeEquals(this.value, request.getParameter(this.name));
153                }
154        }
155
156}