From 2a989b31aacdb923191952c1144b5a00397516be Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 14 Nov 2022 14:48:01 -0800 Subject: [PATCH] Create Environment and apply ConversionService to management contexts Update `ManagementContextFactory` implementations to create an appropriate `Environment` type and to apply the `ConversionService` from the parent context. Prior to this commit, the management context `Environment` would not be able to convert values from a `configtree:` source due to a missing converter. Fixes gh-32941 --- .../ReactiveManagementContextFactory.java | 13 ++++++- .../ServletManagementContextFactory.java | 13 ++++++- ...dContextConfigurationIntegrationTests.java | 36 ++++++++++++++++++- ...dContextConfigurationIntegrationTests.java | 36 ++++++++++++++++++- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java index 431ec29b6353..117d2d549ad9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementContextFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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,12 +23,16 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.ApplicationContextFactory; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.util.ObjectUtils; /** @@ -41,7 +45,14 @@ class ReactiveManagementContextFactory implements ManagementContextFactory { @Override public ConfigurableWebServerApplicationContext createManagementContext(ApplicationContext parent, Class... configClasses) { + Environment parentEnvironment = parent.getEnvironment(); + ConfigurableEnvironment childEnvironment = ApplicationContextFactory.DEFAULT + .createEnvironment(WebApplicationType.REACTIVE); + if (parentEnvironment instanceof ConfigurableEnvironment) { + childEnvironment.setConversionService(((ConfigurableEnvironment) parentEnvironment).getConversionService()); + } AnnotationConfigReactiveWebServerApplicationContext child = new AnnotationConfigReactiveWebServerApplicationContext(); + child.setEnvironment(childEnvironment); child.setParent(parent); Class[] combinedClasses = ObjectUtils.addObjectToArray(configClasses, ReactiveWebServerFactoryAutoConfiguration.class); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementContextFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementContextFactory.java index 65ace83969c3..0f676b6669d3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementContextFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/ServletManagementContextFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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,12 +26,16 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.ApplicationContextFactory; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.util.ClassUtils; /** @@ -44,7 +48,14 @@ class ServletManagementContextFactory implements ManagementContextFactory { @Override public ConfigurableWebServerApplicationContext createManagementContext(ApplicationContext parent, Class... configClasses) { + Environment parentEnvironment = parent.getEnvironment(); + ConfigurableEnvironment childEnvironment = ApplicationContextFactory.DEFAULT + .createEnvironment(WebApplicationType.SERVLET); + if (parentEnvironment instanceof ConfigurableEnvironment) { + childEnvironment.setConversionService(((ConfigurableEnvironment) parentEnvironment).getConversionService()); + } AnnotationConfigServletWebServerApplicationContext child = new AnnotationConfigServletWebServerApplicationContext(); + child.setEnvironment(childEnvironment); child.setParent(parent); List> combinedClasses = new ArrayList<>(Arrays.asList(configClasses)); combinedClasses.add(ServletWebServerFactoryAutoConfiguration.class); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfigurationIntegrationTests.java index c9cfda2b0a8d..e785ca9d5665 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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,9 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.web.reactive; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; @@ -29,12 +33,17 @@ import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.boot.env.ConfigTreePropertySource; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.http.MediaType; +import org.springframework.util.FileCopyUtils; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; @@ -57,6 +66,9 @@ class ReactiveManagementChildContextConfigurationIntegrationTests { .withInitializer(new ServerPortInfoApplicationContextInitializer()).withPropertyValues( "server.port=0", "management.server.port=0", "management.endpoints.web.exposure.include=*"); + @TempDir + Path temp; + @Test void endpointsAreBeneathActuatorByDefault() { this.runner.withPropertyValues("management.server.port:0").run(withWebTestClient((client) -> { @@ -76,6 +88,28 @@ void whenManagementServerBasePathIsConfiguredThenEndpointsAreBeneathThatPath() { })); } + @Test // gh-32941 + void whenManagementServerPortLoadedFromConfigTree() { + this.runner.withInitializer(this::addConfigTreePropertySource) + .run((context) -> assertThat(context).hasNotFailed()); + } + + private void addConfigTreePropertySource(ConfigurableApplicationContext applicationContext) { + try { + applicationContext.getEnvironment().setConversionService( + (ConfigurableConversionService) ApplicationConversionService.getSharedInstance()); + Path configtree = this.temp.resolve("configtree"); + Path file = configtree.resolve("management/server/port"); + file.toFile().getParentFile().mkdirs(); + FileCopyUtils.copy("0".getBytes(StandardCharsets.UTF_8), file.toFile()); + ConfigTreePropertySource source = new ConfigTreePropertySource("configtree", configtree); + applicationContext.getEnvironment().getPropertySources().addFirst(source); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + private ContextConsumer withWebTestClient(Consumer webClient) { return (context) -> { String port = context.getEnvironment().getProperty("local.management.port"); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java index 24c88be5253d..4aa64c4fc858 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.autoconfigure.web.servlet; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -25,6 +28,7 @@ import javax.validation.constraints.NotEmpty; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; @@ -37,13 +41,18 @@ import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; +import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.boot.env.ConfigTreePropertySource; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.http.MediaType; +import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -75,6 +84,9 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests { "management.endpoints.web.exposure.include=*", "server.error.include-exception=true", "server.error.include-message=always", "server.error.include-binding-errors=always"); + @TempDir + Path temp; + @Test // gh-17938 void errorEndpointIsUsedWithEndpoint() { this.runner.run(withWebTestClient((client) -> { @@ -135,6 +147,28 @@ void whenManagementServerBasePathIsConfiguredThenEndpointsAreBeneathThatPath() { })); } + @Test // gh-32941 + void whenManagementServerPortLoadedFromConfigTree() { + this.runner.withInitializer(this::addConfigTreePropertySource) + .run((context) -> assertThat(context).hasNotFailed()); + } + + private void addConfigTreePropertySource(ConfigurableApplicationContext applicationContext) { + try { + applicationContext.getEnvironment().setConversionService( + (ConfigurableConversionService) ApplicationConversionService.getSharedInstance()); + Path configtree = this.temp.resolve("configtree"); + Path file = configtree.resolve("management/server/port"); + file.toFile().getParentFile().mkdirs(); + FileCopyUtils.copy("0".getBytes(StandardCharsets.UTF_8), file.toFile()); + ConfigTreePropertySource source = new ConfigTreePropertySource("configtree", configtree); + applicationContext.getEnvironment().getPropertySources().addFirst(source); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + private ContextConsumer withWebTestClient(Consumer webClient) { return (context) -> { String port = context.getEnvironment().getProperty("local.management.port");