Skip to content

Commit 0326abf

Browse files
committed
Support for composed "any" condition
Add `AnyNestedCondition` which can be used to create a logical 'or' of other conditions contained on nested classes. For example: static class OnJndiOrProperty extends AnyNestedCondition { @ConditionalOnJndi() static class OnJndi { } @ConditionalOnProperty("something") static class OnProperty { } } Fixes gh-1490
1 parent 6e0b5e4 commit 0326abf

File tree

3 files changed

+284
-0
lines changed

3 files changed

+284
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.condition;
18+
19+
import java.io.IOException;
20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Map;
24+
25+
import org.springframework.beans.BeanUtils;
26+
import org.springframework.context.annotation.Condition;
27+
import org.springframework.context.annotation.ConditionContext;
28+
import org.springframework.context.annotation.Conditional;
29+
import org.springframework.context.annotation.ConfigurationCondition;
30+
import org.springframework.core.type.AnnotatedTypeMetadata;
31+
import org.springframework.core.type.AnnotationMetadata;
32+
import org.springframework.core.type.classreading.MetadataReaderFactory;
33+
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
34+
import org.springframework.util.Assert;
35+
import org.springframework.util.ClassUtils;
36+
import org.springframework.util.LinkedMultiValueMap;
37+
import org.springframework.util.MultiValueMap;
38+
import org.springframework.util.StringUtils;
39+
40+
/**
41+
* {@link Condition} that will match when any nested class condition matches. Can be used
42+
* to create composite conditions, for example:
43+
*
44+
* <pre class="code">
45+
* static class OnJndiOrProperty extends AnyNestedCondition {
46+
*
47+
* &#064ConditionalOnJndi()
48+
* static class OnJndi {
49+
* }
50+
51+
* &#064ConditionalOnProperty("something")
52+
* static class OnProperty {
53+
* }
54+
*
55+
* }
56+
* </pre>
57+
*
58+
* @author Phillip Webb
59+
* @since 1.2.0
60+
*/
61+
public abstract class AnyNestedCondition extends SpringBootCondition implements
62+
ConfigurationCondition {
63+
64+
private final ConfigurationPhase configurationPhase;
65+
66+
public AnyNestedCondition(ConfigurationPhase configurationPhase) {
67+
Assert.notNull(configurationPhase, "ConfigurationPhase must not be null");
68+
this.configurationPhase = configurationPhase;
69+
}
70+
71+
@Override
72+
public ConfigurationPhase getConfigurationPhase() {
73+
return this.configurationPhase;
74+
}
75+
76+
@Override
77+
public ConditionOutcome getMatchOutcome(ConditionContext context,
78+
AnnotatedTypeMetadata metadata) {
79+
MemberConditions memberConditions = new MemberConditions(context, getClass()
80+
.getName());
81+
List<ConditionOutcome> outcomes = memberConditions.getMatchOutcomes();
82+
List<ConditionOutcome> match = new ArrayList<ConditionOutcome>();
83+
List<ConditionOutcome> nonMatch = new ArrayList<ConditionOutcome>();
84+
for (ConditionOutcome outcome : outcomes) {
85+
if (outcome.isMatch()) {
86+
match.add(outcome);
87+
}
88+
else {
89+
nonMatch.add(outcome);
90+
}
91+
}
92+
return new ConditionOutcome(match.size() > 0, "any match resulted in " + match
93+
+ " matches and " + nonMatch + " non matches");
94+
}
95+
96+
private static class MemberConditions {
97+
98+
private final ConditionContext context;
99+
100+
private final MetadataReaderFactory readerFactory;
101+
102+
private final Map<AnnotationMetadata, List<Condition>> memberConditions;
103+
104+
public MemberConditions(ConditionContext context, String className) {
105+
this.context = context;
106+
this.readerFactory = new SimpleMetadataReaderFactory(
107+
context.getResourceLoader());
108+
String[] members = getMetadata(className).getMemberClassNames();
109+
this.memberConditions = getMemberConditions(members);
110+
}
111+
112+
private Map<AnnotationMetadata, List<Condition>> getMemberConditions(
113+
String[] members) {
114+
MultiValueMap<AnnotationMetadata, Condition> memberConditions = new LinkedMultiValueMap<AnnotationMetadata, Condition>();
115+
for (String member : members) {
116+
AnnotationMetadata metadata = getMetadata(member);
117+
for (String[] conditionClasses : getConditionClasses(metadata)) {
118+
for (String conditionClass : conditionClasses) {
119+
Condition condition = getCondition(conditionClass);
120+
memberConditions.add(metadata, condition);
121+
}
122+
}
123+
}
124+
return Collections.unmodifiableMap(memberConditions);
125+
}
126+
127+
private AnnotationMetadata getMetadata(String className) {
128+
try {
129+
return this.readerFactory.getMetadataReader(className)
130+
.getAnnotationMetadata();
131+
}
132+
catch (IOException ex) {
133+
throw new IllegalStateException(ex);
134+
}
135+
}
136+
137+
@SuppressWarnings("unchecked")
138+
private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
139+
MultiValueMap<String, Object> attributes = metadata
140+
.getAllAnnotationAttributes(Conditional.class.getName(), true);
141+
Object values = (attributes != null ? attributes.get("value") : null);
142+
return (List<String[]>) (values != null ? values : Collections.emptyList());
143+
}
144+
145+
private Condition getCondition(String conditionClassName) {
146+
Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName,
147+
this.context.getClassLoader());
148+
return (Condition) BeanUtils.instantiateClass(conditionClass);
149+
}
150+
151+
public List<ConditionOutcome> getMatchOutcomes() {
152+
List<ConditionOutcome> outcomes = new ArrayList<ConditionOutcome>();
153+
for (Map.Entry<AnnotationMetadata, List<Condition>> entry : this.memberConditions
154+
.entrySet()) {
155+
AnnotationMetadata metadata = entry.getKey();
156+
for (Condition condition : entry.getValue()) {
157+
outcomes.add(getConditionOutcome(metadata, condition));
158+
}
159+
}
160+
return Collections.unmodifiableList(outcomes);
161+
}
162+
163+
private ConditionOutcome getConditionOutcome(AnnotationMetadata metadata,
164+
Condition condition) {
165+
String messagePrefix = "member condition on " + metadata.getClassName();
166+
if (condition instanceof SpringBootCondition) {
167+
ConditionOutcome outcome = ((SpringBootCondition) condition)
168+
.getMatchOutcome(this.context, metadata);
169+
String message = outcome.getMessage();
170+
return new ConditionOutcome(outcome.isMatch(), messagePrefix
171+
+ (StringUtils.hasLength(message) ? " : " + message : ""));
172+
}
173+
boolean matches = condition.matches(this.context, metadata);
174+
return new ConditionOutcome(matches, messagePrefix);
175+
}
176+
177+
}
178+
179+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,8 @@ public boolean equals(Object obj) {
9393
return super.equals(obj);
9494
}
9595

