diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java
new file mode 100644
index 0000000000..abbad780b8
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AnyNestedCondition.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2012-2014 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.boot.autoconfigure.condition;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.ConfigurationCondition;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Condition} that will match when any nested class condition matches. Can be used
+ * to create composite conditions, for example:
+ *
+ *
+ * static class OnJndiOrProperty extends AnyNestedCondition {
+ *
+ * @ConditionalOnJndi()
+ * static class OnJndi {
+ * }
+
+ * @ConditionalOnProperty("something")
+ * static class OnProperty {
+ * }
+ *
+ * }
+ *
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public abstract class AnyNestedCondition extends SpringBootCondition implements
+ ConfigurationCondition {
+
+ private final ConfigurationPhase configurationPhase;
+
+ public AnyNestedCondition(ConfigurationPhase configurationPhase) {
+ Assert.notNull(configurationPhase, "ConfigurationPhase must not be null");
+ this.configurationPhase = configurationPhase;
+ }
+
+ @Override
+ public ConfigurationPhase getConfigurationPhase() {
+ return this.configurationPhase;
+ }
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context,
+ AnnotatedTypeMetadata metadata) {
+ MemberConditions memberConditions = new MemberConditions(context, getClass()
+ .getName());
+ List outcomes = memberConditions.getMatchOutcomes();
+ List match = new ArrayList();
+ List nonMatch = new ArrayList();
+ for (ConditionOutcome outcome : outcomes) {
+ if (outcome.isMatch()) {
+ match.add(outcome);
+ }
+ else {
+ nonMatch.add(outcome);
+ }
+ }
+ return new ConditionOutcome(match.size() > 0, "any match resulted in " + match
+ + " matches and " + nonMatch + " non matches");
+ }
+
+ private static class MemberConditions {
+
+ private final ConditionContext context;
+
+ private final MetadataReaderFactory readerFactory;
+
+ private final Map> memberConditions;
+
+ public MemberConditions(ConditionContext context, String className) {
+ this.context = context;
+ this.readerFactory = new SimpleMetadataReaderFactory(
+ context.getResourceLoader());
+ String[] members = getMetadata(className).getMemberClassNames();
+ this.memberConditions = getMemberConditions(members);
+ }
+
+ private Map> getMemberConditions(
+ String[] members) {
+ MultiValueMap memberConditions = new LinkedMultiValueMap();
+ for (String member : members) {
+ AnnotationMetadata metadata = getMetadata(member);
+ for (String[] conditionClasses : getConditionClasses(metadata)) {
+ for (String conditionClass : conditionClasses) {
+ Condition condition = getCondition(conditionClass);
+ memberConditions.add(metadata, condition);
+ }
+ }
+ }
+ return Collections.unmodifiableMap(memberConditions);
+ }
+
+ private AnnotationMetadata getMetadata(String className) {
+ try {
+ return this.readerFactory.getMetadataReader(className)
+ .getAnnotationMetadata();
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List getConditionClasses(AnnotatedTypeMetadata metadata) {
+ MultiValueMap attributes = metadata
+ .getAllAnnotationAttributes(Conditional.class.getName(), true);
+ Object values = (attributes != null ? attributes.get("value") : null);
+ return (List) (values != null ? values : Collections.emptyList());
+ }
+
+ private Condition getCondition(String conditionClassName) {
+ Class> conditionClass = ClassUtils.resolveClassName(conditionClassName,
+ this.context.getClassLoader());
+ return (Condition) BeanUtils.instantiateClass(conditionClass);
+ }
+
+ public List getMatchOutcomes() {
+ List outcomes = new ArrayList();
+ for (Map.Entry> entry : this.memberConditions
+ .entrySet()) {
+ AnnotationMetadata metadata = entry.getKey();
+ for (Condition condition : entry.getValue()) {
+ outcomes.add(getConditionOutcome(metadata, condition));
+ }
+ }
+ return Collections.unmodifiableList(outcomes);
+ }
+
+ private ConditionOutcome getConditionOutcome(AnnotationMetadata metadata,
+ Condition condition) {
+ String messagePrefix = "member condition on " + metadata.getClassName();
+ if (condition instanceof SpringBootCondition) {
+ ConditionOutcome outcome = ((SpringBootCondition) condition)
+ .getMatchOutcome(this.context, metadata);
+ String message = outcome.getMessage();
+ return new ConditionOutcome(outcome.isMatch(), messagePrefix
+ + (StringUtils.hasLength(message) ? " : " + message : ""));
+ }
+ boolean matches = condition.matches(this.context, metadata);
+ return new ConditionOutcome(matches, messagePrefix);
+ }
+
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java
index 015bd58fd1..846895b6dc 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionOutcome.java
@@ -93,4 +93,8 @@ public class ConditionOutcome {
return super.equals(obj);
}
+ @Override
+ public String toString() {
+ return (this.message == null ? "" : this.message);
+ }
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AnyNestedConditionTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AnyNestedConditionTests.java
new file mode 100644
index 0000000000..ad1fbb1d12
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AnyNestedConditionTests.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2014 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.boot.autoconfigure.condition;
+
+import org.junit.Test;
+import org.springframework.boot.test.EnvironmentTestUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for {@link AnyNestedCondition}.
+ *
+ * @author Phillip Webb
+ */
+public class AnyNestedConditionTests {
+
+ @Test
+ public void neither() throws Exception {
+ AnnotationConfigApplicationContext context = load(OnPropertyAorBCondition.class);
+ assertThat(context.containsBean("myBean"), equalTo(false));
+ context.close();
+ }
+
+ @Test
+ public void propertyA() throws Exception {
+ AnnotationConfigApplicationContext context = load(Config.class, "a:a");
+ assertThat(context.containsBean("myBean"), equalTo(true));
+ context.close();
+ }
+
+ @Test
+ public void propertyB() throws Exception {
+ AnnotationConfigApplicationContext context = load(Config.class, "b:b");
+ assertThat(context.containsBean("myBean"), equalTo(true));
+ context.close();
+ }
+
+ @Test
+ public void both() throws Exception {
+ AnnotationConfigApplicationContext context = load(Config.class, "a:a", "b:b");
+ assertThat(context.containsBean("myBean"), equalTo(true));
+ context.close();
+ }
+
+ private AnnotationConfigApplicationContext load(Class> config, String... env) {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ EnvironmentTestUtils.addEnvironment(context, env);
+ context.register(config);
+ context.refresh();
+ return context;
+ }
+
+ @Configuration
+ @Conditional(OnPropertyAorBCondition.class)
+ public static class Config {
+
+ @Bean
+ public String myBean() {
+ return "myBean";
+ }
+
+ }
+
+ static class OnPropertyAorBCondition extends AnyNestedCondition {
+
+ public OnPropertyAorBCondition() {
+ super(ConfigurationPhase.PARSE_CONFIGURATION);
+ }
+
+ @ConditionalOnProperty("a")
+ static class HasPropertyA {
+
+ }
+
+ @ConditionalOnProperty("b")
+ static class HasPropertyB {
+
+ }
+
+ }
+
+}