From ea4061fe994f3fcdce5b8c5dbe16614c84f40224 Mon Sep 17 00:00:00 2001 From: Eric Fenderbosch Date: Thu, 22 Jan 2015 14:00:32 -0500 Subject: [PATCH] Add AllNestedConditions and NoneOfNestedConditions Fixes gh-2400 --- .../condition/AbstractNestedCondition.java | 134 ++++++++++++++++++ .../condition/AllNestedConditions.java | 52 +++++++ .../condition/AnyNestedCondition.java | 129 ++--------------- .../condition/NoneOfNestedConditions.java | 50 +++++++ .../condition/AllNestedConditionsTests.java | 99 +++++++++++++ .../NoneOfNestedConditionsTests.java | 99 +++++++++++++ 6 files changed, 442 insertions(+), 121 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditions.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AllNestedConditionsTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditionsTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java new file mode 100644 index 0000000000..0992bb6172 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractNestedCondition.java @@ -0,0 +1,134 @@ +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; + +public abstract class AbstractNestedCondition extends SpringBootCondition implements + ConfigurationCondition { + + private final ConfigurationPhase configurationPhase; + + public AbstractNestedCondition(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(); + return buildConditionOutcome(outcomes); + } + + protected abstract ConditionOutcome buildConditionOutcome( + List outcomes); + + 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/AllNestedConditions.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java new file mode 100644 index 0000000000..69e5077b0e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java @@ -0,0 +1,52 @@ +package org.springframework.boot.autoconfigure.condition; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.annotation.Condition; + +/** + * {@link Condition} that will match when all nested class conditions match. + * Can be used to create composite conditions, for example: + * + *
+ * static class OnJndiOrProperty extends AllNestedConditions {
+ *
+ *    @ConditionalOnJndi()
+ *    static class OnJndi {
+ *    }
+
+ *    @ConditionalOnProperty("something")
+ *    static class OnProperty {
+ *    }
+ *
+ * }
+ * 
+ * + * @author Phillip Webb + * @since 1.2.0 + */ +public abstract class AllNestedConditions extends AbstractNestedCondition { + + public AllNestedConditions(ConfigurationPhase configurationPhase) { + super(configurationPhase); + } + + @Override + protected ConditionOutcome buildConditionOutcome(List outcomes) { + 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() == outcomes.size(), + "all match resulted in " + match + " matches and " + nonMatch + + " non matches"); + } + +} 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 index 963ed222fb..02779b9e9f 100644 --- 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 @@ -16,32 +16,16 @@ 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.Ordered; import org.springframework.core.annotation.Order; -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: + * {@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 {
@@ -61,122 +45,25 @@ import org.springframework.util.StringUtils;
  * @since 1.2.0
  */
 @Order(Ordered.LOWEST_PRECEDENCE - 20)
-public abstract class AnyNestedCondition extends SpringBootCondition implements
-		ConfigurationCondition {
-
-	private final ConfigurationPhase configurationPhase;
+public abstract class AnyNestedCondition extends AbstractNestedCondition {
 
 	public AnyNestedCondition(ConfigurationPhase configurationPhase) {
-		Assert.notNull(configurationPhase, "ConfigurationPhase must not be null");
-		this.configurationPhase = configurationPhase;
-	}
-
-	@Override
-	public ConfigurationPhase getConfigurationPhase() {
-		return this.configurationPhase;
+		super(configurationPhase);
 	}
 
 	@Override
-	public ConditionOutcome getMatchOutcome(ConditionContext context,
-			AnnotatedTypeMetadata metadata) {
-		MemberConditions memberConditions = new MemberConditions(context, getClass()
-				.getName());
-		List outcomes = memberConditions.getMatchOutcomes();
+	protected ConditionOutcome buildConditionOutcome(List outcomes) {
 		List match = new ArrayList();
 		List nonMatch = new ArrayList();
 		for (ConditionOutcome outcome : outcomes) {
 			if (outcome.isMatch()) {
 				match.add(outcome);
-			}
-			else {
+			} 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);
-		}
-
+		return new ConditionOutcome(match.size() > 0, "any match resulted in " + match + " matches and " + nonMatch
+				+ " non matches");
 	}
 
 }
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditions.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditions.java
new file mode 100644
index 0000000000..292386541f
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditions.java
@@ -0,0 +1,50 @@
+package org.springframework.boot.autoconfigure.condition;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.context.annotation.Condition;
+
+/**
+ * {@link Condition} that will match when none of the nested class conditions match.
+ * Can be used to create composite conditions, for example:
+ *
+ * 
+ * static class OnJndiOrProperty extends NoneOfNestedConditions {
+ *
+ *    @ConditionalOnJndi()
+ *    static class OnJndi {
+ *    }
+
+ *    @ConditionalOnProperty("something")
+ *    static class OnProperty {
+ *    }
+ *
+ * }
+ * 
+ * + * @author Phillip Webb + * @since 1.2.0 + */ +public abstract class NoneOfNestedConditions extends AbstractNestedCondition { + + public NoneOfNestedConditions(ConfigurationPhase configurationPhase) { + super(configurationPhase); + } + + @Override + protected ConditionOutcome buildConditionOutcome(List outcomes) { + 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, "none of match resulted in " + match + " matches and " + + nonMatch + " non matches"); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AllNestedConditionsTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AllNestedConditionsTests.java new file mode 100644 index 0000000000..94945c8224 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AllNestedConditionsTests.java @@ -0,0 +1,99 @@ +/* + * 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 static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +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; + +/** + * Tests for {@link AllNestedConditions}. + */ +public class AllNestedConditionsTests { + + @Test + public void neither() throws Exception { + AnnotationConfigApplicationContext context = load(Config.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(false)); + context.close(); + } + + @Test + public void propertyB() throws Exception { + AnnotationConfigApplicationContext context = load(Config.class, "b:b"); + assertThat(context.containsBean("myBean"), equalTo(false)); + 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(OnPropertyAAndBCondition.class) + public static class Config { + + @Bean + public String myBean() { + return "myBean"; + } + + } + + static class OnPropertyAAndBCondition extends AllNestedConditions { + + public OnPropertyAAndBCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty("a") + static class HasPropertyA { + + } + + @ConditionalOnProperty("b") + static class HasPropertyB { + + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditionsTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditionsTests.java new file mode 100644 index 0000000000..ee9f536d9d --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditionsTests.java @@ -0,0 +1,99 @@ +/* + * 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 static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +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; + +/** + * Tests for {@link NoneOfNestedConditions}. + */ +public class NoneOfNestedConditionsTests { + + @Test + public void neither() throws Exception { + AnnotationConfigApplicationContext context = load(Config.class); + assertThat(context.containsBean("myBean"), equalTo(true)); + context.close(); + } + + @Test + public void propertyA() throws Exception { + AnnotationConfigApplicationContext context = load(Config.class, "a:a"); + assertThat(context.containsBean("myBean"), equalTo(false)); + context.close(); + } + + @Test + public void propertyB() throws Exception { + AnnotationConfigApplicationContext context = load(Config.class, "b:b"); + assertThat(context.containsBean("myBean"), equalTo(false)); + context.close(); + } + + @Test + public void both() throws Exception { + AnnotationConfigApplicationContext context = load(Config.class, "a:a", "b:b"); + assertThat(context.containsBean("myBean"), equalTo(false)); + 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(NeitherPropertyANorPropertyBCondition.class) + public static class Config { + + @Bean + public String myBean() { + return "myBean"; + } + + } + + static class NeitherPropertyANorPropertyBCondition extends NoneOfNestedConditions { + + public NeitherPropertyANorPropertyBCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty("a") + static class HasPropertyA { + + } + + @ConditionalOnProperty("b") + static class HasPropertyB { + + } + + } + +}