Skip to content

Commit 5b8a2f9

Browse files
committed
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
1 parent 81d6afe commit 5b8a2f9

File tree

25 files changed

+551
-314
lines changed

25 files changed

+551
-314
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpoint.java

+54-15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.actuate.autoconfigure.condition;
1818

1919
import java.util.ArrayList;
20+
import java.util.HashMap;
2021
import java.util.LinkedHashMap;
2122
import java.util.List;
2223
import java.util.Map;
@@ -32,6 +33,8 @@
3233
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
3334
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
3435
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
36+
import org.springframework.context.ApplicationContext;
37+
import org.springframework.context.ConfigurableApplicationContext;
3538
import org.springframework.context.annotation.Condition;
3639
import org.springframework.util.ClassUtils;
3740
import org.springframework.util.LinkedMultiValueMap;
@@ -50,24 +53,58 @@
5053
@Endpoint(id = "conditions")
5154
public class ConditionsReportEndpoint {
5255

53-
private final ConditionEvaluationReport conditionEvaluationReport;
56+
private final ConfigurableApplicationContext context;
5457

55-
public ConditionsReportEndpoint(ConditionEvaluationReport conditionEvaluationReport) {
56-
this.conditionEvaluationReport = conditionEvaluationReport;
58+
public ConditionsReportEndpoint(ConfigurableApplicationContext context) {
59+
this.context = context;
5760
}
5861

5962
@ReadOperation
60-
public Report getEvaluationReport() {
61-
return new Report(this.conditionEvaluationReport);
63+
public ApplicationConditionEvaluation applicationConditionEvaluation() {
64+
Map<String, ContextConditionEvaluation> contextConditionEvaluations = new HashMap<>();
65+
ConfigurableApplicationContext target = this.context;
66+
while (target != null) {
67+
contextConditionEvaluations.put(target.getId(),
68+
new ContextConditionEvaluation(target));
69+
target = getConfigurableParent(target);
70+
}
71+
return new ApplicationConditionEvaluation(contextConditionEvaluations);
72+
}
73+
74+
private ConfigurableApplicationContext getConfigurableParent(
75+
ConfigurableApplicationContext context) {
76+
ApplicationContext parent = context.getParent();
77+
if (parent instanceof ConfigurableApplicationContext) {
78+
return (ConfigurableApplicationContext) parent;
79+
}
80+
return null;
81+
}
82+
83+
/**
84+
* A description of an application's condition evaluation, primarily intended for
85+
* serialization to JSON.
86+
*/
87+
public static final class ApplicationConditionEvaluation {
88+
89+
private final Map<String, ContextConditionEvaluation> contexts;
90+
91+
private ApplicationConditionEvaluation(
92+
Map<String, ContextConditionEvaluation> contexts) {
93+
this.contexts = contexts;
94+
}
95+
96+
public Map<String, ContextConditionEvaluation> getContexts() {
97+
return this.contexts;
98+
}
99+
62100
}
63101

64102
/**
65-
* Adapts {@link ConditionEvaluationReport} to a JSON friendly structure.
103+
* A description of an application context's condition evaluation, primarily intended
104+
* for serialization to JSON.
66105
*/
67-
@JsonPropertyOrder({ "positiveMatches", "negativeMatches", "exclusions",
68-
"unconditionalClasses" })
69106
@JsonInclude(Include.NON_EMPTY)
70-
public static class Report {
107+
public static final class ContextConditionEvaluation {
71108

72109
private final MultiValueMap<String, MessageAndCondition> positiveMatches;
73110

@@ -77,9 +114,11 @@ public static class Report {
77114

78115
private final Set<String> unconditionalClasses;
79116

80-
private final Report parent;
117+
private final String parentId;
81118

82-
public Report(ConditionEvaluationReport report) {
119+
public ContextConditionEvaluation(ConfigurableApplicationContext context) {
120+
ConditionEvaluationReport report = ConditionEvaluationReport
121+
.get(context.getBeanFactory());
83122
this.positiveMatches = new LinkedMultiValueMap<>();
84123
this.negativeMatches = new LinkedHashMap<>();
85124
this.exclusions = report.getExclusions();
@@ -93,8 +132,8 @@ public Report(ConditionEvaluationReport report) {
93132
add(this.negativeMatches, entry.getKey(), entry.getValue());
94133
}
95134
}
96-
boolean hasParent = report.getParent() != null;
97-
this.parent = (hasParent ? new Report(report.getParent()) : null);
135+
this.parentId = context.getParent() == null ? null
136+
: context.getParent().getId();
98137
}
99138

100139
private void add(Map<String, MessageAndConditions> map, String source,
@@ -127,8 +166,8 @@ public Set<String> getUnconditionalClasses() {
127166
return this.unconditionalClasses;
128167
}
129168

130-
public Report getParent() {
131-
return this.parent;
169+
public String getParentId() {
170+
return this.parentId;
132171
}
133172

134173
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointAutoConfiguration.java

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
1818

1919
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
2020
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
21-
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
2221
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2322
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
2423
import org.springframework.context.ConfigurableApplicationContext;
@@ -35,19 +34,12 @@
3534
@Configuration
3635
public class ConditionsReportEndpointAutoConfiguration {
3736

38-
private ConfigurableApplicationContext context;
39-
40-
public ConditionsReportEndpointAutoConfiguration(
41-
ConfigurableApplicationContext context) {
42-
this.context = context;
43-
}
44-
4537
@Bean
4638
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
4739
@ConditionalOnEnabledEndpoint
48-
public ConditionsReportEndpoint conditionsReportEndpoint() {
49-
return new ConditionsReportEndpoint(
50-
ConditionEvaluationReport.get(this.context.getBeanFactory()));
40+
public ConditionsReportEndpoint conditionsReportEndpoint(
41+
ConfigurableApplicationContext context) {
42+
return new ConditionsReportEndpoint(context);
5143
}
5244

5345
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/flyway/FlywayEndpointAutoConfiguration.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.flyway;
1818

19-
import java.util.Map;
20-
2119
import org.flywaydb.core.Flyway;
2220

2321
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
@@ -28,6 +26,7 @@
2826
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2927
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3028
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
29+
import org.springframework.context.ApplicationContext;
3130
import org.springframework.context.annotation.Bean;
3231
import org.springframework.context.annotation.Configuration;
3332

@@ -46,8 +45,8 @@ public class FlywayEndpointAutoConfiguration {
4645
@ConditionalOnBean(Flyway.class)
4746
@ConditionalOnMissingBean
4847
@ConditionalOnEnabledEndpoint
49-
public FlywayEndpoint flywayEndpoint(Map<String, Flyway> flywayBeans) {
50-
return new FlywayEndpoint(flywayBeans);
48+
public FlywayEndpoint flywayEndpoint(ApplicationContext context) {
49+
return new FlywayEndpoint(context);
5150
}
5251

5352
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/liquibase/LiquibaseEndpointAutoConfiguration.java

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.liquibase;
1818

19-
import java.util.Map;
20-
2119
import liquibase.integration.spring.SpringLiquibase;
2220

2321
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
@@ -28,6 +26,7 @@
2826
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2927
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3028
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
29+
import org.springframework.context.ApplicationContext;
3130
import org.springframework.context.annotation.Bean;
3231
import org.springframework.context.annotation.Configuration;
3332

@@ -46,9 +45,8 @@ public class LiquibaseEndpointAutoConfiguration {
4645
@ConditionalOnBean(SpringLiquibase.class)
4746
@ConditionalOnMissingBean
4847
@ConditionalOnEnabledEndpoint
49-
public LiquibaseEndpoint liquibaseEndpoint(
50-
Map<String, SpringLiquibase> liquibaseBeans) {
51-
return new LiquibaseEndpoint(liquibaseBeans);
48+
public LiquibaseEndpoint liquibaseEndpoint(ApplicationContext context) {
49+
return new LiquibaseEndpoint(context);
5250
}
5351

5452
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/condition/ConditionsReportEndpointTests.java

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
2323

2424
import org.junit.Test;
2525

26-
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint.Report;
26+
import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint.ContextConditionEvaluation;
2727
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
2828
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
2929
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -49,8 +49,10 @@ public class ConditionsReportEndpointTests {
4949
public void invoke() {
5050
new ApplicationContextRunner().withUserConfiguration(Config.class)
5151
.run((context) -> {
52-
Report report = context.getBean(ConditionsReportEndpoint.class)
53-
.getEvaluationReport();
52+
ContextConditionEvaluation report = context
53+
.getBean(ConditionsReportEndpoint.class)
54+
.applicationConditionEvaluation().getContexts()
55+
.get(context.getId());
5456
assertThat(report.getPositiveMatches()).isEmpty();
5557
assertThat(report.getNegativeMatches()).containsKey("a");
5658
assertThat(report.getUnconditionalClasses()).contains("b");
@@ -79,9 +81,8 @@ public void setupAutoConfigurationReport() {
7981
}
8082

8183
@Bean
82-
public ConditionsReportEndpoint endpoint(
83-
ConditionEvaluationReport report) {
84-
return new ConditionsReportEndpoint(report);
84+
public ConditionsReportEndpoint endpoint() {
85+
return new ConditionsReportEndpoint(this.context);
8586
}
8687

8788
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/context/properties/ConfigurationPropertiesReportEndpointAutoConfigurationTests.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
2121
import org.junit.Test;
2222

2323
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint;
24-
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
24+
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties;
2525
import org.springframework.boot.autoconfigure.AutoConfigurations;
2626
import org.springframework.boot.context.properties.ConfigurationProperties;
2727
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -73,10 +73,11 @@ private ContextConsumer<AssertableApplicationContext> validateTestProperties(
7373
.hasSingleBean(ConfigurationPropertiesReportEndpoint.class);
7474
ConfigurationPropertiesReportEndpoint endpoint = context
7575
.getBean(ConfigurationPropertiesReportEndpoint.class);
76-
ConfigurationPropertiesDescriptor properties = endpoint
76+
ApplicationConfigurationProperties properties = endpoint
7777
.configurationProperties();
78-
Map<String, Object> nestedProperties = properties.getBeans()
79-
.get("testProperties").getProperties();
78+
Map<String, Object> nestedProperties = properties.getContexts()
79+
.get(context.getId()).getBeans().get("testProperties")
80+
.getProperties();
8081
assertThat(nestedProperties).isNotNull();
8182
assertThat(nestedProperties.get("dbPassword")).isEqualTo(dbPassword);
8283
assertThat(nestedProperties.get("myTestProperty")).isEqualTo(myTestProperty);

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java

+34-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,12 +46,16 @@
4646
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
4747
import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor;
4848
import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
49+
import org.springframework.restdocs.payload.FieldDescriptor;
50+
import org.springframework.restdocs.payload.JsonFieldType;
4951
import org.springframework.test.context.junit4.SpringRunner;
5052
import org.springframework.test.web.servlet.MockMvc;
5153
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
5254
import org.springframework.util.StringUtils;
5355
import org.springframework.web.context.WebApplicationContext;
5456

57+
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
58+
5559
/**
5660
* Abstract base class for tests that generate endpoint documentation using Spring REST
5761
* Docs.
@@ -79,30 +83,46 @@ public void before() {
7983
.build();
8084
}
8185

86+
protected WebApplicationContext getApplicationContext() {
87+
return this.applicationContext;
88+
}
89+
8290
protected String describeEnumValues(Class<? extends Enum<?>> enumType) {
8391
return StringUtils
8492
.collectionToCommaDelimitedString(Stream.of(enumType.getEnumConstants())
8593
.map((constant) -> "`" + constant.name() + "`")
8694
.collect(Collectors.toList()));
8795
}
8896

89-
protected OperationPreprocessor limit(String key) {
90-
return limit(key, (candidate) -> true);
97+
protected OperationPreprocessor limit(String... keys) {
98+
return limit((candidate) -> true, keys);
9199
}
92100

93101
@SuppressWarnings("unchecked")
94-
protected <T> OperationPreprocessor limit(String key, Predicate<T> filter) {
102+
protected <T> OperationPreprocessor limit(Predicate<T> filter, String... keys) {
95103
return new ContentModifyingOperationPreprocessor((content, mediaType) -> {
96104
ObjectMapper objectMapper = new ObjectMapper()
97105
.enable(SerializationFeature.INDENT_OUTPUT);
98106
try {
99107
Map<String, Object> payload = objectMapper.readValue(content, Map.class);
100-
Object entry = payload.get(key);
101-
if (entry instanceof Map) {
102-
payload.put(key, select((Map<String, Object>) entry, filter));
108+
Object target = payload;
109+
Map<Object, Object> parent = null;
110+
for (String key : keys) {
111+
if (target instanceof Map) {
112+
parent = (Map<Object, Object>) target;
113+
target = parent.get(key);
114+
}
115+
else {
116+
throw new IllegalStateException();
117+
}
118+
}
119+
if (target instanceof Map) {
120+
parent.put(keys[keys.length - 1],
121+
select((Map<String, Object>) target, filter));
103122
}
104123
else {
105-
payload.put(key, select((List<Object>) entry, filter));
124+
parent.put(keys[keys.length - 1],
125+
select((List<Object>) target, filter));
106126
}
107127
return objectMapper.writeValueAsBytes(payload);
108128
}
@@ -112,6 +132,12 @@ protected <T> OperationPreprocessor limit(String key, Predicate<T> filter) {
112132
});
113133
}
114134

135+
protected FieldDescriptor parentIdField() {
136+
return fieldWithPath("contexts.*.parentId")
137+
.description("Id of the parent application context, if any.").optional()
138+
.type(JsonFieldType.STRING);
139+
}
140+
115141
@SuppressWarnings("unchecked")
116142
private <T> Map<String, Object> select(Map<String, Object> candidates,
117143
Predicate<T> filter) {

0 commit comments

Comments
 (0)