View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.servlet.mvc.method;
18  
19  import javax.servlet.http.HttpServletRequest;
20  
21  import org.springframework.util.StringUtils;
22  import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
23  import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
24  import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
25  import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
26  import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
27  import org.springframework.web.servlet.mvc.condition.RequestCondition;
28  import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
29  import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
30  
31  /**
32   * Encapsulates the following request mapping conditions:
33   * <ol>
34   * 	<li>{@link PatternsRequestCondition}
35   * 	<li>{@link RequestMethodsRequestCondition}
36   * 	<li>{@link ParamsRequestCondition}
37   * 	<li>{@link HeadersRequestCondition}
38   * 	<li>{@link ConsumesRequestCondition}
39   * 	<li>{@link ProducesRequestCondition}
40   * 	<li>{@code RequestCondition} (optional, custom request condition)
41   * </ol>
42   *
43   * @author Arjen Poutsma
44   * @author Rossen Stoyanchev
45   * @since 3.1
46   */
47  public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
48  
49  	private final String name;
50  
51  	private final PatternsRequestCondition patternsCondition;
52  
53  	private final RequestMethodsRequestCondition methodsCondition;
54  
55  	private final ParamsRequestCondition paramsCondition;
56  
57  	private final HeadersRequestCondition headersCondition;
58  
59  	private final ConsumesRequestCondition consumesCondition;
60  
61  	private final ProducesRequestCondition producesCondition;
62  
63  	private final RequestConditionHolder customConditionHolder;
64  
65  
66  	public RequestMappingInfo(String name, PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
67  			ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
68  			ProducesRequestCondition produces, RequestCondition<?> custom) {
69  
70  		this.name = (StringUtils.hasText(name) ? name : null);
71  		this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());
72  		this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());
73  		this.paramsCondition = (params != null ? params : new ParamsRequestCondition());
74  		this.headersCondition = (headers != null ? headers : new HeadersRequestCondition());
75  		this.consumesCondition = (consumes != null ? consumes : new ConsumesRequestCondition());
76  		this.producesCondition = (produces != null ? produces : new ProducesRequestCondition());
77  		this.customConditionHolder = new RequestConditionHolder(custom);
78  	}
79  
80  	/**
81  	 * Creates a new instance with the given request conditions.
82  	 */
83  	public RequestMappingInfo(PatternsRequestCondition patterns, RequestMethodsRequestCondition methods,
84  			ParamsRequestCondition params, HeadersRequestCondition headers, ConsumesRequestCondition consumes,
85  			ProducesRequestCondition produces, RequestCondition<?> custom) {
86  
87  		this(null, patterns, methods, params, headers, consumes, produces, custom);
88  	}
89  
90  	/**
91  	 * Re-create a RequestMappingInfo with the given custom request condition.
92  	 */
93  	public RequestMappingInfo(RequestMappingInfo info, RequestCondition<?> customRequestCondition) {
94  		this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
95  				info.consumesCondition, info.producesCondition, customRequestCondition);
96  	}
97  
98  
99  	/**
100 	 * Return the name for this mapping, or {@code null}.
101 	 */
102 	public String getName() {
103 		return this.name;
104 	}
105 
106 	/**
107 	 * Returns the URL patterns of this {@link RequestMappingInfo};
108 	 * or instance with 0 patterns, never {@code null}.
109 	 */
110 	public PatternsRequestCondition getPatternsCondition() {
111 		return this.patternsCondition;
112 	}
113 
114 	/**
115 	 * Returns the HTTP request methods of this {@link RequestMappingInfo};
116 	 * or instance with 0 request methods, never {@code null}.
117 	 */
118 	public RequestMethodsRequestCondition getMethodsCondition() {
119 		return this.methodsCondition;
120 	}
121 
122 	/**
123 	 * Returns the "parameters" condition of this {@link RequestMappingInfo};
124 	 * or instance with 0 parameter expressions, never {@code null}.
125 	 */
126 	public ParamsRequestCondition getParamsCondition() {
127 		return this.paramsCondition;
128 	}
129 
130 	/**
131 	 * Returns the "headers" condition of this {@link RequestMappingInfo};
132 	 * or instance with 0 header expressions, never {@code null}.
133 	 */
134 	public HeadersRequestCondition getHeadersCondition() {
135 		return this.headersCondition;
136 	}
137 
138 	/**
139 	 * Returns the "consumes" condition of this {@link RequestMappingInfo};
140 	 * or instance with 0 consumes expressions, never {@code null}.
141 	 */
142 	public ConsumesRequestCondition getConsumesCondition() {
143 		return this.consumesCondition;
144 	}
145 
146 	/**
147 	 * Returns the "produces" condition of this {@link RequestMappingInfo};
148 	 * or instance with 0 produces expressions, never {@code null}.
149 	 */
150 	public ProducesRequestCondition getProducesCondition() {
151 		return this.producesCondition;
152 	}
153 
154 	/**
155 	 * Returns the "custom" condition of this {@link RequestMappingInfo}; or {@code null}.
156 	 */
157 	public RequestCondition<?> getCustomCondition() {
158 		return this.customConditionHolder.getCondition();
159 	}
160 
161 
162 	/**
163 	 * Combines "this" request mapping info (i.e. the current instance) with another request mapping info instance.
164 	 * <p>Example: combine type- and method-level request mappings.
165 	 * @return a new request mapping info instance; never {@code null}
166 	 */
167 	@Override
168 	public RequestMappingInfo combine(RequestMappingInfo other) {
169 		String name = combineNames(other);
170 		PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
171 		RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
172 		ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
173 		HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
174 		ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
175 		ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
176 		RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
177 
178 		return new RequestMappingInfo(name, patterns,
179 				methods, params, headers, consumes, produces, custom.getCondition());
180 	}
181 
182 	private String combineNames(RequestMappingInfo other) {
183 		if (this.name != null && other.name != null) {
184 			String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR;
185 			return this.name + separator + other.name;
186 		}
187 		else if (this.name != null) {
188 			return this.name;
189 		}
190 		else {
191 			return (other.name != null ? other.name : null);
192 		}
193 	}
194 
195 	/**
196 	 * Checks if all conditions in this request mapping info match the provided request and returns
197 	 * a potentially new request mapping info with conditions tailored to the current request.
198 	 * <p>For example the returned instance may contain the subset of URL patterns that match to
199 	 * the current request, sorted with best matching patterns on top.
200 	 * @return a new instance in case all conditions match; or {@code null} otherwise
201 	 */
202 	@Override
203 	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
204 		RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
205 		ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
206 		HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
207 		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
208 		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
209 
210 		if (methods == null || params == null || headers == null || consumes == null || produces == null) {
211 			return null;
212 		}
213 
214 		PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
215 		if (patterns == null) {
216 			return null;
217 		}
218 
219 		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
220 		if (custom == null) {
221 			return null;
222 		}
223 
224 		return new RequestMappingInfo(this.name, patterns,
225 				methods, params, headers, consumes, produces, custom.getCondition());
226 	}
227 
228 
229 	/**
230 	 * Compares "this" info (i.e. the current instance) with another info in the context of a request.
231 	 * <p>Note: It is assumed both instances have been obtained via
232 	 * {@link #getMatchingCondition(HttpServletRequest)} to ensure they have conditions with
233 	 * content relevant to current request.
234 	 */
235 	@Override
236 	public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
237 		int result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
238 		if (result != 0) {
239 			return result;
240 		}
241 		result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
242 		if (result != 0) {
243 			return result;
244 		}
245 		result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
246 		if (result != 0) {
247 			return result;
248 		}
249 		result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
250 		if (result != 0) {
251 			return result;
252 		}
253 		result = this.producesCondition.compareTo(other.getProducesCondition(), request);
254 		if (result != 0) {
255 			return result;
256 		}
257 		result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
258 		if (result != 0) {
259 			return result;
260 		}
261 		result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
262 		if (result != 0) {
263 			return result;
264 		}
265 		return 0;
266 	}
267 
268 	@Override
269 	public boolean equals(Object obj) {
270 		if (this == obj) {
271 			return true;
272 		}
273 		if (obj != null && obj instanceof RequestMappingInfo) {
274 			RequestMappingInfo other = (RequestMappingInfo) obj;
275 			return (this.patternsCondition.equals(other.patternsCondition) &&
276 					this.methodsCondition.equals(other.methodsCondition) &&
277 					this.paramsCondition.equals(other.paramsCondition) &&
278 					this.headersCondition.equals(other.headersCondition) &&
279 					this.consumesCondition.equals(other.consumesCondition) &&
280 					this.producesCondition.equals(other.producesCondition) &&
281 					this.customConditionHolder.equals(other.customConditionHolder));
282 		}
283 		return false;
284 	}
285 
286 	@Override
287 	public int hashCode() {
288 		return (this.patternsCondition.hashCode() * 31 +  // primary differentiation
289 				this.methodsCondition.hashCode() + this.paramsCondition.hashCode() +
290 				this.headersCondition.hashCode() + this.consumesCondition.hashCode() +
291 				this.producesCondition.hashCode() + this.customConditionHolder.hashCode());
292 	}
293 
294 	@Override
295 	public String toString() {
296 		StringBuilder builder = new StringBuilder("{");
297 		builder.append(this.patternsCondition);
298 		builder.append(",methods=").append(this.methodsCondition);
299 		builder.append(",params=").append(this.paramsCondition);
300 		builder.append(",headers=").append(this.headersCondition);
301 		builder.append(",consumes=").append(this.consumesCondition);
302 		builder.append(",produces=").append(this.producesCondition);
303 		builder.append(",custom=").append(this.customConditionHolder);
304 		builder.append('}');
305 		return builder.toString();
306 	}
307 
308 }