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
This commit is contained in:
parent
636898f9ad
commit
11b7fd832d
|
@ -20,6 +20,7 @@ import java.util.Collections;
|
|||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
@ -27,6 +28,8 @@ import java.util.TreeMap;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
|
@ -36,13 +39,18 @@ import org.springframework.util.ObjectUtils;
|
|||
* @author Greg Turnquist
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ConditionEvaluationReport {
|
||||
|
||||
private static final String BEAN_NAME = "autoConfigurationReport";
|
||||
|
||||
private static final AncestorsMatchedCondition ANCESTOR_CONDITION = new AncestorsMatchedCondition();
|
||||
|
||||
private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<String, ConditionAndOutcomes>();
|
||||
|
||||
private boolean addedAncestorOutcomes;
|
||||
|
||||
private ConditionEvaluationReport parent;
|
||||
|
||||
/**
|
||||
|
@ -67,6 +75,7 @@ public class ConditionEvaluationReport {
|
|||
this.outcomes.put(source, new ConditionAndOutcomes());
|
||||
}
|
||||
this.outcomes.get(source).add(condition, outcome);
|
||||
this.addedAncestorOutcomes = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,9 +83,28 @@ public class ConditionEvaluationReport {
|
|||
* @return the condition outcomes
|
||||
*/
|
||||
public Map<String, ConditionAndOutcomes> getConditionAndOutcomesBySource() {
|
||||
if (!this.addedAncestorOutcomes) {
|
||||
for (Map.Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
|
||||
if (!entry.getValue().isFullMatch()) {
|
||||
addNoMatchOutcomeToAncestors(entry.getKey());
|
||||
}
|
||||
}
|
||||
this.addedAncestorOutcomes = true;
|
||||
}
|
||||
return Collections.unmodifiableMap(this.outcomes);
|
||||
}
|
||||
|
||||
private void addNoMatchOutcomeToAncestors(String source) {
|
||||
String prefix = source + "$";
|
||||
for (Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
|
||||
if (entry.getKey().startsWith(prefix)) {
|
||||
ConditionOutcome outcome = new ConditionOutcome(false, "Ancestor '"
|
||||
+ source + "' did not match");
|
||||
entry.getValue().add(ANCESTOR_CONDITION, outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent report (from a parent BeanFactory if there is one).
|
||||
* @return the parent report (or null if there isn't one)
|
||||
|
@ -186,6 +214,20 @@ public class ConditionEvaluationReport {
|
|||
public int hashCode() {
|
||||
return this.condition.getClass().hashCode() * 31 + this.outcome.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.condition.getClass() + " " + this.outcome;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AncestorsMatchedCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2015 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.
|
||||
|
@ -26,16 +26,23 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
|
||||
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ConfigurationCondition;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
@ -51,7 +58,7 @@ import static org.junit.Assert.assertThat;
|
|||
* @author Greg Turnquist
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class AutoConfigurationReportTests {
|
||||
public class ConditionEvaluationReportTests {
|
||||
|
||||
private DefaultListableBeanFactory beanFactory;
|
||||
|
||||
|
@ -225,6 +232,23 @@ public class AutoConfigurationReportTests {
|
|||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negativeOuterPositiveInnerBean() throws Exception {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(context, "test.present=true");
|
||||
context.register(NegativeOuterConfig.class);
|
||||
context.refresh();
|
||||
ConditionEvaluationReport report = ConditionEvaluationReport.get(context
|
||||
.getBeanFactory());
|
||||
Map<String, ConditionAndOutcomes> sourceOutcomes = report
|
||||
.getConditionAndOutcomesBySource();
|
||||
assertThat(context.containsBean("negativeOuterPositiveInnerBean"), equalTo(false));
|
||||
String negativeConfig = NegativeOuterConfig.class.getName();
|
||||
assertThat(sourceOutcomes.get(negativeConfig).isFullMatch(), equalTo(false));
|
||||
String positiveConfig = NegativeOuterConfig.PositiveInnerConfig.class.getName();
|
||||
assertThat(sourceOutcomes.get(positiveConfig).isFullMatch(), equalTo(false));
|
||||
}
|
||||
|
||||
private int getNumberOfOutcomes(ConditionAndOutcomes outcomes) {
|
||||
Iterator<ConditionAndOutcome> iterator = outcomes.iterator();
|
||||
int numberOfOutcomesAdded = 0;
|
||||
|
@ -235,16 +259,89 @@ public class AutoConfigurationReportTests {
|
|||
return numberOfOutcomesAdded;
|
||||
}
|
||||
|
||||
@Configurable
|
||||
@Configuration
|
||||
@Import(WebMvcAutoConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
@Configurable
|
||||
@Configuration
|
||||
@Import(MultipartAutoConfiguration.class)
|
||||
static class DuplicateConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Conditional({ ConditionEvaluationReportTests.MatchParseCondition.class,
|
||||
ConditionEvaluationReportTests.NoMatchBeanCondition.class })
|
||||
public static class NegativeOuterConfig {
|
||||
|
||||
@Configuration
|
||||
@Conditional({ ConditionEvaluationReportTests.MatchParseCondition.class })
|
||||
public static class PositiveInnerConfig {
|
||||
|
||||
@Bean
|
||||
public String negativeOuterPositiveInnerBean() {
|
||||
return "negativeOuterPositiveInnerBean";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static class TestMatchCondition extends SpringBootCondition implements
|
||||
ConfigurationCondition {
|
||||
|
||||
private final ConfigurationPhase phase;
|
||||
private final boolean match;
|
||||
|
||||
public TestMatchCondition(ConfigurationPhase phase, boolean match) {
|
||||
this.phase = phase;
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationPhase getConfigurationPhase() {
|
||||
return this.phase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
return new ConditionOutcome(this.match, ClassUtils.getShortName(getClass()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MatchParseCondition extends TestMatchCondition {
|
||||
|
||||
public MatchParseCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MatchBeanCondition extends TestMatchCondition {
|
||||
|
||||
public MatchBeanCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NoMatchParseCondition extends TestMatchCondition {
|
||||
|
||||
public NoMatchParseCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NoMatchBeanCondition extends TestMatchCondition {
|
||||
|
||||
public NoMatchBeanCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue