Skip to content

Commit 34420a8

Browse files
committed
Allow @ConditionalOnProperty to be used as a meta-annotation
Closes gh-5819
1 parent 147956a commit 34420a8

File tree

2 files changed

+138
-6
lines changed

2 files changed

+138
-6
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
1717
package org.springframework.boot.autoconfigure.condition;
1818

1919
import java.util.ArrayList;
20+
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
23+
import java.util.Map.Entry;
2224

2325
import org.springframework.boot.bind.RelaxedPropertyResolver;
2426
import org.springframework.context.annotation.Condition;
@@ -27,6 +29,7 @@
2729
import org.springframework.core.env.PropertyResolver;
2830
import org.springframework.core.type.AnnotatedTypeMetadata;
2931
import org.springframework.util.Assert;
32+
import org.springframework.util.MultiValueMap;
3033
import org.springframework.util.StringUtils;
3134

3235
/**
@@ -35,6 +38,7 @@
3538
* @author Maciej Walkowiak
3639
* @author Phillip Webb
3740
* @author Stephane Nicoll
41+
* @author Andy Wilkinson
3842
* @since 1.1.0
3943
* @see ConditionalOnProperty
4044
*/
@@ -43,10 +47,57 @@ class OnPropertyCondition extends SpringBootCondition {
4347
@Override
4448
public ConditionOutcome getMatchOutcome(ConditionContext context,
4549
AnnotatedTypeMetadata metadata) {
50+
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
51+
metadata.getAllAnnotationAttributes(
52+
ConditionalOnProperty.class.getName()));
53+
List<ConditionOutcome> noMatchOutcomes = findNoMatchOutcomes(
54+
allAnnotationAttributes, context.getEnvironment());
55+
if (noMatchOutcomes.isEmpty()) {
56+
return ConditionOutcome.match();
57+
}
58+
return ConditionOutcome.noMatch(getCompositeMessage(noMatchOutcomes));
59+
}
60+
61+
private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
62+
MultiValueMap<String, Object> multiValueMap) {
63+
List<Map<String, Object>> maps = new ArrayList<Map<String, Object>>();
64+
for (Entry<String, List<Object>> entry : multiValueMap.entrySet()) {
65+
for (int i = 0; i < entry.getValue().size(); i++) {
66+
Map<String, Object> map;
67+
if (i < maps.size()) {
68+
map = maps.get(i);
69+
}
70+
else {
71+
map = new HashMap<String, Object>();
72+
maps.add(map);
73+
}
74+
map.put(entry.getKey(), entry.getValue().get(i));
75+
}
76+
}
77+
List<AnnotationAttributes> annotationAttributes = new ArrayList<AnnotationAttributes>(
78+
maps.size());
79+
for (Map<String, Object> map : maps) {
80+
annotationAttributes.add(AnnotationAttributes.fromMap(map));
81+
}
82+
return annotationAttributes;
83+
}
4684

47-
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
48-
metadata.getAnnotationAttributes(ConditionalOnProperty.class.getName()));
85+
private List<ConditionOutcome> findNoMatchOutcomes(
86+
List<AnnotationAttributes> allAnnotationAttributes,
87+
PropertyResolver resolver) {
88+
List<ConditionOutcome> noMatchOutcomes = new ArrayList<ConditionOutcome>(
89+
allAnnotationAttributes.size());
90+
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
91+
ConditionOutcome outcome = determineOutcome(annotationAttributes, resolver);
92+
if (!outcome.isMatch()) {
93+
noMatchOutcomes.add(outcome);
94+
}
95+
}
96+
return noMatchOutcomes;
97+
}
4998

99+
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,
100+
PropertyResolver resolver) {
50101
String prefix = annotationAttributes.getString("prefix").trim();
51102
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
52103
prefix = prefix + ".";
@@ -56,7 +107,6 @@ public ConditionOutcome getMatchOutcome(ConditionContext context,
56107
boolean relaxedNames = annotationAttributes.getBoolean("relaxedNames");
57108
boolean matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
58109

59-
PropertyResolver resolver = context.getEnvironment();
60110
if (relaxedNames) {
61111
resolver = new RelaxedPropertyResolver(resolver, prefix);
62112
}
@@ -91,7 +141,6 @@ public ConditionOutcome getMatchOutcome(ConditionContext context,
91141
message.append("expected '").append(expected).append("' for properties ")
92142
.append(expandNames(prefix, nonMatchingProperties));
93143
}
94-
95144
return ConditionOutcome.noMatch(message.toString());
96145
}
97146

@@ -122,4 +171,15 @@ private String expandNames(String prefix, List<String> names) {
122171
return expanded.toString();
123172
}
124173

174+
private String getCompositeMessage(List<ConditionOutcome> noMatchOutcomes) {
175+
StringBuilder message = new StringBuilder();
176+
for (ConditionOutcome noMatchOutcome : noMatchOutcomes) {
177+
if (message.length() > 0) {
178+
message.append(". ");
179+
}
180+
message.append(noMatchOutcome.getMessage().trim());
181+
}
182+
return message.toString();
183+
}
184+
125185
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,11 @@
1616

1717
package org.springframework.boot.autoconfigure.condition;
1818

19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
1924
import org.junit.After;
2025
import org.junit.Rule;
2126
import org.junit.Test;
@@ -37,6 +42,7 @@
3742
* @author Maciej Walkowiak
3843
* @author Stephane Nicoll
3944
* @author Phillip Webb
45+
* @author Andy Wilkinson
4046
*/
4147
public class ConditionalOnPropertyTests {
4248

@@ -227,6 +233,44 @@ public void nameAndValueMustNotBeSpecified() throws Exception {
227233
load(NameAndValueAttribute.class, "some.property");
228234
}
229235

236+
@Test
237+
public void metaAnnotationConditionMatchesWhenPropertyIsSet() throws Exception {
238+
load(MetaAnnotation.class, "my.feature.enabled=true");
239+
assertTrue(this.context.containsBean("foo"));
240+
}
241+
242+
@Test
243+
public void metaAnnotationConditionDoesNotMatchWhenPropertyIsNotSet()
244+
throws Exception {
245+
load(MetaAnnotation.class);
246+
assertFalse(this.context.containsBean("foo"));
247+
}
248+
249+
@Test
250+
public void metaAndDirectAnnotationConditionDoesNotMatchWhenOnlyDirectPropertyIsSet() {
251+
load(MetaAnnotationAndDirectAnnotation.class, "my.other.feature.enabled=true");
252+
assertFalse(this.context.containsBean("foo"));
253+
}
254+
255+
@Test
256+
public void metaAndDirectAnnotationConditionDoesNotMatchWhenOnlyMetaPropertyIsSet() {
257+
load(MetaAnnotationAndDirectAnnotation.class, "my.feature.enabled=true");
258+
assertFalse(this.context.containsBean("foo"));
259+
}
260+
261+
@Test
262+
public void metaAndDirectAnnotationConditionDoesNotMatchWhenNeitherPropertyIsSet() {
263+
load(MetaAnnotationAndDirectAnnotation.class);
264+
assertFalse(this.context.containsBean("foo"));
265+
}
266+
267+
@Test
268+
public void metaAndDirectAnnotationConditionMatchesWhenBothPropertiesAreSet() {
269+
load(MetaAnnotationAndDirectAnnotation.class, "my.feature.enabled=true",
270+
"my.other.feature.enabled=true");
271+
assertTrue(this.context.containsBean("foo"));
272+
}
273+
230274
private void load(Class<?> config, String... environment) {
231275
this.context = new AnnotationConfigApplicationContext();
232276
EnvironmentTestUtils.addEnvironment(this.context, environment);
@@ -390,4 +434,32 @@ public String foo() {
390434
}
391435

392436
}
437+
438+
@ConditionalOnMyFeature
439+
protected static class MetaAnnotation {
440+
441+
@Bean
442+
public String foo() {
443+
return "foo";
444+
}
445+
446+
}
447+
448+
@ConditionalOnMyFeature
449+
@ConditionalOnProperty(prefix = "my.other.feature", name = "enabled", havingValue = "true", matchIfMissing = false)
450+
protected static class MetaAnnotationAndDirectAnnotation {
451+
452+
@Bean
453+
public String foo() {
454+
return "foo";
455+
}
456+
457+
}
458+
459+
@Retention(RetentionPolicy.RUNTIME)
460+
@Target({ ElementType.TYPE, ElementType.METHOD })
461+
@ConditionalOnProperty(prefix = "my.feature", name = "enabled", havingValue = "true", matchIfMissing = false)
462+
public @interface ConditionalOnMyFeature {
463+
464+
}
393465
}

0 commit comments

Comments
 (0)