diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java index d80a6999ba..f075b0f840 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -17,8 +17,10 @@ package org.springframework.boot.autoconfigure.condition; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.annotation.Condition; @@ -27,6 +29,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.PropertyResolver; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** @@ -35,6 +38,7 @@ import org.springframework.util.StringUtils; * @author Maciej Walkowiak * @author Phillip Webb * @author Stephane Nicoll + * @author Andy Wilkinson * @since 1.1.0 * @see ConditionalOnProperty */ @@ -43,10 +47,57 @@ class OnPropertyCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + List allAnnotationAttributes = annotationAttributesFromMultiValueMap( + metadata.getAllAnnotationAttributes( + ConditionalOnProperty.class.getName())); + List noMatchOutcomes = findNoMatchOutcomes( + allAnnotationAttributes, context.getEnvironment()); + if (noMatchOutcomes.isEmpty()) { + return ConditionOutcome.match(); + } + return ConditionOutcome.noMatch(getCompositeMessage(noMatchOutcomes)); + } + + private List annotationAttributesFromMultiValueMap( + MultiValueMap multiValueMap) { + List> maps = new ArrayList>(); + for (Entry> entry : multiValueMap.entrySet()) { + for (int i = 0; i < entry.getValue().size(); i++) { + Map map; + if (i < maps.size()) { + map = maps.get(i); + } + else { + map = new HashMap(); + maps.add(map); + } + map.put(entry.getKey(), entry.getValue().get(i)); + } + } + List annotationAttributes = new ArrayList( + maps.size()); + for (Map map : maps) { + annotationAttributes.add(AnnotationAttributes.fromMap(map)); + } + return annotationAttributes; + } - AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap( - metadata.getAnnotationAttributes(ConditionalOnProperty.class.getName())); + private List findNoMatchOutcomes( + List allAnnotationAttributes, + PropertyResolver resolver) { + List noMatchOutcomes = new ArrayList( + allAnnotationAttributes.size()); + for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { + ConditionOutcome outcome = determineOutcome(annotationAttributes, resolver); + if (!outcome.isMatch()) { + noMatchOutcomes.add(outcome); + } + } + return noMatchOutcomes; + } + private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, + PropertyResolver resolver) { String prefix = annotationAttributes.getString("prefix").trim(); if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) { prefix = prefix + "."; @@ -56,7 +107,6 @@ class OnPropertyCondition extends SpringBootCondition { boolean relaxedNames = annotationAttributes.getBoolean("relaxedNames"); boolean matchIfMissing = annotationAttributes.getBoolean("matchIfMissing"); - PropertyResolver resolver = context.getEnvironment(); if (relaxedNames) { resolver = new RelaxedPropertyResolver(resolver, prefix); } @@ -91,7 +141,6 @@ class OnPropertyCondition extends SpringBootCondition { message.append("expected '").append(expected).append("' for properties ") .append(expandNames(prefix, nonMatchingProperties)); } - return ConditionOutcome.noMatch(message.toString()); } @@ -122,4 +171,15 @@ class OnPropertyCondition extends SpringBootCondition { return expanded.toString(); } + private String getCompositeMessage(List noMatchOutcomes) { + StringBuilder message = new StringBuilder(); + for (ConditionOutcome noMatchOutcome : noMatchOutcomes) { + if (message.length() > 0) { + message.append(". "); + } + message.append(noMatchOutcome.getMessage().trim()); + } + return message.toString(); + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java index 622a176a8c..9008e06a44 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java @@ -16,6 +16,11 @@ package org.springframework.boot.autoconfigure.condition; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -36,6 +41,7 @@ import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; * @author Maciej Walkowiak * @author Stephane Nicoll * @author Phillip Webb + * @author Andy Wilkinson */ public class ConditionalOnPropertyTests { @@ -226,6 +232,44 @@ public class ConditionalOnPropertyTests { load(NameAndValueAttribute.class, "some.property"); } + @Test + public void metaAnnotationConditionMatchesWhenPropertyIsSet() throws Exception { + load(MetaAnnotation.class, "my.feature.enabled=true"); + assertThat(this.context.containsBean("foo")).isTrue(); + } + + @Test + public void metaAnnotationConditionDoesNotMatchWhenPropertyIsNotSet() + throws Exception { + load(MetaAnnotation.class); + assertThat(this.context.containsBean("foo")).isTrue(); + } + + @Test + public void metaAndDirectAnnotationConditionDoesNotMatchWhenOnlyDirectPropertyIsSet() { + load(MetaAnnotationAndDirectAnnotation.class, "my.other.feature.enabled=true"); + assertThat(this.context.containsBean("foo")).isFalse(); + } + + @Test + public void metaAndDirectAnnotationConditionDoesNotMatchWhenOnlyMetaPropertyIsSet() { + load(MetaAnnotationAndDirectAnnotation.class, "my.feature.enabled=true"); + assertThat(this.context.containsBean("foo")).isTrue(); + } + + @Test + public void metaAndDirectAnnotationConditionDoesNotMatchWhenNeitherPropertyIsSet() { + load(MetaAnnotationAndDirectAnnotation.class); + assertThat(this.context.containsBean("foo")).isFalse(); + } + + @Test + public void metaAndDirectAnnotationConditionMatchesWhenBothPropertiesAreSet() { + load(MetaAnnotationAndDirectAnnotation.class, "my.feature.enabled=true", + "my.other.feature.enabled=true"); + assertThat(this.context.containsBean("foo")).isTrue(); + } + private void load(Class config, String... environment) { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, environment); @@ -389,4 +433,32 @@ public class ConditionalOnPropertyTests { } } + + @ConditionalOnMyFeature + protected static class MetaAnnotation { + + @Bean + public String foo() { + return "foo"; + } + + } + + @ConditionalOnMyFeature + @ConditionalOnProperty(prefix = "my.other.feature", name = "enabled", havingValue = "true", matchIfMissing = false) + protected static class MetaAnnotationAndDirectAnnotation { + + @Bean + public String foo() { + return "foo"; + } + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.METHOD }) + @ConditionalOnProperty(prefix = "my.feature", name = "enabled", havingValue = "true", matchIfMissing = false) + public @interface ConditionalOnMyFeature { + + } }