From 11b7fd832d15856244bcaf3b1ec031e9eb1b6dc6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 14 Jan 2015 18:04:58 +0000 Subject: [PATCH] Report non-matching outer class conditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../condition/ConditionEvaluationReport.java | 42 +++++++ ...va => ConditionEvaluationReportTests.java} | 107 +++++++++++++++++- 2 files changed, 144 insertions(+), 5 deletions(-) rename spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/{AutoConfigurationReportTests.java => ConditionEvaluationReportTests.java} (74%) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index 14947198312..0fd8d031b8f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -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 outcomes = new TreeMap(); + 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 getConditionAndOutcomesBySource() { + if (!this.addedAncestorOutcomes) { + for (Map.Entry 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 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(); + } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AutoConfigurationReportTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java similarity index 74% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AutoConfigurationReportTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java index 81f132f119c..8a7ec303bd9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/AutoConfigurationReportTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReportTests.java @@ -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 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 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); + } + + } + }