Skip to content

Commit bdc3599

Browse files
committed
Add CompositeRequestCondition
The new type makes it easier providing multiple custom request mapping conditions via setters on RequestMappingHandlerMapping. Issue: SPR-9350
1 parent fbb2103 commit bdc3599

6 files changed

Lines changed: 385 additions & 51 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright 2002-2012 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.condition;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.List;
22+
23+
import javax.servlet.http.HttpServletRequest;
24+
25+
import org.springframework.util.Assert;
26+
import org.springframework.util.ObjectUtils;
27+
28+
import edu.emory.mathcs.backport.java.util.Collections;
29+
30+
/**
31+
* Implements the {@link RequestCondition} contract by delegating to multiple
32+
* {@code RequestCondition} types and using a logical conjunction (' && ') to
33+
* ensure all conditions match a given request.
34+
*
35+
* <p>When {@code CompositeRequestCondition} instances are combined or compared
36+
* they are expected to (a) contain the same number of conditions and (b) that
37+
* conditions in the respective index are of the same type. It is acceptable to
38+
* provide {@code null} conditions or no conditions at all to the constructor.
39+
*
40+
* @author Rossen Stoyanchev
41+
* @since 3.2
42+
*/
43+
public class CompositeRequestCondition extends AbstractRequestCondition<CompositeRequestCondition> {
44+
45+
private final RequestConditionHolder[] requestConditions;
46+
47+
/**
48+
* Create an instance with 0 or more {@code RequestCondition} types. It is
49+
* important to create {@code CompositeRequestCondition} instances with the
50+
* same number of conditions so they may be compared and combined.
51+
* It is acceptable to provide {@code null} conditions.
52+
*/
53+
public CompositeRequestCondition(RequestCondition<?>... requestConditions) {
54+
this.requestConditions = wrap(requestConditions);
55+
}
56+
57+
private RequestConditionHolder[] wrap(RequestCondition<?>... rawConditions) {
58+
RequestConditionHolder[] wrappedConditions = new RequestConditionHolder[rawConditions.length];
59+
for (int i = 0; i < rawConditions.length; i++) {
60+
wrappedConditions[i] = new RequestConditionHolder(rawConditions[i]);
61+
}
62+
return wrappedConditions;
63+
}
64+
65+
private CompositeRequestCondition(RequestConditionHolder[] requestConditions) {
66+
this.requestConditions = requestConditions;
67+
}
68+
69+
/**
70+
* Whether this instance contains 0 conditions or not.
71+
*/
72+
public boolean isEmpty() {
73+
return ObjectUtils.isEmpty(this.requestConditions);
74+
}
75+
76+
/**
77+
* Return the underlying conditions, possibly empty but never {@code null}.
78+
*/
79+
public List<RequestCondition<?>> getConditions() {
80+
return unwrap();
81+
}
82+
83+
private List<RequestCondition<?>> unwrap() {
84+
List<RequestCondition<?>> result = new ArrayList<RequestCondition<?>>();
85+
for (RequestConditionHolder holder : this.requestConditions) {
86+
result.add(holder.getCondition());
87+
}
88+
return result;
89+
}
90+
91+
@Override
92+
protected Collection<?> getContent() {
93+
return (isEmpty()) ? Collections.emptyList() : getConditions();
94+
}
95+
96+
@Override
97+
protected String getToStringInfix() {
98+
return " && ";
99+
}
100+
101+
private int getLength() {
102+
return this.requestConditions.length;
103+
}
104+
105+
/**
106+
* If one instance is empty, return the other.
107+
* If both instances have conditions, combine the individual conditions
108+
* after ensuring they are of the same type and number.
109+
*/
110+
public CompositeRequestCondition combine(CompositeRequestCondition other) {
111+
if (isEmpty() && other.isEmpty()) {
112+
return this;
113+
}
114+
else if (other.isEmpty()) {
115+
return this;
116+
}
117+
else if (isEmpty()) {
118+
return other;
119+
}
120+
else {
121+
assertNumberOfConditions(other);
122+
RequestConditionHolder[] combinedConditions = new RequestConditionHolder[getLength()];
123+
for (int i = 0; i < getLength(); i++) {
124+
combinedConditions[i] = this.requestConditions[i].combine(other.requestConditions[i]);
125+
}
126+
return new CompositeRequestCondition(combinedConditions);
127+
}
128+
}
129+
130+
private void assertNumberOfConditions(CompositeRequestCondition other) {
131+
Assert.isTrue(getLength() == other.getLength(),
132+
"Cannot combine CompositeRequestConditions with a different number of conditions. "
133+
+ this.requestConditions + " and " + other.requestConditions);
134+
}
135+
136+
/**
137+
* Delegate to <em>all</em> contained conditions to match the request and return the
138+
* resulting "matching" condition instances.
139+
* <p>An empty {@code CompositeRequestCondition} matches to all requests.
140+
*/
141+
public CompositeRequestCondition getMatchingCondition(HttpServletRequest request) {
142+
if (isEmpty()) {
143+
return this;
144+
}
145+
RequestConditionHolder[] matchingConditions = new RequestConditionHolder[getLength()];
146+
for (int i = 0; i < getLength(); i++) {
147+
matchingConditions[i] = this.requestConditions[i].getMatchingCondition(request);
148+
if (matchingConditions[i] == null) {
149+
return null;
150+
}
151+
}
152+
return new CompositeRequestCondition(matchingConditions);
153+
}
154+
155+
/**
156+
* If one instance is empty, the other "wins". If both instances have
157+
* conditions, compare them in the order in which they were provided.
158+
*/
159+
public int compareTo(CompositeRequestCondition other, HttpServletRequest request) {
160+
if (isEmpty() && other.isEmpty()) {
161+
return 0;
162+
}
163+
else if (isEmpty()) {
164+
return 1;
165+
}
166+
else if (other.isEmpty()) {
167+
return -1;
168+
}
169+
else {
170+
assertNumberOfConditions(other);
171+
for (int i = 0; i < getLength(); i++) {
172+
int result = this.requestConditions[i].compareTo(other.requestConditions[i], request);
173+
if (result != 0) {
174+
return result;
175+
}
176+
}
177+
return 0;
178+
}
179+
}
180+
181+
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolder.java

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,40 +23,42 @@
2323

2424

2525
/**
26-
* A holder for a {@link RequestCondition} useful when the type of the held
27-
* request condition is not known ahead of time - e.g. custom condition.
26+
* A holder for a {@link RequestCondition} useful when the type of the request
27+
* condition is not known ahead of time, e.g. custom condition. Since this
28+
* class is also an implementation of {@code RequestCondition}, effectively it
29+
* decorates the held request condition and allows it to be combined and compared
30+
* with other request conditions in a type and null safe way.
2831
*
29-
* <p>An implementation of {@code RequestCondition} itself, a
30-
* {@code RequestConditionHolder} decorates the held request condition allowing
31-
* it to be combined and compared with other custom request conditions while
32-
* ensuring type and null safety.
32+
* <p>When two {@code RequestConditionHolder} instances are combined or compared
33+
* with each other, it is expected the conditions they hold are of the same type.
34+
* If they are not, a {@link ClassCastException} is raised.
3335
*
3436
* @author Rossen Stoyanchev
3537
* @since 3.1
3638
*/
3739
public final class RequestConditionHolder extends AbstractRequestCondition<RequestConditionHolder> {
3840

39-
@SuppressWarnings("rawtypes")
40-
private final RequestCondition condition;
41+
private final RequestCondition<Object> condition;
4142

4243
/**
4344
* Create a new holder to wrap the given request condition.
4445
* @param requestCondition the condition to hold, may be {@code null}
4546
*/
47+
@SuppressWarnings("unchecked")
4648
public RequestConditionHolder(RequestCondition<?> requestCondition) {
47-
this.condition = requestCondition;
49+
this.condition = (RequestCondition<Object>) requestCondition;
4850
}
4951

5052
/**
5153
* Return the held request condition, or {@code null} if not holding one.
5254
*/
5355
public RequestCondition<?> getCondition() {
54-
return condition;
56+
return this.condition;
5557
}
5658

5759
@Override
5860
protected Collection<?> getContent() {
59-
return condition != null ? Collections.singleton(condition) : Collections.emptyList();
61+
return this.condition != null ? Collections.singleton(this.condition) : Collections.emptyList();
6062
}
6163

6264
@Override
@@ -69,29 +71,28 @@ protected String getToStringInfix() {
6971
* instances after making sure the conditions are of the same type.
7072
* Or if one holder is empty, the other holder is returned.
7173
*/
72-
@SuppressWarnings("unchecked")
7374
public RequestConditionHolder combine(RequestConditionHolder other) {
74-
if (condition == null && other.condition == null) {
75+
if (this.condition == null && other.condition == null) {
7576
return this;
7677
}
77-
else if (condition == null) {
78+
else if (this.condition == null) {
7879
return other;
7980
}
8081
else if (other.condition == null) {
8182
return this;
8283
}
8384
else {
84-
assertIsCompatible(other);
85-
RequestCondition<?> combined = (RequestCondition<?>) condition.combine(other.condition);
85+
assertEqualConditionTypes(other);
86+
RequestCondition<?> combined = (RequestCondition<?>) this.condition.combine(other.condition);
8687
return new RequestConditionHolder(combined);
8788
}
8889
}
8990

9091
/**
9192
* Ensure the held request conditions are of the same type.
9293
*/
93-
private void assertIsCompatible(RequestConditionHolder other) {
94-
Class<?> clazz = condition.getClass();
94+
private void assertEqualConditionTypes(RequestConditionHolder other) {
95+
Class<?> clazz = this.condition.getClass();
9596
Class<?> otherClazz = other.condition.getClass();
9697
if (!clazz.equals(otherClazz)) {
9798
throw new ClassCastException("Incompatible request conditions: " + clazz + " and " + otherClazz);
@@ -104,10 +105,10 @@ private void assertIsCompatible(RequestConditionHolder other) {
104105
* holder, return the same holder instance.
105106
*/
106107
public RequestConditionHolder getMatchingCondition(HttpServletRequest request) {
107-
if (condition == null) {
108+
if (this.condition == null) {
108109
return this;
109110
}
110-
RequestCondition<?> match = (RequestCondition<?>) condition.getMatchingCondition(request);
111+
RequestCondition<?> match = (RequestCondition<?>) this.condition.getMatchingCondition(request);
111112
return (match != null) ? new RequestConditionHolder(match) : null;
112113
}
113114

@@ -116,20 +117,19 @@ public RequestConditionHolder getMatchingCondition(HttpServletRequest request) {
116117
* instances after making sure the conditions are of the same type.
117118
* Or if one holder is empty, the other holder is preferred.
118119
*/
119-
@SuppressWarnings("unchecked")
120120
public int compareTo(RequestConditionHolder other, HttpServletRequest request) {
121-
if (condition == null && other.condition == null) {
121+
if (this.condition == null && other.condition == null) {
122122
return 0;
123123
}
124-
else if (condition == null) {
124+
else if (this.condition == null) {
125125
return 1;
126126
}
127127
else if (other.condition == null) {
128128
return -1;
129129
}
130130
else {
131-
assertIsCompatible(other);
132-
return condition.compareTo(other.condition, request);
131+
assertEqualConditionTypes(other);
132+
return this.condition.compareTo(other.condition, request);
133133
}
134134
}
135135

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.springframework.core.annotation.AnnotationUtils;
2222
import org.springframework.stereotype.Controller;
2323
import org.springframework.web.bind.annotation.RequestMapping;
24+
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
25+
import org.springframework.web.servlet.mvc.condition.CompositeRequestCondition;
2426
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
2527
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
2628
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
@@ -114,26 +116,36 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
114116
}
115117

116118
/**
117-
* Provide a custom method-level request condition.
119+
* Provide a custom type-level request condition.
118120
* The custom {@link RequestCondition} can be of any type so long as the
119121
* same condition type is returned from all calls to this method in order
120122
* to ensure custom request conditions can be combined and compared.
121-
* @param method the handler method for which to create the condition
123+
*
124+
* <p>Consider extending {@link AbstractRequestCondition} for custom
125+
* condition types and using {@link CompositeRequestCondition} to provide
126+
* multiple custom conditions.
127+
*
128+
* @param handlerType the handler type for which to create the condition
122129
* @return the condition, or {@code null}
123130
*/
124-
protected RequestCondition<?> getCustomMethodCondition(Method method) {
131+
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
125132
return null;
126133
}
127134

128135
/**
129-
* Provide a custom type-level request condition.
136+
* Provide a custom method-level request condition.
130137
* The custom {@link RequestCondition} can be of any type so long as the
131138
* same condition type is returned from all calls to this method in order
132139
* to ensure custom request conditions can be combined and compared.
133-
* @param handlerType the handler type for which to create the condition
140+
*
141+
* <p>Consider extending {@link AbstractRequestCondition} for custom
142+
* condition types and using {@link CompositeRequestCondition} to provide
143+
* multiple custom conditions.
144+
*
145+
* @param method the handler method for which to create the condition
134146
* @return the condition, or {@code null}
135147
*/
136-
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
148+
protected RequestCondition<?> getCustomMethodCondition(Method method) {
137149
return null;
138150
}
139151

0 commit comments

Comments
 (0)