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 000000000000..0992bb617266 --- /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 000000000000..4aad435b27df --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AllNestedConditions.java @@ -0,0 +1,29 @@ +package org.springframework.boot.autoconfigure.condition; + +import java.util.ArrayList; +import java.util.List; + +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 9a636ed180c7..8e5e7244afde 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,44 +16,28 @@ 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 {
- *
+ * 
  *    @ConditionalOnJndi()
- *    static class OnJndi {
+ *    static class OnJndi { 
  *    }
-
+ * 
  *    @ConditionalOnProperty("something")
  *    static class OnProperty {
  *    }
- *
+ * 
  * }
  * 
* @@ -61,122 +45,25 @@ * @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; + super(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(); + 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 000000000000..6d09c4eca834 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NoneOfNestedConditions.java @@ -0,0 +1,27 @@ +package org.springframework.boot.autoconfigure.condition; + +import java.util.ArrayList; +import java.util.List; + +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 000000000000..94945c822463 --- /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 000000000000..ee9f536d9d90 --- /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 { + + } + + } + +}