Improve context hierarchy handling in Actuator endpoints

Previously, a number of Actuator endpoints ignored a context hierarchy
or assumed that it would always be linear. This commit reworks the
affected endpoints so that the no longer assume a linear hierarchy.

A side-effect of a non-linear hierarchy is that there may be multiple
different beans with the same name (in a linear hierarchy, a bean
with the same name as one in an ancestor context, replaces that bean).
The affected endpoints have also been updated so that, when bean names
are used as keys, those keys are grouped by application context. This
prevents a bean in one context from accidentially overwriting a bean
in another context.

Closes gh-11019
This commit is contained in:
Andy Wilkinson 2018-01-15 13:36:20 +00:00
parent 81d6afe5ac
commit 5b8a2f9675
25 changed files with 551 additions and 314 deletions

View File

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure.condition;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -32,6 +33,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionEvaluationRepor
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Condition;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
@ -50,24 +53,58 @@ import org.springframework.util.StringUtils;
@Endpoint(id = "conditions")
public class ConditionsReportEndpoint {
private final ConditionEvaluationReport conditionEvaluationReport;
private final ConfigurableApplicationContext context;
public ConditionsReportEndpoint(ConditionEvaluationReport conditionEvaluationReport) {
this.conditionEvaluationReport = conditionEvaluationReport;
public ConditionsReportEndpoint(ConfigurableApplicationContext context) {
this.context = context;
}
@ReadOperation
public Report getEvaluationReport() {
return new Report(this.conditionEvaluationReport);
public ApplicationConditionEvaluation applicationConditionEvaluation() {
Map<String, ContextConditionEvaluation> contextConditionEvaluations = new HashMap<>();
ConfigurableApplicationContext target = this.context;
while (target != null) {
contextConditionEvaluations.put(target.getId(),
new ContextConditionEvaluation(target));
target = getConfigurableParent(target);
}
return new ApplicationConditionEvaluation(contextConditionEvaluations);
}
private ConfigurableApplicationContext getConfigurableParent(
ConfigurableApplicationContext context) {
ApplicationContext parent = context.getParent();
if (parent instanceof ConfigurableApplicationContext) {
return (ConfigurableApplicationContext) parent;
}
return null;
}
/**
* Adapts {@link ConditionEvaluationReport} to a JSON friendly structure.
* A description of an application's condition evaluation, primarily intended for
* serialization to JSON.
*/
public static final class ApplicationConditionEvaluation {
private final Map<String, ContextConditionEvaluation> contexts;
private ApplicationConditionEvaluation(
Map<String, ContextConditionEvaluation> contexts) {
this.contexts = contexts;
}
public Map<String, ContextConditionEvaluation> getContexts() {
return this.contexts;
}
}
/**
* A description of an application context's condition evaluation, primarily intended
* for serialization to JSON.
*/
@JsonPropertyOrder({ "positiveMatches", "negativeMatches", "exclusions",
"unconditionalClasses" })
@JsonInclude(Include.NON_EMPTY)
public static class Report {
public static final class ContextConditionEvaluation {
private final MultiValueMap<String, MessageAndCondition> positiveMatches;
@ -77,9 +114,11 @@ public class ConditionsReportEndpoint {
private final Set<String> unconditionalClasses;
private final Report parent;
private final String parentId;
public Report(ConditionEvaluationReport report) {
public ContextConditionEvaluation(ConfigurableApplicationContext context) {
ConditionEvaluationReport report = ConditionEvaluationReport
.get(context.getBeanFactory());
this.positiveMatches = new LinkedMultiValueMap<>();
this.negativeMatches = new LinkedHashMap<>();
this.exclusions = report.getExclusions();
@ -93,8 +132,8 @@ public class ConditionsReportEndpoint {
add(this.negativeMatches, entry.getKey(), entry.getValue());
}
}
boolean hasParent = report.getParent() != null;
this.parent = (hasParent ? new Report(report.getParent()) : null);
this.parentId = context.getParent() == null ? null
: context.getParent().getId();
}
private void add(Map<String, MessageAndConditions> map, String source,
@ -127,8 +166,8 @@ public class ConditionsReportEndpoint {
return this.unconditionalClasses;
}
public Report getParent() {
return this.parent;
public String getParentId() {
return this.parentId;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,7 +18,6 @@ package org.springframework.boot.actuate.autoconfigure.condition;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.context.ConfigurableApplicationContext;
@ -35,19 +34,12 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class ConditionsReportEndpointAutoConfiguration {
private ConfigurableApplicationContext context;
public ConditionsReportEndpointAutoConfiguration(
ConfigurableApplicationContext context) {
this.context = context;
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnEnabledEndpoint
public ConditionsReportEndpoint conditionsReportEndpoint() {
return new ConditionsReportEndpoint(
ConditionEvaluationReport.get(this.context.getBeanFactory()));
public ConditionsReportEndpoint conditionsReportEndpoint(
ConfigurableApplicationContext context) {
return new ConditionsReportEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.flyway;
import java.util.Map;
import org.flywaydb.core.Flyway;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
@ -28,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -46,8 +45,8 @@ public class FlywayEndpointAutoConfiguration {
@ConditionalOnBean(Flyway.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public FlywayEndpoint flywayEndpoint(Map<String, Flyway> flywayBeans) {
return new FlywayEndpoint(flywayBeans);
public FlywayEndpoint flywayEndpoint(ApplicationContext context) {
return new FlywayEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.liquibase;
import java.util.Map;
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
@ -28,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -46,9 +45,8 @@ public class LiquibaseEndpointAutoConfiguration {
@ConditionalOnBean(SpringLiquibase.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public LiquibaseEndpoint liquibaseEndpoint(
Map<String, SpringLiquibase> liquibaseBeans) {
return new LiquibaseEndpoint(liquibaseBeans);
public LiquibaseEndpoint liquibaseEndpoint(ApplicationContext context) {
return new LiquibaseEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -23,7 +23,7 @@ import javax.annotation.PostConstruct;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint.Report;
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint.ContextConditionEvaluation;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -49,8 +49,10 @@ public class ConditionsReportEndpointTests {
public void invoke() {
new ApplicationContextRunner().withUserConfiguration(Config.class)
.run((context) -> {
Report report = context.getBean(ConditionsReportEndpoint.class)
.getEvaluationReport();
ContextConditionEvaluation report = context
.getBean(ConditionsReportEndpoint.class)
.applicationConditionEvaluation().getContexts()
.get(context.getId());
assertThat(report.getPositiveMatches()).isEmpty();
assertThat(report.getNegativeMatches()).containsKey("a");
assertThat(report.getUnconditionalClasses()).contains("b");
@ -79,9 +81,8 @@ public class ConditionsReportEndpointTests {
}
@Bean
public ConditionsReportEndpoint endpoint(
ConditionEvaluationReport report) {
return new ConditionsReportEndpoint(report);
public ConditionsReportEndpoint endpoint() {
return new ConditionsReportEndpoint(this.context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -21,7 +21,7 @@ import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -73,10 +73,11 @@ public class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
.hasSingleBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties properties = endpoint
.configurationProperties();
Map<String, Object> nestedProperties = properties.getBeans()
.get("testProperties").getProperties();
Map<String, Object> nestedProperties = properties.getContexts()
.get(context.getId()).getBeans().get("testProperties")
.getProperties();
assertThat(nestedProperties).isNotNull();
assertThat(nestedProperties.get("dbPassword")).isEqualTo(dbPassword);
assertThat(nestedProperties.get("myTestProperty")).isEqualTo(myTestProperty);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -46,12 +46,16 @@ import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
/**
* Abstract base class for tests that generate endpoint documentation using Spring REST
* Docs.
@ -79,6 +83,10 @@ public abstract class AbstractEndpointDocumentationTests {
.build();
}
protected WebApplicationContext getApplicationContext() {
return this.applicationContext;
}
protected String describeEnumValues(Class<? extends Enum<?>> enumType) {
return StringUtils
.collectionToCommaDelimitedString(Stream.of(enumType.getEnumConstants())
@ -86,23 +94,35 @@ public abstract class AbstractEndpointDocumentationTests {
.collect(Collectors.toList()));
}
protected OperationPreprocessor limit(String key) {
return limit(key, (candidate) -> true);
protected OperationPreprocessor limit(String... keys) {
return limit((candidate) -> true, keys);
}
@SuppressWarnings("unchecked")
protected <T> OperationPreprocessor limit(String key, Predicate<T> filter) {
protected <T> OperationPreprocessor limit(Predicate<T> filter, String... keys) {
return new ContentModifyingOperationPreprocessor((content, mediaType) -> {
ObjectMapper objectMapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT);
try {
Map<String, Object> payload = objectMapper.readValue(content, Map.class);
Object entry = payload.get(key);
if (entry instanceof Map) {
payload.put(key, select((Map<String, Object>) entry, filter));
Object target = payload;
Map<Object, Object> parent = null;
for (String key : keys) {
if (target instanceof Map) {
parent = (Map<Object, Object>) target;
target = parent.get(key);
}
else {
throw new IllegalStateException();
}
}
if (target instanceof Map) {
parent.put(keys[keys.length - 1],
select((Map<String, Object>) target, filter));
}
else {
payload.put(key, select((List<Object>) entry, filter));
parent.put(keys[keys.length - 1],
select((List<Object>) target, filter));
}
return objectMapper.writeValueAsBytes(payload);
}
@ -112,6 +132,12 @@ public abstract class AbstractEndpointDocumentationTests {
});
}
protected FieldDescriptor parentIdField() {
return fieldWithPath("contexts.*.parentId")
.description("Id of the parent application context, if any.").optional()
.type(JsonFieldType.STRING);
}
@SuppressWarnings("unchecked")
private <T> Map<String, Object> select(Map<String, Object> candidates,
Predicate<T> filter) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -30,7 +30,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.payload.ResponseFieldsSnippet;
import org.springframework.util.CollectionUtils;
@ -38,7 +37,6 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.docu
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -61,17 +59,16 @@ public class BeansEndpointDocumentationTests extends AbstractEndpointDocumentati
.optional(),
fieldWithPath("dependencies").description("Names of any dependencies."));
ResponseFieldsSnippet responseFields = responseFields(
fieldWithPath("contextId").description("ID of the application context."),
fieldWithPath("beans.*")
fieldWithPath("contexts")
.description("Application contexts keyed by id."),
parentIdField(),
fieldWithPath("contexts.*.beans")
.description("Beans in the application context keyed by name."))
.andWithPrefix("beans.*.", beanFields)
.and(subsectionWithPath("parent")
.description("Beans in the parent application "
+ "context, if any.")
.type(JsonFieldType.OBJECT).optional());
.andWithPrefix("contexts.*.beans.*.", beanFields);
this.mockMvc.perform(get("/actuator/beans")).andExpect(status().isOk())
.andDo(document("beans",
preprocessResponse(limit("beans", this::isIndependentBean)),
preprocessResponse(limit(this::isIndependentBean, "contexts",
getApplicationContext().getId(), "beans")),
responseFields));
}

View File

@ -17,21 +17,23 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@ -70,37 +72,46 @@ public class ConditionsReportEndpointDocumentationTests
@Test
public void conditions() throws Exception {
List<FieldDescriptor> positiveMatchFields = Arrays.asList(
fieldWithPath("").description(
"Classes and methods with conditions that were " + "matched."),
fieldWithPath(".*.[].condition").description("Name of the condition."),
fieldWithPath(".*.[].message")
.description("Details of why the condition was matched."));
List<FieldDescriptor> negativeMatchFields = Arrays.asList(
fieldWithPath("").description("Classes and methods with conditions that "
+ "were not matched."),
fieldWithPath(".*.notMatched")
.description("Conditions that were matched."),
fieldWithPath(".*.notMatched.[].condition")
.description("Name of the condition."),
fieldWithPath(".*.notMatched.[].message").description(
"Details of why the condition was" + " not matched."),
fieldWithPath(".*.matched").description("Conditions that were matched."),
fieldWithPath(".*.matched.[].condition")
.description("Name of the condition.").type(JsonFieldType.STRING)
.optional(),
fieldWithPath(".*.matched.[].message")
.description("Details of why the condition was matched.")
.type(JsonFieldType.STRING).optional());
FieldDescriptor unconditionalClassesField = fieldWithPath(
"contexts.*.unconditionalClasses").description(
"Names of unconditional auto-configuration classes if any.");
this.mockMvc.perform(get("/actuator/conditions")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("conditions",
preprocessResponse(limit("positiveMatches"),
limit("negativeMatches")),
responseFields(
fieldWithPath("positiveMatches").description(
"Classes and methods with conditions that were matched."),
fieldWithPath("positiveMatches.*.[].condition")
.description("Name of the condition."),
fieldWithPath("positiveMatches.*.[].message").description(
"Details of why the condition was matched."),
fieldWithPath("negativeMatches").description(
"Classes and methods with conditions that were not matched."),
fieldWithPath("negativeMatches.*.notMatched")
.description("Conditions that were matched."),
fieldWithPath("negativeMatches.*.notMatched.[].condition")
.description("Name of the condition."),
fieldWithPath("negativeMatches.*.notMatched.[].message")
.description(
"Details of why the condition was not matched."),
fieldWithPath("negativeMatches.*.matched")
.description("Conditions that were matched."),
fieldWithPath("negativeMatches.*.matched.[].condition")
.description("Name of the condition.")
.type(JsonFieldType.STRING).optional(),
fieldWithPath("negativeMatches.*.matched.[].message")
.description(
"Details of why the condition was matched.")
.type(JsonFieldType.STRING).optional(),
fieldWithPath("unconditionalClasses").description(
"Names of unconditional auto-configuration classes, if any."))));
preprocessResponse(
limit("contexts", getApplicationContext().getId(),
"positiveMatches"),
limit("contexts", getApplicationContext().getId(),
"negativeMatches")),
responseFields(fieldWithPath("contexts")
.description("Application contexts keyed by id."))
.andWithPrefix("contexts.*.positiveMatches",
positiveMatchFields)
.andWithPrefix("contexts.*.negativeMatches",
negativeMatchFields)
.and(unconditionalClassesField,
parentIdField())));
}
@Configuration
@ -109,12 +120,12 @@ public class ConditionsReportEndpointDocumentationTests
@Bean
public ConditionsReportEndpoint autoConfigurationReportEndpoint(
ConfigurableListableBeanFactory beanFactory) {
ConfigurableApplicationContext context) {
ConditionEvaluationReport conditionEvaluationReport = ConditionEvaluationReport
.get(beanFactory);
.get(context.getBeanFactory());
conditionEvaluationReport.recordEvaluationCandidates(
Arrays.asList(PropertyPlaceholderAutoConfiguration.class.getName()));
return new ConditionsReportEndpoint(conditionEvaluationReport);
return new ConditionsReportEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -23,7 +23,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.payload.JsonFieldType;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
@ -45,21 +44,19 @@ public class ConfigurationPropertiesReportEndpointDocumentationTests
public void configProps() throws Exception {
this.mockMvc.perform(get("/actuator/configprops")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("configprops",
preprocessResponse(limit("beans")),
preprocessResponse(limit("contexts",
getApplicationContext().getId(), "beans")),
responseFields(
fieldWithPath("contextId")
.description("ID of the application context."),
fieldWithPath("beans.*").description(
fieldWithPath("contexts")
.description("Application contexts keyed by id."),
fieldWithPath("contexts.*.beans.*").description(
"`@ConfigurationProperties` beans keyed by bean name."),
fieldWithPath("beans.*.prefix").description(
fieldWithPath("contexts.*.beans.*.prefix").description(
"Prefix applied to the names of the bean's properties."),
subsectionWithPath("beans.*.properties").description(
"Properties of the bean as name-value pairs."),
subsectionWithPath("parent")
subsectionWithPath("contexts.*.beans.*.properties")
.description(
"`@ConfigurationProperties` beans in the parent "
+ "context, if any.")
.optional().type(JsonFieldType.OBJECT))));
"Properties of the bean as name-value pairs."),
parentIdField())));
}
@Configuration

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,9 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentatio
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationState;
import org.flywaydb.core.api.MigrationType;
import org.junit.Test;
@ -29,6 +27,7 @@ import org.springframework.boot.actuate.flyway.FlywayEndpoint;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -52,11 +51,16 @@ public class FlywayEndpointDocumentationTests extends AbstractEndpointDocumentat
public void flyway() throws Exception {
this.mockMvc.perform(get("/actuator/flyway")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("flyway",
responseFields(fieldWithPath("*.migrations").description(
responseFields(
fieldWithPath("contexts")
.description("Application contexts keyed by id"),
fieldWithPath("contexts.*.flywayBeans.*.migrations").description(
"Migrations performed by the Flyway instance, keyed by"
+ " bean name.")).andWithPrefix(
"*.migrations.[].",
migrationFieldDescriptors())));
+ " Flyway bean name."))
.andWithPrefix(
"contexts.*.flywayBeans.*.migrations.[].",
migrationFieldDescriptors())
.and(parentIdField())));
}
private List<FieldDescriptor> migrationFieldDescriptors() {
@ -100,8 +104,8 @@ public class FlywayEndpointDocumentationTests extends AbstractEndpointDocumentat
static class TestConfiguration {
@Bean
public FlywayEndpoint endpoint(Map<String, Flyway> flyways) {
return new FlywayEndpoint(flyways);
public FlywayEndpoint endpoint(ApplicationContext context) {
return new FlywayEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,15 +18,14 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentatio
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import liquibase.changelog.ChangeSet.ExecType;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.Test;
import org.springframework.boot.actuate.liquibase.LiquibaseEndpoint;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -48,12 +47,20 @@ public class LiquibaseEndpointDocumentationTests
@Test
public void liquibase() throws Exception {
FieldDescriptor changeSetsField = fieldWithPath(
"contexts.*.liquibaseBeans.*.changeSets")
.description("Change sets made by the Liquibase beans, keyed by "
+ "bean name.");
this.mockMvc.perform(get("/actuator/liquibase")).andExpect(status().isOk())
.andDo(MockMvcRestDocumentation.document("liquibase",
responseFields(fieldWithPath("*.changeSets").description(
"Change sets made by the Liquibase beans, keyed by "
+ "bean name.")).andWithPrefix("*.changeSets[].",
getChangeSetFieldDescriptors())));
responseFields(
fieldWithPath("contexts")
.description("Application contexts keyed by id"),
changeSetsField)
.andWithPrefix(
"contexts.*.liquibaseBeans.*.changeSets[].",
getChangeSetFieldDescriptors())
.and(parentIdField())));
}
private List<FieldDescriptor> getChangeSetFieldDescriptors() {
@ -86,8 +93,8 @@ public class LiquibaseEndpointDocumentationTests
static class TestConfiguration {
@Bean
public LiquibaseEndpoint endpoint(Map<String, SpringLiquibase> liquibases) {
return new LiquibaseEndpoint(liquibases);
public LiquibaseEndpoint endpoint(ApplicationContext context) {
return new LiquibaseEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -53,49 +53,73 @@ public class BeansEndpoint {
}
@ReadOperation
public ApplicationContextDescriptor beans() {
return ApplicationContextDescriptor.describing(this.context);
public ApplicationBeans beans() {
Map<String, ContextBeans> contexts = new HashMap<>();
ConfigurableApplicationContext context = this.context;
while (context != null) {
contexts.put(context.getId(), ContextBeans.describing(context));
context = getConfigurableParent(context);
}
return new ApplicationBeans(contexts);
}
private static ConfigurableApplicationContext getConfigurableParent(
ConfigurableApplicationContext context) {
ApplicationContext parent = context.getParent();
if (parent instanceof ConfigurableApplicationContext) {
return (ConfigurableApplicationContext) parent;
}
return null;
}
/**
* A description of an application's beans, primarily intended for serialization to
* JSON.
*/
public static final class ApplicationBeans {
private final Map<String, ContextBeans> contexts;
private ApplicationBeans(Map<String, ContextBeans> contexts) {
this.contexts = contexts;
}
public Map<String, ContextBeans> getContexts() {
return this.contexts;
}
}
/**
* A description of an application context, primarily intended for serialization to
* JSON.
*/
public static final class ApplicationContextDescriptor {
private final String contextId;
public static final class ContextBeans {
private final Map<String, BeanDescriptor> beans;
private final ApplicationContextDescriptor parent;
private final String parentId;
private ApplicationContextDescriptor(String contextId,
Map<String, BeanDescriptor> beans, ApplicationContextDescriptor parent) {
this.contextId = contextId;
private ContextBeans(Map<String, BeanDescriptor> beans, String parentId) {
this.beans = beans;
this.parent = parent;
this.parentId = parentId;
}
public String getContextId() {
return this.contextId;
}
public ApplicationContextDescriptor getParent() {
return this.parent;
public String getParentId() {
return this.parentId;
}
public Map<String, BeanDescriptor> getBeans() {
return this.beans;
}
private static ApplicationContextDescriptor describing(
ConfigurableApplicationContext context) {
private static ContextBeans describing(ConfigurableApplicationContext context) {
if (context == null) {
return null;
}
return new ApplicationContextDescriptor(context.getId(),
describeBeans(context.getBeanFactory()),
describing(getConfigurableParent(context)));
ConfigurableApplicationContext parent = getConfigurableParent(context);
return new ContextBeans(describeBeans(context.getBeanFactory()),
parent == null ? null : parent.getId());
}
private static Map<String, BeanDescriptor> describeBeans(
@ -123,15 +147,6 @@ public class BeansEndpoint {
&& (!bd.isLazyInit() || bf.containsSingleton(beanName)));
}
private static ConfigurableApplicationContext getConfigurableParent(
ConfigurableApplicationContext context) {
ApplicationContext parent = context.getParent();
if (parent instanceof ConfigurableApplicationContext) {
return (ConfigurableApplicationContext) parent;
}
return null;
}
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -88,21 +88,25 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
}
@ReadOperation
public ConfigurationPropertiesDescriptor configurationProperties() {
public ApplicationConfigurationProperties configurationProperties() {
return extract(this.context);
}
private ConfigurationPropertiesDescriptor extract(ApplicationContext context) {
private ApplicationConfigurationProperties extract(ApplicationContext context) {
ObjectMapper mapper = new ObjectMapper();
configureObjectMapper(mapper);
return describeConfigurationProperties(context, mapper);
Map<String, ContextConfigurationProperties> contextProperties = new HashMap<>();
ApplicationContext target = context;
while (target != null) {
contextProperties.put(target.getId(),
describeConfigurationProperties(target, mapper));
target = target.getParent();
}
return new ApplicationConfigurationProperties(contextProperties);
}
private ConfigurationPropertiesDescriptor describeConfigurationProperties(
private ContextConfigurationProperties describeConfigurationProperties(
ApplicationContext context, ObjectMapper mapper) {
if (context == null) {
return null;
}
ConfigurationBeanFactoryMetaData beanFactoryMetaData = getBeanFactoryMetaData(
context);
Map<String, Object> beans = getConfigurationPropertiesBeans(context,
@ -115,8 +119,8 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
beanDescriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(
prefix, sanitize(prefix, safeSerialize(mapper, bean, prefix))));
}
return new ConfigurationPropertiesDescriptor(context.getId(), beanDescriptors,
describeConfigurationProperties(context.getParent(), mapper));
return new ContextConfigurationProperties(beanDescriptors,
context.getParent() == null ? null : context.getParent().getId());
}
private ConfigurationBeanFactoryMetaData getBeanFactoryMetaData(
@ -389,36 +393,48 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
}
/**
* A description of an application's {@link ConfigurationProperties} beans. Primarily
* intended for serialization to JSON.
*/
public static final class ApplicationConfigurationProperties {
private final Map<String, ContextConfigurationProperties> contexts;
private ApplicationConfigurationProperties(
Map<String, ContextConfigurationProperties> contexts) {
this.contexts = contexts;
}
public Map<String, ContextConfigurationProperties> getContexts() {
return this.contexts;
}
}
/**
* A description of an application context's {@link ConfigurationProperties} beans.
* Primarily intended for serialization to JSON.
*/
public static final class ConfigurationPropertiesDescriptor {
private final String contextId;
public static final class ContextConfigurationProperties {
private final Map<String, ConfigurationPropertiesBeanDescriptor> beans;
private final ConfigurationPropertiesDescriptor parent;
private final String parentId;
private ConfigurationPropertiesDescriptor(String contextId,
private ContextConfigurationProperties(
Map<String, ConfigurationPropertiesBeanDescriptor> beans,
ConfigurationPropertiesDescriptor parent) {
this.contextId = contextId;
String parentId) {
this.beans = beans;
this.parent = parent;
}
public String getContextId() {
return this.contextId;
this.parentId = parentId;
}
public Map<String, ConfigurationPropertiesBeanDescriptor> getBeans() {
return this.beans;
}
public ConfigurationPropertiesDescriptor getParent() {
return this.parent;
public String getParentId() {
return this.parentId;
}
}

View File

@ -30,7 +30,7 @@ import org.flywaydb.core.api.MigrationType;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.util.Assert;
import org.springframework.context.ApplicationContext;
/**
* {@link Endpoint} to expose flyway info.
@ -43,34 +43,85 @@ import org.springframework.util.Assert;
@Endpoint(id = "flyway")
public class FlywayEndpoint {
private final Map<String, Flyway> flywayBeans;
private final ApplicationContext context;
public FlywayEndpoint(Map<String, Flyway> flywayBeans) {
Assert.notEmpty(flywayBeans, "FlywayBeans must be specified");
this.flywayBeans = flywayBeans;
public FlywayEndpoint(ApplicationContext context) {
this.context = context;
}
@ReadOperation
public Map<String, FlywayReport> flywayReports() {
Map<String, FlywayReport> reports = new HashMap<>();
this.flywayBeans.forEach((name, flyway) -> reports.put(name,
new FlywayReport(flyway.info().all())));
return reports;
public ApplicationFlywayBeans flywayBeans() {
ApplicationContext target = this.context;
Map<String, ContextFlywayBeans> contextFlywayBeans = new HashMap<>();
while (target != null) {
Map<String, FlywayDescriptor> flywayBeans = new HashMap<>();
target.getBeansOfType(Flyway.class).forEach((name, flyway) -> flywayBeans
.put(name, new FlywayDescriptor(flyway.info().all())));
ApplicationContext parent = target.getParent();
contextFlywayBeans.put(target.getId(), new ContextFlywayBeans(flywayBeans,
parent == null ? null : parent.getId()));
target = parent;
}
return new ApplicationFlywayBeans(contextFlywayBeans);
}
/**
* Report for one {@link Flyway} instance.
* Description of an application's {@link Flyway} beans, primarily intended for
* serialization to JSON.
*/
public static class FlywayReport {
public static final class ApplicationFlywayBeans {
private final Map<String, ContextFlywayBeans> contexts;
private ApplicationFlywayBeans(Map<String, ContextFlywayBeans> contexts) {
this.contexts = contexts;
}
public Map<String, ContextFlywayBeans> getContexts() {
return this.contexts;
}
}
/**
* Description of an application context's {@link Flyway} beans, primarily intended
* for serialization to JSON.
*/
public static final class ContextFlywayBeans {
private final Map<String, FlywayDescriptor> flywayBeans;
private final String parentId;
private ContextFlywayBeans(Map<String, FlywayDescriptor> flywayBeans,
String parentId) {
this.flywayBeans = flywayBeans;
this.parentId = parentId;
}
public Map<String, FlywayDescriptor> getFlywayBeans() {
return this.flywayBeans;
}
public String getParentId() {
return this.parentId;
}
}
/**
* Description of a {@link Flyway} bean, primarly intended for serialization to JSON.
*/
public static class FlywayDescriptor {
private final List<FlywayMigration> migrations;
public FlywayReport(MigrationInfo[] migrations) {
private FlywayDescriptor(MigrationInfo[] migrations) {
this.migrations = Stream.of(migrations).map(FlywayMigration::new)
.collect(Collectors.toList());
}
public FlywayReport(List<FlywayMigration> migrations) {
public FlywayDescriptor(List<FlywayMigration> migrations) {
this.migrations = migrations;
}
@ -83,7 +134,7 @@ public class FlywayEndpoint {
/**
* Details of a migration performed by Flyway.
*/
public static class FlywayMigration {
public static final class FlywayMigration {
private final MigrationType type;
@ -105,7 +156,7 @@ public class FlywayEndpoint {
private final Integer executionTime;
public FlywayMigration(MigrationInfo info) {
private FlywayMigration(MigrationInfo info) {
this.type = info.getType();
this.checksum = info.getChecksum();
this.version = nullSafeToString(info.getVersion());

View File

@ -36,6 +36,7 @@ import liquibase.integration.spring.SpringLiquibase;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -48,24 +49,33 @@ import org.springframework.util.StringUtils;
@Endpoint(id = "liquibase")
public class LiquibaseEndpoint {
private final Map<String, SpringLiquibase> liquibaseBeans;
private final ApplicationContext context;
public LiquibaseEndpoint(Map<String, SpringLiquibase> liquibaseBeans) {
Assert.notEmpty(liquibaseBeans, "LiquibaseBeans must be specified");
this.liquibaseBeans = liquibaseBeans;
public LiquibaseEndpoint(ApplicationContext context) {
Assert.notNull(context, "Context must be specified");
this.context = context;
}
@ReadOperation
public Map<String, LiquibaseReport> liquibaseReports() {
Map<String, LiquibaseReport> reports = new HashMap<>();
DatabaseFactory factory = DatabaseFactory.getInstance();
StandardChangeLogHistoryService service = new StandardChangeLogHistoryService();
this.liquibaseBeans.forEach((name, liquibase) -> reports.put(name,
createReport(liquibase, service, factory)));
return reports;
public ApplicationLiquibaseBeans liquibaseBeans() {
ApplicationContext target = this.context;
Map<String, ContextLiquibaseBeans> contextBeans = new HashMap<>();
while (target != null) {
Map<String, LiquibaseBean> liquibaseBeans = new HashMap<>();
DatabaseFactory factory = DatabaseFactory.getInstance();
StandardChangeLogHistoryService service = new StandardChangeLogHistoryService();
this.context.getBeansOfType(SpringLiquibase.class)
.forEach((name, liquibase) -> liquibaseBeans.put(name,
createReport(liquibase, service, factory)));
ApplicationContext parent = target.getParent();
contextBeans.put(target.getId(), new ContextLiquibaseBeans(liquibaseBeans,
parent == null ? null : parent.getId()));
target = parent;
}
return new ApplicationLiquibaseBeans(contextBeans);
}
private LiquibaseReport createReport(SpringLiquibase liquibase,
private LiquibaseBean createReport(SpringLiquibase liquibase,
ChangeLogHistoryService service, DatabaseFactory factory) {
try {
DataSource dataSource = liquibase.getDataSource();
@ -77,7 +87,7 @@ public class LiquibaseEndpoint {
database.setDefaultSchemaName(defaultSchema);
}
service.setDatabase(database);
return new LiquibaseReport(service.getRanChangeSets().stream()
return new LiquibaseBean(service.getRanChangeSets().stream()
.map(ChangeSet::new).collect(Collectors.toList()));
}
finally {
@ -90,13 +100,58 @@ public class LiquibaseEndpoint {
}
/**
* Report for a single {@link SpringLiquibase} instance.
* Description of an application's {@link SpringLiquibase} beans, primarily intended
* for serialization to JSON.
*/
public static class LiquibaseReport {
public static final class ApplicationLiquibaseBeans {
private final Map<String, ContextLiquibaseBeans> contexts;
private ApplicationLiquibaseBeans(Map<String, ContextLiquibaseBeans> contexts) {
this.contexts = contexts;
}
public Map<String, ContextLiquibaseBeans> getContexts() {
return this.contexts;
}
}
/**
* Description of an application context's {@link SpringLiquibase} beans, primarily
* intended for serialization to JSON.
*/
public static final class ContextLiquibaseBeans {
private final Map<String, LiquibaseBean> liquibaseBeans;
private final String parentId;
private ContextLiquibaseBeans(Map<String, LiquibaseBean> liquibaseBeans,
String parentId) {
this.liquibaseBeans = liquibaseBeans;
this.parentId = parentId;
}
public Map<String, LiquibaseBean> getLiquibaseBeans() {
return this.liquibaseBeans;
}
public String getParentId() {
return this.parentId;
}
}
/**
* Description of a {@link SpringLiquibase} bean, primarly intended for serialization
* to JSON.
*/
public static final class LiquibaseBean {
private final List<ChangeSet> changeSets;
public LiquibaseReport(List<ChangeSet> changeSets) {
public LiquibaseBean(List<ChangeSet> changeSets) {
this.changeSets = changeSets;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -25,8 +25,9 @@ import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.beans.BeansEndpoint.ApplicationContextDescriptor;
import org.springframework.boot.actuate.beans.BeansEndpoint.ApplicationBeans;
import org.springframework.boot.actuate.beans.BeansEndpoint.BeanDescriptor;
import org.springframework.boot.actuate.beans.BeansEndpoint.ContextBeans;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@ -48,11 +49,10 @@ public class BeansEndpointTests {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> {
ApplicationContextDescriptor result = context.getBean(BeansEndpoint.class)
.beans();
assertThat(result.getParent()).isNull();
assertThat(result.getContextId()).isEqualTo(context.getId());
Map<String, BeanDescriptor> beans = result.getBeans();
ApplicationBeans result = context.getBean(BeansEndpoint.class).beans();
ContextBeans descriptor = result.getContexts().get(context.getId());
assertThat(descriptor.getParentId()).isNull();
Map<String, BeanDescriptor> beans = descriptor.getBeans();
assertThat(beans.size())
.isLessThanOrEqualTo(context.getBeanDefinitionCount());
assertThat(beans).containsKey("endpoint");
@ -70,9 +70,9 @@ public class BeansEndpointTests {
.filter((name) -> BeanDefinition.ROLE_INFRASTRUCTURE == factory
.getBeanDefinition(name).getRole())
.collect(Collectors.toList());
ApplicationContextDescriptor result = context.getBean(BeansEndpoint.class)
.beans();
Map<String, BeanDescriptor> beans = result.getBeans();
ApplicationBeans result = context.getBean(BeansEndpoint.class).beans();
ContextBeans contextDescriptor = result.getContexts().get(context.getId());
Map<String, BeanDescriptor> beans = contextDescriptor.getBeans();
for (String infrastructureBean : infrastructureBeans) {
assertThat(beans).doesNotContainKey(infrastructureBean);
}
@ -85,10 +85,10 @@ public class BeansEndpointTests {
.withUserConfiguration(EndpointConfiguration.class,
LazyBeanConfiguration.class);
contextRunner.run((context) -> {
ApplicationContextDescriptor result = context.getBean(BeansEndpoint.class)
.beans();
ApplicationBeans result = context.getBean(BeansEndpoint.class).beans();
ContextBeans contextDescriptor = result.getContexts().get(context.getId());
assertThat(context).hasBean("lazyBean");
assertThat(result.getBeans()).doesNotContainKey("lazyBean");
assertThat(contextDescriptor.getBeans()).doesNotContainKey("lazyBean");
});
}
@ -100,10 +100,11 @@ public class BeansEndpointTests {
new ApplicationContextRunner()
.withUserConfiguration(EndpointConfiguration.class).withParent(parent)
.run(child -> {
BeansEndpoint endpoint = child.getBean(BeansEndpoint.class);
ApplicationContextDescriptor result = endpoint.beans();
assertThat(result.getParent().getBeans()).containsKey("bean");
assertThat(result.getBeans()).containsKey("endpoint");
ApplicationBeans result = child.getBean(BeansEndpoint.class).beans();
assertThat(result.getContexts().get(parent.getId()).getBeans())
.containsKey("bean");
assertThat(result.getContexts().get(child.getId()).getBeans())
.containsKey("endpoint");
});
});
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,8 +18,9 @@ package org.springframework.boot.actuate.context.properties;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -44,9 +45,13 @@ public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor other = properties.getBeans()
assertThat(applicationProperties.getContexts())
.containsOnlyKeys(context.getId());
ContextConfigurationProperties contextProperties = applicationProperties
.getContexts().get(context.getId());
ConfigurationPropertiesBeanDescriptor other = contextProperties.getBeans()
.get("other");
assertThat(other).isNotNull();
assertThat(other.getPrefix()).isEqualTo("other");
@ -63,9 +68,14 @@ public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor bar = properties.getBeans().get("bar");
assertThat(applicationProperties.getContexts())
.containsOnlyKeys(context.getId());
ContextConfigurationProperties contextProperties = applicationProperties
.getContexts().get(context.getId());
ConfigurationPropertiesBeanDescriptor bar = contextProperties.getBeans()
.get("bar");
assertThat(bar).isNotNull();
assertThat(bar.getPrefix()).isEqualTo("other");
assertThat(bar.getProperties()).isNotNull();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,7 +18,7 @@ package org.springframework.boot.actuate.context.properties;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -45,12 +45,15 @@ public class ConfigurationPropertiesReportEndpointParentTests {
.withParent(parent).run(child -> {
ConfigurationPropertiesReportEndpoint endpoint = child
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor result = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
assertThat(result.getBeans().keySet())
.containsExactlyInAnyOrder("someProperties");
assertThat((result.getParent().getBeans().keySet()))
.containsExactly("testProperties");
assertThat(applicationProperties.getContexts())
.containsOnlyKeys(child.getId(), parent.getId());
assertThat(applicationProperties.getContexts().get(child.getId())
.getBeans().keySet()).containsExactly("someProperties");
assertThat((applicationProperties.getContexts()
.get(parent.getId()).getBeans().keySet()))
.containsExactly("testProperties");
});
});
}
@ -65,12 +68,14 @@ public class ConfigurationPropertiesReportEndpointParentTests {
.withParent(parent).run(child -> {
ConfigurationPropertiesReportEndpoint endpoint = child
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor result = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
assertThat(result.getBeans().keySet())
.containsExactlyInAnyOrder("otherProperties");
assertThat((result.getParent().getBeans().keySet()))
.containsExactly("testProperties");
assertThat(applicationProperties.getContexts().get(child.getId())
.getBeans().keySet())
.containsExactlyInAnyOrder("otherProperties");
assertThat((applicationProperties.getContexts()
.get(parent.getId()).getBeans().keySet()))
.containsExactly("testProperties");
});
});
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -20,8 +20,8 @@ import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -52,10 +52,11 @@ public class ConfigurationPropertiesReportEndpointProxyTests {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(Config.class, SqlExecutor.class);
contextRunner.run((context) -> {
ConfigurationPropertiesDescriptor report = context
ApplicationConfigurationProperties applicationProperties = context
.getBean(ConfigurationPropertiesReportEndpoint.class)
.configurationProperties();
assertThat(report.getBeans().values().stream()
assertThat(applicationProperties.getContexts().get(context.getId()).getBeans()
.values().stream()
.map(ConfigurationPropertiesBeanDescriptor::getPrefix)
.filter("executor.sql"::equals).findFirst()).isNotEmpty();
});

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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,8 +26,8 @@ import com.zaxxer.hikari.HikariDataSource;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -55,9 +55,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo).isNotNull();
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> map = foo.getProperties();
@ -76,9 +77,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo).isNotNull();
Map<String, Object> map = foo.getProperties();
assertThat(map).isNotNull();
@ -97,9 +99,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> map = foo.getProperties();
assertThat(map).isNotNull();
@ -119,10 +122,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor cycle = properties.getBeans()
.get("cycle");
ConfigurationPropertiesBeanDescriptor cycle = applicationProperties
.getContexts().get(context.getId()).getBeans().get("cycle");
assertThat(cycle.getPrefix()).isEqualTo("cycle");
Map<String, Object> map = cycle.getProperties();
assertThat(map).isNotNull();
@ -140,10 +143,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor fooProperties = properties.getBeans()
.get("foo");
ConfigurationPropertiesBeanDescriptor fooProperties = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(fooProperties).isNotNull();
assertThat(fooProperties.getPrefix()).isEqualTo("foo");
Map<String, Object> map = fooProperties.getProperties();
@ -161,9 +164,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo).isNotNull();
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> map = foo.getProperties();
@ -182,9 +186,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo).isNotNull();
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> map = foo.getProperties();
@ -202,9 +207,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo).isNotNull();
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> map = foo.getProperties();
@ -223,10 +229,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
assertThat(properties.getBeans()).containsKeys("foo");
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
ConfigurationPropertiesBeanDescriptor foo = applicationProperties
.getContexts().get(context.getId()).getBeans().get("foo");
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> propertiesMap = foo.getProperties();
assertThat(propertiesMap).containsOnlyKeys("bar", "name", "map", "list");
@ -244,9 +250,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
ApplicationConfigurationProperties applicationProperties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor hikariDataSource = properties.getBeans()
ConfigurationPropertiesBeanDescriptor hikariDataSource = applicationProperties
.getContexts().get(context.getId()).getBeans()
.get("hikariDataSource");
Map<String, Object> nestedProperties = hikariDataSource.getProperties();
assertThat(nestedProperties).doesNotContainKey("error");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -27,7 +27,7 @@ import java.util.function.BiConsumer;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -49,7 +49,6 @@ public class ConfigurationPropertiesReportEndpointTests {
@Test
public void configurationPropertiesAreReturned() {
load((context, properties) -> {
assertThat(properties.getContextId()).isEqualTo(context.getId());
assertThat(properties.getBeans().size()).isGreaterThan(0);
ConfigurationPropertiesBeanDescriptor nestedProperties = properties.getBeans()
.get("testProperties");
@ -161,17 +160,17 @@ public class ConfigurationPropertiesReportEndpointTests {
}
private void load(
BiConsumer<ApplicationContext, ConfigurationPropertiesDescriptor> properties) {
BiConsumer<ApplicationContext, ContextConfigurationProperties> properties) {
load(Collections.emptyList(), properties);
}
private void load(String keyToSanitize,
BiConsumer<ApplicationContext, ConfigurationPropertiesDescriptor> properties) {
BiConsumer<ApplicationContext, ContextConfigurationProperties> properties) {
load(Collections.singletonList(keyToSanitize), properties);
}
private void load(List<String> keysToSanitize,
BiConsumer<ApplicationContext, ConfigurationPropertiesDescriptor> properties) {
BiConsumer<ApplicationContext, ContextConfigurationProperties> properties) {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(Config.class);
contextRunner.run((context) -> {
@ -181,7 +180,8 @@ public class ConfigurationPropertiesReportEndpointTests {
endpoint.setKeysToSanitize(
keysToSanitize.toArray(new String[keysToSanitize.size()]));
}
properties.accept(context, endpoint.configurationProperties());
properties.accept(context, endpoint.configurationProperties().getContexts()
.get(context.getId()));
});
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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,14 +16,12 @@
package org.springframework.boot.actuate.flyway;
import java.util.Map;
import org.flywaydb.core.Flyway;
import org.junit.Test;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -43,8 +41,8 @@ public class FlywayEndpointTests {
public void flywayReportIsProduced() {
new ApplicationContextRunner().withUserConfiguration(Config.class)
.run((context) -> assertThat(
context.getBean(FlywayEndpoint.class).flywayReports())
.hasSize(1));
context.getBean(FlywayEndpoint.class).flywayBeans().getContexts()
.get(context.getId()).getFlywayBeans()).hasSize(1));
}
@Configuration
@ -52,8 +50,8 @@ public class FlywayEndpointTests {
public static class Config {
@Bean
public FlywayEndpoint endpoint(Map<String, Flyway> flyways) {
return new FlywayEndpoint(flyways);
public FlywayEndpoint endpoint(ApplicationContext context) {
return new FlywayEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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,15 +16,13 @@
package org.springframework.boot.actuate.liquibase;
import java.util.Map;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -48,8 +46,9 @@ public class LiquibaseEndpointTests {
public void liquibaseReportIsReturned() {
this.contextRunner.withUserConfiguration(Config.class)
.run((context) -> assertThat(
context.getBean(LiquibaseEndpoint.class).liquibaseReports())
.hasSize(1));
context.getBean(LiquibaseEndpoint.class).liquibaseBeans()
.getContexts().get(context.getId()).getLiquibaseBeans())
.hasSize(1));
}
@Test
@ -58,16 +57,17 @@ public class LiquibaseEndpointTests {
.withPropertyValues("spring.liquibase.default-schema=CUSTOMSCHEMA",
"spring.datasource.schema=classpath:/db/create-custom-schema.sql")
.run((context) -> assertThat(
context.getBean(LiquibaseEndpoint.class).liquibaseReports())
.hasSize(1));
context.getBean(LiquibaseEndpoint.class).liquibaseBeans()
.getContexts().get(context.getId()).getLiquibaseBeans())
.hasSize(1));
}
@Configuration
public static class Config {
@Bean
public LiquibaseEndpoint endpoint(Map<String, SpringLiquibase> liquibases) {
return new LiquibaseEndpoint(liquibases);
public LiquibaseEndpoint endpoint(ApplicationContext context) {
return new LiquibaseEndpoint(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -51,6 +52,9 @@ public class SampleActuatorApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHomeIsSecure() {
@SuppressWarnings("rawtypes")
@ -217,9 +221,7 @@ public class SampleActuatorApplicationTests {
.withBasicAuth("user", getPassword())
.getForEntity("/actuator/beans", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).containsOnlyKeys("beans", "parent", "contextId");
assertThat(((String) entity.getBody().get("contextId")))
.startsWith("application");
assertThat(entity.getBody()).containsOnlyKeys("contexts");
}
@SuppressWarnings("unchecked")
@ -231,7 +233,11 @@ public class SampleActuatorApplicationTests {
.getForEntity("/actuator/configprops", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
Map<String, Object> body = entity.getBody();
assertThat((Map<String, Object>) body.get("beans"))
Map<String, Object> contexts = (Map<String, Object>) body.get("contexts");
Map<String, Object> context = (Map<String, Object>) contexts
.get(this.applicationContext.getId());
Map<String, Object> beans = (Map<String, Object>) context.get("beans");
assertThat(beans)
.containsKey("spring.datasource-" + DataSourceProperties.class.getName());
}