96+
@Override
97+
public String toString() {
98+
return (this.message == null ? "" : this.message);
99+
}
96100
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.condition;
18+
19+
import org.junit.Test;
20+
import org.springframework.boot.test.EnvironmentTestUtils;
21+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Conditional;
24+
import org.springframework.context.annotation.Configuration;
25+
26+
import static org.hamcrest.Matchers.equalTo;
27+
import static org.junit.Assert.assertThat;
28+
29+
/**
30+
* Tests for {@link AnyNestedCondition}.
31+
*
32+
* @author Phillip Webb
33+
*/
34+
public class AnyNestedConditionTests {
35+
36+
@Test
37+
public void neither() throws Exception {
38+
AnnotationConfigApplicationContext context = load(OnPropertyAorBCondition.class);
39+
assertThat(context.containsBean("myBean"), equalTo(false));
40+
context.close();
41+
}
42+
43+
@Test
44+
public void propertyA() throws Exception {
45+
AnnotationConfigApplicationContext context = load(Config.class, "a:a");
46+
assertThat(context.containsBean("myBean"), equalTo(true));
47+
context.close();
48+
}
49+
50+
@Test
51+
public void propertyB() throws Exception {
52+
AnnotationConfigApplicationContext context = load(Config.class, "b:b");
53+
assertThat(context.containsBean("myBean"), equalTo(true));
54+
context.close();
55+
}
56+
57+
@Test
58+
public void both() throws Exception {
59+
AnnotationConfigApplicationContext context = load(Config.class, "a:a", "b:b");
60+
assertThat(context.containsBean("myBean"), equalTo(true));
61+
context.close();
62+
}
63+
64+
private AnnotationConfigApplicationContext load(Class<?> config, String... env) {
65+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
66+
EnvironmentTestUtils.addEnvironment(context, env);
67+
context.register(config);
68+
context.refresh();
69+
return context;
70+
}
71+
72+
@Configuration
73+
@Conditional(OnPropertyAorBCondition.class)
74+
public static class Config {
75+
76+
@Bean
77+
public String myBean() {
78+
return "myBean";
79+
}
80+
81+
}
82+
83+
static class OnPropertyAorBCondition extends AnyNestedCondition {
84+
85+
public OnPropertyAorBCondition() {
86+
super(ConfigurationPhase.PARSE_CONFIGURATION);
87+
}
88+
89+
@ConditionalOnProperty("a")
90+
static class HasPropertyA {
91+
92+
}
93+
94+
@ConditionalOnProperty("b")
95+
static class HasPropertyB {
96+
97+
}
98+
99+
}
100+
101+
}

0 commit comments

Comments
 (0)