Skip to content

Commit 11b7fd8

Browse files
wilkinsonaphilwebb
authored andcommitted
Report non-matching outer class conditions
Update ConditionEvaluationReport so that, whenever a negative outcome is added for a source, any existing outcomes for inner classes of that source are updated with a non-matching outcome that indicates that the outer configuration did not match. Conditions are evaluated in two phases; PARSE_CONFIGURATION first and REGISTER_BEAN second. If a parent class’s conditions match in PARSE_CONFIGURATION then its inner classes will have their PARSE_CONFIGURATION conditions evaluated. If they all match, the inner class will be reported as a positive match in the auto-configuration report even if the outer class does not match as a result of the subsequent evaluation of a REGISTER_BEAN condition. Fixes gh-2122
1 parent 636898f commit 11b7fd8

File tree

2 files changed

+144
-5
lines changed

2 files changed

+144
-5
lines changed

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@
2020
import java.util.Iterator;
2121
import java.util.LinkedHashSet;
2222
import java.util.Map;
23+
import java.util.Map.Entry;
2324
import java.util.Set;
2425
import java.util.SortedMap;
2526
import java.util.TreeMap;
2627

2728
import org.springframework.beans.factory.BeanFactory;
2829
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2930
import org.springframework.context.annotation.Condition;
31+
import org.springframework.context.annotation.ConditionContext;
32+
import org.springframework.core.type.AnnotatedTypeMetadata;
3033
import org.springframework.util.Assert;
3134
import org.springframework.util.ObjectUtils;
3235

