From 5b8a2f96750c969c06b0765546312c37b22f0282 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 15 Jan 2018 13:36:20 +0000 Subject: [PATCH] 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 --- .../condition/ConditionsReportEndpoint.java | 69 +++++++++++---- ...itionsReportEndpointAutoConfiguration.java | 16 +--- .../FlywayEndpointAutoConfiguration.java | 9 +- .../LiquibaseEndpointAutoConfiguration.java | 10 +-- .../ConditionsReportEndpointTests.java | 15 ++-- ...sReportEndpointAutoConfigurationTests.java | 11 +-- .../AbstractEndpointDocumentationTests.java | 42 +++++++-- .../BeansEndpointDocumentationTests.java | 19 ++-- ...tionsReportEndpointDocumentationTests.java | 77 +++++++++------- ...rtiesReportEndpointDocumentationTests.java | 23 +++-- .../FlywayEndpointDocumentationTests.java | 22 +++-- .../LiquibaseEndpointDocumentationTests.java | 25 ++++-- .../boot/actuate/beans/BeansEndpoint.java | 77 +++++++++------- ...ConfigurationPropertiesReportEndpoint.java | 64 +++++++++----- .../boot/actuate/flyway/FlywayEndpoint.java | 83 ++++++++++++++---- .../actuate/liquibase/LiquibaseEndpoint.java | 87 +++++++++++++++---- .../actuate/beans/BeansEndpointTests.java | 35 ++++---- ...sReportEndpointMethodAnnotationsTests.java | 22 +++-- ...onPropertiesReportEndpointParentTests.java | 29 ++++--- ...ionPropertiesReportEndpointProxyTests.java | 9 +- ...rtiesReportEndpointSerializationTests.java | 57 ++++++------ ...gurationPropertiesReportEndpointTests.java | 14 +-- .../actuate/flyway/FlywayEndpointTests.java | 14 ++- .../liquibase/LiquibaseEndpointTests.java | 20 ++--- .../SampleActuatorApplicationTests.java | 16 ++-- 25 files changed, 551 insertions(+), 314 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpoint.java index 6d22777325..7dbb50b82d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpoint.java @@ -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 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 contexts; + + private ApplicationConditionEvaluation( + Map contexts) { + this.contexts = contexts; + } + + public Map 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 positiveMatches; @@ -77,9 +114,11 @@ public class ConditionsReportEndpoint { private final Set 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 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; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java index 34e7d9c3f0..52ce19a106 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java index 8f0a9fbcf2..609117746f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java @@ -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 flywayBeans) { - return new FlywayEndpoint(flywayBeans); + public FlywayEndpoint flywayEndpoint(ApplicationContext context) { + return new FlywayEndpoint(context); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java index 59f6cb08b2..67859757b0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java @@ -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 liquibaseBeans) { - return new LiquibaseEndpoint(liquibaseBeans); + public LiquibaseEndpoint liquibaseEndpoint(ApplicationContext context) { + return new LiquibaseEndpoint(context); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointTests.java index 546c92e5c5..e9859429b6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointTests.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java index 827620ac26..5ec5b06a2d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java @@ -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 nestedProperties = properties.getBeans() - .get("testProperties").getProperties(); + Map 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); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java index 0c9f42f991..2ff5eb4234 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java @@ -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> 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 OperationPreprocessor limit(String key, Predicate filter) { + protected OperationPreprocessor limit(Predicate filter, String... keys) { return new ContentModifyingOperationPreprocessor((content, mediaType) -> { ObjectMapper objectMapper = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT); try { Map payload = objectMapper.readValue(content, Map.class); - Object entry = payload.get(key); - if (entry instanceof Map) { - payload.put(key, select((Map) entry, filter)); + Object target = payload; + Map parent = null; + for (String key : keys) { + if (target instanceof Map) { + parent = (Map) target; + target = parent.get(key); + } + else { + throw new IllegalStateException(); + } + } + if (target instanceof Map) { + parent.put(keys[keys.length - 1], + select((Map) target, filter)); } else { - payload.put(key, select((List) entry, filter)); + parent.put(keys[keys.length - 1], + select((List) 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 Map select(Map candidates, Predicate filter) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java index 536d69569e..96d9fc1fc3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java @@ -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)); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java index 28f343c52c..e7afb97f2c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java @@ -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 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 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); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java index 8b5e350c4d..c80ee76dde 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java @@ -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 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java index e283c12a20..55a198d4df 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java @@ -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 migrationFieldDescriptors() { @@ -100,8 +104,8 @@ public class FlywayEndpointDocumentationTests extends AbstractEndpointDocumentat static class TestConfiguration { @Bean - public FlywayEndpoint endpoint(Map flyways) { - return new FlywayEndpoint(flyways); + public FlywayEndpoint endpoint(ApplicationContext context) { + return new FlywayEndpoint(context); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java index 5001865d03..1056b603af 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java @@ -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 getChangeSetFieldDescriptors() { @@ -86,8 +93,8 @@ public class LiquibaseEndpointDocumentationTests static class TestConfiguration { @Bean - public LiquibaseEndpoint endpoint(Map liquibases) { - return new LiquibaseEndpoint(liquibases); + public LiquibaseEndpoint endpoint(ApplicationContext context) { + return new LiquibaseEndpoint(context); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/beans/BeansEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/beans/BeansEndpoint.java index 9d28b20c12..7fb7314a25 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/beans/BeansEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/beans/BeansEndpoint.java @@ -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 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 contexts; + + private ApplicationBeans(Map contexts) { + this.contexts = contexts; + } + + public Map 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 beans; - private final ApplicationContextDescriptor parent; + private final String parentId; - private ApplicationContextDescriptor(String contextId, - Map beans, ApplicationContextDescriptor parent) { - this.contextId = contextId; + private ContextBeans(Map 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 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 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; - } - } /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index 818487264d..afc6b8356a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -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 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 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 contexts; + + private ApplicationConfigurationProperties( + Map contexts) { + this.contexts = contexts; + } + + public Map 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 beans; - private final ConfigurationPropertiesDescriptor parent; + private final String parentId; - private ConfigurationPropertiesDescriptor(String contextId, + private ContextConfigurationProperties( Map 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 getBeans() { return this.beans; } - public ConfigurationPropertiesDescriptor getParent() { - return this.parent; + public String getParentId() { + return this.parentId; } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/flyway/FlywayEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/flyway/FlywayEndpoint.java index 37c7d4ae85..23881a9b66 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/flyway/FlywayEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/flyway/FlywayEndpoint.java @@ -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 flywayBeans; + private final ApplicationContext context; - public FlywayEndpoint(Map flywayBeans) { - Assert.notEmpty(flywayBeans, "FlywayBeans must be specified"); - this.flywayBeans = flywayBeans; + public FlywayEndpoint(ApplicationContext context) { + this.context = context; } @ReadOperation - public Map flywayReports() { - Map 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 contextFlywayBeans = new HashMap<>(); + while (target != null) { + Map 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 contexts; + + private ApplicationFlywayBeans(Map contexts) { + this.contexts = contexts; + } + + public Map 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 flywayBeans; + + private final String parentId; + + private ContextFlywayBeans(Map flywayBeans, + String parentId) { + this.flywayBeans = flywayBeans; + this.parentId = parentId; + } + + public Map 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 migrations; - public FlywayReport(MigrationInfo[] migrations) { + private FlywayDescriptor(MigrationInfo[] migrations) { this.migrations = Stream.of(migrations).map(FlywayMigration::new) .collect(Collectors.toList()); } - public FlywayReport(List migrations) { + public FlywayDescriptor(List 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()); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpoint.java index 012305e688..80236bd29e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpoint.java @@ -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 liquibaseBeans; + private final ApplicationContext context; - public LiquibaseEndpoint(Map 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 liquibaseReports() { - Map 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 contextBeans = new HashMap<>(); + while (target != null) { + Map 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 contexts; + + private ApplicationLiquibaseBeans(Map contexts) { + this.contexts = contexts; + } + + public Map 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 liquibaseBeans; + + private final String parentId; + + private ContextLiquibaseBeans(Map liquibaseBeans, + String parentId) { + this.liquibaseBeans = liquibaseBeans; + this.parentId = parentId; + } + + public Map 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 changeSets; - public LiquibaseReport(List changeSets) { + public LiquibaseBean(List changeSets) { this.changeSets = changeSets; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/beans/BeansEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/beans/BeansEndpointTests.java index 7cd0787313..34e5dbb658 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/beans/BeansEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/beans/BeansEndpointTests.java @@ -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 beans = result.getBeans(); + ApplicationBeans result = context.getBean(BeansEndpoint.class).beans(); + ContextBeans descriptor = result.getContexts().get(context.getId()); + assertThat(descriptor.getParentId()).isNull(); + Map 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 beans = result.getBeans(); + ApplicationBeans result = context.getBean(BeansEndpoint.class).beans(); + ContextBeans contextDescriptor = result.getContexts().get(context.getId()); + Map 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"); }); }); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java index a5662a1207..9d961bcd8c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointMethodAnnotationsTests.java @@ -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(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointParentTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointParentTests.java index 91bb8d9e6d..e2b6e4eca4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointParentTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointParentTests.java @@ -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"); }); }); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java index 18527e1918..a5adf3997d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java @@ -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(); }); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java index 3b2f39854d..e9752550c9 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointSerializationTests.java @@ -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 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 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 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 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 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 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 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 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 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 nestedProperties = hikariDataSource.getProperties(); assertThat(nestedProperties).doesNotContainKey("error"); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java index 76dd872933..d5450dcbee 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java @@ -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 properties) { + BiConsumer properties) { load(Collections.emptyList(), properties); } private void load(String keyToSanitize, - BiConsumer properties) { + BiConsumer properties) { load(Collections.singletonList(keyToSanitize), properties); } private void load(List keysToSanitize, - BiConsumer properties) { + BiConsumer 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())); }); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/flyway/FlywayEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/flyway/FlywayEndpointTests.java index 2b6fcf65d9..360d117b7c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/flyway/FlywayEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/flyway/FlywayEndpointTests.java @@ -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 flyways) { - return new FlywayEndpoint(flyways); + public FlywayEndpoint endpoint(ApplicationContext context) { + return new FlywayEndpoint(context); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java index e61aa68364..014e95f7f5 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java @@ -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 liquibases) { - return new LiquibaseEndpoint(liquibases); + public LiquibaseEndpoint endpoint(ApplicationContext context) { + return new LiquibaseEndpoint(context); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java index 02d086dee3..b49be6d0ee 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java @@ -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 body = entity.getBody(); - assertThat((Map) body.get("beans")) + Map contexts = (Map) body.get("contexts"); + Map context = (Map) contexts + .get(this.applicationContext.getId()); + Map beans = (Map) context.get("beans"); + assertThat(beans) .containsKey("spring.datasource-" + DataSourceProperties.class.getName()); }