diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/CompositeRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/CompositeRequestCondition.java
new file mode 100644
index 00000000000..5166ebee09d
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/CompositeRequestCondition.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.condition;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+import edu.emory.mathcs.backport.java.util.Collections;
+
+/**
+ * Implements the {@link RequestCondition} contract by delegating to multiple
+ * {@code RequestCondition} types and using a logical conjunction (' && ') to
+ * ensure all conditions match a given request.
+ *
+ *
When {@code CompositeRequestCondition} instances are combined or compared
+ * they are expected to (a) contain the same number of conditions and (b) that
+ * conditions in the respective index are of the same type. It is acceptable to
+ * provide {@code null} conditions or no conditions at all to the constructor.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class CompositeRequestCondition extends AbstractRequestCondition {
+
+ private final RequestConditionHolder[] requestConditions;
+
+ /**
+ * Create an instance with 0 or more {@code RequestCondition} types. It is
+ * important to create {@code CompositeRequestCondition} instances with the
+ * same number of conditions so they may be compared and combined.
+ * It is acceptable to provide {@code null} conditions.
+ */
+ public CompositeRequestCondition(RequestCondition>... requestConditions) {
+ this.requestConditions = wrap(requestConditions);
+ }
+
+ private RequestConditionHolder[] wrap(RequestCondition>... rawConditions) {
+ RequestConditionHolder[] wrappedConditions = new RequestConditionHolder[rawConditions.length];
+ for (int i = 0; i < rawConditions.length; i++) {
+ wrappedConditions[i] = new RequestConditionHolder(rawConditions[i]);
+ }
+ return wrappedConditions;
+ }
+
+ private CompositeRequestCondition(RequestConditionHolder[] requestConditions) {
+ this.requestConditions = requestConditions;
+ }
+
+ /**
+ * Whether this instance contains 0 conditions or not.
+ */
+ public boolean isEmpty() {
+ return ObjectUtils.isEmpty(this.requestConditions);
+ }
+
+ /**
+ * Return the underlying conditions, possibly empty but never {@code null}.
+ */
+ public List> getConditions() {
+ return unwrap();
+ }
+
+ private List> unwrap() {
+ List> result = new ArrayList>();
+ for (RequestConditionHolder holder : this.requestConditions) {
+ result.add(holder.getCondition());
+ }
+ return result;
+ }
+
+ @Override
+ protected Collection> getContent() {
+ return (isEmpty()) ? Collections.emptyList() : getConditions();
+ }
+
+ @Override
+ protected String getToStringInfix() {
+ return " && ";
+ }
+
+ private int getLength() {
+ return this.requestConditions.length;
+ }
+
+ /**
+ * If one instance is empty, return the other.
+ * If both instances have conditions, combine the individual conditions
+ * after ensuring they are of the same type and number.
+ */
+ public CompositeRequestCondition combine(CompositeRequestCondition other) {
+ if (isEmpty() && other.isEmpty()) {
+ return this;
+ }
+ else if (other.isEmpty()) {
+ return this;
+ }
+ else if (isEmpty()) {
+ return other;
+ }
+ else {
+ assertNumberOfConditions(other);
+ RequestConditionHolder[] combinedConditions = new RequestConditionHolder[getLength()];
+ for (int i = 0; i < getLength(); i++) {
+ combinedConditions[i] = this.requestConditions[i].combine(other.requestConditions[i]);
+ }
+ return new CompositeRequestCondition(combinedConditions);
+ }
+ }
+
+ private void assertNumberOfConditions(CompositeRequestCondition other) {
+ Assert.isTrue(getLength() == other.getLength(),
+ "Cannot combine CompositeRequestConditions with a different number of conditions. "
+ + this.requestConditions + " and " + other.requestConditions);
+ }
+
+ /**
+ * Delegate to all contained conditions to match the request and return the
+ * resulting "matching" condition instances.
+ *
An empty {@code CompositeRequestCondition} matches to all requests.
+ */
+ public CompositeRequestCondition getMatchingCondition(HttpServletRequest request) {
+ if (isEmpty()) {
+ return this;
+ }
+ RequestConditionHolder[] matchingConditions = new RequestConditionHolder[getLength()];
+ for (int i = 0; i < getLength(); i++) {
+ matchingConditions[i] = this.requestConditions[i].getMatchingCondition(request);
+ if (matchingConditions[i] == null) {
+ return null;
+ }
+ }
+ return new CompositeRequestCondition(matchingConditions);
+ }
+
+ /**
+ * If one instance is empty, the other "wins". If both instances have
+ * conditions, compare them in the order in which they were provided.
+ */
+ public int compareTo(CompositeRequestCondition other, HttpServletRequest request) {
+ if (isEmpty() && other.isEmpty()) {
+ return 0;
+ }
+ else if (isEmpty()) {
+ return 1;
+ }
+ else if (other.isEmpty()) {
+ return -1;
+ }
+ else {
+ assertNumberOfConditions(other);
+ for (int i = 0; i < getLength(); i++) {
+ int result = this.requestConditions[i].compareTo(other.requestConditions[i], request);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolder.java
index b28fef5f175..8779839fbbd 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/RequestConditionHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,40 +23,42 @@ import javax.servlet.http.HttpServletRequest;
/**
- * A holder for a {@link RequestCondition} useful when the type of the held
- * request condition is not known ahead of time - e.g. custom condition.
+ * A holder for a {@link RequestCondition} useful when the type of the request
+ * condition is not known ahead of time, e.g. custom condition. Since this
+ * class is also an implementation of {@code RequestCondition}, effectively it
+ * decorates the held request condition and allows it to be combined and compared
+ * with other request conditions in a type and null safe way.
*
- *
An implementation of {@code RequestCondition} itself, a
- * {@code RequestConditionHolder} decorates the held request condition allowing
- * it to be combined and compared with other custom request conditions while
- * ensuring type and null safety.
+ *
When two {@code RequestConditionHolder} instances are combined or compared
+ * with each other, it is expected the conditions they hold are of the same type.
+ * If they are not, a {@link ClassCastException} is raised.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class RequestConditionHolder extends AbstractRequestCondition {
- @SuppressWarnings("rawtypes")
- private final RequestCondition condition;
+ private final RequestCondition