@@ -36,13 +39,18 @@
3639
* @author Greg Turnquist
3740
* @author Dave Syer
3841
* @author Phillip Webb
42+
* @author Andy Wilkinson
3943
*/
4044
public class ConditionEvaluationReport {
4145

4246
private static final String BEAN_NAME = "autoConfigurationReport";
4347

48+
private static final AncestorsMatchedCondition ANCESTOR_CONDITION = new AncestorsMatchedCondition();
49+
4450
private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<String, ConditionAndOutcomes>();
4551

52+
private boolean addedAncestorOutcomes;
53+
4654
private ConditionEvaluationReport parent;
4755

4856
/**
@@ -67,16 +75,36 @@ public void recordConditionEvaluation(String source, Condition condition,
6775
this.outcomes.put(source, new ConditionAndOutcomes());
6876
}
6977
this.outcomes.get(source).add(condition, outcome);
78+
this.addedAncestorOutcomes = false;
7079
}
7180

7281
/**
7382
* Returns condition outcomes from this report, grouped by the source.
7483
* @return the condition outcomes
7584
*/
7685
public Map<String, ConditionAndOutcomes> getConditionAndOutcomesBySource() {
86+
if (!this.addedAncestorOutcomes) {
87+
for (Map.Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
88+
if (!entry.getValue().isFullMatch()) {
89+
addNoMatchOutcomeToAncestors(entry.getKey());
90+
}
91+
}
92+
this.addedAncestorOutcomes = true;
93+
}
7794
return Collections.unmodifiableMap(this.outcomes);
7895
}
7996

97+
private void addNoMatchOutcomeToAncestors(String source) {
98+
String prefix = source + "$";
99+
for (Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
100+
if (entry.getKey().startsWith(prefix)) {
101+
ConditionOutcome outcome = new ConditionOutcome(false, "Ancestor '"
102+
+ source + "' did not match");
103+
entry.getValue().add(ANCESTOR_CONDITION, outcome);
104+
}
105+
}
106+
}
107+
80108
/**
81109
* The parent report (from a parent BeanFactory if there is one).
82110
* @return the parent report (or null if there isn't one)
@@ -186,6 +214,20 @@ public boolean equals(Object obj) {
186214
public int hashCode() {
187215
return this.condition.getClass().hashCode() * 31 + this.outcome.hashCode();
188216
}
217+
218+
@Override
219+
public String toString() {
220+
return this.condition.getClass() + " " + this.outcome;
221+
}
222+
}
223+
224+
private static class AncestorsMatchedCondition implements Condition {
225+
226+
@Override
227+
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
228+
throw new UnsupportedOperationException();
229+
}
230+
189231
}
190232

191233
}
Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2015 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.
@@ -26,16 +26,23 @@
2626
import org.junit.Test;
2727
import org.mockito.Mock;
2828
import org.mockito.MockitoAnnotations;
29-
import org.springframework.beans.factory.annotation.Configurable;
3029
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3130
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3231
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
3332
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
3433
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
3534
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
35+
import org.springframework.boot.test.EnvironmentTestUtils;
3636
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
37+
import org.springframework.context.annotation.Bean;
3738
import org.springframework.context.annotation.Condition;
39+
import org.springframework.context.annotation.ConditionContext;
40+
import org.springframework.context.annotation.Conditional;
41+
import org.springframework.context.annotation.Configuration;
42+
import org.springframework.context.annotation.ConfigurationCondition;
3843
import org.springframework.context.annotation.Import;
44+
import org.springframework.core.type.AnnotatedTypeMetadata;
45+
import org.springframework.util.ClassUtils;
3946

4047
import static org.hamcrest.Matchers.containsString;
4148
import static org.hamcrest.Matchers.equalTo;
@@ -51,7 +58,7 @@
5158
* @author Greg Turnquist
5259
* @author Phillip Webb
5360
*/
54-
public class AutoConfigurationReportTests {
61+
public class ConditionEvaluationReportTests {
5562

5663
private DefaultListableBeanFactory beanFactory;
5764

@@ -225,6 +232,23 @@ public void duplicateOutcomes() {
225232
context.close();
226233
}
227234

235+
@Test
236+
public void negativeOuterPositiveInnerBean() throws Exception {
237+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
238+
EnvironmentTestUtils.addEnvironment(context, "test.present=true");
239+
context.register(NegativeOuterConfig.class);
240+
context.refresh();
241+
ConditionEvaluationReport report = ConditionEvaluationReport.get(context
242+
.getBeanFactory());
243+
Map<String, ConditionAndOutcomes> sourceOutcomes = report
244+
.getConditionAndOutcomesBySource();
245+
assertThat(context.containsBean("negativeOuterPositiveInnerBean"), equalTo(false));
246+
String negativeConfig = NegativeOuterConfig.class.getName();
247+
assertThat(sourceOutcomes.get(negativeConfig).isFullMatch(), equalTo(false));
248+
String positiveConfig = NegativeOuterConfig.PositiveInnerConfig.class.getName();
249+
assertThat(sourceOutcomes.get(positiveConfig).isFullMatch(), equalTo(false));
250+
}
251+
228252
private int getNumberOfOutcomes(ConditionAndOutcomes outcomes) {
229253
Iterator<ConditionAndOutcome> iterator = outcomes.iterator();
230254
int numberOfOutcomesAdded = 0;
@@ -235,16 +259,89 @@ private int getNumberOfOutcomes(ConditionAndOutcomes outcomes) {
235259
return numberOfOutcomesAdded;
236260
}
237261

238-
@Configurable
262+
@Configuration
239263
@Import(WebMvcAutoConfiguration.class)
240264
static class Config {
241265

242266
}
243267

244-
@Configurable
268+
@Configuration
245269
@Import(MultipartAutoConfiguration.class)
246270
static class DuplicateConfig {
247271

248272
}
249273

274+
@Configuration
275+
@Conditional({ ConditionEvaluationReportTests.MatchParseCondition.class,
276+
ConditionEvaluationReportTests.NoMatchBeanCondition.class })
277+
public static class NegativeOuterConfig {
278+
279+
@Configuration
280+
@Conditional({ ConditionEvaluationReportTests.MatchParseCondition.class })
281+
public static class PositiveInnerConfig {
282+
283+
@Bean
284+
public String negativeOuterPositiveInnerBean() {
285+
return "negativeOuterPositiveInnerBean";
286+
}
287+
288+
}
289+
}
290+
291+
static class TestMatchCondition extends SpringBootCondition implements
292+
ConfigurationCondition {
293+
294+
private final ConfigurationPhase phase;
295+
private final boolean match;
296+
297+
public TestMatchCondition(ConfigurationPhase phase, boolean match) {
298+
this.phase = phase;
299+
this.match = match;
300+
}
301+
302+
@Override
303+
public ConfigurationPhase getConfigurationPhase() {
304+
return this.phase;
305+
}
306+
307+
@Override
308+
public ConditionOutcome getMatchOutcome(ConditionContext context,
309+
AnnotatedTypeMetadata metadata) {
310+
return new ConditionOutcome(this.match, ClassUtils.getShortName(getClass()));
311+
}
312+
313+
}
314+
315+
static class MatchParseCondition extends TestMatchCondition {
316+
317+
public MatchParseCondition() {
318+
super(ConfigurationPhase.PARSE_CONFIGURATION, true);
319+
}
320+
321+
}
322+
323+
static class MatchBeanCondition extends TestMatchCondition {
324+
325+
public MatchBeanCondition() {
326+
super(ConfigurationPhase.REGISTER_BEAN, true);
327+
}
328+
329+
}
330+
331+
static class NoMatchParseCondition extends TestMatchCondition {
332+
333+
public NoMatchParseCondition() {
334+
super(ConfigurationPhase.PARSE_CONFIGURATION, false);
335+
}
336+
337+
}
338+
339+
static class NoMatchBeanCondition extends TestMatchCondition {
340+
341+
public NoMatchBeanCondition() {
342+
super(ConfigurationPhase.REGISTER_BEAN, false);
343+
}
344+
345+
}
346+
250347
}

0 commit comments

Comments
 (0)