Allow @ConditionalOnProperty to be used as a meta-annotation

Closes gh-5819
This commit is contained in:
Andy Wilkinson 2016-05-05 17:33:18 +01:00
parent 147956a7b2
commit 34420a8768
2 changed files with 138 additions and 6 deletions

View File

@ -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<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(
ConditionalOnProperty.class.getName()));
List<ConditionOutcome> noMatchOutcomes = findNoMatchOutcomes(
allAnnotationAttributes, context.getEnvironment());
if (noMatchOutcomes.isEmpty()) {
return ConditionOutcome.match();
}
return ConditionOutcome.noMatch(getCompositeMessage(noMatchOutcomes));
}
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(ConditionalOnProperty.class.getName()));
private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
MultiValueMap<String, Object> multiValueMap) {
List<Map<String, Object>> maps = new ArrayList<Map<String, Object>>();
for (Entry<String, List<Object>> entry : multiValueMap.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
Map<String, Object> map;
if (i < maps.size()) {
map = maps.get(i);
}
else {
map = new HashMap<String, Object>();
maps.add(map);
}
map.put(entry.getKey(), entry.getValue().get(i));
}
}
List<AnnotationAttributes> annotationAttributes = new ArrayList<AnnotationAttributes>(
maps.size());
for (Map<String, Object> map : maps) {
annotationAttributes.add(AnnotationAttributes.fromMap(map));
}
return annotationAttributes;
}
private List<ConditionOutcome> findNoMatchOutcomes(
List<AnnotationAttributes> allAnnotationAttributes,
PropertyResolver resolver) {
List<ConditionOutcome> noMatchOutcomes = new ArrayList<ConditionOutcome>(
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<ConditionOutcome> noMatchOutcomes) {
StringBuilder message = new StringBuilder();
for (ConditionOutcome noMatchOutcome : noMatchOutcomes) {
if (message.length() > 0) {
message.append(". ");
}
message.append(noMatchOutcome.getMessage().trim());
}
return message.toString();
}
}

View File

@ -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.
@ -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;
@ -37,6 +42,7 @@ import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
* @author Maciej Walkowiak
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class ConditionalOnPropertyTests {
@ -227,6 +233,44 @@ public class ConditionalOnPropertyTests {
load(NameAndValueAttribute.class, "some.property");
}
@Test
public void metaAnnotationConditionMatchesWhenPropertyIsSet() throws Exception {
load(MetaAnnotation.class, "my.feature.enabled=true");
assertTrue(this.context.containsBean("foo"));
}
@Test
public void metaAnnotationConditionDoesNotMatchWhenPropertyIsNotSet()
throws Exception {
load(MetaAnnotation.class);
assertFalse(this.context.containsBean("foo"));
}
@Test
public void metaAndDirectAnnotationConditionDoesNotMatchWhenOnlyDirectPropertyIsSet() {
load(MetaAnnotationAndDirectAnnotation.class, "my.other.feature.enabled=true");
assertFalse(this.context.containsBean("foo"));
}
@Test
public void metaAndDirectAnnotationConditionDoesNotMatchWhenOnlyMetaPropertyIsSet() {
load(MetaAnnotationAndDirectAnnotation.class, "my.feature.enabled=true");
assertFalse(this.context.containsBean("foo"));
}
@Test
public void metaAndDirectAnnotationConditionDoesNotMatchWhenNeitherPropertyIsSet() {
load(MetaAnnotationAndDirectAnnotation.class);
assertFalse(this.context.containsBean("foo"));
}
@Test
public void metaAndDirectAnnotationConditionMatchesWhenBothPropertiesAreSet() {
load(MetaAnnotationAndDirectAnnotation.class, "my.feature.enabled=true",
"my.other.feature.enabled=true");
assertTrue(this.context.containsBean("foo"));
}
private void load(Class<?> config, String... environment) {
this.context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
@ -390,4 +434,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 {
}
}