Commit 88502869 authored by Andy Wilkinson's avatar Andy Wilkinson

List excluded auto-config classes in the auto-config report and endpoint

Prior to this commit, the auto-configuration report (both in its logged
form and the actuator endpoint) listed the positive and negative matches
but did not list the classes, if any, that the user had excluded.

This commit updates the logged report and the actuator endpoint to
expose a list of the excluded class names configured via the exclude
attribute on @EnableAutoConfiguration.

Closes gh-2085
parent 075b5c1b
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -42,6 +42,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; ...@@ -42,6 +42,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* @author Greg Turnquist * @author Greg Turnquist
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson
*/ */
@ConfigurationProperties(prefix = "endpoints.autoconfig", ignoreUnknownFields = false) @ConfigurationProperties(prefix = "endpoints.autoconfig", ignoreUnknownFields = false)
public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> { public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
...@@ -61,7 +62,7 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> { ...@@ -61,7 +62,7 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
/** /**
* Adapts {@link ConditionEvaluationReport} to a JSON friendly structure. * Adapts {@link ConditionEvaluationReport} to a JSON friendly structure.
*/ */
@JsonPropertyOrder({ "positiveMatches", "negativeMatches" }) @JsonPropertyOrder({ "positiveMatches", "negativeMatches", "exclusions" })
@JsonInclude(Include.NON_EMPTY) @JsonInclude(Include.NON_EMPTY)
public static class Report { public static class Report {
...@@ -69,11 +70,14 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> { ...@@ -69,11 +70,14 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
private MultiValueMap<String, MessageAndCondition> negativeMatches; private MultiValueMap<String, MessageAndCondition> negativeMatches;
private List<String> exclusions;
private Report parent; private Report parent;
public Report(ConditionEvaluationReport report) { public Report(ConditionEvaluationReport report) {
this.positiveMatches = new LinkedMultiValueMap<String, MessageAndCondition>(); this.positiveMatches = new LinkedMultiValueMap<String, MessageAndCondition>();
this.negativeMatches = new LinkedMultiValueMap<String, MessageAndCondition>(); this.negativeMatches = new LinkedMultiValueMap<String, MessageAndCondition>();
this.exclusions = report.getExclusions();
for (Map.Entry<String, ConditionAndOutcomes> entry : report for (Map.Entry<String, ConditionAndOutcomes> entry : report
.getConditionAndOutcomesBySource().entrySet()) { .getConditionAndOutcomesBySource().entrySet()) {
add(entry.getValue().isFullMatch() ? this.positiveMatches add(entry.getValue().isFullMatch() ? this.positiveMatches
...@@ -101,6 +105,10 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> { ...@@ -101,6 +105,10 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
return this.negativeMatches; return this.negativeMatches;
} }
public List<String> getExclusions() {
return this.exclusions;
}
public Report getParent() { public Report getParent() {
return this.parent; return this.parent;
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
import java.util.Arrays;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.junit.Test; import org.junit.Test;
...@@ -37,6 +39,7 @@ import static org.mockito.Mockito.mock; ...@@ -37,6 +39,7 @@ import static org.mockito.Mockito.mock;
* *
* @author Greg Turnquist * @author Greg Turnquist
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
*/ */
public class AutoConfigurationReportEndpointTests extends public class AutoConfigurationReportEndpointTests extends
AbstractEndpointTests<AutoConfigurationReportEndpoint> { AbstractEndpointTests<AutoConfigurationReportEndpoint> {
...@@ -51,6 +54,7 @@ public class AutoConfigurationReportEndpointTests extends ...@@ -51,6 +54,7 @@ public class AutoConfigurationReportEndpointTests extends
Report report = getEndpointBean().invoke(); Report report = getEndpointBean().invoke();
assertTrue(report.getPositiveMatches().isEmpty()); assertTrue(report.getPositiveMatches().isEmpty());
assertTrue(report.getNegativeMatches().containsKey("a")); assertTrue(report.getNegativeMatches().containsKey("a"));
assertTrue(report.getExclusions().contains("com.foo.Bar"));
} }
@Configuration @Configuration
...@@ -66,6 +70,7 @@ public class AutoConfigurationReportEndpointTests extends ...@@ -66,6 +70,7 @@ public class AutoConfigurationReportEndpointTests extends
.getBeanFactory()); .getBeanFactory());
report.recordConditionEvaluation("a", mock(Condition.class), report.recordConditionEvaluation("a", mock(Condition.class),
mock(ConditionOutcome.class)); mock(ConditionOutcome.class));
report.recordExclusions(Arrays.asList("com.foo.Bar"));
} }
@Bean @Bean
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,7 +22,12 @@ import java.util.Arrays; ...@@ -22,7 +22,12 @@ import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware; 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.ResourceLoaderAware;
import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
...@@ -43,7 +48,9 @@ import org.springframework.util.Assert; ...@@ -43,7 +48,9 @@ import org.springframework.util.Assert;
*/ */
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
class EnableAutoConfigurationImportSelector implements DeferredImportSelector, class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware, ResourceLoaderAware { BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
private ClassLoader beanClassLoader; private ClassLoader beanClassLoader;
...@@ -66,7 +73,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector, ...@@ -66,7 +73,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
this.beanClassLoader))); this.beanClassLoader)));
// Remove those specifically disabled // Remove those specifically disabled
factories.removeAll(Arrays.asList(attributes.getStringArray("exclude"))); List<String> excluded = new ArrayList<String>(Arrays.asList(attributes
.getStringArray("exclude")));
factories.removeAll(excluded);
ConditionEvaluationReport.get(this.beanFactory).recordExclusions(excluded);
// Sort // Sort
factories = new AutoConfigurationSorter(this.resourceLoader) factories = new AutoConfigurationSorter(this.resourceLoader)
...@@ -89,4 +99,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector, ...@@ -89,4 +99,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
} }
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
} }
...@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.condition; ...@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.condition;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
...@@ -53,6 +54,8 @@ public class ConditionEvaluationReport { ...@@ -53,6 +54,8 @@ public class ConditionEvaluationReport {
private ConditionEvaluationReport parent; private ConditionEvaluationReport parent;
private List<String> exclusions = Collections.emptyList();
/** /**
* Private constructor. * Private constructor.
* @see #get(ConfigurableListableBeanFactory) * @see #get(ConfigurableListableBeanFactory)
...@@ -78,6 +81,15 @@ public class ConditionEvaluationReport { ...@@ -78,6 +81,15 @@ public class ConditionEvaluationReport {
this.addedAncestorOutcomes = false; 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<String> exclusions) {
Assert.notNull(exclusions, "exclusions must not be null");
this.exclusions = exclusions;
}
/** /**
* Returns condition outcomes from this report, grouped by the source. * Returns condition outcomes from this report, grouped by the source.
* @return the condition outcomes * @return the condition outcomes
...@@ -94,6 +106,14 @@ public class ConditionEvaluationReport { ...@@ -94,6 +106,14 @@ public class ConditionEvaluationReport {
return Collections.unmodifiableMap(this.outcomes); 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<String> getExclusions() {
return Collections.unmodifiableList(this.exclusions);
}
private void addNoMatchOutcomeToAncestors(String source) { private void addNoMatchOutcomeToAncestors(String source) {
String prefix = source + "$"; String prefix = source + "$";
for (Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) { for (Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
......
...@@ -53,6 +53,7 @@ import org.springframework.util.StringUtils; ...@@ -53,6 +53,7 @@ import org.springframework.util.StringUtils;
* @author Greg Turnquist * @author Greg Turnquist
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
*/ */
public class AutoConfigurationReportLoggingInitializer implements public class AutoConfigurationReportLoggingInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> { ApplicationContextInitializer<ConfigurableApplicationContext> {
...@@ -110,13 +111,12 @@ public class AutoConfigurationReportLoggingInitializer implements ...@@ -110,13 +111,12 @@ public class AutoConfigurationReportLoggingInitializer implements
+ "debug logging (start with --debug)\n\n"); + "debug logging (start with --debug)\n\n");
} }
if (this.logger.isDebugEnabled()) { if (this.logger.isDebugEnabled()) {
this.logger.debug(getLogMessage(this.report this.logger.debug(getLogMessage(this.report));
.getConditionAndOutcomesBySource()));
} }
} }
} }
private StringBuilder getLogMessage(Map<String, ConditionAndOutcomes> outcomes) { private StringBuilder getLogMessage(ConditionEvaluationReport report) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.append("\n\n\n"); message.append("\n\n\n");
message.append("=========================\n"); message.append("=========================\n");
...@@ -124,7 +124,8 @@ public class AutoConfigurationReportLoggingInitializer implements ...@@ -124,7 +124,8 @@ public class AutoConfigurationReportLoggingInitializer implements
message.append("=========================\n\n\n"); message.append("=========================\n\n\n");
message.append("Positive matches:\n"); message.append("Positive matches:\n");
message.append("-----------------\n"); message.append("-----------------\n");
Map<String, ConditionAndOutcomes> shortOutcomes = orderByName(outcomes); Map<String, ConditionAndOutcomes> shortOutcomes = orderByName(report
.getConditionAndOutcomesBySource());
for (Map.Entry<String, ConditionAndOutcomes> entry : shortOutcomes.entrySet()) { for (Map.Entry<String, ConditionAndOutcomes> entry : shortOutcomes.entrySet()) {
if (entry.getValue().isFullMatch()) { if (entry.getValue().isFullMatch()) {
addLogMessage(message, entry.getKey(), entry.getValue()); addLogMessage(message, entry.getKey(), entry.getValue());
...@@ -139,6 +140,17 @@ public class AutoConfigurationReportLoggingInitializer implements ...@@ -139,6 +140,17 @@ public class AutoConfigurationReportLoggingInitializer implements
} }
} }
message.append("\n\n"); 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; return message;
} }
......
/*
* 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<String> getAutoConfigurationClassNames() {
return SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
getClass().getClassLoader());
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.logging; package org.springframework.boot.autoconfigure.logging;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
...@@ -58,6 +59,7 @@ import static org.mockito.Mockito.mock; ...@@ -58,6 +59,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link AutoConfigurationReportLoggingInitializer}. * Tests for {@link AutoConfigurationReportLoggingInitializer}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
*/ */
public class AutoConfigurationReportLoggingInitializerTests { public class AutoConfigurationReportLoggingInitializerTests {
...@@ -161,6 +163,8 @@ public class AutoConfigurationReportLoggingInitializerTests { ...@@ -161,6 +163,8 @@ public class AutoConfigurationReportLoggingInitializerTests {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
this.initializer.initialize(context); this.initializer.initialize(context);
context.register(Config.class); context.register(Config.class);
ConditionEvaluationReport.get(context.getBeanFactory()).recordExclusions(
Arrays.asList("com.foo.Bar"));
context.refresh(); context.refresh();
this.initializer.onApplicationEvent(new ContextRefreshedEvent(context)); this.initializer.onApplicationEvent(new ContextRefreshedEvent(context));
for (String message : this.debugLog) { for (String message : this.debugLog) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment