diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java index bc3911dc7a..a8e36c4aec 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 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. @@ -42,6 +42,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; * @author Greg Turnquist * @author Phillip Webb * @author Dave Syer + * @author Andy Wilkinson */ @ConfigurationProperties(prefix = "endpoints.autoconfig", ignoreUnknownFields = false) public class AutoConfigurationReportEndpoint extends AbstractEndpoint { @@ -61,7 +62,7 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { /** * Adapts {@link ConditionEvaluationReport} to a JSON friendly structure. */ - @JsonPropertyOrder({ "positiveMatches", "negativeMatches" }) + @JsonPropertyOrder({ "positiveMatches", "negativeMatches", "exclusions" }) @JsonInclude(Include.NON_EMPTY) public static class Report { @@ -69,11 +70,14 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { private MultiValueMap negativeMatches; + private List exclusions; + private Report parent; public Report(ConditionEvaluationReport report) { this.positiveMatches = new LinkedMultiValueMap(); this.negativeMatches = new LinkedMultiValueMap(); + this.exclusions = report.getExclusions(); for (Map.Entry entry : report .getConditionAndOutcomesBySource().entrySet()) { add(entry.getValue().isFullMatch() ? this.positiveMatches @@ -101,6 +105,10 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { return this.negativeMatches; } + public List getExclusions() { + return this.exclusions; + } + public Report getParent() { return this.parent; } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java index 4b5477fa40..af159457e4 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.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. @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.endpoint; +import java.util.Arrays; + import javax.annotation.PostConstruct; import org.junit.Test; @@ -37,6 +39,7 @@ import static org.mockito.Mockito.mock; * * @author Greg Turnquist * @author Phillip Webb + * @author Andy Wilkinson */ public class AutoConfigurationReportEndpointTests extends AbstractEndpointTests { @@ -51,6 +54,7 @@ public class AutoConfigurationReportEndpointTests extends Report report = getEndpointBean().invoke(); assertTrue(report.getPositiveMatches().isEmpty()); assertTrue(report.getNegativeMatches().containsKey("a")); + assertTrue(report.getExclusions().contains("com.foo.Bar")); } @Configuration @@ -66,6 +70,7 @@ public class AutoConfigurationReportEndpointTests extends .getBeanFactory()); report.recordConditionEvaluation("a", mock(Condition.class), mock(ConditionOutcome.class)); + report.recordExclusions(Arrays.asList("com.foo.Bar")); } @Bean diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java index fc5a0bde10..37075e8e68 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 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. @@ -22,7 +22,12 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.Ordered; @@ -43,7 +48,9 @@ import org.springframework.util.Assert; */ @Order(Ordered.LOWEST_PRECEDENCE) class EnableAutoConfigurationImportSelector implements DeferredImportSelector, - BeanClassLoaderAware, ResourceLoaderAware { + BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware { + + private ConfigurableListableBeanFactory beanFactory; private ClassLoader beanClassLoader; @@ -66,7 +73,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector, this.beanClassLoader))); // Remove those specifically disabled - factories.removeAll(Arrays.asList(attributes.getStringArray("exclude"))); + List excluded = new ArrayList(Arrays.asList(attributes + .getStringArray("exclude"))); + factories.removeAll(excluded); + ConditionEvaluationReport.get(this.beanFactory).recordExclusions(excluded); // Sort factories = new AutoConfigurationSorter(this.resourceLoader) @@ -89,4 +99,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector, this.resourceLoader = resourceLoader; } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + } 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 0fd8d031b8..8cb7e34437 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 @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.condition; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -53,6 +54,8 @@ public class ConditionEvaluationReport { private ConditionEvaluationReport parent; + private List exclusions = Collections.emptyList(); + /** * Private constructor. * @see #get(ConfigurableListableBeanFactory) @@ -78,6 +81,15 @@ public class ConditionEvaluationReport { this.addedAncestorOutcomes = false; } + /** + * Records the name of the classes that have been excluded from condition evaluation + * @param exclusions the names of the excluded classes + */ + public void recordExclusions(List exclusions) { + Assert.notNull(exclusions, "exclusions must not be null"); + this.exclusions = exclusions; + } + /** * Returns condition outcomes from this report, grouped by the source. * @return the condition outcomes @@ -94,6 +106,14 @@ public class ConditionEvaluationReport { return Collections.unmodifiableMap(this.outcomes); } + /** + * Returns the name of the classes that have been excluded from condition evaluation. + * @return the names of the excluded classes + */ + public List getExclusions() { + return Collections.unmodifiableList(this.exclusions); + } + private void addNoMatchOutcomeToAncestors(String source) { String prefix = source + "$"; for (Entry entry : this.outcomes.entrySet()) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java index 64fb9404f5..15e75efbf9 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java @@ -53,6 +53,7 @@ import org.springframework.util.StringUtils; * @author Greg Turnquist * @author Dave Syer * @author Phillip Webb + * @author Andy Wilkinson */ public class AutoConfigurationReportLoggingInitializer implements ApplicationContextInitializer { @@ -110,13 +111,12 @@ public class AutoConfigurationReportLoggingInitializer implements + "debug logging (start with --debug)\n\n"); } if (this.logger.isDebugEnabled()) { - this.logger.debug(getLogMessage(this.report - .getConditionAndOutcomesBySource())); + this.logger.debug(getLogMessage(this.report)); } } } - private StringBuilder getLogMessage(Map outcomes) { + private StringBuilder getLogMessage(ConditionEvaluationReport report) { StringBuilder message = new StringBuilder(); message.append("\n\n\n"); message.append("=========================\n"); @@ -124,7 +124,8 @@ public class AutoConfigurationReportLoggingInitializer implements message.append("=========================\n\n\n"); message.append("Positive matches:\n"); message.append("-----------------\n"); - Map shortOutcomes = orderByName(outcomes); + Map shortOutcomes = orderByName(report + .getConditionAndOutcomesBySource()); for (Map.Entry entry : shortOutcomes.entrySet()) { if (entry.getValue().isFullMatch()) { addLogMessage(message, entry.getKey(), entry.getValue()); @@ -139,6 +140,17 @@ public class AutoConfigurationReportLoggingInitializer implements } } message.append("\n\n"); + message.append("Exclusions:\n"); + message.append("-----------\n"); + if (report.getExclusions().isEmpty()) { + message.append("\n None\n"); + } + else { + for (String exclusion : report.getExclusions()) { + message.append("\n " + exclusion + "\n"); + } + } + message.append("\n\n"); return message; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java new file mode 100644 index 0000000000..d9017a9b5d --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java @@ -0,0 +1,101 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.type.AnnotationMetadata; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Tests for {@link EnableAutoConfigurationImportSelector} + * + * @author Andy Wilkinson + */ +@RunWith(MockitoJUnitRunner.class) +public class EnableAutoConfigurationImportSelectorTests { + + private final EnableAutoConfigurationImportSelector importSelector = new EnableAutoConfigurationImportSelector(); + + private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + + @Mock + private AnnotationMetadata annotationMetadata; + + @Mock + private AnnotationAttributes annotationAttributes; + + @Before + public void configureImportSelector() { + this.importSelector.setBeanFactory(this.beanFactory); + this.importSelector.setResourceLoader(new DefaultResourceLoader()); + } + + @Test + public void importsAreSelected() { + configureExclusions(); + String[] imports = this.importSelector.selectImports(this.annotationMetadata); + assertThat( + imports.length, + is(equalTo(SpringFactoriesLoader.loadFactoryNames( + EnableAutoConfiguration.class, getClass().getClassLoader()) + .size()))); + assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions(), + hasSize(0)); + } + + @Test + public void exclusionsAreApplied() { + configureExclusions(FreeMarkerAutoConfiguration.class.getName()); + String[] imports = this.importSelector.selectImports(this.annotationMetadata); + assertThat(imports.length, + is(equalTo(getAutoConfigurationClassNames().size() - 1))); + assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions(), + contains(FreeMarkerAutoConfiguration.class.getName())); + } + + private void configureExclusions(String... exclusions) { + given( + this.annotationMetadata.getAnnotationAttributes( + EnableAutoConfiguration.class.getName(), true)).willReturn( + this.annotationAttributes); + given(this.annotationAttributes.getStringArray("exclude")).willReturn(exclusions); + } + + private List getAutoConfigurationClassNames() { + return SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, + getClass().getClassLoader()); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java index a8d3f727c4..8221caaede 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 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. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.logging; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; @@ -58,6 +59,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link AutoConfigurationReportLoggingInitializer}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class AutoConfigurationReportLoggingInitializerTests { @@ -161,6 +163,8 @@ public class AutoConfigurationReportLoggingInitializerTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); this.initializer.initialize(context); context.register(Config.class); + ConditionEvaluationReport.get(context.getBeanFactory()).recordExclusions( + Arrays.asList("com.foo.Bar")); context.refresh(); this.initializer.onApplicationEvent(new ContextRefreshedEvent(context)); for (String message : this.debugLog) {