From 3c535e0de340a718d6cf0d863a6529b314a38f42 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 20 Aug 2019 14:18:27 -0700 Subject: [PATCH] Add HealthContributor and refactor HealthEndpoint Overhaul `HealthEndpoint` support to make it easier to support health groups. Prior to this commit the `HealthIndicator` interface was used for both regular indicators and composite indicators. In addition the `Health` result was used to both represent individual, system and composite health. This design unfortunately means that all health contributors need to be aware of the `HealthAggregator` and could not easily support heath groups if per-group aggregation is required. This commit reworks many aspects of the health support in order to provide a cleaner separation between a `HealthIndicator`and a composite. The following changes have been made: - A `HealthContributor` interface has been introduced to represent the general concept of something that contributes health information. A contributor can either be a `HealthIndicator` or a `CompositeHealthContributor`. - A `HealthComponent` class has been introduced to mirror the contributor arrangement. The component can be either `CompositeHealth` or `Health`. - The `HealthAggregator` interface has been replaced with a more focused `StatusAggregator` interface which only deals with `Status` results. - `CompositeHealthIndicator` has been replaced with `CompositeHealthContributor` which only provides access to other contributors. A composite can no longer directly return `Health`. - `HealthIndicatorRegistry` has been replaced with `HealthContributorRegistry` and the default implementation now uses a copy-on-write strategy. - `HealthEndpoint`, `HealthEndpointWebExtension` and `ReactiveHealthEndpointWebExtension` now extend a common `HealthEndpointSupport` class. They are now driven by a health contributor registry and `HealthEndpointSettings`. - The `HealthStatusHttpMapper` class has been replaced by a `HttpCodeStatusMapper` interface. - The `HealthWebEndpointResponseMapper` class has been replaced by a `HealthEndpointSettings` strategy. This allows us to move role related logic and `ShowDetails` to the auto-configure module. - `SimpleHttpCodeStatusMapper` and `SimpleStatusAggregator` implementations have been added which are configured via constructor arguments rather than setters. - Endpoint auto-configuration has been reworked and the `CompositeHealthIndicatorConfiguration` class has been replaced by `CompositeHealthContributorConfiguration`. - The endpoint JSON has been changed make `details` distinct from `components`. See gh-17926 --- ...dryReactiveHealthEndpointWebExtension.java | 16 +- ...loudFoundryHealthEndpointWebExtension.java | 15 +- ...mpositeHealthContributorConfiguration.java | 73 ++++ .../AutoConfiguredHealthEndpointSettings.java | 91 ++++ ...mpositeHealthContributorConfiguration.java | 43 ++ ...CompositeHealthIndicatorConfiguration.java | 2 + ...eactiveHealthContributorConfiguration.java | 43 ++ ...eReactiveHealthIndicatorConfiguration.java | 3 + ...althAggregatorStatusAggregatorAdapter.java | 59 +++ .../HealthContributorAutoConfiguration.java | 44 ++ .../HealthEndpointAutoConfiguration.java | 10 +- .../health/HealthEndpointConfiguration.java | 58 ++- .../health/HealthEndpointProperties.java | 65 ++- ...ointReactiveWebExtensionConfiguration.java | 49 +++ ...althEndpointWebExtensionConfiguration.java | 71 +--- .../HealthIndicatorAutoConfiguration.java | 63 +-- .../health/HealthIndicatorProperties.java | 27 +- .../health/HealthIndicatorRegistryBeans.java | 65 --- ...HttpMapperHttpCodeStatusMapperAdapter.java | 43 ++ .../IncludeExcludeGroupMemberPredicate.java | 68 +++ ...cyHealthEndpointAdaptersConfiguration.java | 49 +++ ...althEndpointCompatibiltyConfiguration.java | 61 +++ .../ReactiveHealthEndpointConfiguration.java | 57 +++ .../main/resources/META-INF/spring.factories | 1 + ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...loudFoundryWebEndpointDiscovererTests.java | 7 +- ...activeHealthEndpointWebExtensionTests.java | 24 +- ...FoundryActuatorAutoConfigurationTests.java | 6 +- ...FoundryActuatorAutoConfigurationTests.java | 6 +- ...oundryHealthEndpointWebExtensionTests.java | 24 +- ...HealthIndicatorAutoConfigurationTests.java | 8 +- ...HealthIndicatorAutoConfigurationTests.java | 8 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- .../HealthEndpointDocumentationTests.java | 65 ++- ...atorAutoConfigurationIntegrationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...teHealthContributorConfigurationTests.java | 78 ++++ ...ConfiguredHealthEndpointSettingsTests.java | 122 ++++++ ...teHealthContributorConfigurationTests.java | 54 +++ ...veHealthContributorConfigurationTests.java | 57 +++ ...ggregatorStatusAggregatorAdapterTests.java | 53 +++ ...lthContributorAutoConfigurationTests.java} | 55 +-- .../HealthEndpointAutoConfigurationTests.java | 394 +++++++++++++++--- .../HealthEndpointWebExtensionTests.java | 349 ---------------- ...apperHttpCodeStatusMapperAdapterTests.java | 51 +++ ...cludeExcludeGroupMemberPredicateTests.java | 93 +++++ ...activeHealthEndpointWebExtensionTests.java | 221 ---------- ...HealthIndicatorAutoConfigurationTests.java | 8 +- .../JmxEndpointIntegrationTests.java | 3 + ...ebMvcEndpointExposureIntegrationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 8 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 8 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...mentWebSecurityAutoConfigurationTests.java | 14 +- ...mentWebSecurityAutoConfigurationTests.java | 4 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- ...HealthIndicatorAutoConfigurationTests.java | 5 +- .../health/AbstractHealthAggregator.java | 2 + .../boot/actuate/health/CompositeHealth.java | 58 +++ .../health/CompositeHealthContributor.java | 56 +++ .../CompositeHealthContributorMapAdapter.java | 35 ++ ...ositeHealthContributorReactiveAdapter.java | 67 +++ .../health/CompositeHealthIndicator.java | 2 + .../CompositeReactiveHealthContributor.java | 57 +++ ...teReactiveHealthContributorMapAdapter.java | 37 ++ .../CompositeReactiveHealthIndicator.java | 2 + .../actuate/health/ContributorRegistry.java | 50 +++ .../health/DefaultContributorRegistry.java | 114 +++++ .../DefaultHealthContributorRegistry.java | 43 ++ .../DefaultHealthIndicatorRegistry.java | 2 + ...aultReactiveHealthContributorRegistry.java | 43 ++ ...efaultReactiveHealthIndicatorRegistry.java | 2 + .../boot/actuate/health/Health.java | 30 +- .../boot/actuate/health/HealthAggregator.java | 2 + .../boot/actuate/health/HealthComponent.java | 41 ++ .../actuate/health/HealthContributor.java | 31 ++ .../health/HealthContributorNameFactory.java | 48 +++ .../health/HealthContributorRegistry.java | 27 ++ .../boot/actuate/health/HealthEndpoint.java | 69 ++- .../health/HealthEndpointSettings.java | 49 +++ .../actuate/health/HealthEndpointSupport.java | 127 ++++++ .../health/HealthEndpointWebExtension.java | 62 ++- .../boot/actuate/health/HealthIndicator.java | 19 +- .../health/HealthIndicatorNameFactory.java | 17 +- .../HealthIndicatorReactiveAdapter.java | 4 + .../health/HealthIndicatorRegistry.java | 9 +- .../HealthIndicatorRegistryFactory.java | 2 + .../health/HealthStatusHttpMapper.java | 3 + .../HealthWebEndpointResponseMapper.java | 3 + .../actuate/health/HttpCodeStatusMapper.java | 37 ++ .../boot/actuate/health/NamedContributor.java | 62 +++ .../actuate/health/NamedContributors.java | 48 +++ .../health/NamedContributorsMapAdapter.java | 78 ++++ .../health/OrderedHealthAggregator.java | 2 + .../health/ReactiveHealthContributor.java | 46 ++ .../ReactiveHealthContributorRegistry.java | 28 ++ .../ReactiveHealthEndpointWebExtension.java | 105 +++-- .../health/ReactiveHealthIndicator.java | 16 +- .../ReactiveHealthIndicatorRegistry.java | 3 + ...eactiveHealthIndicatorRegistryFactory.java | 2 + .../boot/actuate/health/ShowDetails.java | 2 + .../health/SimpleHttpCodeStatusMapper.java | 92 ++++ .../health/SimpleStatusAggregator.java | 111 +++++ .../boot/actuate/health/StatusAggregator.java | 51 +++ ...HealthContributorReactiveAdapterTests.java | 88 ++++ .../CompositeHealthContributorTests.java | 58 +++ .../health/CompositeHealthIndicatorTests.java | 1 + .../actuate/health/CompositeHealthTests.java | 68 +++ ...mpositeReactiveHealthContributorTests.java | 60 +++ ...CompositeReactiveHealthIndicatorTests.java | 1 + .../DefaultContributorRegistryTests.java | 165 ++++++++ .../DefaultHealthIndicatorRegistryTests.java | 1 + ...tReactiveHealthIndicatorRegistryTests.java | 1 + .../HealthContributorNameFactoryTests.java | 55 +++ .../health/HealthEndpointSupportTests.java | 142 +++++++ .../actuate/health/HealthEndpointTests.java | 104 ++--- .../HealthEndpointWebExtensionTests.java | 103 +++++ .../HealthEndpointWebIntegrationTests.java | 171 ++++---- .../HealthIndicatorReactiveAdapterTests.java | 1 + .../actuate/health/HealthIndicatorTests.java | 46 ++ .../boot/actuate/health/HealthTests.java | 10 + .../HealthWebEndpointResponseMapperTests.java | 1 + .../actuate/health/NamedContributorTests.java | 51 +++ .../NamedContributorsMapAdapterTests.java | 97 +++++ .../health/OrderedHealthAggregatorTests.java | 1 + .../ReactiveHealthContributorTests.java | 68 +++ ...activeHealthEndpointWebExtensionTests.java | 106 +++++ ...iveHealthIndicatorImplementationTests.java | 102 +++++ ...veHealthIndicatorRegistryFactoryTests.java | 1 + .../health/ReactiveHealthIndicatorTests.java | 79 +--- .../SimpleHttpCodeStatusMapperTests.java | 64 +++ .../health/SimpleStatusAggregatorTests.java | 70 ++++ .../boot/actuate/health/StatusTests.java | 78 ++++ .../health/TestHealthEndpointSettings.java | 53 +++ .../asciidoc/production-ready-features.adoc | 54 +-- 143 files changed, 5269 insertions(+), 1303 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettings.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicate.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibiltyConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettingsTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java rename spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/{HealthIndicatorAutoConfigurationTests.java => HealthContributorAutoConfigurationTests.java} (57%) delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicateTests.java delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointWebExtensionTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorMapAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorMapAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ContributorRegistry.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultContributorRegistry.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthContributorRegistry.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthContributorRegistry.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthComponent.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorNameFactory.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorRegistry.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSettings.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HttpCodeStatusMapper.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributors.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapter.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributorRegistry.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapper.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleStatusAggregator.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/StatusAggregator.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapterTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultContributorRegistryTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthContributorNameFactoryTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapterTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorImplementationTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapperTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleStatusAggregatorTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/StatusTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointSettings.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java index 11c9a09e6f..5646182221 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java @@ -19,13 +19,15 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension; +import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; -import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthComponent; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; -import org.springframework.boot.actuate.health.ShowDetails; /** * Reactive {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint} @@ -44,8 +46,14 @@ public class CloudFoundryReactiveHealthEndpointWebExtension { } @ReadOperation - public Mono> health() { - return this.delegate.health(null, ShowDetails.ALWAYS); + public Mono> health() { + return this.delegate.health(SecurityContext.NONE, true); + } + + @ReadOperation + public Mono> health( + @Selector(match = Match.ALL_REMAINING) String... path) { + return this.delegate.health(SecurityContext.NONE, true, path); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java index c12fb63da7..8f1328b0c5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java @@ -17,13 +17,15 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension; +import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; -import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthComponent; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; -import org.springframework.boot.actuate.health.ShowDetails; /** * {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint} that always @@ -42,8 +44,13 @@ public class CloudFoundryHealthEndpointWebExtension { } @ReadOperation - public WebEndpointResponse getHealth() { - return this.delegate.getHealth(null, ShowDetails.ALWAYS); + public WebEndpointResponse health() { + return this.delegate.health(SecurityContext.NONE, true); + } + + @ReadOperation + public WebEndpointResponse health(@Selector(match = Match.ALL_REMAINING) String... path) { + return this.delegate.health(SecurityContext.NONE, true, path); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java new file mode 100644 index 0000000000..7f6ccfd197 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.lang.reflect.Constructor; +import java.util.Map; + +import org.springframework.beans.BeanUtils; +import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; + +/** + * Base class for health contributor configurations that can combine source beans into a + * composite. + * + * @param the contributor type + * @param the health indicator type + * @param the bean type + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.2.0 + */ +public abstract class AbstractCompositeHealthContributorConfiguration { + + private final Class indicatorType; + + private final Class beanType; + + AbstractCompositeHealthContributorConfiguration() { + ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class, + getClass()); + this.indicatorType = type.resolveGeneric(1); + this.beanType = type.resolveGeneric(2); + + } + + protected final C createContributor(Map beans) { + Assert.notEmpty(beans, "Beans must not be empty"); + if (beans.size() == 1) { + return createIndicator(beans.values().iterator().next()); + } + return createComposite(beans); + } + + protected abstract C createComposite(Map beans); + + @SuppressWarnings("unchecked") + protected I createIndicator(B bean) { + try { + Constructor constructor = (Constructor) this.indicatorType.getDeclaredConstructor(this.beanType); + return BeanUtils.instantiateClass(constructor, bean); + } + catch (Exception ex) { + throw new IllegalStateException( + "Unable to create health indicator " + this.indicatorType + " for bean type " + this.beanType, ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettings.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettings.java new file mode 100644 index 0000000000..47dc3c845c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettings.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Collection; + +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails; +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.health.HealthEndpointSettings; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.StatusAggregator; +import org.springframework.util.CollectionUtils; + +/** + * Auto-configured {@link HealthEndpointSettings} backed by + * {@link HealthEndpointProperties}. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +class AutoConfiguredHealthEndpointSettings implements HealthEndpointSettings { + + private final StatusAggregator statusAggregator; + + private final HttpCodeStatusMapper httpCodeStatusMapper; + + private final ShowDetails showDetails; + + private final Collection roles; + + /** + * Create a new {@link AutoConfiguredHealthEndpointSettings} instance. + * @param statusAggregator the status aggregator to use + * @param httpCodeStatusMapper the HTTP code status mapper to use + * @param showDetails the show details setting + * @param roles the roles to match + */ + AutoConfiguredHealthEndpointSettings(StatusAggregator statusAggregator, HttpCodeStatusMapper httpCodeStatusMapper, + ShowDetails showDetails, Collection roles) { + this.statusAggregator = statusAggregator; + this.httpCodeStatusMapper = httpCodeStatusMapper; + this.showDetails = showDetails; + this.roles = roles; + } + + @Override + public boolean includeDetails(SecurityContext securityContext) { + ShowDetails showDetails = this.showDetails; + switch (showDetails) { + case NEVER: + return false; + case ALWAYS: + return true; + case WHEN_AUTHORIZED: + return isAuthorized(securityContext); + } + throw new IllegalStateException("Unsupported ShowDetails value " + showDetails); + } + + private boolean isAuthorized(SecurityContext securityContext) { + if (securityContext.getPrincipal() == null) { + return false; + } + return CollectionUtils.isEmpty(this.roles) || this.roles.stream().anyMatch(securityContext::isUserInRole); + } + + @Override + public StatusAggregator getStatusAggregator() { + return this.statusAggregator; + } + + @Override + public HttpCodeStatusMapper getHttpCodeStatusMapper() { + return this.httpCodeStatusMapper; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java new file mode 100644 index 0000000000..8ac500c7af --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Map; + +import org.springframework.boot.actuate.health.CompositeHealthContributor; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthIndicator; + +/** + * Base class for health contributor configurations that can combine source beans into a + * composite. + * + * @param the health indicator type + * @param the bean type + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.2.0 + */ +public abstract class CompositeHealthContributorConfiguration + extends AbstractCompositeHealthContributorConfiguration { + + @Override + protected final HealthContributor createComposite(Map beans) { + return CompositeHealthContributor.fromMap(beans, this::createIndicator); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java index 85b713751e..c159c78dac 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java @@ -34,7 +34,9 @@ import org.springframework.core.ResolvableType; * @param the bean source type * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 2.0.0 in favor of {@link CompositeHealthContributorConfiguration} */ +@Deprecated public abstract class CompositeHealthIndicatorConfiguration { @Autowired diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java new file mode 100644 index 0000000000..8d14c0d4d8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Map; + +import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; + +/** + * Base class for health contributor configurations that can combine source beans into a + * composite. + * + * @param the health indicator type + * @param the bean type + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.2.0 + */ +public abstract class CompositeReactiveHealthContributorConfiguration + extends AbstractCompositeHealthContributorConfiguration { + + @Override + protected final ReactiveHealthContributor createComposite(Map beans) { + return CompositeReactiveHealthContributor.fromMap(beans, this::createIndicator); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java index 629c65539c..d3ca609aeb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java @@ -33,7 +33,10 @@ import org.springframework.core.ResolvableType; * @param the bean source type * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 2.2.0 in favor of + * {@link CompositeReactiveHealthContributorConfiguration} */ +@Deprecated public abstract class CompositeReactiveHealthIndicatorConfiguration { @Autowired diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java new file mode 100644 index 0000000000..ac52d400f4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.health.StatusAggregator; + +/** + * Adapter class to convert a legacy {@link HealthAggregator} to a + * {@link StatusAggregator}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthAggregatorStatusAggregatorAdapter implements StatusAggregator { + + private HealthAggregator healthAggregator; + + HealthAggregatorStatusAggregatorAdapter(HealthAggregator healthAggregator) { + this.healthAggregator = healthAggregator; + } + + @Override + public Status getAggregateStatus(Set statuses) { + int index = 0; + Map healths = new LinkedHashMap(); + for (Status status : statuses) { + index++; + healths.put("health" + index, asHealth(status)); + } + Health aggregate = this.healthAggregator.aggregate(healths); + return aggregate.getStatus(); + } + + private Health asHealth(Status status) { + return Health.status(status).build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfiguration.java new file mode 100644 index 0000000000..a1e91c5c1f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.ApplicationHealthIndicator; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthContributor health + * contributors}. Technology specific auto-configurations should be ordered before this + * auto-configuration. + * + * @author Phillip Webb + * @since 2.2.0 + */ +@Configuration(proxyBeanMethods = false) +public class HealthContributorAutoConfiguration { + + @Bean + @ConditionalOnMissingBean({ HealthContributor.class, ReactiveHealthContributor.class }) + public ApplicationHealthIndicator applicationHealthContributor() { + return new ApplicationHealthIndicator(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java index 1b02e8a50c..46503375c2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.health; +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -32,9 +33,12 @@ import org.springframework.context.annotation.Import; * @since 2.0.0 */ @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ HealthEndpointProperties.class, HealthIndicatorProperties.class }) -@AutoConfigureAfter(HealthIndicatorAutoConfiguration.class) -@Import({ HealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class }) +@EnableConfigurationProperties(HealthEndpointProperties.class) +@AutoConfigureAfter(HealthContributorAutoConfiguration.class) +@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) +@Import({ LegacyHealthEndpointAdaptersConfiguration.class, LegacyHealthEndpointCompatibiltyConfiguration.class, + HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, + HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class }) public class HealthEndpointAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java index 8e2cfde2a4..cad0b861b0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java @@ -16,30 +16,66 @@ package org.springframework.boot.actuate.autoconfigure.health; -import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; -import org.springframework.boot.actuate.health.HealthAggregator; +import java.util.Map; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthEndpoint; -import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.HealthEndpointSettings; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper; +import org.springframework.boot.actuate.health.SimpleStatusAggregator; +import org.springframework.boot.actuate.health.StatusAggregator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * Configuration for {@link HealthEndpoint}. + * Configuration for {@link HealthEndpoint} infrastructure beans. * - * @author Stephane Nicoll + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration */ @Configuration(proxyBeanMethods = false) -@ConditionalOnSingleCandidate(HealthIndicatorRegistry.class) -@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) class HealthEndpointConfiguration { @Bean @ConditionalOnMissingBean - HealthEndpoint healthEndpoint(HealthAggregator healthAggregator, HealthIndicatorRegistry registry) { - return new HealthEndpoint(new CompositeHealthIndicator(healthAggregator, registry)); + StatusAggregator healthStatusAggregator(HealthEndpointProperties properties) { + return new SimpleStatusAggregator(properties.getStatus().getOrder()); + } + + @Bean + @ConditionalOnMissingBean + HttpCodeStatusMapper healthHttpCodeStatusMapper(HealthEndpointProperties properties) { + return new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping()); + } + + @Bean + @ConditionalOnMissingBean + HealthEndpointSettings healthEndpointSettings(HealthEndpointProperties properties, + ObjectProvider statusAggregatorProvider, + ObjectProvider httpCodeStatusMapperProvider) { + StatusAggregator statusAggregator = statusAggregatorProvider + .getIfAvailable(() -> new SimpleStatusAggregator(properties.getStatus().getOrder())); + HttpCodeStatusMapper httpCodeStatusMapper = httpCodeStatusMapperProvider + .getIfAvailable(() -> new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping())); + return new AutoConfiguredHealthEndpointSettings(statusAggregator, httpCodeStatusMapper, + properties.getShowDetails(), properties.getRoles()); + } + + @Bean + @ConditionalOnMissingBean + HealthContributorRegistry healthContributorRegistry(Map healthContributors) { + return new DefaultHealthContributorRegistry(healthContributors); + } + + @Bean + @ConditionalOnMissingBean + HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointSettings settings) { + return new HealthEndpoint(registry, settings); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointProperties.java index 224e8b8506..12dfc3b8e2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointProperties.java @@ -16,11 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.health; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import org.springframework.boot.actuate.health.HealthEndpoint; -import org.springframework.boot.actuate.health.ShowDetails; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -32,6 +34,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("management.endpoint.health") public class HealthEndpointProperties { + private final Status status = new Status(); + /** * When to show full health details. */ @@ -43,6 +47,10 @@ public class HealthEndpointProperties { */ private Set roles = new HashSet<>(); + public Status getStatus() { + return this.status; + } + public ShowDetails getShowDetails() { return this.showDetails; } @@ -59,4 +67,59 @@ public class HealthEndpointProperties { this.roles = roles; } + /** + * Status properties for the group. + */ + public static class Status { + + /** + * Comma-separated list of health statuses in order of severity. + */ + private List order = null; + + /** + * Mapping of health statuses to HTTP status codes. By default, registered health + * statuses map to sensible defaults (for example, UP maps to 200). + */ + private final Map httpMapping = new HashMap<>(); + + public List getOrder() { + return this.order; + } + + public void setOrder(List statusOrder) { + if (statusOrder != null && !statusOrder.isEmpty()) { + this.order = statusOrder; + } + } + + public Map getHttpMapping() { + return this.httpMapping; + } + + } + + /** + * Options for showing details in responses from the {@link HealthEndpoint} web + * extensions. + */ + public enum ShowDetails { + + /** + * Never show details in the response. + */ + NEVER, + + /** + * Show details in the response when accessed by an authorized user. + */ + WHEN_AUTHORIZED, + + /** + * Always show details in the response. + */ + ALWAYS + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java new file mode 100644 index 0000000000..38d9168b55 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.actuate.health.HealthEndpointSettings; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration for {@link HealthEndpoint} reactive web extensions. + * + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnWebApplication(type = Type.REACTIVE) +@ConditionalOnBean(HealthEndpoint.class) +class HealthEndpointReactiveWebExtensionConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(HealthEndpoint.class) + ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension( + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointSettings settings) { + return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, settings); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java index af893e2ef8..1b2221427e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java @@ -16,83 +16,34 @@ package org.springframework.boot.actuate.autoconfigure.health; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; -import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicator; -import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.actuate.health.HealthEndpointSettings; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; -import org.springframework.boot.actuate.health.HealthStatusHttpMapper; -import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; -import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * Configuration for health endpoint web extensions. + * Configuration for {@link HealthEndpoint} web extensions. * - * @author Stephane Nicoll + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration */ @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(HealthIndicatorProperties.class) -@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) +@ConditionalOnWebApplication(type = Type.SERVLET) +@ConditionalOnBean(HealthEndpoint.class) class HealthEndpointWebExtensionConfiguration { @Bean + @ConditionalOnBean(HealthEndpoint.class) @ConditionalOnMissingBean - HealthStatusHttpMapper createHealthStatusHttpMapper(HealthIndicatorProperties healthIndicatorProperties) { - HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper(); - if (healthIndicatorProperties.getHttpMapping() != null) { - statusHttpMapper.addStatusMapping(healthIndicatorProperties.getHttpMapping()); - } - return statusHttpMapper; - } - - @Bean - @ConditionalOnMissingBean - HealthWebEndpointResponseMapper healthWebEndpointResponseMapper(HealthStatusHttpMapper statusHttpMapper, - HealthEndpointProperties properties) { - return new HealthWebEndpointResponseMapper(statusHttpMapper, properties.getShowDetails(), - properties.getRoles()); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnWebApplication(type = Type.REACTIVE) - @ConditionalOnSingleCandidate(ReactiveHealthIndicatorRegistry.class) - static class ReactiveWebHealthConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(HealthEndpoint.class) - ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension( - ObjectProvider healthAggregator, ReactiveHealthIndicatorRegistry registry, - HealthWebEndpointResponseMapper responseMapper) { - return new ReactiveHealthEndpointWebExtension(new CompositeReactiveHealthIndicator( - healthAggregator.getIfAvailable(OrderedHealthAggregator::new), registry), responseMapper); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnWebApplication(type = Type.SERVLET) - static class ServletWebHealthConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(HealthEndpoint.class) - HealthEndpointWebExtension healthEndpointWebExtension(HealthEndpoint healthEndpoint, - HealthWebEndpointResponseMapper responseMapper) { - return new HealthEndpointWebExtension(healthEndpoint, responseMapper); - } - + HealthEndpointWebExtension healthEndpointWebExtension(HealthContributorRegistry healthContributorRegistry, + HealthEndpointSettings settings) { + return new HealthEndpointWebExtension(healthContributorRegistry, settings); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java index b680c9d220..3861fd0054 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java @@ -16,74 +16,25 @@ package org.springframework.boot.actuate.autoconfigure.health; -import java.util.Map; - -import reactor.core.publisher.Flux; - -import org.springframework.boot.actuate.health.ApplicationHealthIndicator; -import org.springframework.boot.actuate.health.HealthAggregator; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorRegistry; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.actuate.health.ReactiveHealthIndicator; -import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; -import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistryFactory; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. + * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthContributor health + * contributors}. * * @author Andy Wilkinson * @author Stephane Nicoll * @author Phillip Webb * @author Vedran Pavic * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthContributorAutoConfiguration} */ +@Deprecated @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ HealthIndicatorProperties.class }) +@AutoConfigureBefore(HealthContributorAutoConfiguration.class) public class HealthIndicatorAutoConfiguration { - @Bean - @ConditionalOnMissingBean({ HealthIndicator.class, ReactiveHealthIndicator.class }) - public ApplicationHealthIndicator applicationHealthIndicator() { - return new ApplicationHealthIndicator(); - } - - @Bean - @ConditionalOnMissingBean(HealthAggregator.class) - public OrderedHealthAggregator healthAggregator(HealthIndicatorProperties properties) { - OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator(); - if (properties.getOrder() != null) { - healthAggregator.setStatusOrder(properties.getOrder()); - } - return healthAggregator; - } - - @Bean - @ConditionalOnMissingBean(HealthIndicatorRegistry.class) - public HealthIndicatorRegistry healthIndicatorRegistry(ApplicationContext applicationContext) { - return HealthIndicatorRegistryBeans.get(applicationContext); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Flux.class) - static class ReactiveHealthIndicatorConfiguration { - - @Bean - @ConditionalOnMissingBean - ReactiveHealthIndicatorRegistry reactiveHealthIndicatorRegistry( - Map reactiveHealthIndicators, - Map healthIndicators) { - return new ReactiveHealthIndicatorRegistryFactory() - .createReactiveHealthIndicatorRegistry(reactiveHealthIndicators, healthIndicators); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java index 79bccb47d4..8294b2099a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java @@ -16,44 +16,41 @@ package org.springframework.boot.actuate.autoconfigure.health; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; /** * Configuration properties for some health properties. * * @author Christian Dupuis * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthEndpointProperties} */ +@Deprecated @ConfigurationProperties(prefix = "management.health.status") public class HealthIndicatorProperties { - /** - * Comma-separated list of health statuses in order of severity. - */ - private List order = null; + private final HealthEndpointProperties healthEndpointProperties; - /** - * Mapping of health statuses to HTTP status codes. By default, registered health - * statuses map to sensible defaults (for example, UP maps to 200). - */ - private final Map httpMapping = new HashMap<>(); + HealthIndicatorProperties(HealthEndpointProperties healthEndpointProperties) { + this.healthEndpointProperties = healthEndpointProperties; + } + @DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.order") public List getOrder() { - return this.order; + return this.healthEndpointProperties.getStatus().getOrder(); } public void setOrder(List statusOrder) { - if (statusOrder != null && !statusOrder.isEmpty()) { - this.order = statusOrder; - } + this.healthEndpointProperties.getStatus().setOrder(statusOrder); } + @DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.http-mapping") public Map getHttpMapping() { - return this.httpMapping; + return this.healthEndpointProperties.getStatus().getHttpMapping(); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java deleted file mode 100644 index 6d8f4fb3d4..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.health; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorRegistry; -import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory; -import org.springframework.boot.actuate.health.ReactiveHealthIndicator; -import org.springframework.context.ApplicationContext; -import org.springframework.util.ClassUtils; - -/** - * Creates a {@link HealthIndicatorRegistry} from beans in the {@link ApplicationContext}. - * - * @author Phillip Webb - * @author Stephane Nicoll - */ -final class HealthIndicatorRegistryBeans { - - private HealthIndicatorRegistryBeans() { - } - - static HealthIndicatorRegistry get(ApplicationContext applicationContext) { - Map indicators = new LinkedHashMap<>( - applicationContext.getBeansOfType(HealthIndicator.class)); - if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) { - new ReactiveHealthIndicators().get(applicationContext).forEach(indicators::putIfAbsent); - } - HealthIndicatorRegistryFactory factory = new HealthIndicatorRegistryFactory(); - return factory.createHealthIndicatorRegistry(indicators); - } - - private static class ReactiveHealthIndicators { - - Map get(ApplicationContext applicationContext) { - Map indicators = new LinkedHashMap<>(); - applicationContext.getBeansOfType(ReactiveHealthIndicator.class) - .forEach((name, indicator) -> indicators.put(name, adapt(indicator))); - return indicators; - } - - private HealthIndicator adapt(ReactiveHealthIndicator indicator) { - return () -> indicator.health().block(); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java new file mode 100644 index 0000000000..fcade05e3c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.Status; + +/** + * Adapter class to convert a legacy {@link HealthStatusHttpMapper} to a + * {@link HttpCodeStatusMapper}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthStatusHttpMapperHttpCodeStatusMapperAdapter implements HttpCodeStatusMapper { + + private final HealthStatusHttpMapper healthStatusHttpMapper; + + HealthStatusHttpMapperHttpCodeStatusMapperAdapter(HealthStatusHttpMapper healthStatusHttpMapper) { + this.healthStatusHttpMapper = healthStatusHttpMapper; + } + + @Override + public int getStatusCode(Status status) { + return this.healthStatusHttpMapper.mapStatus(status); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicate.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicate.java new file mode 100644 index 0000000000..8cee26e099 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicate.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Predicate; + +/** + * Member predicate that matches based on {@code include} and {@code exclude} sets. + * + * @author Phillip Webb + */ +class IncludeExcludeGroupMemberPredicate implements Predicate { + + private final Set include; + + private final Set exclude; + + IncludeExcludeGroupMemberPredicate(Set include, Set exclude) { + this.include = clean(include); + this.exclude = clean(exclude); + } + + @Override + public boolean test(String name) { + return isIncluded(name) && !isExcluded(name); + } + + private boolean isIncluded(String name) { + return this.include.contains("*") || this.include.contains(clean(name)); + } + + private boolean isExcluded(String name) { + return this.exclude.contains("*") || this.exclude.contains(clean(name)); + } + + private Set clean(Set names) { + if (names == null) { + return Collections.emptySet(); + } + Set cleaned = new LinkedHashSet<>(names.size()); + for (String name : names) { + cleaned.add(name); + } + return Collections.unmodifiableSet(cleaned); + } + + private String clean(String name) { + return name.trim().toLowerCase(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java new file mode 100644 index 0000000000..e04f0a2c5a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.StatusAggregator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to adapt legacy deprecated health endpoint classes and interfaces. + * + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration + */ +@Configuration(proxyBeanMethods = false) +@SuppressWarnings("deprecation") +class LegacyHealthEndpointAdaptersConfiguration { + + @Bean + @ConditionalOnBean(org.springframework.boot.actuate.health.HealthAggregator.class) + StatusAggregator healthAggregatorStatusAggregatorAdapter( + org.springframework.boot.actuate.health.HealthAggregator healthAggregator) { + return new HealthAggregatorStatusAggregatorAdapter(healthAggregator); + } + + @Bean + @ConditionalOnBean(org.springframework.boot.actuate.health.HealthStatusHttpMapper.class) + HttpCodeStatusMapper healthStatusHttpMapperHttpCodeStatusMapperAdapter( + org.springframework.boot.actuate.health.HealthStatusHttpMapper healthStatusHttpMapper) { + return new HealthStatusHttpMapperHttpCodeStatusMapperAdapter(healthStatusHttpMapper); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibiltyConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibiltyConfiguration.java new file mode 100644 index 0000000000..a499b95d06 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibiltyConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to adapt legacy deprecated health endpoint classes and interfaces. + * + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration + */ +@Configuration(proxyBeanMethods = false) +@SuppressWarnings("deprecation") +class LegacyHealthEndpointCompatibiltyConfiguration { + + @Bean + HealthIndicatorProperties healthIndicatorProperties(HealthEndpointProperties healthEndpointProperties) { + return new HealthIndicatorProperties(healthEndpointProperties); + } + + @Bean + @ConditionalOnMissingBean + HealthAggregator healthAggregator(HealthIndicatorProperties healthIndicatorProperties) { + OrderedHealthAggregator aggregator = new OrderedHealthAggregator(); + if (healthIndicatorProperties.getOrder() != null) { + aggregator.setStatusOrder(healthIndicatorProperties.getOrder()); + } + return aggregator; + } + + @Bean + @ConditionalOnMissingBean + HealthStatusHttpMapper healthStatusHttpMapper(HealthIndicatorProperties healthIndicatorProperties) { + HealthStatusHttpMapper mapper = new HealthStatusHttpMapper(); + if (healthIndicatorProperties.getHttpMapping() != null) { + mapper.setStatusMapping(healthIndicatorProperties.getHttpMapping()); + } + return mapper; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointConfiguration.java new file mode 100644 index 0000000000..624ed26f64 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import reactor.core.publisher.Flux; + +import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration for reactive {@link HealthEndpoint} infrastructure beans. + * + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(Flux.class) +@ConditionalOnBean(HealthEndpoint.class) +class ReactiveHealthEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry( + Map healthContributors, + Map reactiveHealthContributors) { + Map allContributors = new LinkedHashMap<>(reactiveHealthContributors); + healthContributors.forEach((name, contributor) -> allContributors.computeIfAbsent(name, + (key) -> ReactiveHealthContributor.adapt(contributor))); + return new DefaultReactiveHealthContributorRegistry(allContributors); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index cdfd9396c3..469d05b27f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -21,6 +21,7 @@ org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfi org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.influx.InfluxDbHealthIndicatorAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/amqp/RabbitHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/amqp/RabbitHealthIndicatorAutoConfigurationTests.java index 9d8fca1a20..810b1b1daf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/amqp/RabbitHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/amqp/RabbitHealthIndicatorAutoConfigurationTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.amqp; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.amqp.RabbitHealthIndicator; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -36,7 +38,8 @@ class RabbitHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, - RabbitHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + RabbitHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthIndicatorAutoConfigurationTests.java index ffe24123c5..3a70183a96 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.cassandra; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; @@ -40,7 +42,8 @@ class CassandraHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class, - CassandraHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + CassandraHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthIndicatorAutoConfigurationTests.java index 1e9aaa9dc2..f15d44d384 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cassandra/CassandraReactiveHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.cassandra; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator; @@ -40,7 +42,8 @@ class CassandraReactiveHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class)) .withConfiguration(AutoConfigurations.of(CassandraReactiveHealthIndicatorAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class)); + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index 4a3117554b..84b371eab0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -35,8 +35,9 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; +import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthEndpoint; -import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthEndpointSettings; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -110,7 +111,9 @@ class CloudFoundryWebEndpointDiscovererTests { @Bean HealthEndpoint healthEndpoint() { - return new HealthEndpoint(mock(HealthIndicator.class)); + HealthContributorRegistry registry = mock(HealthContributorRegistry.class); + HealthEndpointSettings settings = mock(HealthEndpointSettings.class); + return new HealthEndpoint(registry, settings); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java index da109ef8c5..0890ce3d0f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java @@ -22,9 +22,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.health.CompositeHealth; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthComponent; +import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -53,16 +57,28 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests { ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - ReactiveCloudFoundryActuatorAutoConfiguration.class)); + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, + ReactiveCloudFoundryActuatorAutoConfiguration.class)) + .withUserConfiguration(TestHealthIndicator.class); @Test void healthDetailsAlwaysPresent() { this.contextRunner.run((context) -> { CloudFoundryReactiveHealthEndpointWebExtension extension = context .getBean(CloudFoundryReactiveHealthEndpointWebExtension.class); - assertThat(extension.health().block(Duration.ofSeconds(30)).getBody().getDetails()).isNotEmpty(); + HealthComponent body = extension.health().block(Duration.ofSeconds(30)).getBody(); + HealthComponent health = ((CompositeHealth) body).getDetails().entrySet().iterator().next().getValue(); + assertThat(((Health) health).getDetails()).containsEntry("spring", "boot"); }); } + private static class TestHealthIndicator implements HealthIndicator { + + @Override + public Health health() { + return Health.up().withDetail("spring", "boot").build(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index e0d6068492..ce7f8aac15 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -32,8 +32,8 @@ import reactor.netty.http.HttpResources; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryInfoEndpointWebExtension; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; @@ -87,7 +87,7 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); @@ -237,7 +237,7 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { .run((context) -> { Collection endpoints = getHandlerMapping(context).getEndpoints(); ExposableWebEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getOperations()).hasSize(3); + assertThat(endpoint.getOperations()).hasSize(2); WebOperation webOperation = findOperationWithRequestPath(endpoint, "health"); Object invoker = ReflectionTestUtils.getField(webOperation, "invoker"); assertThat(ReflectionTestUtils.getField(invoker, "target")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java index eae2dc9490..dea8001a8d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.EndpointId; @@ -220,7 +220,7 @@ class CloudFoundryActuatorAutoConfigurationTests { this.contextRunner .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", "vcap.application.cf_api:https://my-cloud-controller.com") - .withConfiguration(AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) .run((context) -> { Collection endpoints = context @@ -228,7 +228,7 @@ class CloudFoundryActuatorAutoConfigurationTests { CloudFoundryWebEndpointServletHandlerMapping.class) .getEndpoints(); ExposableWebEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getOperations()).hasSize(3); + assertThat(endpoint.getOperations()).hasSize(2); WebOperation webOperation = findOperationWithRequestPath(endpoint, "health"); Object invoker = ReflectionTestUtils.getField(webOperation, "invoker"); assertThat(ReflectionTestUtils.getField(invoker, "target")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java index 6c50c1f181..9db6e3df49 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtensionTests.java @@ -20,10 +20,14 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; +import org.springframework.boot.actuate.health.CompositeHealth; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthComponent; +import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -50,16 +54,28 @@ class CloudFoundryHealthEndpointWebExtensionTests { HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, RestTemplateAutoConfiguration.class, ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, - HealthEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class)); + WebEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class)) + .withUserConfiguration(TestHealthIndicator.class); @Test void healthDetailsAlwaysPresent() { this.contextRunner.run((context) -> { CloudFoundryHealthEndpointWebExtension extension = context .getBean(CloudFoundryHealthEndpointWebExtension.class); - assertThat(extension.getHealth().getBody().getDetails()).isNotEmpty(); + HealthComponent body = extension.health().getBody(); + HealthComponent health = ((CompositeHealth) body).getDetails().entrySet().iterator().next().getValue(); + assertThat(((Health) health).getDetails()).containsEntry("spring", "boot"); }); } + private static class TestHealthIndicator implements HealthIndicator { + + @Override + public Health health() { + return Health.up().withDetail("spring", "boot").build(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseHealthIndicatorAutoConfigurationTests.java index 7228dbb03e..f9940eff5c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.couchbase; import com.couchbase.client.java.Cluster; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator; import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator; @@ -37,8 +39,10 @@ import static org.mockito.Mockito.mock; class CouchbaseHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(Cluster.class, () -> mock(Cluster.class)).withConfiguration(AutoConfigurations - .of(CouchbaseHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + .withBean(Cluster.class, () -> mock(Cluster.class)) + .withConfiguration(AutoConfigurations.of(CouchbaseHealthIndicatorAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseReactiveHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseReactiveHealthIndicatorAutoConfigurationTests.java index 32613f5713..5d78e844a5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseReactiveHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/couchbase/CouchbaseReactiveHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.couchbase; import com.couchbase.client.java.Cluster; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator; import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator; @@ -36,8 +38,10 @@ import static org.mockito.Mockito.mock; class CouchbaseReactiveHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(Cluster.class, () -> mock(Cluster.class)).withConfiguration(AutoConfigurations.of( - CouchbaseReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + .withBean(Cluster.class, () -> mock(Cluster.class)) + .withConfiguration(AutoConfigurations.of(CouchbaseReactiveHealthIndicatorAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchHealthIndicatorAutoConfigurationTests.java index 2044e1ea6c..e1666049b2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchHealthIndicatorAutoConfigurationTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.elasticsearch; import io.searchbox.client.JestClient; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator; import org.springframework.boot.actuate.elasticsearch.ElasticsearchJestHealthIndicator; @@ -41,7 +43,8 @@ class ElasticsearchHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations .of(ElasticsearchAutoConfiguration.class, ElasticSearchClientHealthIndicatorAutoConfiguration.class, - ElasticSearchJestHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + ElasticSearchJestHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java index 5c3f7763b5..91a8c9427b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java @@ -26,12 +26,19 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.health.CompositeHealthContributor; +import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry; import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.actuate.health.HealthEndpointSettings; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper; +import org.springframework.boot.actuate.health.SimpleStatusAggregator; +import org.springframework.boot.actuate.health.StatusAggregator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -63,14 +70,18 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests @Test void health() throws Exception { + FieldDescriptor status = fieldWithPath("status").description("Overall status of the application."); + FieldDescriptor components = fieldWithPath("details").description("The components that make up the health."); + FieldDescriptor componentStatus = fieldWithPath("details.*.status") + .description("Status of a specific part of the application."); + FieldDescriptor componentDetails = subsectionWithPath("details.*.details") + .description("Details of the health of a specific part of the application. " + + "Presence is controlled by `management.endpoint.health.show-details`.") + .optional(); + FieldDescriptor nestedComponents = subsectionWithPath("details.*.details") + .description("Nested components that make up the health.").optional(); this.mockMvc.perform(get("/actuator/health")).andExpect(status().isOk()).andDo(document("health", - responseFields(fieldWithPath("status").description("Overall status of the application."), - fieldWithPath("details") - .description("Details of the health of the application. Presence is controlled by " - + "`management.endpoint.health.show-details`)."), - fieldWithPath("details.*.status").description("Status of a specific part of the application."), - subsectionWithPath("details.*.details") - .description("Details of the health of a specific part of the application.")))); + responseFields(status, components, componentStatus, componentDetails, nestedComponents))); } @Test @@ -91,9 +102,10 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests static class TestConfiguration { @Bean - HealthEndpoint endpoint(Map healthIndicators) { - return new HealthEndpoint(new CompositeHealthIndicator(new OrderedHealthAggregator(), - new HealthIndicatorRegistryFactory().createHealthIndicatorRegistry(healthIndicators))); + HealthEndpoint healthEndpoint(Map healthContributors) { + HealthContributorRegistry registry = new DefaultHealthContributorRegistry(healthContributors); + HealthEndpointSettings settings = new TestHealthEndpointSettings(); + return new HealthEndpoint(registry, settings); } @Bean @@ -107,11 +119,34 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests } @Bean - CompositeHealthIndicator brokerHealthIndicator() { + CompositeHealthContributor brokerHealthContributor() { Map indicators = new LinkedHashMap<>(); indicators.put("us1", () -> Health.up().withDetail("version", "1.0.2").build()); indicators.put("us2", () -> Health.up().withDetail("version", "1.0.4").build()); - return new CompositeHealthIndicator(new OrderedHealthAggregator(), indicators); + return CompositeHealthContributor.fromMap(indicators); + } + + } + + private static class TestHealthEndpointSettings implements HealthEndpointSettings { + + private final StatusAggregator statusAggregator = new SimpleStatusAggregator(); + + private final HttpCodeStatusMapper httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(); + + @Override + public boolean includeDetails(SecurityContext securityContext) { + return true; + } + + @Override + public StatusAggregator getStatusAggregator() { + return this.statusAggregator; + } + + @Override + public HttpCodeStatusMapper getHttpCodeStatusMapper() { + return this.httpCodeStatusMapper; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java index da8cb533b3..551a92c8a6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.hazelcast; import com.hazelcast.core.HazelcastInstance; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; import org.springframework.boot.actuate.health.Health; @@ -38,7 +40,8 @@ class HazelcastHealthIndicatorAutoConfigurationIntegrationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(HazelcastHealthIndicatorAutoConfiguration.class, - HazelcastAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + HazelcastAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void hazelcastUp() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java index 3e9e980776..6b4c888ee3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.hazelcast; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; @@ -36,7 +38,8 @@ class HazelcastHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class, - HazelcastHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + HazelcastHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfigurationTests.java new file mode 100644 index 0000000000..ce2b322614 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AbstractCompositeHealthContributorConfigurationTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.lettuce.core.dynamic.support.ResolvableType; +import org.junit.jupiter.api.Test; + +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link AbstractCompositeHealthContributorConfiguration}. + * + * @param the contributor type + * @param the health indicator type + * @author Phillip Webb + */ +abstract class AbstractCompositeHealthContributorConfigurationTests { + + private final Class indicatorType; + + AbstractCompositeHealthContributorConfigurationTests() { + ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfigurationTests.class, + getClass()); + this.indicatorType = type.resolveGeneric(1); + } + + @Test + void createContributorWhenBeansIsEmptyThrowsException() { + Map beans = Collections.emptyMap(); + assertThatIllegalArgumentException().isThrownBy(() -> newComposite().createContributor(beans)) + .withMessage("Beans must not be empty"); + } + + @Test + void createContributorWhenBeansHasSingleElementCreatesIndicator() { + Map beans = Collections.singletonMap("test", new TestBean()); + C contributor = newComposite().createContributor(beans); + assertThat(contributor).isInstanceOf(this.indicatorType); + } + + @Test + void createContributorWhenBeansHasMultipleElementsCreatesComposite() { + Map beans = new LinkedHashMap<>(); + beans.put("test1", new TestBean()); + beans.put("test2", new TestBean()); + C contributor = newComposite().createContributor(beans); + assertThat(contributor).isNotInstanceOf(this.indicatorType); + assertThat(ClassUtils.getShortName(contributor.getClass())).startsWith("Composite"); + } + + protected abstract AbstractCompositeHealthContributorConfiguration newComposite(); + + static class TestBean { + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettingsTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettingsTests.java new file mode 100644 index 0000000000..cc6353de56 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettingsTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails; +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.StatusAggregator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Tests for {@link AutoConfiguredHealthEndpointSettings}. + * + * @author Phillip Webb + */ +class AutoConfiguredHealthEndpointSettingsTests { + + @Mock + private StatusAggregator statusAggregator; + + @Mock + private HttpCodeStatusMapper httpCodeStatusMapper; + + @Mock + private SecurityContext securityContext; + + @Mock + private Principal principal; + + @BeforeEach + void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + void includeDetailsWhenShowDetailsIsNeverReturnsFalse() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.NEVER, Collections.emptySet()); + assertThat(settings.includeDetails(SecurityContext.NONE)).isFalse(); + } + + @Test + void includeDetailsWhenShowDetailsIsAlwaysReturnsTrue() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + assertThat(settings.includeDetails(SecurityContext.NONE)).isTrue(); + } + + @Test + void includeDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet()); + given(this.securityContext.getPrincipal()).willReturn(null); + assertThat(settings.includeDetails(this.securityContext)).isFalse(); + } + + @Test + void includeDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet()); + given(this.securityContext.getPrincipal()).willReturn(this.principal); + assertThat(settings.includeDetails(this.securityContext)).isTrue(); + } + + @Test + void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Arrays.asList("admin", "root", "bossmode")); + given(this.securityContext.getPrincipal()).willReturn(this.principal); + given(this.securityContext.isUserInRole("root")).willReturn(true); + assertThat(settings.includeDetails(this.securityContext)).isTrue(); + } + + @Test + void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Arrays.asList("admin", "rot", "bossmode")); + given(this.securityContext.getPrincipal()).willReturn(this.principal); + given(this.securityContext.isUserInRole("root")).willReturn(true); + assertThat(settings.includeDetails(this.securityContext)).isFalse(); + } + + @Test + void getStatusAggregatorReturnsStatusAggregator() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + assertThat(settings.getStatusAggregator()).isSameAs(this.statusAggregator); + } + + @Test + void getHttpCodeStatusMapperReturnsHttpCodeStatusMapper() { + AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator, + this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet()); + assertThat(settings.getHttpCodeStatusMapper()).isSameAs(this.httpCodeStatusMapper); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationTests.java new file mode 100644 index 0000000000..ceb40b3dea --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthContributorConfigurationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfigurationTests.TestHealthIndicator; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.HealthContributor; + +/** + * Tests for {@link CompositeHealthContributorConfiguration}. + * + * @author Phillip Webb + */ +class CompositeHealthContributorConfigurationTests + extends AbstractCompositeHealthContributorConfigurationTests { + + @Override + protected AbstractCompositeHealthContributorConfiguration newComposite() { + return new TestCompositeHealthContributorConfiguration(); + } + + static class TestCompositeHealthContributorConfiguration + extends CompositeHealthContributorConfiguration { + + } + + static class TestHealthIndicator extends AbstractHealthIndicator { + + TestHealthIndicator(TestBean testBean) { + } + + @Override + protected void doHealthCheck(Builder builder) throws Exception { + builder.up(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationTests.java new file mode 100644 index 0000000000..ce8ca3bc3d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthContributorConfigurationTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfigurationTests.TestReactiveHealthIndicator; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; + +/** + * Tests for {@link CompositeReactiveHealthContributorConfiguration}. + * + * @author Phillip Webb + */ +class CompositeReactiveHealthContributorConfigurationTests extends + AbstractCompositeHealthContributorConfigurationTests { + + @Override + protected AbstractCompositeHealthContributorConfiguration newComposite() { + return new TestCompositeReactiveHealthContributorConfiguration(); + } + + static class TestCompositeReactiveHealthContributorConfiguration + extends CompositeReactiveHealthContributorConfiguration { + + } + + static class TestReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + TestReactiveHealthIndicator(TestBean testBean) { + } + + @Override + protected Mono doHealthCheck(Builder builder) { + return Mono.just(builder.up().build()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java new file mode 100644 index 0000000000..6aa7be19c9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.AbstractHealthAggregator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.health.StatusAggregator; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HealthAggregatorStatusAggregatorAdapter}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthAggregatorStatusAggregatorAdapterTests { + + @Test + void getAggregateStatusDelegateToHealthAggregator() { + StatusAggregator adapter = new HealthAggregatorStatusAggregatorAdapter(new TestHealthAggregator()); + Status status = adapter.getAggregateStatus(Status.UP, Status.DOWN); + assertThat(status.getCode()).isEqualTo("called2"); + } + + private static class TestHealthAggregator extends AbstractHealthAggregator { + + @Override + protected Status aggregateStatus(List candidates) { + return new Status("called" + candidates.size()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfigurationTests.java similarity index 57% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfigurationTests.java index c020e1665a..041c552dbc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorAutoConfigurationTests.java @@ -16,17 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.health; -import java.util.LinkedHashMap; -import java.util.Map; - import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.actuate.health.Status; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -35,31 +29,31 @@ import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link HealthIndicatorAutoConfiguration}. + * Tests for {@link HealthContributorAutoConfiguration}. * * @author Phillip Webb * @author Stephane Nicoll */ -class HealthIndicatorAutoConfigurationTests { +class HealthContributorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(HealthIndicatorAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class)); @Test - void runWhenNoOtherIndicatorsShouldCreateDefaultApplicationHealthIndicator() { + void runWhenNoOtherIndicatorsCreatesDefaultApplicationHealthIndicator() { this.contextRunner.run((context) -> assertThat(context).getBean(HealthIndicator.class) .isInstanceOf(ApplicationHealthIndicator.class)); } @Test - void runWhenHasDefinedIndicatorShouldNotCreateDefaultApplicationHealthIndicator() { + void runWhenHasDefinedIndicatorDoesNotCreateDefaultApplicationHealthIndicator() { this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class) .run((context) -> assertThat(context).getBean(HealthIndicator.class) .isInstanceOf(CustomHealthIndicator.class)); } @Test - void runWhenHasDefaultsDisabledAndNoSingleIndicatorEnabledShouldCreateDefaultApplicationHealthIndicator() { + void runWhenHasDefaultsDisabledAndNoSingleIndicatorEnabledCreatesDefaultApplicationHealthIndicator() { this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class) .withPropertyValues("management.health.defaults.enabled:false").run((context) -> assertThat(context) .getBean(HealthIndicator.class).isInstanceOf(ApplicationHealthIndicator.class)); @@ -67,7 +61,7 @@ class HealthIndicatorAutoConfigurationTests { } @Test - void runWhenHasDefaultsDisabledAndSingleIndicatorEnabledShouldCreateEnabledIndicator() { + void runWhenHasDefaultsDisabledAndSingleIndicatorEnabledDoesNotCreateEnabledIndicator() { this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class) .withPropertyValues("management.health.defaults.enabled:false", "management.health.custom.enabled:true") .run((context) -> assertThat(context).getBean(HealthIndicator.class) @@ -75,31 +69,6 @@ class HealthIndicatorAutoConfigurationTests { } - @Test - void runShouldCreateOrderedHealthAggregator() { - this.contextRunner.run((context) -> assertThat(context).getBean(HealthAggregator.class) - .isInstanceOf(OrderedHealthAggregator.class)); - } - - @Test - void runWhenHasCustomOrderPropertyShouldCreateOrderedHealthAggregator() { - this.contextRunner.withPropertyValues("management.health.status.order:UP,DOWN").run((context) -> { - OrderedHealthAggregator aggregator = context.getBean(OrderedHealthAggregator.class); - Map healths = new LinkedHashMap<>(); - healths.put("foo", Health.up().build()); - healths.put("bar", Health.down().build()); - Health aggregate = aggregator.aggregate(healths); - assertThat(aggregate.getStatus()).isEqualTo(Status.UP); - }); - } - - @Test - void runWhenHasCustomHealthAggregatorShouldNotCreateOrderedHealthAggregator() { - this.contextRunner.withUserConfiguration(CustomHealthAggregatorConfiguration.class) - .run((context) -> assertThat(context).getBean(HealthAggregator.class) - .isNotInstanceOf(OrderedHealthAggregator.class)); - } - @Configuration(proxyBeanMethods = false) static class CustomHealthIndicatorConfiguration { @@ -120,14 +89,4 @@ class HealthIndicatorAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) - static class CustomHealthAggregatorConfiguration { - - @Bean - HealthAggregator healthAggregator() { - return (healths) -> Health.down().build(); - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java index 2c46621f97..6ce4abd7b5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java @@ -16,91 +16,387 @@ package org.springframework.boot.actuate.autoconfigure.health; +import java.util.List; + import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import reactor.core.publisher.Mono; +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.health.AbstractHealthAggregator; +import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry; +import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry; import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthComponent; +import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.actuate.health.HealthEndpointSettings; +import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.NamedContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.health.StatusAggregator; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * Tests for {@link HealthEndpointAutoConfiguration}. * - * @author Stephane Nicoll * @author Phillip Webb + * @author Andy Wilkinson + * @author Stephane Nicoll */ +@SuppressWarnings("deprecation") class HealthEndpointAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( - AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations + .of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); + + private ReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner() + .withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations + .of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test - void healthEndpointShowDetailsDefault() { - this.contextRunner.withBean(ReactiveHealthIndicator.class, this::reactiveHealthIndicator).run((context) -> { - ReactiveHealthIndicator indicator = context.getBean("reactiveHealthIndicator", - ReactiveHealthIndicator.class); - verify(indicator, never()).health(); - Health health = context.getBean(HealthEndpoint.class).health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).isNotEmpty(); - verify(indicator, times(1)).health(); + void runWhenHealthEndpointIsDisabledDoesNotCreateBeans() { + this.contextRunner.withPropertyValues("management.endpoint.health.enabled=false").run((context) -> { + assertThat(context).doesNotHaveBean(StatusAggregator.class); + assertThat(context).doesNotHaveBean(HttpCodeStatusMapper.class); + assertThat(context).doesNotHaveBean(HealthEndpointSettings.class); + assertThat(context).doesNotHaveBean(HealthContributorRegistry.class); + assertThat(context).doesNotHaveBean(HealthEndpoint.class); + assertThat(context).doesNotHaveBean(ReactiveHealthContributorRegistry.class); + assertThat(context).doesNotHaveBean(HealthEndpointWebExtension.class); + assertThat(context).doesNotHaveBean(ReactiveHealthEndpointWebExtension.class); }); } @Test - void healthEndpointAdaptReactiveHealthIndicator() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always") - .withBean(ReactiveHealthIndicator.class, this::reactiveHealthIndicator).run((context) -> { - ReactiveHealthIndicator indicator = context.getBean("reactiveHealthIndicator", - ReactiveHealthIndicator.class); - verify(indicator, never()).health(); - Health health = context.getBean(HealthEndpoint.class).health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnlyKeys("reactive"); - verify(indicator, times(1)).health(); + void runWhenHasHealthAggregatorAdaptsToStatusAggregator() { + this.contextRunner.withUserConfiguration(HealthAggregatorConfiguration.class).run((context) -> { + StatusAggregator aggregator = context.getBean(StatusAggregator.class); + assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UNKNOWN); + }); + } + + @Test + void runWhenHasHealthStatusHttpMapperAdaptsToHttpCodeStatusMapper() { + this.contextRunner.withUserConfiguration(HealthStatusHttpMapperConfiguration.class).run((context) -> { + HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123); + }); + } + + @Test + void runCreatesStatusAggregatorFromProperties() { + this.contextRunner.withPropertyValues("management.endpoint.health.status.order=up,down").run((context) -> { + StatusAggregator aggregator = context.getBean(StatusAggregator.class); + assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UP); + }); + } + + @Test + void runWhenUsingDeprecatedPropertyCreatesStatusAggregatorFromProperties() { + this.contextRunner.withPropertyValues("management.health.status.order=up,down").run((context) -> { + StatusAggregator aggregator = context.getBean(StatusAggregator.class); + assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UP); + }); + } + + @Test + void runWhenHasStatusAggregatorBeanIgnoresProperties() { + this.contextRunner.withUserConfiguration(StatusAggregatorConfiguration.class) + .withPropertyValues("management.health.status.order=up,down").run((context) -> { + StatusAggregator aggregator = context.getBean(StatusAggregator.class); + assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UNKNOWN); }); } @Test - void healthEndpointMergeRegularAndReactive() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always") - .withBean("simpleHealthIndicator", HealthIndicator.class, this::simpleHealthIndicator) - .withBean("reactiveHealthIndicator", ReactiveHealthIndicator.class, this::reactiveHealthIndicator) + void runCreatesHttpCodeStatusMapperFromProperties() { + this.contextRunner.withPropertyValues("management.endpoint.health.status.http-mapping.up=123") .run((context) -> { - HealthIndicator indicator = context.getBean("simpleHealthIndicator", HealthIndicator.class); - ReactiveHealthIndicator reactiveHealthIndicator = context.getBean("reactiveHealthIndicator", - ReactiveHealthIndicator.class); - verify(indicator, never()).health(); - verify(reactiveHealthIndicator, never()).health(); - Health health = context.getBean(HealthEndpoint.class).health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnlyKeys("simple", "reactive"); - verify(indicator, times(1)).health(); - verify(reactiveHealthIndicator, times(1)).health(); + HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123); }); } - private HealthIndicator simpleHealthIndicator() { - HealthIndicator mock = mock(HealthIndicator.class); - given(mock.health()).willReturn(Health.status(Status.UP).build()); - return mock; + @Test + void runUsingDeprecatedPropertyCreatesHttpCodeStatusMapperFromProperties() { + this.contextRunner.withPropertyValues("management.health.status.http-mapping.up=123").run((context) -> { + HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123); + }); } - private ReactiveHealthIndicator reactiveHealthIndicator() { - ReactiveHealthIndicator mock = mock(ReactiveHealthIndicator.class); - given(mock.health()).willReturn(Mono.just(Health.status(Status.UP).build())); - return mock; + @Test + void runWhenHasHttpCodeStatusMapperBeanIgnoresProperties() { + this.contextRunner.withUserConfiguration(HttpCodeStatusMapperConfiguration.class) + .withPropertyValues("management.health.status.http-mapping.up=123").run((context) -> { + HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(456); + }); + } + + @Test + void runCreatesHealthEndpointSettings() { + this.contextRunner.run((context) -> { + HealthEndpointSettings settings = context.getBean(HealthEndpointSettings.class); + assertThat(settings).isInstanceOf(AutoConfiguredHealthEndpointSettings.class); + }); + } + + @Test + void runWhenHasHealthEndpointSettingsBeanDoesNotCreateAdditionalHealthEndpointSettings() { + this.contextRunner.withUserConfiguration(HealthEndpointSettingsConfiguration.class).run((context) -> { + HealthEndpointSettings settings = context.getBean(HealthEndpointSettings.class); + assertThat(Mockito.mockingDetails(settings).isMock()).isTrue(); + }); + } + + @Test + void runCreatesHealthContributorRegistryContainingHealthBeans() { + this.contextRunner.run((context) -> { + HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class); + Object[] names = registry.stream().map(NamedContributor::getName).toArray(); + assertThat(names).containsExactlyInAnyOrder("simple", "additional"); + }); + } + + @Test + void runWhenHasHealthContributorRegistryBeanDoesNotCreateAdditionalRegistry() { + this.contextRunner.withUserConfiguration(HealthContributorRegistryConfiguration.class).run((context) -> { + HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class); + Object[] names = registry.stream().map(NamedContributor::getName).toArray(); + assertThat(names).isEmpty(); + }); + } + + @Test + void runCreatesHealthEndpoint() { + this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { + HealthEndpoint endpoint = context.getBean(HealthEndpoint.class); + Health health = (Health) endpoint.healthForPath("simple"); + assertThat(health.getDetails()).containsEntry("counter", 42); + }); + } + + @Test + void runWhenHasHealthEndpointBeanDoesNotCreateAdditionalHealthEndpoint() { + this.contextRunner.withUserConfiguration(HealthEndpointConfiguration.class).run((context) -> { + HealthEndpoint endpoint = context.getBean(HealthEndpoint.class); + assertThat(endpoint.health()).isNull(); + }); + } + + @Test + void runCreatesReactiveHealthContributorRegistryContainingAdaptedBeans() { + this.reactiveContextRunner.run((context) -> { + ReactiveHealthContributorRegistry registry = context.getBean(ReactiveHealthContributorRegistry.class); + Object[] names = registry.stream().map(NamedContributor::getName).toArray(); + assertThat(names).containsExactlyInAnyOrder("simple", "additional", "reactive"); + }); + } + + @Test + void runWhenHasReactiveHealthContributorRegistryBeanDoesNotCreateAdditionalReactiveHealthContributorRegistry() { + this.reactiveContextRunner.withUserConfiguration(ReactiveHealthContributorRegistryConfiguration.class) + .run((context) -> { + ReactiveHealthContributorRegistry registry = context + .getBean(ReactiveHealthContributorRegistry.class); + Object[] names = registry.stream().map(NamedContributor::getName).toArray(); + assertThat(names).isEmpty(); + }); + } + + @Test + void runCreatesHealthEndpointWebExtension() { + this.contextRunner.run((context) -> { + HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class); + WebEndpointResponse response = webExtension.health(SecurityContext.NONE, true, "simple"); + Health health = (Health) response.getBody(); + assertThat(response.getStatus()).isEqualTo(200); + assertThat(health.getDetails()).containsEntry("counter", 42); + }); + } + + @Test + void runWhenHasHealthEndpointWebExtensionBeanDoesNotCreateExtraHealthEndpointWebExtension() { + this.contextRunner.withUserConfiguration(HealthEndpointWebExtensionConfiguration.class).run((context) -> { + HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class); + WebEndpointResponse response = webExtension.health(SecurityContext.NONE, true, "simple"); + assertThat(response).isNull(); + }); + } + + @Test + void runCreatesReactiveHealthEndpointWebExtension() { + this.reactiveContextRunner.run((context) -> { + ReactiveHealthEndpointWebExtension webExtension = context.getBean(ReactiveHealthEndpointWebExtension.class); + Mono> response = webExtension.health(SecurityContext.NONE, + true, "simple"); + Health health = (Health) (response.block().getBody()); + assertThat(health.getDetails()).containsEntry("counter", 42); + }); + } + + @Test + void runWhenHasReactiveHealthEndpointWebExtensionBeanDoesNotCreateExtraReactiveHealthEndpointWebExtension() { + this.reactiveContextRunner.withUserConfiguration(ReactiveHealthEndpointWebExtensionConfiguration.class) + .run((context) -> { + ReactiveHealthEndpointWebExtension webExtension = context + .getBean(ReactiveHealthEndpointWebExtension.class); + Mono> response = webExtension + .health(SecurityContext.NONE, true, "simple"); + assertThat(response).isNull(); + }); + } + + @Configuration(proxyBeanMethods = false) + static class HealthIndicatorsConfiguration { + + @Bean + HealthIndicator simpleHealthIndicator() { + return () -> Health.up().withDetail("counter", 42).build(); + } + + @Bean + HealthIndicator additionalHealthIndicator() { + return () -> Health.up().build(); + } + + @Bean + ReactiveHealthIndicator reactiveHealthIndicator() { + return () -> Mono.just(Health.up().build()); + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthAggregatorConfiguration { + + @Bean + HealthAggregator healthAggregator() { + return new AbstractHealthAggregator() { + + @Override + protected Status aggregateStatus(List candidates) { + return Status.UNKNOWN; + } + + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthStatusHttpMapperConfiguration { + + @Bean + HealthStatusHttpMapper healthStatusHttpMapper() { + return new HealthStatusHttpMapper() { + + @Override + public int mapStatus(Status status) { + return 123; + } + + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class StatusAggregatorConfiguration { + + @Bean + StatusAggregator statusAggregator() { + return (statuses) -> Status.UNKNOWN; + } + + } + + @Configuration(proxyBeanMethods = false) + static class HttpCodeStatusMapperConfiguration { + + @Bean + HttpCodeStatusMapper httpCodeStatusMapper() { + return (status) -> 456; + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthEndpointSettingsConfiguration { + + @Bean + HealthEndpointSettings healthEndpointSettings() { + return mock(HealthEndpointSettings.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthContributorRegistryConfiguration { + + @Bean + HealthContributorRegistry healthContributorRegistry() { + return new DefaultHealthContributorRegistry(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthEndpointConfiguration { + + @Bean + HealthEndpoint healthEndpoint() { + return mock(HealthEndpoint.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ReactiveHealthContributorRegistryConfiguration { + + @Bean + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry() { + return new DefaultReactiveHealthContributorRegistry(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthEndpointWebExtensionConfiguration { + + @Bean + HealthEndpointWebExtension healthEndpointWebExtension() { + return mock(HealthEndpointWebExtension.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ReactiveHealthEndpointWebExtensionConfiguration { + + @Bean + ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension() { + return mock(ReactiveHealthEndpointWebExtension.class); + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionTests.java deleted file mode 100644 index 3307a3b783..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionTests.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.health; - -import java.security.Principal; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.actuate.endpoint.SecurityContext; -import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthEndpointWebExtension; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link HealthEndpointAutoConfiguration} in a servlet environment. - * - * @author Andy Wilkinson - * @author Stephane Nicoll - * @author Phillip Webb - */ -class HealthEndpointWebExtensionTests { - - private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations - .of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); - - @Test - void runShouldCreateExtensionBeans() { - this.contextRunner.run((context) -> assertThat(context).hasSingleBean(HealthEndpointWebExtension.class)); - } - - @Test - void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans() { - this.contextRunner.withPropertyValues("management.endpoint.health.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(HealthEndpointWebExtension.class)); - } - - @Test - void runWithCustomHealthMappingShouldMapStatusCode() { - this.contextRunner.withPropertyValues("management.health.status.http-mapping.CUSTOM=500").run((context) -> { - Object extension = context.getBean(HealthEndpointWebExtension.class); - HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils - .getField(extension, "responseMapper"); - Class securityContext = SecurityContext.class; - assertThat(responseMapper.map(Health.down().build(), mock(securityContext)).getStatus()).isEqualTo(503); - assertThat(responseMapper.map(Health.status("OUT_OF_SERVICE").build(), mock(securityContext)).getStatus()) - .isEqualTo(503); - assertThat(responseMapper.map(Health.status("CUSTOM").build(), mock(securityContext)).getStatus()) - .isEqualTo(500); - }); - } - - @Test - void unauthenticatedUsersAreNotShownDetailsByDefault() { - this.contextRunner.run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertThat(extension.health(mock(SecurityContext.class)).getBody().getDetails()).isEmpty(); - }); - } - - @Test - void authenticatedUsersAreNotShownDetailsByDefault() { - this.contextRunner.run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertThat(extension.health(securityContext).getBody().getDetails()).isEmpty(); - }); - } - - @Test - void authenticatedUsersWhenAuthorizedCanBeShownDetails() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized") - .run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertThat(extension.health(securityContext).getBody().getDetails()).isNotEmpty(); - }); - } - - @Test - void unauthenticatedUsersCanBeShownDetails() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertThat(extension.health(null).getBody().getDetails()).isNotEmpty(); - }); - } - - @Test - void detailsCanBeHiddenFromAuthenticatedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertThat(extension.health(mock(SecurityContext.class)).getBody().getDetails()).isEmpty(); - }); - } - - @Test - void detailsCanBeHiddenFromUnauthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(false); - assertThat(extension.health(securityContext).getBody().getDetails()).isEmpty(); - }); - } - - @Test - void detailsCanBeShownToAuthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(true); - assertThat(extension.health(securityContext).getBody().getDetails()).isNotEmpty(); - }); - } - - @Test - void unauthenticatedUsersAreNotShownComponentByDefault() { - this.contextRunner.run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertDetailsNotFound(extension.healthForComponent(mock(SecurityContext.class), "simple")); - }); - } - - @Test - void authenticatedUsersAreNotShownComponentByDefault() { - this.contextRunner.run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertDetailsNotFound(extension.healthForComponent(securityContext, "simple")); - }); - } - - @Test - void authenticatedUsersWhenAuthorizedCanBeShownComponent() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized") - .run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertSimpleComponent(extension.healthForComponent(securityContext, "simple")); - }); - } - - @Test - void unauthenticatedUsersCanBeShownComponent() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertSimpleComponent(extension.healthForComponent(null, "simple")); - }); - } - - @Test - void componentCanBeHiddenFromAuthenticatedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertDetailsNotFound(extension.healthForComponent(mock(SecurityContext.class), "simple")); - }); - } - - @Test - void componentCanBeHiddenFromUnauthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(false); - assertDetailsNotFound(extension.healthForComponent(securityContext, "simple")); - }); - } - - @Test - void componentCanBeShownToAuthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(true); - assertSimpleComponent(extension.healthForComponent(securityContext, "simple")); - }); - } - - @Test - void componentThatDoesNotExistMapTo404() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertDetailsNotFound(extension.healthForComponent(null, "does-not-exist")); - }); - } - - @Test - void unauthenticatedUsersAreNotShownComponentInstanceByDefault() { - this.contextRunner.run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertDetailsNotFound( - extension.healthForComponentInstance(mock(SecurityContext.class), "composite", "one")); - }); - } - - @Test - void authenticatedUsersAreNotShownComponentInstanceByDefault() { - this.contextRunner.run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertDetailsNotFound(extension.healthForComponentInstance(securityContext, "composite", "one")); - }); - } - - @Test - void authenticatedUsersWhenAuthorizedCanBeShownComponentInstance() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized") - .run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertSimpleComponent(extension.healthForComponentInstance(securityContext, "composite", "one")); - }); - } - - @Test - void unauthenticatedUsersCanBeShownComponentInstance() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertSimpleComponent(extension.healthForComponentInstance(null, "composite", "one")); - }); - } - - @Test - void componentInstanceCanBeHiddenFromAuthenticatedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertDetailsNotFound( - extension.healthForComponentInstance(mock(SecurityContext.class), "composite", "one")); - }); - } - - @Test - void componentInstanceCanBeHiddenFromUnauthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(false); - assertDetailsNotFound(extension.healthForComponentInstance(securityContext, "composite", "one")); - }); - } - - @Test - void componentInstanceCanBeShownToAuthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(true); - assertSimpleComponent(extension.healthForComponentInstance(securityContext, "composite", "one")); - }); - } - - @Test - void componentInstanceThatDoesNotExistMapTo404() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - assertDetailsNotFound(extension.healthForComponentInstance(null, "composite", "does-not-exist")); - }); - } - - private void assertDetailsNotFound(WebEndpointResponse response) { - assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); - assertThat(response.getBody()).isNull(); - } - - private void assertSimpleComponent(WebEndpointResponse response) { - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getBody().getDetails()).containsOnly(entry("counter", 42)); - } - - @Test - void roleCanBeCustomized() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ADMIN").run((context) -> { - HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ADMIN")).willReturn(true); - assertThat(extension.health(securityContext).getBody().getDetails()).isNotEmpty(); - }); - } - - @Configuration(proxyBeanMethods = false) - static class HealthIndicatorsConfiguration { - - @Bean - HealthIndicator simpleHealthIndicator() { - return () -> Health.up().withDetail("counter", 42).build(); - } - - @Bean - HealthIndicator compositeHealthIndicator(HealthIndicator healthIndicator) { - Map nestedIndicators = new HashMap<>(); - nestedIndicators.put("one", healthIndicator); - nestedIndicators.put("two", () -> Health.up().build()); - return new CompositeHealthIndicator(new OrderedHealthAggregator(), nestedIndicators); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java new file mode 100644 index 0000000000..32bdeb8863 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HealthStatusHttpMapperHttpCodeStatusMapperAdapter}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests { + + @Test + void getStatusCodeDelegatesToHealthStatusHttpMapper() { + HttpCodeStatusMapper adapter = new HealthStatusHttpMapperHttpCodeStatusMapperAdapter( + new TestHealthStatusHttpMapper()); + assertThat(adapter.getStatusCode(Status.UP)).isEqualTo(123); + } + + static class TestHealthStatusHttpMapper extends HealthStatusHttpMapper { + + @Override + public int mapStatus(Status status) { + return 123; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicateTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicateTests.java new file mode 100644 index 0000000000..3650dd65af --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/IncludeExcludeGroupMemberPredicateTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link IncludeExcludeGroupMemberPredicate}. + * + * @author Phillip Webb + */ +class IncludeExcludeGroupMemberPredicateTests { + + @Test + void testWhenEmptyIncludeAndExcludeRejectsAll() { + Predicate predicate = new IncludeExcludeGroupMemberPredicate(null, null); + assertThat(predicate).rejects("a", "b", "c"); + } + + @Test + void testWhenStarIncludeAndEmptyExcludeAcceptsAll() { + Predicate predicate = include("*").exclude(); + assertThat(predicate).accepts("a", "b", "c"); + } + + @Test + void testWhenStarIncludeAndSpecificExcludeDoesNotAcceptExclude() { + Predicate predicate = include("*").exclude("c"); + assertThat(predicate).accepts("a", "b").rejects("c"); + } + + @Test + void testWhenSpecificIncludeAcceptsOnlyIncluded() { + Predicate predicate = include("a", "b").exclude(); + assertThat(predicate).accepts("a", "b").rejects("c"); + } + + @Test + void testWhenSpecifiedIncludeAndSpecifiedExcludeAcceptsAsExpected() { + Predicate predicate = include("a", "b", "c").exclude("c"); + assertThat(predicate).accepts("a", "b").rejects("c", "d"); + } + + @Test + void testWhenSpecifiedIncludeAndStarExcludeRejectsAll() { + Predicate predicate = include("a", "b", "c").exclude("*"); + assertThat(predicate).rejects("a", "b", "c", "d"); + } + + private Builder include(String... include) { + return new Builder(include); + } + + private static class Builder { + + private final String[] include; + + Builder(String[] include) { + this.include = include; + } + + Predicate exclude(String... exclude) { + return new IncludeExcludeGroupMemberPredicate(asSet(this.include), asSet(exclude)); + } + + private Set asSet(String[] names) { + return (names != null) ? new LinkedHashSet<>(Arrays.asList(names)) : null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointWebExtensionTests.java deleted file mode 100644 index af80e3a9f0..0000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthEndpointWebExtensionTests.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.health; - -import java.security.Principal; -import java.time.Duration; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.boot.actuate.endpoint.SecurityContext; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthEndpoint; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper; -import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; -import org.springframework.boot.actuate.health.ReactiveHealthIndicator; -import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; -import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link HealthEndpointAutoConfiguration} in a reactive environment. - * - * @author Andy Wilkinson - * @author Stephane Nicoll - * @author Phillip Webb - */ -class ReactiveHealthEndpointWebExtensionTests { - - private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withUserConfiguration(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class); - - @Test - void runShouldCreateExtensionBeans() { - this.contextRunner - .run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class)); - } - - @Test - void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans() { - this.contextRunner.withPropertyValues("management.endpoint.health.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(ReactiveHealthEndpointWebExtension.class)); - } - - @Test - void runWithCustomHealthMappingShouldMapStatusCode() { - this.contextRunner.withPropertyValues("management.health.status.http-mapping.CUSTOM=500").run((context) -> { - Object extension = context.getBean(ReactiveHealthEndpointWebExtension.class); - HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils - .getField(extension, "responseMapper"); - Class securityContext = SecurityContext.class; - assertThat(responseMapper.map(Health.down().build(), mock(securityContext)).getStatus()).isEqualTo(503); - assertThat(responseMapper.map(Health.status("OUT_OF_SERVICE").build(), mock(securityContext)).getStatus()) - .isEqualTo(503); - assertThat(responseMapper.map(Health.status("CUSTOM").build(), mock(securityContext)).getStatus()) - .isEqualTo(500); - }); - } - - @Test - void regularAndReactiveHealthIndicatorsMatch() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always") - .withUserConfiguration(HealthIndicatorsConfiguration.class).run((context) -> { - HealthEndpoint endpoint = context.getBean(HealthEndpoint.class); - ReactiveHealthEndpointWebExtension extension = context - .getBean(ReactiveHealthEndpointWebExtension.class); - Health endpointHealth = endpoint.health(); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - Health extensionHealth = extension.health(securityContext).block(Duration.ofSeconds(30)).getBody(); - assertThat(endpointHealth.getDetails()).containsOnlyKeys("application", "first", "second"); - assertThat(extensionHealth.getDetails()).containsOnlyKeys("application", "first", "second"); - }); - } - - @Test - void unauthenticatedUsersAreNotShownDetailsByDefault() { - this.contextRunner.run((context) -> { - ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class); - assertThat( - extension.health(mock(SecurityContext.class)).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isEmpty(); - }); - } - - @Test - void authenticatedUsersAreNotShownDetailsByDefault() { - this.contextRunner.run((context) -> { - ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isEmpty(); - }); - } - - @Test - void authenticatedUsersWhenAuthorizedCanBeShownDetails() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized") - .run((context) -> { - ReactiveHealthEndpointWebExtension extension = context - .getBean(ReactiveHealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isNotEmpty(); - }); - } - - @Test - void unauthenticatedUsersCanBeShownDetails() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class); - assertThat(extension.health(null).block(Duration.ofSeconds(30)).getBody().getDetails()).isNotEmpty(); - }); - } - - @Test - void detailsCanBeHiddenFromAuthenticatedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> { - ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isEmpty(); - }); - } - - @Test - void detailsCanBeHiddenFromUnauthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - ReactiveHealthEndpointWebExtension extension = context - .getBean(ReactiveHealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(false); - assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isEmpty(); - }); - } - - @Test - void detailsCanBeShownToAuthorizedUsers() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ACTUATOR").run((context) -> { - ReactiveHealthEndpointWebExtension extension = context - .getBean(ReactiveHealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ACTUATOR")).willReturn(true); - assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isNotEmpty(); - }); - } - - @Test - void roleCanBeCustomized() { - this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized", - "management.endpoint.health.roles=ADMIN").run((context) -> { - ReactiveHealthEndpointWebExtension extension = context - .getBean(ReactiveHealthEndpointWebExtension.class); - SecurityContext securityContext = mock(SecurityContext.class); - given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); - given(securityContext.isUserInRole("ADMIN")).willReturn(true); - assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails()) - .isNotEmpty(); - }); - } - - @Test - void registryCanBeAltered() { - this.contextRunner.withUserConfiguration(HealthIndicatorsConfiguration.class) - .withPropertyValues("management.endpoint.health.show-details=always").run((context) -> { - ReactiveHealthIndicatorRegistry registry = context.getBean(ReactiveHealthIndicatorRegistry.class); - ReactiveHealthEndpointWebExtension extension = context - .getBean(ReactiveHealthEndpointWebExtension.class); - assertThat(extension.health(null).block(Duration.ofSeconds(30)).getBody().getDetails()) - .containsOnlyKeys("application", "first", "second"); - assertThat(registry.unregister("second")).isNotNull(); - assertThat(extension.health(null).block(Duration.ofSeconds(30)).getBody().getDetails()) - .containsKeys("application", "first"); - }); - } - - @Configuration(proxyBeanMethods = false) - static class HealthIndicatorsConfiguration { - - @Bean - HealthIndicator firstHealthIndicator() { - return () -> Health.up().build(); - } - - @Bean - ReactiveHealthIndicator secondHealthIndicator() { - return () -> Mono.just(Health.up().build()); - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthIndicatorAutoConfigurationTests.java index 297fb9d77e..61154b4edf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/influx/InfluxDbHealthIndicatorAutoConfigurationTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.influx; import org.influxdb.InfluxDB; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.influx.InfluxDbHealthIndicator; @@ -36,8 +38,10 @@ import static org.mockito.Mockito.mock; class InfluxDbHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(InfluxDB.class, () -> mock(InfluxDB.class)).withConfiguration(AutoConfigurations - .of(InfluxDbHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + .withBean(InfluxDB.class, () -> mock(InfluxDB.class)) + .withConfiguration(AutoConfigurations.of(InfluxDbHealthIndicatorAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java index 60fa3b3c3d..626ad5f50e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository; @@ -52,6 +54,7 @@ class JmxEndpointIntegrationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, HttpTraceAutoConfiguration.class)) .withUserConfiguration(HttpTraceRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class) .withPropertyValues("spring.jmx.enabled=true") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java index 50fc7d5fd3..6a84f49971 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; @@ -73,7 +75,8 @@ class WebMvcEndpointExposureIntegrationTests { EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, - HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) + HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) .withUserConfiguration(CustomMvcEndpoint.class, CustomServletEndpoint.class, HttpTraceRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthIndicatorAutoConfigurationTests.java index 10378b2ee8..36963721fc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthIndicatorAutoConfigurationTests.java @@ -20,6 +20,8 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator; @@ -49,7 +51,8 @@ class DataSourceHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, - HealthIndicatorAutoConfiguration.class, DataSourceHealthIndicatorAutoConfiguration.class)) + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class, DataSourceHealthIndicatorAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialization-mode=never"); @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthIndicatorAutoConfigurationTests.java index 2bb7c081c6..1668bef843 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.jms; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.jms.JmsHealthIndicator; @@ -37,7 +39,8 @@ class JmsHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, - JmsHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + JmsHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ldap/LdapHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ldap/LdapHealthIndicatorAutoConfigurationTests.java index f3a297d0e0..4d5c810513 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ldap/LdapHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ldap/LdapHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.ldap; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.ldap.LdapHealthIndicator; @@ -37,8 +39,10 @@ import static org.mockito.Mockito.mock; class LdapHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(LdapOperations.class, () -> mock(LdapOperations.class)).withConfiguration(AutoConfigurations - .of(LdapHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + .withBean(LdapOperations.class, () -> mock(LdapOperations.class)) + .withConfiguration(AutoConfigurations.of(LdapHealthIndicatorAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mail/MailHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mail/MailHealthIndicatorAutoConfigurationTests.java index 6696c1aebe..9273f0649a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mail/MailHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mail/MailHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.mail; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.mail.MailHealthIndicator; @@ -36,7 +38,8 @@ class MailHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, - MailHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) + MailHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) .withPropertyValues("spring.mail.host:smtp.example.com"); @Test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoHealthIndicatorAutoConfigurationTests.java index 25fa7c3a89..6cfde6df38 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.mongo; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.mongo.MongoHealthIndicator; @@ -37,7 +39,8 @@ class MongoHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, - MongoHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + MongoHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoReactiveHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoReactiveHealthIndicatorAutoConfigurationTests.java index 3ebb15b242..88a520e692 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoReactiveHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/mongo/MongoReactiveHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.mongo; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.mongo.MongoHealthIndicator; @@ -41,7 +43,8 @@ class MongoReactiveHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, - MongoReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + MongoReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthIndicatorAutoConfigurationTests.java index 5dc999133c..5bafe2dfc3 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthIndicatorAutoConfigurationTests.java @@ -20,6 +20,8 @@ import org.junit.jupiter.api.Test; import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.SessionFactory; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.Health; @@ -42,8 +44,10 @@ import static org.mockito.Mockito.mock; class Neo4jHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withUserConfiguration(Neo4jConfiguration.class).withConfiguration(AutoConfigurations - .of(Neo4jHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + .withUserConfiguration(Neo4jConfiguration.class) + .withConfiguration(AutoConfigurations.of(Neo4jHealthIndicatorAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisHealthIndicatorAutoConfigurationTests.java index ad78f473c0..f233ab697e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.redis; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.redis.RedisHealthIndicator; @@ -39,7 +41,8 @@ class RedisHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, - RedisHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + RedisHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisReactiveHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisReactiveHealthIndicatorAutoConfigurationTests.java index 542bf23c97..91f3f6e5d0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisReactiveHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/redis/RedisReactiveHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.redis; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.redis.RedisHealthIndicator; @@ -37,7 +39,8 @@ class RedisReactiveHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, - RedisReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + RedisReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java index ff5f00f9ec..c714bf980f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java @@ -28,8 +28,8 @@ import org.springframework.beans.BeansException; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; @@ -66,12 +66,12 @@ import static org.mockito.Mockito.mock; class ReactiveManagementWebSecurityAutoConfigurationTests { private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, - EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class, - ReactiveManagementWebSecurityAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, + EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, + WebEndpointAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class, + ReactiveUserDetailsServiceAutoConfiguration.class, + ReactiveManagementWebSecurityAutoConfiguration.class)); @Test void permitAllForHealth() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java index 375b6e3cf7..4d680bf46c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfigurationTests.java @@ -23,8 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; @@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ManagementWebSecurityAutoConfigurationTests { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration( - AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, + AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/solr/SolrHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/solr/SolrHealthIndicatorAutoConfigurationTests.java index 2f9e1fe2a7..bc55af479e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/solr/SolrHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/solr/SolrHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.solr; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.solr.SolrHealthIndicator; @@ -36,7 +38,8 @@ class SolrHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class, - SolrHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + SolrHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorAutoConfigurationTests.java index 1dcac730cb..eb8ffb3590 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/system/DiskSpaceHealthIndicatorAutoConfigurationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.system; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; @@ -36,7 +38,8 @@ import static org.assertj.core.api.Assertions.assertThat; class DiskSpaceHealthIndicatorAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations - .of(DiskSpaceHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + .of(DiskSpaceHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); @Test void runShouldCreateIndicator() { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java index a39c13efec..fbe099b4b8 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java @@ -28,7 +28,9 @@ import java.util.stream.Collectors; * @author Christian Dupuis * @author Vedran Pavic * @since 1.1.0 + * @deprecated since 2.2.0 as {@link HealthAggregator} has been deprecated */ +@Deprecated public abstract class AbstractHealthAggregator implements HealthAggregator { @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java new file mode 100644 index 0000000000..f55a40ce20 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealth.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.TreeMap; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import org.springframework.util.Assert; + +/** + * A {@link HealthComponent} that is composed of other {@link HealthComponent} instances. + * Used to provide a unified view of related components. For example, a database health + * indicator may be a composite containing the {@link Health} of each datasource + * connection. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public class CompositeHealth extends HealthComponent { + + private Status status; + + private Map details; + + CompositeHealth(Status status, Map details) { + Assert.notNull(status, "Status must not be null"); + this.status = status; + this.details = (details != null) ? new TreeMap<>(details) : details; + } + + @Override + public Status getStatus() { + return this.status; + } + + @JsonInclude(Include.NON_EMPTY) + public Map getDetails() { + return this.details; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributor.java new file mode 100644 index 0000000000..d7388204cb --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +/** + * A {@link HealthContributor} that is composed of other {@link HealthContributor} + * instances. + * + * @author Phillip Webb + * @since 2.2.0 + * @see CompositeHealth + * @see CompositeReactiveHealthContributor + */ +public interface CompositeHealthContributor extends HealthContributor, NamedContributors { + + /** + * Factory method that will create a {@link CompositeHealthContributor} from the + * specified map. + * @param map the source map + * @return a composite health contributor instance + */ + static CompositeHealthContributor fromMap(Map map) { + return fromMap(map, Function.identity()); + } + + /** + * Factory method that will create a {@link CompositeHealthContributor} from the + * specified map. + * @param the value type + * @param map the source map + * @param valueAdapter function used to adapt the map value + * @return a composite health contributor instance + */ + static CompositeHealthContributor fromMap(Map map, + Function valueAdapter) { + return new CompositeHealthContributorMapAdapter<>(map, valueAdapter); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorMapAdapter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorMapAdapter.java new file mode 100644 index 0000000000..f528637615 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorMapAdapter.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +/** + * {@link CompositeHealthContributor} backed by a map with values adapted as necessary. + * + * @param the value type + * @author Phillip Webb + */ +class CompositeHealthContributorMapAdapter extends NamedContributorsMapAdapter + implements CompositeHealthContributor { + + CompositeHealthContributorMapAdapter(Map map, Function valueAdapter) { + super(map, valueAdapter); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapter.java new file mode 100644 index 0000000000..4c3048890c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Iterator; + +import org.springframework.util.Assert; + +/** + * Adapts a {@link CompositeHealthContributor} to a + * {@link CompositeReactiveHealthContributor} so that it can be safely invoked in a + * reactive environment. + * + * @author Phillip Webb + * @see ReactiveHealthContributor#adapt(HealthContributor) + */ +class CompositeHealthContributorReactiveAdapter implements CompositeReactiveHealthContributor { + + private final CompositeHealthContributor delegate; + + CompositeHealthContributorReactiveAdapter(CompositeHealthContributor delegate) { + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + Iterator> iterator = this.delegate.iterator(); + return new Iterator>() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public NamedContributor next() { + NamedContributor namedContributor = iterator.next(); + return NamedContributor.of(namedContributor.getName(), + ReactiveHealthContributor.adapt(namedContributor.getContributor())); + } + + }; + } + + @Override + public ReactiveHealthContributor getContributor(String name) { + HealthContributor contributor = this.delegate.getContributor(name); + return (contributor != null) ? ReactiveHealthContributor.adapt(contributor) : null; + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java index e86b567258..6a40d818a4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java @@ -26,7 +26,9 @@ import java.util.Map; * @author Phillip Webb * @author Christian Dupuis * @since 1.1.0 + * @deprecated since 2.2.0 in favor of a {@link CompositeHealthContributor} */ +@Deprecated public class CompositeHealthIndicator implements HealthIndicator { private final HealthIndicatorRegistry registry; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributor.java new file mode 100644 index 0000000000..62bb07db8e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +/** + * A {@link ReactiveHealthContributor} that is composed of other + * {@link ReactiveHealthContributor} instances. + * + * @author Phillip Webb + * @since 2.2.0 + * @see CompositeHealth + * @see CompositeHealthContributor + */ +public interface CompositeReactiveHealthContributor + extends ReactiveHealthContributor, NamedContributors { + + /** + * Factory method that will create a {@link CompositeReactiveHealthContributor} from + * the specified map. + * @param map the source map + * @return a composite health contributor instance + */ + static CompositeReactiveHealthContributor fromMap(Map map) { + return fromMap(map, Function.identity()); + } + + /** + * Factory method that will create a {@link CompositeReactiveHealthContributor} from + * the specified map. + * @param the value type + * @param map the source map + * @param valueAdapter function used to adapt the map value + * @return a composite health contributor instance + */ + static CompositeReactiveHealthContributor fromMap(Map map, + Function valueAdapter) { + return new CompositeReactiveHealthContributorMapAdapter<>(map, valueAdapter); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorMapAdapter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorMapAdapter.java new file mode 100644 index 0000000000..5dcdde5642 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorMapAdapter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +/** + * {@link CompositeReactiveHealthContributor} backed by a map with values adapted as + * necessary. + * + * @param the value type + * @author Phillip Webb + */ +class CompositeReactiveHealthContributorMapAdapter extends NamedContributorsMapAdapter + implements CompositeReactiveHealthContributor { + + CompositeReactiveHealthContributorMapAdapter(Map map, + Function valueAdapter) { + super(map, valueAdapter); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java index 548d8fec80..3b3d36a916 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java @@ -30,7 +30,9 @@ import reactor.util.function.Tuple2; * * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 2.2.0 in favor of a {@link CompositeReactiveHealthContributor} */ +@Deprecated public class CompositeReactiveHealthIndicator implements ReactiveHealthIndicator { private final ReactiveHealthIndicatorRegistry registry; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ContributorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ContributorRegistry.java new file mode 100644 index 0000000000..4b1f62ba9f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ContributorRegistry.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * A mutable registry of health endpoint contributors (either {@link HealthContributor} or + * {@link ReactiveHealthContributor}). + * + * @param the contributor type + * @author Phillip Webb + * @author Andy Wilkinson + * @author Vedran Pavic + * @author Stephane Nicoll + * @since 2.2.0 + * @see NamedContributors + */ +public interface ContributorRegistry extends NamedContributors { + + /** + * Register a contributor with the given {@code name}. + * @param name the name of the contributor + * @param contributor the contributor to register + * @throws IllegalStateException if the contributor cannot be registered with the + * given {@code name}. + */ + void registerContributor(String name, C contributor); + + /** + * Unregister a previously registered contributor. + * @param name the name of the contributor to unregister + * @return the unregistered indicator, or {@code null} if no indicator was found in + * the registry for the given {@code name}. + */ + C unregisterContributor(String name); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultContributorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultContributorRegistry.java new file mode 100644 index 0000000000..7a255ee305 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultContributorRegistry.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; + +import org.springframework.util.Assert; + +/** + * Default {@link ContributorRegistry} implementation. + * + * @param the health contributor type + * @author Phillip Webb + * @see DefaultHealthContributorRegistry + * @see DefaultReactiveHealthContributorRegistry + */ +class DefaultContributorRegistry implements ContributorRegistry { + + private final Function nameFactory; + + private final Object monitor = new Object(); + + private volatile Map contributors; + + DefaultContributorRegistry() { + this(Collections.emptyMap()); + } + + DefaultContributorRegistry(Map contributors) { + this(contributors, HealthContributorNameFactory.INSTANCE); + } + + DefaultContributorRegistry(Map contributors, Function nameFactory) { + Assert.notNull(contributors, "Contributors must not be null"); + Assert.notNull(nameFactory, "NameFactory must not be null"); + this.nameFactory = nameFactory; + Map namedContributors = new LinkedHashMap<>(); + contributors.forEach((name, contributor) -> namedContributors.put(nameFactory.apply(name), contributor)); + this.contributors = Collections.unmodifiableMap(namedContributors); + } + + @Override + public void registerContributor(String name, C contributor) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(contributor, "Contributor must not be null"); + String adaptedName = this.nameFactory.apply(name); + synchronized (this.monitor) { + Assert.state(!this.contributors.containsKey(adaptedName), + () -> "A contributor named \"" + adaptedName + "\" has already been registered"); + Map contributors = new LinkedHashMap<>(this.contributors); + contributors.put(adaptedName, contributor); + this.contributors = Collections.unmodifiableMap(contributors); + } + } + + @Override + public C unregisterContributor(String name) { + Assert.notNull(name, "Name must not be null"); + String adaptedName = this.nameFactory.apply(name); + synchronized (this.monitor) { + C unregistered = this.contributors.get(adaptedName); + if (unregistered != null) { + Map contributors = new LinkedHashMap<>(this.contributors); + contributors.remove(adaptedName); + this.contributors = Collections.unmodifiableMap(contributors); + } + return unregistered; + } + } + + @Override + public C getContributor(String name) { + return this.contributors.get(name); + } + + @Override + public Iterator> iterator() { + Iterator> iterator = this.contributors.entrySet().iterator(); + return new Iterator>() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public NamedContributor next() { + Entry entry = iterator.next(); + return NamedContributor.of(entry.getKey(), entry.getValue()); + } + + }; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthContributorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthContributorRegistry.java new file mode 100644 index 0000000000..db45f4ddf0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthContributorRegistry.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +/** + * Default {@link HealthContributorRegistry} implementation. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public class DefaultHealthContributorRegistry extends DefaultContributorRegistry + implements HealthContributorRegistry { + + public DefaultHealthContributorRegistry() { + } + + public DefaultHealthContributorRegistry(Map contributors) { + super(contributors); + } + + public DefaultHealthContributorRegistry(Map contributors, + Function nameFactory) { + super(contributors, nameFactory); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java index eedf373827..612b2a7b7b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java @@ -28,7 +28,9 @@ import org.springframework.util.Assert; * @author Vedran Pavic * @author Stephane Nicoll * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultContributorRegistry} */ +@Deprecated public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { private final Object monitor = new Object(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthContributorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthContributorRegistry.java new file mode 100644 index 0000000000..8ec4dba16d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthContributorRegistry.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +/** + * Default {@link ReactiveHealthContributorRegistry} implementation. + * + * @author Phillip Webb + * @since 2.0.0 + */ +public class DefaultReactiveHealthContributorRegistry extends DefaultContributorRegistry + implements ReactiveHealthContributorRegistry { + + public DefaultReactiveHealthContributorRegistry() { + } + + public DefaultReactiveHealthContributorRegistry(Map contributors) { + super(contributors); + } + + public DefaultReactiveHealthContributorRegistry(Map contributors, + Function nameFactory) { + super(contributors, nameFactory); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java index aae88785dc..78c74d7c99 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java @@ -28,7 +28,9 @@ import org.springframework.util.Assert; * @author Vedran Pavic * @author Stephane Nicoll * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultContributorRegistry} */ +@Deprecated public class DefaultReactiveHealthIndicatorRegistry implements ReactiveHealthIndicatorRegistry { private final Object monitor = new Object(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java index 7dc0641e1c..9c394bbb4c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/Health.java @@ -22,15 +22,13 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import org.springframework.util.Assert; /** - * Carries information about the health of a component or subsystem. - *

- * {@link Health} contains a {@link Status} to express the state of a component or - * subsystem and some additional details to carry some contextual information. + * Carries information about the health of a component or subsystem. Extends + * {@link HealthComponent} so that additional contextual details about the system can be + * returned along with the {@link Status}. *

* {@link Health} instances can be created by using {@link Builder}'s fluent API. Typical * usage in a {@link HealthIndicator} would be: @@ -38,10 +36,10 @@ import org.springframework.util.Assert; *

  * try {
  * 	// do some test to determine state of component
- * 	return new Health.Builder().up().withDetail("version", "1.1.2").build();
+ * 	return Health.up().withDetail("version", "1.1.2").build();
  * }
  * catch (Exception ex) {
- * 	return new Health.Builder().down(ex).build();
+ * 	return Health.down(ex).build();
  * }
  * 
* @@ -51,7 +49,7 @@ import org.springframework.util.Assert; * @since 1.1.0 */ @JsonInclude(Include.NON_EMPTY) -public final class Health { +public final class Health extends HealthComponent { private final Status status; @@ -71,7 +69,7 @@ public final class Health { * Return the status of the health. * @return the status (never {@code null}) */ - @JsonUnwrapped + @Override public Status getStatus() { return this.status; } @@ -80,10 +78,24 @@ public final class Health { * Return the details of the health. * @return the details (or an empty map) */ + @JsonInclude(Include.NON_EMPTY) public Map getDetails() { return this.details; } + /** + * Return a new instance of this {@link Health} with all {@link #getDetails() details} + * removed. + * @return a new instance without details + * @since 2.2.0 + */ + Health withoutDetails() { + if (this.details.isEmpty()) { + return this; + } + return status(getStatus()).build(); + } + @Override public boolean equals(Object obj) { if (obj == this) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java index 7b9c6caa6f..3049fe742b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java @@ -32,8 +32,10 @@ import java.util.Map; * * @author Christian Dupuis * @since 1.1.0 + * @deprecated since 2.2.0 in favor of {@link StatusAggregator} */ @FunctionalInterface +@Deprecated public interface HealthAggregator { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthComponent.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthComponent.java new file mode 100644 index 0000000000..47b669f423 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthComponent.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +/** + * An component that contributes data to results returned from the {@link HealthEndpoint}. + * + * @author Phillip Webb + * @since 2.2.0 + * @see Health + * @see CompositeHealth + */ +public abstract class HealthComponent { + + HealthComponent() { + } + + /** + * Return the status of the component. + * @return the component status + */ + @JsonUnwrapped + public abstract Status getStatus(); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributor.java new file mode 100644 index 0000000000..9795a1ca48 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributor.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * Tagging interface for classes that contribute to {@link HealthComponent health + * components} to the results returned from the {@link HealthEndpoint}. A contributor must + * be either a {@link HealthIndicator} or a {@link CompositeHealthContributor}. + * + * @author Phillip Webb + * @since 2.2.0 + * @see HealthIndicator + * @see CompositeHealthContributor + */ +public interface HealthContributor { + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorNameFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorNameFactory.java new file mode 100644 index 0000000000..ea91cce5ad --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorNameFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Locale; +import java.util.function.Function; + +/** + * Generate a sensible health indicator name based on its bean name. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.0.0 + */ +public class HealthContributorNameFactory implements Function { + + private static final String[] SUFFIXES = { "healthindicator", "healthcontributor" }; + + /** + * A shared singleton {@link HealthContributorNameFactory} instance. + */ + public static final HealthContributorNameFactory INSTANCE = new HealthContributorNameFactory(); + + @Override + public String apply(String name) { + for (String suffix : SUFFIXES) { + if (name != null && name.toLowerCase(Locale.ENGLISH).endsWith(suffix)) { + return name.substring(0, name.length() - suffix.length()); + } + } + return name; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorRegistry.java new file mode 100644 index 0000000000..0ca69edf03 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthContributorRegistry.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * {@link ContributorRegistry} for {@link HealthContributor HealthContributors}. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public interface HealthContributorRegistry extends ContributorRegistry { + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java index 541b52cbc1..96a5c76d49 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java @@ -16,10 +16,13 @@ package org.springframework.boot.actuate.health; +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; -import org.springframework.util.Assert; +import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; /** * {@link Endpoint @Endpoint} to expose application health information. @@ -31,57 +34,49 @@ import org.springframework.util.Assert; * @since 2.0.0 */ @Endpoint(id = "health") -public class HealthEndpoint { +public class HealthEndpoint extends HealthEndpointSupport { - private final HealthIndicator healthIndicator; + private static final String[] EMPTY_PATH = {}; /** - * Create a new {@link HealthEndpoint} instance that will use the given - * {@code healthIndicator} to generate its response. + * Create a new {@link HealthEndpoint} instance that will use the given {@code + * healthIndicator} to generate its response. * @param healthIndicator the health indicator + * @deprecated since 2.2.0 in favor of + * {@link #HealthEndpoint(HealthContributorRegistry, HealthEndpointSettings)} */ + @Deprecated public HealthEndpoint(HealthIndicator healthIndicator) { - Assert.notNull(healthIndicator, "HealthIndicator must not be null"); - this.healthIndicator = healthIndicator; - } - - @ReadOperation - public Health health() { - return this.healthIndicator.health(); } /** - * Return the {@link Health} of a particular component or {@code null} if such - * component does not exist. - * @param component the name of a particular {@link HealthIndicator} - * @return the {@link Health} for the component or {@code null} + * Create a new {@link HealthEndpoint} instance. + * @param registry the health contributor registry + * @param settings the health endpoint settings */ - @ReadOperation - public Health healthForComponent(@Selector String component) { - HealthIndicator indicator = getNestedHealthIndicator(this.healthIndicator, component); - return (indicator != null) ? indicator.health() : null; + public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointSettings settings) { + super(registry, settings); } - /** - * Return the {@link Health} of a particular {@code instance} managed by the specified - * {@code component} or {@code null} if that particular component is not a - * {@link CompositeHealthIndicator} or if such instance does not exist. - * @param component the name of a particular {@link CompositeHealthIndicator} - * @param instance the name of an instance managed by that component - * @return the {@link Health} for the component instance of {@code null} - */ @ReadOperation - public Health healthForComponentInstance(@Selector String component, @Selector String instance) { - HealthIndicator indicator = getNestedHealthIndicator(this.healthIndicator, component); - HealthIndicator nestedIndicator = getNestedHealthIndicator(indicator, instance); - return (nestedIndicator != null) ? nestedIndicator.health() : null; + public HealthComponent health() { + return healthForPath(EMPTY_PATH); } - private HealthIndicator getNestedHealthIndicator(HealthIndicator healthIndicator, String name) { - if (healthIndicator instanceof CompositeHealthIndicator) { - return ((CompositeHealthIndicator) healthIndicator).getRegistry().get(name); - } - return null; + @ReadOperation + public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) { + return getHealth(SecurityContext.NONE, true, path); + } + + @Override + protected HealthComponent getHealth(HealthContributor contributor, boolean includeDetails) { + return ((HealthIndicator) contributor).getHealth(includeDetails); + } + + @Override + protected HealthComponent aggregateContributions(Map contributions, + StatusAggregator statusAggregator, boolean includeDetails) { + return getCompositeHealth(contributions, statusAggregator, includeDetails); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSettings.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSettings.java new file mode 100644 index 0000000000..75620aadba --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSettings.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.springframework.boot.actuate.endpoint.SecurityContext; + +/** + * Setting for a {@link HealthEndpoint}. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public interface HealthEndpointSettings { + + /** + * Returns if {@link Health#getDetails() health details} should be included in the + * response. + * @param securityContext the endpoint security context + * @return {@code true} to included details or {@code false} to hide them + */ + boolean includeDetails(SecurityContext securityContext); + + /** + * Returns the status agreggator that should be used for the endpoint. + * @return the status aggregator + */ + StatusAggregator getStatusAggregator(); + + /** + * Returns the {@link HttpCodeStatusMapper} that should be used for the endpoint. + * @return the HTTP code status mapper + */ + HttpCodeStatusMapper getHttpCodeStatusMapper(); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java new file mode 100644 index 0000000000..bac86dc596 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.util.Assert; + +/** + * Base class for health endpoints and health endpoint extensions. + * + * @param the contributor type + * @param the contributed health component type + * @author Phillip Webb + */ +abstract class HealthEndpointSupport { + + private final ContributorRegistry registry; + + private final HealthEndpointSettings settings; + + /** + * Throw a new {@link IllegalStateException} to indicate a constructor has been + * deprecated. + * @deprecated since 2.2.0 in order to support deprecated subclass constructors + */ + @Deprecated + HealthEndpointSupport() { + throw new IllegalStateException("Unable to create " + getClass() + " using deprecated constructor"); + } + + /** + * Create a new {@link HealthEndpointSupport} instance. + * @param registry the health contributor registry + * @param settings the health settings + */ + HealthEndpointSupport(ContributorRegistry registry, HealthEndpointSettings settings) { + Assert.notNull(registry, "Registry must not be null"); + Assert.notNull(settings, "Settings must not be null"); + this.registry = registry; + this.settings = settings; + } + + /** + * Return the health endpoint settings. + * @return the settings + */ + protected final HealthEndpointSettings getSettings() { + return this.settings; + } + + T getHealth(SecurityContext securityContext, boolean alwaysIncludeDetails, String... path) { + boolean includeDetails = alwaysIncludeDetails || this.settings.includeDetails(securityContext); + boolean isRoot = path.length == 0; + if (!includeDetails && !isRoot) { + return null; + } + Object contributor = getContributor(path); + return getContribution(contributor, includeDetails); + } + + @SuppressWarnings("unchecked") + private Object getContributor(String[] path) { + Object contributor = this.registry; + int pathOffset = 0; + while (pathOffset < path.length) { + if (!(contributor instanceof NamedContributors)) { + return null; + } + contributor = ((NamedContributors) contributor).getContributor(path[pathOffset]); + pathOffset++; + } + return contributor; + } + + @SuppressWarnings("unchecked") + private T getContribution(Object contributor, boolean includeDetails) { + if (contributor instanceof NamedContributors) { + return getAggregateHealth((NamedContributors) contributor, includeDetails); + } + return (contributor != null) ? getHealth((C) contributor, includeDetails) : null; + } + + private T getAggregateHealth(NamedContributors namedContributors, boolean includeDetails) { + Map contributions = new LinkedHashMap<>(); + for (NamedContributor namedContributor : namedContributors) { + String name = namedContributor.getName(); + T contribution = getContribution(namedContributor.getContributor(), includeDetails); + contributions.put(name, contribution); + } + if (contributions.isEmpty()) { + return null; + } + return aggregateContributions(contributions, this.settings.getStatusAggregator(), includeDetails); + } + + protected abstract T getHealth(C contributor, boolean includeDetails); + + protected abstract T aggregateContributions(Map contributions, StatusAggregator statusAggregator, + boolean includeDetails); + + protected final CompositeHealth getCompositeHealth(Map components, + StatusAggregator statusAggregator, boolean includeDetails) { + Status status = statusAggregator.getAggregateStatus( + components.values().stream().map(HealthComponent::getStatus).collect(Collectors.toSet())); + Map includedComponents = includeDetails ? components : null; + return new CompositeHealth(status, includedComponents); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java index 7cdaed5300..d05595dcf5 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java @@ -16,11 +16,12 @@ package org.springframework.boot.actuate.health; -import java.util.function.Supplier; +import java.util.Map; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; @@ -37,37 +38,60 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten * @since 2.0.0 */ @EndpointWebExtension(endpoint = HealthEndpoint.class) -public class HealthEndpointWebExtension { +public class HealthEndpointWebExtension extends HealthEndpointSupport { - private final HealthEndpoint delegate; - - private final HealthWebEndpointResponseMapper responseMapper; + private static final String[] NO_PATH = {}; + /** + * Create a new {@link HealthEndpointWebExtension} instance using a delegate endpoint. + * @param delegate the delegate endpoint + * @param responseMapper the response mapper + * @deprecated since 2.2.0 in favor of + * {@link #HealthEndpointWebExtension(HealthContributorRegistry, HealthEndpointSettings)} + */ + @Deprecated public HealthEndpointWebExtension(HealthEndpoint delegate, HealthWebEndpointResponseMapper responseMapper) { - this.delegate = delegate; - this.responseMapper = responseMapper; + } + + /** + * Create a new {@link HealthEndpointWebExtension} instance. + * @param registry the health contributor registry + * @param settings the health endpoint settings + */ + public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointSettings settings) { + super(registry, settings); } @ReadOperation - public WebEndpointResponse health(SecurityContext securityContext) { - return this.responseMapper.map(this.delegate.health(), securityContext); + public WebEndpointResponse health(SecurityContext securityContext) { + return health(securityContext, NO_PATH); } @ReadOperation - public WebEndpointResponse healthForComponent(SecurityContext securityContext, @Selector String component) { - Supplier health = () -> this.delegate.healthForComponent(component); - return this.responseMapper.mapDetails(health, securityContext); + public WebEndpointResponse health(SecurityContext securityContext, + @Selector(match = Match.ALL_REMAINING) String... path) { + return health(securityContext, false, path); } - @ReadOperation - public WebEndpointResponse healthForComponentInstance(SecurityContext securityContext, - @Selector String component, @Selector String instance) { - Supplier health = () -> this.delegate.healthForComponentInstance(component, instance); - return this.responseMapper.mapDetails(health, securityContext); + public WebEndpointResponse health(SecurityContext securityContext, boolean alwaysIncludeDetails, + String... path) { + HealthComponent health = getHealth(securityContext, alwaysIncludeDetails, path); + if (health == null) { + return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND); + } + int statusCode = getSettings().getHttpCodeStatusMapper().getStatusCode(health.getStatus()); + return new WebEndpointResponse<>(health, statusCode); } - public WebEndpointResponse getHealth(SecurityContext securityContext, ShowDetails showDetails) { - return this.responseMapper.map(this.delegate.health(), securityContext, showDetails); + @Override + protected HealthComponent getHealth(HealthContributor contributor, boolean includeDetails) { + return ((HealthIndicator) contributor).getHealth(includeDetails); + } + + @Override + protected HealthComponent aggregateContributions(Map contributions, + StatusAggregator statusAggregator, boolean includeDetails) { + return getCompositeHealth(contributions, statusAggregator, includeDetails); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java index 62efd16940..8b3b43c486 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicator.java @@ -17,18 +17,31 @@ package org.springframework.boot.actuate.health; /** - * Strategy interface used to provide an indication of application health. + * Strategy interface used to contribute {@link Health} to the results returned from the + * {@link HealthEndpoint}. * * @author Dave Syer + * @author Phillip Webb * @since 1.0.0 * @see ApplicationHealthIndicator */ @FunctionalInterface -public interface HealthIndicator { +public interface HealthIndicator extends HealthContributor { /** * Return an indication of health. - * @return the health for + * @param includeDetails if details should be included or removed + * @return the health + * @since 2.2.0 + */ + default Health getHealth(boolean includeDetails) { + Health health = health(); + return includeDetails ? health : health.withoutDetails(); + } + + /** + * Return an indication of health. + * @return the health */ Health health(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java index 4138109e8c..12783ff6a3 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorNameFactory.java @@ -16,24 +16,15 @@ package org.springframework.boot.actuate.health; -import java.util.Locale; -import java.util.function.Function; - /** * Generate a sensible health indicator name based on its bean name. * * @author Stephane Nicoll + * @author Phillip Webb * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthContributorNameFactory} */ -public class HealthIndicatorNameFactory implements Function { - - @Override - public String apply(String name) { - int index = name.toLowerCase(Locale.ENGLISH).indexOf("healthindicator"); - if (index > 0) { - return name.substring(0, index); - } - return name; - } +@Deprecated +public class HealthIndicatorNameFactory extends HealthContributorNameFactory { } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java index 8dac711837..9e29835ca1 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapter.java @@ -27,7 +27,11 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 2.2.0 in favor of + * {@link ReactiveHealthContributor#adapt(HealthContributor)} + * @see ReactiveHealthContributor#adapt(HealthContributor) */ +@Deprecated public class HealthIndicatorReactiveAdapter implements ReactiveHealthIndicator { private final HealthIndicator delegate; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java index d2b6ff1e33..21e420d083 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java @@ -19,7 +19,7 @@ package org.springframework.boot.actuate.health; import java.util.Map; /** - * A registry of {@link HealthIndicator HealthIndicators}. + * A mutable registry of {@link HealthIndicator HealthIndicators}. *

* Implementations must be thread-safe. * @@ -27,7 +27,10 @@ import java.util.Map; * @author Vedran Pavic * @author Stephane Nicoll * @since 2.1.0 + * @see HealthEndpoint + * @deprecated since 2.2.0 in favor of a {@link HealthContributorRegistry} */ +@Deprecated public interface HealthIndicatorRegistry { /** @@ -35,8 +38,8 @@ public interface HealthIndicatorRegistry { * {@code name}. * @param name the name of the indicator * @param healthIndicator the indicator - * @throws IllegalStateException if an indicator with the given {@code name} is - * already registered. + * @throws IllegalStateException if the indicator cannot be registered with the given + * {@code name}. */ void register(String name, HealthIndicator healthIndicator); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java index 465214902d..5a34072e90 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java @@ -26,7 +26,9 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultHealthIndicatorRegistry} */ +@Deprecated public class HealthIndicatorRegistryFactory { private final Function healthIndicatorNameFactory; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java index 15be7841bb..c38793ef9b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java @@ -28,7 +28,10 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HttpCodeStatusMapper} or + * {@link SimpleHttpCodeStatusMapper} */ +@Deprecated public class HealthStatusHttpMapper { private Map statusMapping = new HashMap<>(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java index 868d828023..646d20be27 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java @@ -28,7 +28,10 @@ import org.springframework.util.CollectionUtils; * * @author Andy Wilkinson * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthEndpointWebExtension} or + * {@link ReactiveHealthEndpointWebExtension} */ +@Deprecated public class HealthWebEndpointResponseMapper { private final HealthStatusHttpMapper statusHttpMapper; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HttpCodeStatusMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HttpCodeStatusMapper.java new file mode 100644 index 0000000000..2e809040e6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HttpCodeStatusMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * Strategy used to map a {@link Status health status} to an HTTP status code. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.2.0 + */ +@FunctionalInterface +public interface HttpCodeStatusMapper { + + /** + * Return the HTTP status code that corresponds to the given {@link Status health + * status}. + * @param status the health status to map + * @return the corresponding HTTP status code + */ + int getStatusCode(Status status); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributor.java new file mode 100644 index 0000000000..dc7a0584d4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.springframework.util.Assert; + +/** + * A single named health endpoint contributors (either {@link HealthContributor} or + * {@link ReactiveHealthContributor}). + * + * @param the contributor type + * @author Phillip Webb + * @since 2.0.0 + * @see NamedContributors + */ +public interface NamedContributor { + + /** + * Returns the name of the contributor. + * @return the contributor name + */ + String getName(); + + /** + * Returns the contributor instance. + * @return the contributor instance + */ + C getContributor(); + + static NamedContributor of(String name, C contributor) { + Assert.notNull(name, "Name must not be null"); + Assert.notNull(contributor, "Contributor must not be null"); + return new NamedContributor() { + + @Override + public String getName() { + return name; + } + + @Override + public C getContributor() { + return contributor; + } + + }; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributors.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributors.java new file mode 100644 index 0000000000..4cae6dca7b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributors.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A collection of named health endpoint contributors (either {@link HealthContributor} or + * {@link ReactiveHealthContributor}). + * + * @param the contributor type + * @author Phillip Webb + * @since 2.0.0 + * @see NamedContributor + */ +public interface NamedContributors extends Iterable> { + + /** + * Return the contributor with the given name. + * @param name the name of the contributor + * @return a contributor instance of {@code null} + */ + C getContributor(String name); + + /** + * Return a stream of the {@link NamedContributor named contributors}. + * @return the stream of named contributors + */ + default Stream> stream() { + return StreamSupport.stream(spliterator(), false); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapter.java new file mode 100644 index 0000000000..7a35c3eed3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; + +import org.springframework.util.Assert; + +/** + * {@link NamedContributors} backed by a map with values adapted as necessary. + * + * @param the value type + * @param the contributor type + * @author Phillip Webb + * @see CompositeHealthContributorMapAdapter + * @see CompositeReactiveHealthContributorMapAdapter + */ +abstract class NamedContributorsMapAdapter implements NamedContributors { + + private Map map; + + private Function valueAdapter; + + NamedContributorsMapAdapter(Map map, Function valueAdapter) { + Assert.notNull(map, "Map must not be null"); + Assert.notNull(valueAdapter, "ValueAdapter must not be null"); + this.map = Collections.unmodifiableMap(new LinkedHashMap<>(map)); + this.valueAdapter = valueAdapter; + } + + @Override + public Iterator> iterator() { + Iterator> iterator = this.map.entrySet().iterator(); + return new Iterator>() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public NamedContributor next() { + Entry entry = iterator.next(); + return NamedContributor.of(entry.getKey(), adapt(entry.getValue())); + } + + }; + } + + @Override + public C getContributor(String name) { + return adapt(this.map.get(name)); + } + + private C adapt(V value) { + return (value != null) ? this.valueAdapter.apply(value) : null; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java index 49943197e9..3d80f16b65 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java @@ -32,7 +32,9 @@ import org.springframework.util.Assert; * * @author Christian Dupuis * @since 1.1.0 + * @deprecated since 2.2.0 in favor of {@link SimpleStatusAggregator} */ +@Deprecated public class OrderedHealthAggregator extends AbstractHealthAggregator { private List statusOrder; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java new file mode 100644 index 0000000000..3bd7eb245a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.springframework.util.Assert; + +/** + * Tagging interface for classes that contribute to {@link HealthComponent health + * components} to the results returned from the {@link HealthEndpoint}. A contributor must + * be either a {@link ReactiveHealthIndicator} or a + * {@link CompositeReactiveHealthContributor}. + * + * @author Phillip Webb + * @since 2.2.0 + * @see ReactiveHealthIndicator + * @see CompositeReactiveHealthContributor + */ +public interface ReactiveHealthContributor { + + @SuppressWarnings("deprecation") + static ReactiveHealthContributor adapt(HealthContributor healthContributor) { + Assert.notNull(healthContributor, "HealthContributor must not be null"); + if (healthContributor instanceof HealthIndicator) { + return new HealthIndicatorReactiveAdapter((HealthIndicator) healthContributor); + } + if (healthContributor instanceof CompositeHealthContributor) { + return new CompositeHealthContributorReactiveAdapter((CompositeHealthContributor) healthContributor); + } + throw new IllegalStateException("Unknown HealthContributor type"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributorRegistry.java new file mode 100644 index 0000000000..a71bd6ac21 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributorRegistry.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * {@link ContributorRegistry} for {@link ReactiveHealthContributor + * ReactiveHealthContributors}. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public interface ReactiveHealthContributorRegistry extends ContributorRegistry { + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java index 689ea9d46d..26aa6adac8 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java @@ -16,11 +16,15 @@ package org.springframework.boot.actuate.health; +import java.util.Map; + +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; @@ -29,57 +33,102 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten * {@link HealthEndpoint}. * * @author Stephane Nicoll + * @author Phillip Webb * @since 2.0.0 */ @EndpointWebExtension(endpoint = HealthEndpoint.class) -public class ReactiveHealthEndpointWebExtension { +public class ReactiveHealthEndpointWebExtension + extends HealthEndpointSupport> { - private final ReactiveHealthIndicator delegate; - - private final HealthWebEndpointResponseMapper responseMapper; + private static final String[] NO_PATH = {}; + /** + * Create a new {@link ReactiveHealthEndpointWebExtension} instance. + * @param delegate the delegate health indicator + * @param responseMapper the response mapper + * @deprecated since 2.2.0 in favor of + * {@link #ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry, HealthEndpointSettings)} + */ + @Deprecated public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate, HealthWebEndpointResponseMapper responseMapper) { - this.delegate = delegate; - this.responseMapper = responseMapper; + } + + /** + * Create a new {@link ReactiveHealthEndpointWebExtension} instance. + * @param registry the health contributor registry + * @param settings the health endpoint settings + */ + public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry, + HealthEndpointSettings settings) { + super(registry, settings); } @ReadOperation - public Mono> health(SecurityContext securityContext) { - return this.delegate.health().map((health) -> this.responseMapper.map(health, securityContext)); + public Mono> health(SecurityContext securityContext) { + return health(securityContext, NO_PATH); } @ReadOperation - public Mono> healthForComponent(SecurityContext securityContext, - @Selector String component) { - return responseFromIndicator(getNestedHealthIndicator(this.delegate, component), securityContext); + public Mono> health(SecurityContext securityContext, + @Selector(match = Match.ALL_REMAINING) String... path) { + return health(securityContext, false, path); } - @ReadOperation - public Mono> healthForComponentInstance(SecurityContext securityContext, - @Selector String component, @Selector String instance) { - ReactiveHealthIndicator indicator = getNestedHealthIndicator(this.delegate, component); - if (indicator != null) { - indicator = getNestedHealthIndicator(indicator, instance); + public Mono> health(SecurityContext securityContext, + boolean alwaysIncludeDetails, String... path) { + Mono result = getHealth(securityContext, alwaysIncludeDetails, path); + if (result == null) { + return Mono.just(new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND)); } - return responseFromIndicator(indicator, securityContext); + return result.map((health) -> { + int statusCode = getSettings().getHttpCodeStatusMapper().getStatusCode(health.getStatus()); + return new WebEndpointResponse<>(health, statusCode); + }); } - public Mono> health(SecurityContext securityContext, ShowDetails showDetails) { - return this.delegate.health().map((health) -> this.responseMapper.map(health, securityContext, showDetails)); + @Override + protected Mono getHealth(ReactiveHealthContributor contributor, boolean includeDetails) { + return ((ReactiveHealthIndicator) contributor).getHealth(includeDetails); } - private Mono> responseFromIndicator(ReactiveHealthIndicator indicator, - SecurityContext securityContext) { - return (indicator != null) - ? indicator.health().map((health) -> this.responseMapper.map(health, securityContext)) : Mono.empty(); + @Override + protected Mono aggregateContributions( + Map> contributions, StatusAggregator statusAggregator, + boolean includeDetails) { + return Flux.fromIterable(contributions.entrySet()).flatMap(NamedHealthComponent::create) + .collectMap(NamedHealthComponent::getName, NamedHealthComponent::getHealth) + .map((components) -> this.getCompositeHealth(components, statusAggregator, includeDetails)); } - private ReactiveHealthIndicator getNestedHealthIndicator(ReactiveHealthIndicator healthIndicator, String name) { - if (healthIndicator instanceof CompositeReactiveHealthIndicator) { - return ((CompositeReactiveHealthIndicator) healthIndicator).getRegistry().get(name); + /** + * A named {@link HealthComponent}. + */ + private static final class NamedHealthComponent { + + private final String name; + + private final HealthComponent health; + + private NamedHealthComponent(Object... pair) { + this.name = (String) pair[0]; + this.health = (HealthComponent) pair[1]; } - return null; + + String getName() { + return this.name; + } + + HealthComponent getHealth() { + return this.health; + } + + static Mono create(Map.Entry> entry) { + Mono name = Mono.just(entry.getKey()); + Mono health = entry.getValue(); + return Mono.zip(NamedHealthComponent::new, name, health); + } + } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java index f012ca8885..03ee4000cb 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicator.java @@ -19,7 +19,8 @@ package org.springframework.boot.actuate.health; import reactor.core.publisher.Mono; /** - * Defines the {@link Health} of an arbitrary system or component. + * Strategy interface used to contribute {@link Health} to the results returned from the + * reactive variant of the {@link HealthEndpoint}. *

* This is non blocking contract that is meant to be used in a reactive application. See * {@link HealthIndicator} for the traditional contract. @@ -29,7 +30,18 @@ import reactor.core.publisher.Mono; * @see HealthIndicator */ @FunctionalInterface -public interface ReactiveHealthIndicator { +public interface ReactiveHealthIndicator extends ReactiveHealthContributor { + + /** + * Provide the indicator of health. + * @param includeDetails if details should be included or removed + * @return a {@link Mono} that provides the {@link Health} + * @since 2.2.0 + */ + default Mono getHealth(boolean includeDetails) { + Mono health = health(); + return includeDetails ? health : health.map(Health::withoutDetails); + } /** * Provide the indicator of health. diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java index 714410ceae..7d3f68e36c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java @@ -27,7 +27,10 @@ import java.util.Map; * @author Vedran Pavic * @author Stephane Nicoll * @since 2.1.0 + * @see HealthIndicatorRegistry + * @deprecated since 2.2.0 in favor of a {@link ReactiveHealthContributorRegistry} */ +@Deprecated public interface ReactiveHealthIndicatorRegistry { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java index b4bcde5745..9896104a13 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java @@ -28,7 +28,9 @@ import org.springframework.util.ObjectUtils; * * @author Stephane Nicoll * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultReactiveHealthIndicatorRegistry} */ +@Deprecated public class ReactiveHealthIndicatorRegistryFactory { private final Function healthIndicatorNameFactory; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java index c127770bdf..30432741f5 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java @@ -22,7 +22,9 @@ package org.springframework.boot.actuate.health; * * @author Andy Wilkinson * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@code HealthEndpointProperties.ShowDetails} */ +@Deprecated public enum ShowDetails { /** diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapper.java new file mode 100644 index 0000000000..0c6616b8ab --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapper.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.util.CollectionUtils; + +/** + * Simple {@link HttpCodeStatusMapper} backed by map of {@link Status#getCode() status + * code} to HTTP status code. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 2.2.0 + */ +public class SimpleHttpCodeStatusMapper implements HttpCodeStatusMapper { + + private static final Map DEFAULT_MAPPINGS; + static { + Map defaultMappings = new HashMap<>(); + defaultMappings.put(Status.DOWN.getCode(), WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + defaultMappings.put(Status.OUT_OF_SERVICE.getCode(), WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + DEFAULT_MAPPINGS = getUniformMappings(defaultMappings); + } + + private final Map mappings; + + /** + * Create a new {@link SimpleHttpCodeStatusMapper} instance using default mappings. + */ + public SimpleHttpCodeStatusMapper() { + this(null); + } + + /** + * Create a new {@link SimpleHttpCodeStatusMapper} with the specified mappings. + * @param mappings the mappings to use or {@code null} to use the default mappings + */ + public SimpleHttpCodeStatusMapper(Map mappings) { + this.mappings = CollectionUtils.isEmpty(mappings) ? DEFAULT_MAPPINGS : getUniformMappings(mappings); + } + + @Override + public int getStatusCode(Status status) { + String code = getUniformCode(status.getCode()); + return this.mappings.getOrDefault(code, WebEndpointResponse.STATUS_OK); + } + + private static Map getUniformMappings(Map mappings) { + Map result = new LinkedHashMap(); + for (Map.Entry entry : mappings.entrySet()) { + String code = getUniformCode(entry.getKey()); + if (code != null) { + result.putIfAbsent(code, entry.getValue()); + } + } + return Collections.unmodifiableMap(result); + } + + private static String getUniformCode(String code) { + if (code == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (char ch : code.toCharArray()) { + if (Character.isAlphabetic(ch) || Character.isDigit(ch)) { + builder.append(Character.toLowerCase(ch)); + } + } + return builder.toString(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleStatusAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleStatusAggregator.java new file mode 100644 index 0000000000..6fc5d82b6b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/SimpleStatusAggregator.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +/** + * {@link StatusAggregator} backed by an ordered status list. + * + * @author Phillip Webb + * @since 2.2.0 + */ +public class SimpleStatusAggregator implements StatusAggregator { + + private static final List DEFAULT_ORDER; + static { + List defaultOrder = new ArrayList(); + defaultOrder.add(Status.DOWN.getCode()); + defaultOrder.add(Status.OUT_OF_SERVICE.getCode()); + defaultOrder.add(Status.UP.getCode()); + defaultOrder.add(Status.UNKNOWN.getCode()); + DEFAULT_ORDER = Collections.unmodifiableList(getUniformCodes(defaultOrder.stream())); + } + + private final List order; + + private final Comparator comparator = new StatusComparator(); + + public SimpleStatusAggregator() { + this.order = DEFAULT_ORDER; + } + + public SimpleStatusAggregator(Status... order) { + this.order = ObjectUtils.isEmpty(order) ? DEFAULT_ORDER + : getUniformCodes(Arrays.stream(order).map(Status::getCode)); + } + + public SimpleStatusAggregator(String... order) { + this.order = ObjectUtils.isEmpty(order) ? DEFAULT_ORDER : getUniformCodes(Arrays.stream(order)); + } + + public SimpleStatusAggregator(List order) { + this.order = CollectionUtils.isEmpty(order) ? DEFAULT_ORDER : getUniformCodes(order.stream()); + } + + @Override + public Status getAggregateStatus(Set statuses) { + return statuses.stream().filter(this::contains).sorted(this.comparator).findFirst().orElse(Status.UNKNOWN); + } + + private boolean contains(Status status) { + return this.order.contains(getUniformCode(status.getCode())); + } + + private static List getUniformCodes(Stream codes) { + return codes.map(SimpleStatusAggregator::getUniformCode).collect(Collectors.toList()); + } + + private static String getUniformCode(String code) { + if (code == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (char ch : code.toCharArray()) { + if (Character.isAlphabetic(ch) || Character.isDigit(ch)) { + builder.append(Character.toLowerCase(ch)); + } + } + return builder.toString(); + } + + /** + * {@link Comparator} used to order {@link Status}. + */ + private class StatusComparator implements Comparator { + + @Override + public int compare(Status s1, Status s2) { + List order = SimpleStatusAggregator.this.order; + int i1 = order.indexOf(getUniformCode(s1.getCode())); + int i2 = order.indexOf(getUniformCode(s2.getCode())); + return (i1 < i2) ? -1 : (i1 != i2) ? 1 : s1.getCode().compareTo(s2.getCode()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/StatusAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/StatusAggregator.java new file mode 100644 index 0000000000..e83e183492 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/StatusAggregator.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Strategy used to aggregate {@link Status} instances. + *

+ * This is required in order to combine subsystem states expressed through + * {@link Health#getStatus()} into one state for the entire system. + * + * @author Phillip Webb + * @since 2.2.0 + */ +@FunctionalInterface +public interface StatusAggregator { + + /** + * Return the aggregate status for the given set of statuses. + * @param statuses the statuses to aggregate + * @return the aggregate status + */ + default Status getAggregateStatus(Status... statuses) { + return getAggregateStatus(new LinkedHashSet<>(Arrays.asList(statuses))); + } + + /** + * Return the aggregate status for the given set of statuses. + * @param statuses the statuses to aggregate + * @return the aggregate status + */ + Status getAggregateStatus(Set statuses); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapterTests.java new file mode 100644 index 0000000000..ecc87fdb34 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorReactiveAdapterTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.Iterator; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link CompositeHealthContributorReactiveAdapter}. + * + * @author Phillip Webb + */ +class CompositeHealthContributorReactiveAdapterTests { + + @Test + void createWhenDelegateIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new CompositeHealthContributorReactiveAdapter(null)) + .withMessage("Delegate must not be null"); + } + + @Test + void iteratorWhenDelegateContainsHealthIndicatorAdaptsDelegate() { + HealthIndicator indicator = () -> Health.up().withDetail("spring", "boot").build(); + CompositeHealthContributor delegate = CompositeHealthContributor + .fromMap(Collections.singletonMap("test", indicator)); + CompositeHealthContributorReactiveAdapter adapter = new CompositeHealthContributorReactiveAdapter(delegate); + Iterator> iterator = adapter.iterator(); + assertThat(iterator.hasNext()).isTrue(); + NamedContributor adapted = iterator.next(); + assertThat(adapted.getName()).isEqualTo("test"); + assertThat(adapted.getContributor()).isInstanceOf(ReactiveHealthIndicator.class); + Health health = ((ReactiveHealthIndicator) adapted.getContributor()).getHealth(true).block(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsEntry("spring", "boot"); + } + + @Test + void iteratorWhenDelegateContainsCompositeHealthContributorAdaptsDelegate() { + HealthIndicator indicator = () -> Health.up().withDetail("spring", "boot").build(); + CompositeHealthContributor composite = CompositeHealthContributor + .fromMap(Collections.singletonMap("test1", indicator)); + CompositeHealthContributor delegate = CompositeHealthContributor + .fromMap(Collections.singletonMap("test2", composite)); + CompositeHealthContributorReactiveAdapter adapter = new CompositeHealthContributorReactiveAdapter(delegate); + Iterator> iterator = adapter.iterator(); + assertThat(iterator.hasNext()).isTrue(); + NamedContributor adapted = iterator.next(); + assertThat(adapted.getName()).isEqualTo("test2"); + assertThat(adapted.getContributor()).isInstanceOf(CompositeReactiveHealthContributor.class); + ReactiveHealthContributor nested = ((CompositeReactiveHealthContributor) adapted.getContributor()) + .getContributor("test1"); + Health health = ((ReactiveHealthIndicator) nested).getHealth(true).block(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsEntry("spring", "boot"); + } + + @Test + void getContributorAdaptsDelegate() { + HealthIndicator indicator = () -> Health.up().withDetail("spring", "boot").build(); + CompositeHealthContributor delegate = CompositeHealthContributor + .fromMap(Collections.singletonMap("test", indicator)); + CompositeHealthContributorReactiveAdapter adapter = new CompositeHealthContributorReactiveAdapter(delegate); + ReactiveHealthContributor adapted = adapter.getContributor("test"); + Health health = ((ReactiveHealthIndicator) adapted).getHealth(true).block(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsEntry("spring", "boot"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorTests.java new file mode 100644 index 0000000000..59767ecff0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthContributorTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CompositeHealthContributor}. + * + * @author Phillip Webb + */ +class CompositeHealthContributorTests { + + @Test + void fromMapReturnsCompositeHealthContributorMapAdapter() { + Map map = new LinkedHashMap<>(); + HealthIndicator indicator = () -> Health.down().build(); + map.put("test", indicator); + CompositeHealthContributor composite = CompositeHealthContributor.fromMap(map); + assertThat(composite).isInstanceOf(CompositeHealthContributorMapAdapter.class); + NamedContributor namedContributor = composite.iterator().next(); + assertThat(namedContributor.getName()).isEqualTo("test"); + assertThat(namedContributor.getContributor()).isSameAs(indicator); + } + + @Test + void fromMapWithAdapterReturnsCompositeHealthContributorMapAdapter() { + Map map = new LinkedHashMap<>(); + HealthIndicator downIndicator = () -> Health.down().build(); + HealthIndicator upIndicator = () -> Health.up().build(); + map.put("test", downIndicator); + CompositeHealthContributor composite = CompositeHealthContributor.fromMap(map, (value) -> upIndicator); + assertThat(composite).isInstanceOf(CompositeHealthContributorMapAdapter.class); + NamedContributor namedContributor = composite.iterator().next(); + assertThat(namedContributor.getName()).isEqualTo("test"); + assertThat(namedContributor.getContributor()).isSameAs(upIndicator); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java index 7d47f160fd..2a0ce340db 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java @@ -36,6 +36,7 @@ import static org.mockito.BDDMockito.given; * @author Phillip Webb * @author Christian Dupuis */ +@Deprecated class CompositeHealthIndicatorTests { private HealthAggregator healthAggregator; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java new file mode 100644 index 0000000000..cd13ef75f5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Test for {@link CompositeHealth}. + * + * @author Phillip Webb + */ +class CompositeHealthTests { + + @Test + void createWhenStatusIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new CompositeHealth(null, Collections.emptyMap())) + .withMessage("Status must not be null"); + } + + @Test + void getStatusReturnsStatus() { + CompositeHealth health = new CompositeHealth(Status.UP, Collections.emptyMap()); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + @Test + void getComponentReturnsComponents() { + Map components = new LinkedHashMap<>(); + components.put("a", Health.up().build()); + CompositeHealth health = new CompositeHealth(Status.UP, components); + assertThat(health.getDetails()).isEqualTo(components); + } + + @Test + void serializeWithJacksonReturnsValidJson() throws Exception { + Map components = new LinkedHashMap<>(); + components.put("db1", Health.up().build()); + components.put("db2", Health.down().withDetail("a", "b").build()); + CompositeHealth health = new CompositeHealth(Status.UP, components); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(health); + assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{" + "\"db1\":{\"status\":\"UP\"}," + + "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}}"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorTests.java new file mode 100644 index 0000000000..38d9328fa3 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthContributorTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CompositeReactiveHealthContributor}. + * + * @author Phillip Webb + */ +class CompositeReactiveHealthContributorTests { + + @Test + void fromMapReturnsCompositeReactiveHealthContributorMapAdapter() { + Map map = new LinkedHashMap<>(); + ReactiveHealthIndicator indicator = () -> Mono.just(Health.down().build()); + map.put("test", indicator); + CompositeReactiveHealthContributor composite = CompositeReactiveHealthContributor.fromMap(map); + assertThat(composite).isInstanceOf(CompositeReactiveHealthContributorMapAdapter.class); + NamedContributor namedContributor = composite.iterator().next(); + assertThat(namedContributor.getName()).isEqualTo("test"); + assertThat(namedContributor.getContributor()).isSameAs(indicator); + } + + @Test + void fromMapWithAdapterReturnsCompositeReactiveHealthContributorMapAdapter() { + Map map = new LinkedHashMap<>(); + ReactiveHealthIndicator downIndicator = () -> Mono.just(Health.down().build()); + ReactiveHealthIndicator upIndicator = () -> Mono.just(Health.up().build()); + map.put("test", downIndicator); + CompositeReactiveHealthContributor composite = CompositeReactiveHealthContributor.fromMap(map, + (value) -> upIndicator); + assertThat(composite).isInstanceOf(CompositeReactiveHealthContributorMapAdapter.class); + NamedContributor namedContributor = composite.iterator().next(); + assertThat(namedContributor.getName()).isEqualTo("test"); + assertThat(namedContributor.getContributor()).isSameAs(upIndicator); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java index 7789402ff1..f8818b560f 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java @@ -32,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ +@Deprecated class CompositeReactiveHealthIndicatorTests { private static final Health UNKNOWN_HEALTH = Health.unknown().withDetail("detail", "value").build(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultContributorRegistryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultContributorRegistryTests.java new file mode 100644 index 0000000000..512c2bf2c8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultContributorRegistryTests.java @@ -0,0 +1,165 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.Iterator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link DefaultContributorRegistry}. + * + * @author Phillip Webb + * @author Vedran Pavic + * @author Stephane Nicoll + */ +abstract class DefaultContributorRegistryTests { + + private final HealthIndicator one = mock(HealthIndicator.class); + + private final HealthIndicator two = mock(HealthIndicator.class); + + private ContributorRegistry registry; + + @BeforeEach + void setUp() { + given(this.one.health()).willReturn(new Health.Builder().unknown().withDetail("1", "1").build()); + given(this.two.health()).willReturn(new Health.Builder().unknown().withDetail("2", "2").build()); + this.registry = new DefaultContributorRegistry<>(); + } + + @Test + void createWhenContributorsIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new DefaultContributorRegistry<>(null)) + .withMessage("Contributors must not be null"); + } + + @Test + void createWhenNameFactoryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultContributorRegistry<>(Collections.emptyMap(), null)) + .withMessage("NameFactory must not be null"); + } + + @Test + void createUsesHealthIndicatorNameFactoryByDefault() { + this.registry = new DefaultContributorRegistry<>(Collections.singletonMap("oneHealthIndicator", this.one)); + assertThat(this.registry.getContributor("oneHealthIndicator")).isNull(); + assertThat(this.registry.getContributor("one")).isNotNull(); + } + + @Test + void createWithCustomNameFactoryAppliesFunctionToName() { + this.registry = new DefaultContributorRegistry<>(Collections.singletonMap("one", this.one), this::reverse); + assertThat(this.registry.getContributor("one")).isNull(); + assertThat(this.registry.getContributor("eno")).isNotNull(); + } + + @Test + void registerContributorWhenNameIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.registry.registerContributor(null, this.one)) + .withMessage("Name must not be null"); + } + + @Test + void registerContributorWhenContributorIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.registry.registerContributor("one", null)) + .withMessage("Contributor must not be null"); + } + + @Test + void registerContributorRegisteresContributors() { + this.registry.registerContributor("one", this.one); + this.registry.registerContributor("two", this.two); + assertThat(this.registry).hasSize(2); + assertThat(this.registry.getContributor("one")).isSameAs(this.one); + assertThat(this.registry.getContributor("two")).isSameAs(this.two); + } + + @Test + void registerContributorWhenNameAlreadyUsedThrowsException() { + this.registry.registerContributor("one", this.one); + assertThatIllegalStateException().isThrownBy(() -> this.registry.registerContributor("one", this.two)) + .withMessageContaining("A contributor named \"one\" has already been registered"); + } + + @Test + void registerContributorUsesNameFactory() { + this.registry.registerContributor("oneHealthIndicator", this.one); + assertThat(this.registry.getContributor("oneHealthIndicator")).isNull(); + assertThat(this.registry.getContributor("one")).isNotNull(); + } + + @Test + void unregisterContributorUnregistersContributor() { + this.registry.registerContributor("one", this.one); + this.registry.registerContributor("two", this.two); + assertThat(this.registry).hasSize(2); + HealthIndicator two = this.registry.unregisterContributor("two"); + assertThat(two).isSameAs(this.two); + assertThat(this.registry).hasSize(1); + } + + @Test + void unregisterContributorWhenUnknownReturnsNull() { + this.registry.registerContributor("one", this.one); + assertThat(this.registry).hasSize(1); + HealthIndicator two = this.registry.unregisterContributor("two"); + assertThat(two).isNull(); + assertThat(this.registry).hasSize(1); + } + + @Test + void unregisterContributorUsesNameFactory() { + this.registry.registerContributor("oneHealthIndicator", this.one); + assertThat(this.registry.getContributor("oneHealthIndicator")).isNull(); + assertThat(this.registry.getContributor("one")).isNotNull(); + } + + @Test + void getContributorReturnsContributor() { + this.registry.registerContributor("one", this.one); + assertThat(this.registry.getContributor("one")).isEqualTo(this.one); + } + + @Test + void iteratorIteratesContributors() { + this.registry.registerContributor("one", this.one); + this.registry.registerContributor("two", this.two); + Iterator> iterator = this.registry.iterator(); + NamedContributor first = iterator.next(); + NamedContributor second = iterator.next(); + assertThat(iterator.hasNext()).isFalse(); + assertThat(first.getName()).isEqualTo("one"); + assertThat(first.getContributor()).isEqualTo(this.one); + assertThat(second.getName()).isEqualTo("two"); + assertThat(second.getContributor()).isEqualTo(this.two); + } + + private String reverse(String name) { + return new StringBuilder(name).reverse().toString(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java index 00283553a9..53a68f8912 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.mock; * @author Vedran Pavic * @author Stephane Nicoll */ +@Deprecated class DefaultHealthIndicatorRegistryTests { private HealthIndicator one = mock(HealthIndicator.class); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java index ed0e8cf885..117d8ffc6f 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.mock; * @author Vedran Pavic * @author Stephane Nicoll */ +@Deprecated class DefaultReactiveHealthIndicatorRegistryTests { private ReactiveHealthIndicator one = mock(ReactiveHealthIndicator.class); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthContributorNameFactoryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthContributorNameFactoryTests.java new file mode 100644 index 0000000000..e74194bf83 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthContributorNameFactoryTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HealthContributorNameFactory}. + * + * @author Phillip Webb + */ +class HealthContributorNameFactoryTests { + + @Test + void applyWhenNameDoesNotEndWithSuffixReturnsName() { + assertThat(HealthContributorNameFactory.INSTANCE.apply("test")).isEqualTo("test"); + } + + @Test + void applyWhenNameEndsWithSuffixReturnsNewName() { + assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthIndicator")).isEqualTo("test"); + assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthContributor")).isEqualTo("test"); + } + + @Test + void applyWhenNameEndsWithSuffixInDifferentReturnsNewName() { + assertThat(HealthContributorNameFactory.INSTANCE.apply("testHEALTHindicator")).isEqualTo("test"); + assertThat(HealthContributorNameFactory.INSTANCE.apply("testHEALTHcontributor")).isEqualTo("test"); + } + + @Test + void applyWhenNameContainsSuffixReturnsName() { + assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthIndicatorTest")) + .isEqualTo("testHealthIndicatorTest"); + assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthContributorTest")) + .isEqualTo("testHealthContributorTest"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java new file mode 100644 index 0000000000..d15e0d4d3b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.actuate.endpoint.SecurityContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Base class for {@link HealthEndpointSupport} tests. + * + * @param the registry type + * @param the contributor type + * @param the contributed health component type + * @author Phillip Webb + */ +abstract class HealthEndpointSupportTests, C, T> { + + final R registry; + + final Health up = Health.up().withDetail("spring", "boot").build(); + + final Health down = Health.down().build(); + + final TestHealthEndpointSettings settings = new TestHealthEndpointSettings(); + + HealthEndpointSupportTests() { + this.registry = createRegistry(); + } + + @BeforeEach + void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + void createWhenRegistryIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> create(null, this.settings)) + .withMessage("Registry must not be null"); + } + + @Test + void createWhenSettingsIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> create(this.registry, null)) + .withMessage("Settings must not be null"); + } + + @Test + void getHealthWhenPathIsEmptyReturnsHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false); + assertThat(getHealth(result)).isNotSameAs(this.up); + assertThat(getHealth(result).getStatus()).isEqualTo(Status.UP); + } + + @Test + void getHealthWhenHasPathReturnsSubResult() { + this.registry.registerContributor("test", createContributor(this.up)); + T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false, "test"); + assertThat(getHealth(result)).isEqualTo(this.up); + + } + + @Test + void getHealthWhenAlwaysIncludesDetailsIsFalseAndSettingsIsTrueIncludesDetails() { + this.registry.registerContributor("test", createContributor(this.up)); + T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false, "test"); + assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot"); + } + + @Test + void getHealthWhenAlwaysIncludesDetailsIsFalseAndSettingsIsFalseIncludesNoDetails() { + this.settings.setIncludeDetails(false); + this.registry.registerContributor("test", createContributor(this.up)); + HealthEndpointSupport endpoint = create(this.registry, this.settings); + T rootResult = endpoint.getHealth(SecurityContext.NONE, false); + T componentResult = endpoint.getHealth(SecurityContext.NONE, false, "test"); + assertThat(((CompositeHealth) getHealth(rootResult)).getStatus()).isEqualTo(Status.UP); + assertThat(componentResult).isNull(); + } + + @Test + void getHealthWhenAlwaysIncludesDetailsIsTrueIncludesDetails() { + this.settings.setIncludeDetails(false); + this.registry.registerContributor("test", createContributor(this.up)); + T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, true, "test"); + assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot"); + } + + @Test + void getHealthWhenCompositeReturnsAggregateResult() { + Map contributors = new LinkedHashMap<>(); + contributors.put("a", createContributor(this.up)); + contributors.put("b", createContributor(this.down)); + this.registry.registerContributor("test", createCompositeContributor(contributors)); + T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false); + CompositeHealth root = (CompositeHealth) getHealth(result); + CompositeHealth component = (CompositeHealth) root.getDetails().get("test"); + assertThat(root.getStatus()).isEqualTo(Status.DOWN); + assertThat(component.getStatus()).isEqualTo(Status.DOWN); + assertThat(component.getDetails()).containsOnlyKeys("a", "b"); + } + + @Test + void getHealthWhenPathDoesNotExistReturnsNull() { + T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false, "missing"); + assertThat(result).isNull(); + } + + protected abstract HealthEndpointSupport create(R registry, HealthEndpointSettings settings); + + protected abstract R createRegistry(); + + protected abstract C createContributor(Health health); + + protected abstract C createCompositeContributor(Map contributors); + + protected abstract HealthComponent getHealth(T result); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java index bece2beb4c..1fb36a29da 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java @@ -16,94 +16,76 @@ package org.springframework.boot.actuate.health; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; /** * Tests for {@link HealthEndpoint}. * * @author Phillip Webb - * @author Christian Dupuis - * @author Andy Wilkinson - * @author Stephane Nicoll */ -class HealthEndpointTests { - - private static final HealthIndicator one = () -> new Health.Builder().status(Status.UP).withDetail("first", "1") - .build(); - - private static final HealthIndicator two = () -> new Health.Builder().status(Status.UP).withDetail("second", "2") - .build(); +class HealthEndpointTests + extends HealthEndpointSupportTests { @Test - void statusAndFullDetailsAreExposed() { - Map healthIndicators = new HashMap<>(); - healthIndicators.put("up", one); - healthIndicators.put("upAgain", two); - HealthEndpoint endpoint = new HealthEndpoint(createHealthIndicator(healthIndicators)); - Health health = endpoint.health(); + @SuppressWarnings("deprecation") + void createWhenUsingDeprecatedConstructorThrowsException() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + assertThatIllegalStateException().isThrownBy(() -> new HealthEndpoint(healthIndicator)) + .withMessage("Unable to create class org.springframework.boot.actuate.health.HealthEndpoint " + + "using deprecated constructor"); + } + + @Test + void healthReturnsCompositeHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + HealthComponent health = create(this.registry, this.settings).health(); assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain"); - Health upHealth = (Health) health.getDetails().get("up"); - assertThat(upHealth.getDetails()).containsOnly(entry("first", "1")); - Health upAgainHealth = (Health) health.getDetails().get("upAgain"); - assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2")); + assertThat(health).isInstanceOf(CompositeHealth.class); } @Test - void statusForComponentIsExposed() { - HealthEndpoint endpoint = new HealthEndpoint(createHealthIndicator(Collections.singletonMap("test", one))); - Health health = endpoint.healthForComponent("test"); - assertThat(health).isNotNull(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnly(entry("first", "1")); - } - - @Test - void statusForUnknownComponentReturnNull() { - HealthEndpoint endpoint = new HealthEndpoint(createHealthIndicator(Collections.emptyMap())); - Health health = endpoint.healthForComponent("does-not-exist"); + void healthWhenPathDoesNotExistReturnsNull() { + this.registry.registerContributor("test", createContributor(this.up)); + HealthComponent health = create(this.registry, this.settings).healthForPath("missing"); assertThat(health).isNull(); } @Test - void statusForComponentInstanceIsExposed() { - CompositeHealthIndicator compositeIndicator = new CompositeHealthIndicator(new OrderedHealthAggregator(), - Collections.singletonMap("sub", () -> Health.down().build())); - HealthEndpoint endpoint = new HealthEndpoint( - createHealthIndicator(Collections.singletonMap("test", compositeIndicator))); - Health health = endpoint.healthForComponentInstance("test", "sub"); - assertThat(health).isNotNull(); - assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat(health.getDetails()).isEmpty(); + void healthWhenPathExistsReturnsHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + HealthComponent health = create(this.registry, this.settings).healthForPath("test"); + assertThat(health).isEqualTo(this.up); } - @Test - void statusForUnknownComponentInstanceReturnNull() { - CompositeHealthIndicator compositeIndicator = new CompositeHealthIndicator(new OrderedHealthAggregator(), - Collections.singletonMap("sub", () -> Health.down().build())); - HealthEndpoint endpoint = new HealthEndpoint( - createHealthIndicator(Collections.singletonMap("test", compositeIndicator))); - Health health = endpoint.healthForComponentInstance("test", "does-not-exist"); - assertThat(health).isNull(); + @Override + protected HealthEndpoint create(HealthContributorRegistry registry, HealthEndpointSettings settings) { + return new HealthEndpoint(registry, settings); } - @Test - void statusForComponentInstanceThatIsNotACompositeReturnNull() { - HealthEndpoint endpoint = new HealthEndpoint( - createHealthIndicator(Collections.singletonMap("test", () -> Health.up().build()))); - Health health = endpoint.healthForComponentInstance("test", "does-not-exist"); - assertThat(health).isNull(); + @Override + protected HealthContributorRegistry createRegistry() { + return new DefaultHealthContributorRegistry(); } - private HealthIndicator createHealthIndicator(Map healthIndicators) { - return new CompositeHealthIndicator(new OrderedHealthAggregator(), healthIndicators); + @Override + protected HealthContributor createContributor(Health health) { + return (HealthIndicator) () -> health; + } + + @Override + protected HealthContributor createCompositeContributor(Map contributors) { + return CompositeHealthContributor.fromMap(contributors); + } + + @Override + protected HealthComponent getHealth(HealthComponent result) { + return result; } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java new file mode 100644 index 0000000000..8a00caab22 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HealthEndpointWebExtension}. + * + * @author Phillip Webb + */ +class HealthEndpointWebExtensionTests + extends HealthEndpointSupportTests { + + @Test + @SuppressWarnings("deprecation") + void createWhenUsingDeprecatedConstructorThrowsException() { + HealthEndpoint delegate = mock(HealthEndpoint.class); + HealthWebEndpointResponseMapper responseMapper = mock(HealthWebEndpointResponseMapper.class); + assertThatIllegalStateException().isThrownBy(() -> new HealthEndpointWebExtension(delegate, responseMapper)) + .withMessage( + "Unable to create class org.springframework.boot.actuate.health.HealthEndpointWebExtension " + + "using deprecated constructor"); + } + + @Test + void healthReturnsSystemHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + WebEndpointResponse response = create(this.registry, this.settings) + .health(SecurityContext.NONE); + HealthComponent health = response.getBody(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health).isInstanceOf(CompositeHealth.class); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + void healthWhenPathDoesNotExistReturnsHttp404() { + this.registry.registerContributor("test", createContributor(this.up)); + WebEndpointResponse response = create(this.registry, this.settings) + .health(SecurityContext.NONE, "missing"); + assertThat(response.getBody()).isNull(); + assertThat(response.getStatus()).isEqualTo(404); + } + + @Test + void healthWhenPathExistsReturnsHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + WebEndpointResponse response = create(this.registry, this.settings) + .health(SecurityContext.NONE, "test"); + assertThat(response.getBody()).isEqualTo(this.up); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Override + protected HealthEndpointWebExtension create(HealthContributorRegistry registry, HealthEndpointSettings settings) { + return new HealthEndpointWebExtension(registry, settings); + } + + @Override + protected HealthContributorRegistry createRegistry() { + return new DefaultHealthContributorRegistry(); + } + + @Override + protected HealthContributor createContributor(Health health) { + return (HealthIndicator) () -> health; + } + + @Override + protected HealthContributor createCompositeContributor(Map contributors) { + return CompositeHealthContributor.fromMap(contributors); + } + + @Override + protected HealthComponent getHealth(HealthComponent result) { + return result; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java index a20ac1ecc8..a5b4532d03 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java @@ -16,16 +16,13 @@ package org.springframework.boot.actuate.health; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.Callable; -import java.util.function.Consumer; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import reactor.core.publisher.Mono; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; @@ -34,12 +31,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.ReflectionUtils; /** * Integration tests for {@link HealthEndpoint} and {@link HealthEndpointWebExtension} * exposed by Jersey, Spring MVC, and WebFlux. * * @author Andy Wilkinson + * @author Phillip Webb */ class HealthEndpointWebIntegrationTests { @@ -51,91 +50,86 @@ class HealthEndpointWebIntegrationTests { } @WebEndpointTest - void whenHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) throws Exception { - withHealthIndicator(context, "charlie", () -> Health.down().build(), () -> Mono.just(Health.down().build()), - () -> { - client.get().uri("/actuator/health").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN") - .jsonPath("details.alpha.status").isEqualTo("UP").jsonPath("details.bravo.status") - .isEqualTo("UP").jsonPath("details.charlie.status").isEqualTo("DOWN"); - return null; - }); + void whenHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) { + HealthIndicator healthIndicator = () -> Health.down().build(); + ReactiveHealthIndicator reactiveHealthIndicator = () -> Mono.just(Health.down().build()); + withHealthContributor(context, "charlie", healthIndicator, reactiveHealthIndicator, + () -> client.get().uri("/actuator/health").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN") + .jsonPath("details.alpha.status").isEqualTo("UP").jsonPath("details.bravo.status") + .isEqualTo("UP").jsonPath("details.charlie.status").isEqualTo("DOWN")); } @WebEndpointTest - void whenComponentHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) - throws Exception { - withHealthIndicator(context, "charlie", () -> Health.down().build(), () -> Mono.just(Health.down().build()), - () -> { - client.get().uri("/actuator/health/charlie").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") - .isEqualTo("DOWN"); - return null; - }); + void whenComponentHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) { + HealthIndicator healthIndicator = () -> Health.down().build(); + ReactiveHealthIndicator reactiveHealthIndicator = () -> Mono.just(Health.down().build()); + withHealthContributor(context, "charlie", healthIndicator, reactiveHealthIndicator, + () -> client.get().uri("/actuator/health/charlie").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN")); } @WebEndpointTest - void whenComponentInstanceHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) - throws Exception { - CompositeHealthIndicator composite = new CompositeHealthIndicator(new OrderedHealthAggregator(), - Collections.singletonMap("one", () -> Health.down().build())); - CompositeReactiveHealthIndicator reactiveComposite = new CompositeReactiveHealthIndicator( - new OrderedHealthAggregator(), new DefaultReactiveHealthIndicatorRegistry( - Collections.singletonMap("one", () -> Mono.just(Health.down().build())))); - withHealthIndicator(context, "charlie", composite, reactiveComposite, () -> { - client.get().uri("/actuator/health/charlie/one").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN"); - return null; - }); + void whenComponentInstanceHealthIsDown503ResponseIsReturned(ApplicationContext context, WebTestClient client) { + HealthIndicator healthIndicator = () -> Health.down().build(); + CompositeHealthContributor composite = CompositeHealthContributor + .fromMap(Collections.singletonMap("one", healthIndicator)); + ReactiveHealthIndicator reactiveHealthIndicator = () -> Mono.just(Health.down().build()); + CompositeReactiveHealthContributor reactiveComposite = CompositeReactiveHealthContributor + .fromMap(Collections.singletonMap("one", reactiveHealthIndicator)); + withHealthContributor(context, "charlie", composite, reactiveComposite, + () -> client.get().uri("/actuator/health/charlie/one").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status").isEqualTo("DOWN")); } - private void withHealthIndicator(ApplicationContext context, String name, HealthIndicator healthIndicator, - ReactiveHealthIndicator reactiveHealthIndicator, Callable action) throws Exception { - Consumer unregister; - Consumer reactiveUnregister; - try { - ReactiveHealthIndicatorRegistry registry = context.getBean(ReactiveHealthIndicatorRegistry.class); - registry.register(name, reactiveHealthIndicator); - reactiveUnregister = registry::unregister; + private void withHealthContributor(ApplicationContext context, String name, HealthContributor healthContributor, + ReactiveHealthContributor reactiveHealthContributor, ThrowingCallable callable) { + HealthContributorRegistry healthContributorRegistry = getContributorRegistry(context, + HealthContributorRegistry.class); + healthContributorRegistry.registerContributor(name, healthContributor); + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry = getContributorRegistry(context, + ReactiveHealthContributorRegistry.class); + if (reactiveHealthContributorRegistry != null) { + reactiveHealthContributorRegistry.registerContributor(name, reactiveHealthContributor); } - catch (NoSuchBeanDefinitionException ex) { - reactiveUnregister = (indicatorName) -> { - }; - // Continue - } - HealthIndicatorRegistry registry = context.getBean(HealthIndicatorRegistry.class); - registry.register(name, healthIndicator); - unregister = reactiveUnregister.andThen(registry::unregister); try { - action.call(); + callable.call(); + } + catch (Throwable ex) { + ReflectionUtils.rethrowRuntimeException(ex); } finally { - unregister.accept("charlie"); + healthContributorRegistry.unregisterContributor(name); + if (reactiveHealthContributorRegistry != null) { + reactiveHealthContributorRegistry.unregisterContributor(name); + } } } + private > R getContributorRegistry(ApplicationContext context, + Class registryType) { + return context.getBeanProvider(registryType).getIfAvailable(); + } + @WebEndpointTest void whenHealthIndicatorIsRemovedResponseIsAltered(WebTestClient client, ApplicationContext context) { - Consumer reactiveRegister = null; - try { - ReactiveHealthIndicatorRegistry registry = context.getBean(ReactiveHealthIndicatorRegistry.class); - ReactiveHealthIndicator unregistered = registry.unregister("bravo"); - reactiveRegister = (name) -> registry.register(name, unregistered); - } - catch (NoSuchBeanDefinitionException ex) { - // Continue - } - HealthIndicatorRegistry registry = context.getBean(HealthIndicatorRegistry.class); - HealthIndicator bravo = registry.unregister("bravo"); + String name = "bravo"; + HealthContributorRegistry healthContributorRegistry = getContributorRegistry(context, + HealthContributorRegistry.class); + HealthContributor bravo = healthContributorRegistry.unregisterContributor(name); + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry = getContributorRegistry(context, + ReactiveHealthContributorRegistry.class); + ReactiveHealthContributor reactiveBravo = (reactiveHealthContributorRegistry != null) + ? reactiveHealthContributorRegistry.unregisterContributor(name) : null; try { client.get().uri("/actuator/health").exchange().expectStatus().isOk().expectBody().jsonPath("status") .isEqualTo("UP").jsonPath("details.alpha.status").isEqualTo("UP").jsonPath("details.bravo.status") .doesNotExist(); } finally { - registry.register("bravo", bravo); - if (reactiveRegister != null) { - reactiveRegister.accept("bravo"); + healthContributorRegistry.registerContributor(name, bravo); + if (reactiveHealthContributorRegistry != null && reactiveBravo != null) { + reactiveHealthContributorRegistry.registerContributor(name, reactiveBravo); } } } @@ -144,39 +138,46 @@ class HealthEndpointWebIntegrationTests { static class TestConfiguration { @Bean - HealthIndicatorRegistry healthIndicatorFactory(Map healthIndicators) { - return new HealthIndicatorRegistryFactory().createHealthIndicatorRegistry(healthIndicators); + HealthContributorRegistry healthContributorRegistry(Map healthContributorBeans) { + return new DefaultHealthContributorRegistry(healthContributorBeans); } @Bean @ConditionalOnWebApplication(type = Type.REACTIVE) - ReactiveHealthIndicatorRegistry reactiveHealthIndicatorRegistry( - Map reactiveHealthIndicators, - Map healthIndicators) { - return new ReactiveHealthIndicatorRegistryFactory() - .createReactiveHealthIndicatorRegistry(reactiveHealthIndicators, healthIndicators); + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry( + Map healthContributorBeans, + Map reactiveHealthContributorBeans) { + Map allIndicators = new LinkedHashMap( + reactiveHealthContributorBeans); + healthContributorBeans.forEach((name, contributor) -> allIndicators.computeIfAbsent(name, + (key) -> ReactiveHealthContributor.adapt(contributor))); + return new DefaultReactiveHealthContributorRegistry(allIndicators); } @Bean - HealthEndpoint healthEndpoint(HealthIndicatorRegistry registry) { - return new HealthEndpoint(new CompositeHealthIndicator(new OrderedHealthAggregator(), registry)); + HealthEndpoint healthEndpoint(HealthContributorRegistry healthContributorRegistry, + HealthEndpointSettings healthEndpointSettings) { + return new HealthEndpoint(healthContributorRegistry, healthEndpointSettings); } @Bean @ConditionalOnWebApplication(type = Type.SERVLET) - HealthEndpointWebExtension healthWebEndpointExtension(HealthEndpoint healthEndpoint) { - return new HealthEndpointWebExtension(healthEndpoint, new HealthWebEndpointResponseMapper( - new HealthStatusHttpMapper(), ShowDetails.ALWAYS, new HashSet<>(Arrays.asList("ACTUATOR")))); + HealthEndpointWebExtension healthWebEndpointExtension(HealthContributorRegistry healthContributorRegistry, + HealthEndpointSettings healthEndpointSettings) { + return new HealthEndpointWebExtension(healthContributorRegistry, healthEndpointSettings); } @Bean @ConditionalOnWebApplication(type = Type.REACTIVE) - ReactiveHealthEndpointWebExtension reactiveHealthWebEndpointExtension(ReactiveHealthIndicatorRegistry registry, - HealthEndpoint healthEndpoint) { - return new ReactiveHealthEndpointWebExtension( - new CompositeReactiveHealthIndicator(new OrderedHealthAggregator(), registry), - new HealthWebEndpointResponseMapper(new HealthStatusHttpMapper(), ShowDetails.ALWAYS, - new HashSet<>(Arrays.asList("ACTUATOR")))); + ReactiveHealthEndpointWebExtension reactiveHealthWebEndpointExtension( + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, + HealthEndpointSettings healthEndpointSettings) { + return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, healthEndpointSettings); + } + + @Bean + HealthEndpointSettings healthEndpointSettings() { + return new TestHealthEndpointSettings(); } @Bean diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java index e7f89cdc38..72754f953c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.mock; * * @author Stephane Nicoll */ +@SuppressWarnings("deprecation") class HealthIndicatorReactiveAdapterTests { @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorTests.java new file mode 100644 index 0000000000..250c42280b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HealthIndicator}. + * + * @author Phillip Webb + */ +class HealthIndicatorTests { + + private final HealthIndicator indicator = () -> Health.up().withDetail("spring", "boot").build(); + + @Test + void getHealthWhenIncludeDetailsIsTrueReturnsHealthWithDetails() { + Health health = this.indicator.getHealth(true); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsEntry("spring", "boot"); + } + + @Test + void getHealthWhenIncludeDetailsIsFalseReturnsHealthWithoutDetails() { + Health health = this.indicator.getHealth(false); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).isEmpty(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java index a65810e55e..81e88378e1 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthTests.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.entry; * @author Phillip Webb * @author Michael Pratt * @author Stephane Nicoll + * @author Phillip Webb */ class HealthTests { @@ -176,4 +178,12 @@ class HealthTests { assertThat(health.getDetails()).isEmpty(); } + @Test + void serializeWithJacksonReturnsValidJson() throws Exception { + Health health = Health.down().withDetail("a", "b").build(); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(health); + assertThat(json).isEqualTo("{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}"); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java index 5f1165fc9b..9531bce24e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; * * @author Stephane Nicoll */ +@Deprecated class HealthWebEndpointResponseMapperTests { private final HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorTests.java new file mode 100644 index 0000000000..f357eccc53 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link NamedContributor}. + * + * @author Phillip Webb + */ +class NamedContributorTests { + + @Test + void ofNameAndContributorCreatesContributor() { + NamedContributor contributor = NamedContributor.of("one", "two"); + assertThat(contributor.getName()).isEqualTo("one"); + assertThat(contributor.getContributor()).isEqualTo("two"); + } + + @Test + void ofWhenNameIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> NamedContributor.of(null, "two")) + .withMessage("Name must not be null"); + } + + @Test + void ofWhenContributorIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> NamedContributor.of("one", null)) + .withMessage("Contributor must not be null"); + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapterTests.java new file mode 100644 index 0000000000..6befd8cf98 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/NamedContributorsMapAdapterTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link NamedContributorsMapAdapter}. + * + * @author Phillip Webb + */ +class NamedContributorsMapAdapterTests { + + @Test + void createWhenMapIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new TestNamedContributorsMapAdapter<>(null, Function.identity())) + .withMessage("Map must not be null"); + } + + @Test + void createWhenValueAdapterIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new TestNamedContributorsMapAdapter<>(Collections.emptyMap(), null)) + .withMessage("ValueAdapter must not be null"); + } + + @Test + void iterateReturnsAdaptedEntries() { + TestNamedContributorsMapAdapter adapter = createAdapter(); + Iterator> iterator = adapter.iterator(); + NamedContributor one = iterator.next(); + NamedContributor two = iterator.next(); + assertThat(iterator.hasNext()).isFalse(); + assertThat(one.getName()).isEqualTo("one"); + assertThat(one.getContributor()).isEqualTo("eno"); + assertThat(two.getName()).isEqualTo("two"); + assertThat(two.getContributor()).isEqualTo("owt"); + } + + @Test + void getContributorReturnsAdaptedEntry() { + TestNamedContributorsMapAdapter adapter = createAdapter(); + assertThat(adapter.getContributor("one")).isEqualTo("eno"); + assertThat(adapter.getContributor("two")).isEqualTo("owt"); + } + + @Test + void getContributorWhenNotInMapReturnsNull() { + TestNamedContributorsMapAdapter adapter = createAdapter(); + assertThat(adapter.getContributor("missing")).isNull(); + } + + private TestNamedContributorsMapAdapter createAdapter() { + Map map = new LinkedHashMap(); + map.put("one", "one"); + map.put("two", "two"); + TestNamedContributorsMapAdapter adapter = new TestNamedContributorsMapAdapter<>(map, this::reverse); + return adapter; + } + + private String reverse(CharSequence charSequence) { + return new StringBuilder(charSequence).reverse().toString(); + } + + static class TestNamedContributorsMapAdapter extends NamedContributorsMapAdapter { + + TestNamedContributorsMapAdapter(Map map, Function valueAdapter) { + super(map, valueAdapter); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java index 839a4f8d51..d98eb67514 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Christian Dupuis */ +@Deprecated class OrderedHealthAggregatorTests { private OrderedHealthAggregator healthAggregator; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java new file mode 100644 index 0000000000..8f89f860d7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ReactiveHealthContributor}. + * + * @author Phillip Webb + */ +class ReactiveHealthContributorTests { + + @Test + void adaptWhenNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> ReactiveHealthContributor.adapt(null)) + .withMessage("HealthContributor must not be null"); + } + + @Test + @SuppressWarnings("deprecation") + void adaptWhenHealthIndicatorReturnsHealthIndicatorReactiveAdapter() { + HealthIndicator indicator = () -> Health.outOfService().build(); + ReactiveHealthContributor adapted = ReactiveHealthContributor.adapt(indicator); + assertThat(adapted).isInstanceOf(HealthIndicatorReactiveAdapter.class); + assertThat(((ReactiveHealthIndicator) adapted).health().block().getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + } + + @Test + void adaptWhenCompositeHealthContributorReturnsCompositeHealthContributorReactiveAdapter() { + HealthIndicator indicator = () -> Health.outOfService().build(); + CompositeHealthContributor contributor = CompositeHealthContributor + .fromMap(Collections.singletonMap("a", indicator)); + ReactiveHealthContributor adapted = ReactiveHealthContributor.adapt(contributor); + assertThat(adapted).isInstanceOf(CompositeHealthContributorReactiveAdapter.class); + ReactiveHealthContributor contained = ((CompositeReactiveHealthContributor) adapted).getContributor("a"); + assertThat(((ReactiveHealthIndicator) contained).health().block().getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + } + + @Test + void adaptWhenUnknownThrowsException() { + assertThatIllegalStateException() + .isThrownBy(() -> ReactiveHealthContributor.adapt(mock(HealthContributor.class))) + .withMessage("Unknown HealthContributor type"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java new file mode 100644 index 0000000000..be3acef737 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ReactiveHealthEndpointWebExtension}. + * + * @author Phillip Webb + */ +class ReactiveHealthEndpointWebExtensionTests extends + HealthEndpointSupportTests> { + + @Test + @SuppressWarnings("deprecation") + void createWhenUsingDeprecatedConstructorThrowsException() { + ReactiveHealthIndicator delegate = mock(ReactiveHealthIndicator.class); + HealthWebEndpointResponseMapper responseMapper = mock(HealthWebEndpointResponseMapper.class); + assertThatIllegalStateException() + .isThrownBy(() -> new ReactiveHealthEndpointWebExtension(delegate, responseMapper)).withMessage( + "Unable to create class org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension " + + "using deprecated constructor"); + } + + @Test + void healthReturnsSystemHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + WebEndpointResponse response = create(this.registry, this.settings) + .health(SecurityContext.NONE).block(); + HealthComponent health = response.getBody(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health).isInstanceOf(CompositeHealth.class); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + void healthWhenPathDoesNotExistReturnsHttp404() { + this.registry.registerContributor("test", createContributor(this.up)); + WebEndpointResponse response = create(this.registry, this.settings) + .health(SecurityContext.NONE, "missing").block(); + assertThat(response.getBody()).isNull(); + assertThat(response.getStatus()).isEqualTo(404); + } + + @Test + void healthWhenPathExistsReturnsHealth() { + this.registry.registerContributor("test", createContributor(this.up)); + WebEndpointResponse response = create(this.registry, this.settings) + .health(SecurityContext.NONE, "test").block(); + assertThat(response.getBody()).isEqualTo(this.up); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Override + protected ReactiveHealthEndpointWebExtension create(ReactiveHealthContributorRegistry registry, + HealthEndpointSettings settings) { + return new ReactiveHealthEndpointWebExtension(registry, settings); + } + + @Override + protected ReactiveHealthContributorRegistry createRegistry() { + return new DefaultReactiveHealthContributorRegistry(); + } + + @Override + protected ReactiveHealthContributor createContributor(Health health) { + return (ReactiveHealthIndicator) () -> Mono.just(health); + } + + @Override + protected ReactiveHealthContributor createCompositeContributor( + Map contributors) { + return CompositeReactiveHealthContributor.fromMap(contributors); + } + + @Override + protected HealthComponent getHealth(Mono result) { + return result.block(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorImplementationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorImplementationTests.java new file mode 100644 index 0000000000..aaf3fd1281 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorImplementationTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AbstractReactiveHealthIndicator}. + * + * @author Dmytro Nosan + * @author Stephane Nicoll + */ +@ExtendWith(OutputCaptureExtension.class) +class ReactiveHealthIndicatorImplementationTests { + + @Test + void healthUp(CapturedOutput output) { + StepVerifier.create(new SimpleReactiveHealthIndicator().health()) + .consumeNextWith((health) -> assertThat(health).isEqualTo(Health.up().build())).verifyComplete(); + assertThat(output).doesNotContain("Health check failed for simple"); + } + + @Test + void healthDownWithCustomErrorMessage(CapturedOutput output) { + StepVerifier.create(new CustomErrorMessageReactiveHealthIndicator().health()).consumeNextWith( + (health) -> assertThat(health).isEqualTo(Health.down(new UnsupportedOperationException()).build())) + .verifyComplete(); + assertThat(output).contains("Health check failed for custom"); + } + + @Test + void healthDownWithCustomErrorMessageFunction(CapturedOutput output) { + StepVerifier.create(new CustomErrorMessageFunctionReactiveHealthIndicator().health()) + .consumeNextWith((health) -> assertThat(health).isEqualTo(Health.down(new RuntimeException()).build())) + .verifyComplete(); + assertThat(output).contains("Health check failed with RuntimeException"); + } + + private static final class SimpleReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + SimpleReactiveHealthIndicator() { + super("Health check failed for simple"); + } + + @Override + protected Mono doHealthCheck(Builder builder) { + return Mono.just(builder.up().build()); + } + + } + + private static final class CustomErrorMessageReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + CustomErrorMessageReactiveHealthIndicator() { + super("Health check failed for custom"); + } + + @Override + protected Mono doHealthCheck(Builder builder) { + return Mono.error(new UnsupportedOperationException()); + } + + } + + private static final class CustomErrorMessageFunctionReactiveHealthIndicator + extends AbstractReactiveHealthIndicator { + + CustomErrorMessageFunctionReactiveHealthIndicator() { + super((ex) -> "Health check failed with " + ex.getClass().getSimpleName()); + } + + @Override + protected Mono doHealthCheck(Builder builder) { + throw new RuntimeException(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java index ab26ea7811..5939865f9e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java @@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Stephane Nicoll */ +@Deprecated class ReactiveHealthIndicatorRegistryFactoryTests { private static final Health UP = new Health.Builder().status(Status.UP).build(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorTests.java index d2c9ecf6eb..bc3e196305 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorTests.java @@ -17,86 +17,31 @@ package org.springframework.boot.actuate.health; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link AbstractReactiveHealthIndicator}. + * Tests for {@link ReactiveHealthIndicator}. * - * @author Dmytro Nosan - * @author Stephane Nicoll + * @author Phillip Webb */ -@ExtendWith(OutputCaptureExtension.class) class ReactiveHealthIndicatorTests { + private final ReactiveHealthIndicator indicator = () -> Mono.just(Health.up().withDetail("spring", "boot").build()); + @Test - void healthUp(CapturedOutput output) { - StepVerifier.create(new SimpleReactiveHealthIndicator().health()) - .consumeNextWith((health) -> assertThat(health).isEqualTo(Health.up().build())).verifyComplete(); - assertThat(output).doesNotContain("Health check failed for simple"); + void getHealthWhenIncludeDetailsIsTrueReturnsHealthWithDetails() { + Health health = this.indicator.getHealth(true).block(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsEntry("spring", "boot"); } @Test - void healthDownWithCustomErrorMessage(CapturedOutput output) { - StepVerifier.create(new CustomErrorMessageReactiveHealthIndicator().health()).consumeNextWith( - (health) -> assertThat(health).isEqualTo(Health.down(new UnsupportedOperationException()).build())) - .verifyComplete(); - assertThat(output).contains("Health check failed for custom"); - } - - @Test - void healthDownWithCustomErrorMessageFunction(CapturedOutput output) { - StepVerifier.create(new CustomErrorMessageFunctionReactiveHealthIndicator().health()) - .consumeNextWith((health) -> assertThat(health).isEqualTo(Health.down(new RuntimeException()).build())) - .verifyComplete(); - assertThat(output).contains("Health check failed with RuntimeException"); - } - - private static final class SimpleReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - - SimpleReactiveHealthIndicator() { - super("Health check failed for simple"); - } - - @Override - protected Mono doHealthCheck(Builder builder) { - return Mono.just(builder.up().build()); - } - - } - - private static final class CustomErrorMessageReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - - CustomErrorMessageReactiveHealthIndicator() { - super("Health check failed for custom"); - } - - @Override - protected Mono doHealthCheck(Builder builder) { - return Mono.error(new UnsupportedOperationException()); - } - - } - - private static final class CustomErrorMessageFunctionReactiveHealthIndicator - extends AbstractReactiveHealthIndicator { - - CustomErrorMessageFunctionReactiveHealthIndicator() { - super((ex) -> "Health check failed with " + ex.getClass().getSimpleName()); - } - - @Override - protected Mono doHealthCheck(Builder builder) { - throw new RuntimeException(); - } - + void getHealthWhenIncludeDetailsIsFalseReturnsHealthWithoutDetails() { + Health health = this.indicator.getHealth(false).block(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).isEmpty(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapperTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapperTests.java new file mode 100644 index 0000000000..0cc7564b01 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleHttpCodeStatusMapperTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SimpleHttpCodeStatusMapper}. + * + * @author Phillip Webb + */ +class SimpleHttpCodeStatusMapperTests { + + @Test + void createWhenMappingsAreNullUsesDefaultMappings() { + SimpleHttpCodeStatusMapper mapper = new SimpleHttpCodeStatusMapper(null); + assertThat(mapper.getStatusCode(Status.UNKNOWN)).isEqualTo(WebEndpointResponse.STATUS_OK); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(WebEndpointResponse.STATUS_OK); + assertThat(mapper.getStatusCode(Status.DOWN)).isEqualTo(WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + assertThat(mapper.getStatusCode(Status.OUT_OF_SERVICE)) + .isEqualTo(WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + } + + @Test + void getStatusCodeReturnsMappedStatus() { + Map map = new LinkedHashMap<>(); + map.put("up", 123); + map.put("down", 456); + SimpleHttpCodeStatusMapper mapper = new SimpleHttpCodeStatusMapper(map); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123); + assertThat(mapper.getStatusCode(Status.DOWN)).isEqualTo(456); + assertThat(mapper.getStatusCode(Status.OUT_OF_SERVICE)).isEqualTo(200); + } + + @Test + void getStatusCodeWhenMappingsAreNotUniformReturnsMappedStatus() { + Map map = new LinkedHashMap<>(); + map.put("out-of-service", 123); + SimpleHttpCodeStatusMapper mapper = new SimpleHttpCodeStatusMapper(map); + assertThat(mapper.getStatusCode(Status.OUT_OF_SERVICE)).isEqualTo(123); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleStatusAggregatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleStatusAggregatorTests.java new file mode 100644 index 0000000000..2a9ccf4c06 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SimpleStatusAggregatorTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SimpleStatusAggregator} + * + * @author Phillip Webb + * @author Christian Dupuis + */ +class SimpleStatusAggregatorTests { + + @Test + void getAggregateStatusWhenUsingDefaultOrder() { + SimpleStatusAggregator aggregator = new SimpleStatusAggregator(); + Status status = aggregator.getAggregateStatus(Status.DOWN, Status.UP, Status.UNKNOWN, Status.OUT_OF_SERVICE); + assertThat(status).isEqualTo(Status.DOWN); + } + + @Test + void getAggregateStatusWhenUsingCustomOrder() { + SimpleStatusAggregator aggregator = new SimpleStatusAggregator(Status.UNKNOWN, Status.UP, Status.OUT_OF_SERVICE, + Status.DOWN); + Status status = aggregator.getAggregateStatus(Status.DOWN, Status.UP, Status.UNKNOWN, Status.OUT_OF_SERVICE); + assertThat(status).isEqualTo(Status.UNKNOWN); + } + + @Test + void getAggregateStatusWhenHasCustomStatusAndUsingDefaultOrder() { + SimpleStatusAggregator aggregator = new SimpleStatusAggregator(); + Status status = aggregator.getAggregateStatus(Status.DOWN, Status.UP, Status.UNKNOWN, Status.OUT_OF_SERVICE, + new Status("CUSTOM")); + assertThat(status).isEqualTo(Status.DOWN); + } + + @Test + void getAggregateStatusWhenHasCustomStatusAndUsingCustomOrder() { + SimpleStatusAggregator aggregator = new SimpleStatusAggregator("DOWN", "OUT_OF_SERVICE", "UP", "UNKNOWN", + "CUSTOM"); + Status status = aggregator.getAggregateStatus(Status.DOWN, Status.UP, Status.UNKNOWN, Status.OUT_OF_SERVICE, + new Status("CUSTOM")); + assertThat(status).isEqualTo(Status.DOWN); + } + + @Test + void createWithNonUniformCodes() { + SimpleStatusAggregator aggregator = new SimpleStatusAggregator("out-of-service", "up"); + Status status = aggregator.getAggregateStatus(Status.UP, Status.OUT_OF_SERVICE); + assertThat(status).isEqualTo(Status.OUT_OF_SERVICE); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/StatusTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/StatusTests.java new file mode 100644 index 0000000000..eb17a9293b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/StatusTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import com.couchbase.client.deps.com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link Status}. + * + * @author Phillip Webb + */ +class StatusTests { + + @Test + void createWhenCodeIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new Status(null, "")) + .withMessage("Code must not be null"); + } + + @Test + void createWhenDescriptionIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new Status("code", null)) + .withMessage("Description must not be null"); + } + + @Test + void getCodeReturnsCode() { + Status status = new Status("spring", "boot"); + assertThat(status.getCode()).isEqualTo("spring"); + } + + @Test + void getDescriptionReturnsDescription() { + Status status = new Status("spring", "boot"); + assertThat(status.getDescription()).isEqualTo("boot"); + } + + @Test + void equalsAndHashCode() { + Status one = new Status("spring", "boot"); + Status two = new Status("spring", "framework"); + Status three = new Status("spock", "framework"); + assertThat(one).isEqualTo(one).isEqualTo(two).isNotEqualTo(three); + assertThat(one.hashCode()).isEqualTo(two.hashCode()); + } + + @Test + void toStringReturnsCode() { + assertThat(Status.OUT_OF_SERVICE.getCode()).isEqualTo("OUT_OF_SERVICE"); + } + + @Test + void serializeWithJacksonReturnsValidJson() throws Exception { + Status status = new Status("spring", "boot"); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(status); + assertThat(json).isEqualTo("{\"code\":\"spring\",\"description\":\"boot\"}"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointSettings.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointSettings.java new file mode 100644 index 0000000000..aacb1d9302 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/TestHealthEndpointSettings.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.springframework.boot.actuate.endpoint.SecurityContext; + +/** + * Test implementation of {@link HealthEndpointSettings}. + * + * @author Phillip Webb + */ +class TestHealthEndpointSettings implements HealthEndpointSettings { + + private final StatusAggregator statusAggregator = new SimpleStatusAggregator(); + + private final HttpCodeStatusMapper httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(); + + private boolean includeDetails = true; + + @Override + public boolean includeDetails(SecurityContext securityContext) { + return this.includeDetails; + } + + void setIncludeDetails(boolean includeDetails) { + this.includeDetails = includeDetails; + } + + @Override + public StatusAggregator getStatusAggregator() { + return this.statusAggregator; + } + + @Override + public HttpCodeStatusMapper getHttpCodeStatusMapper() { + return this.httpCodeStatusMapper; + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 47f34e634a..5f555abe21 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -726,17 +726,23 @@ configuration must permit access to the health endpoint for both authenticated a unauthenticated users. Health information is collected from the content of a -{sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[ -`HealthIndicatorRegistry`] (by default all -{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] instances +{sc-spring-boot-actuator}/health/HealthContributorRegistry.{sc-ext}[ +`HealthContributorRegistry`] (by default all +{sc-spring-boot-actuator}/health/HealthContributor.{sc-ext}[`HealthContributor`] instances defined in your `ApplicationContext`. Spring Boot includes a number of auto-configured -`HealthIndicators` and you can also write your own. By default, the final system state is -derived by the `HealthAggregator` which sorts the statuses from each `HealthIndicator` -based on an ordered list of statuses. The first status in the sorted list is used as the -overall health status. If no `HealthIndicator` returns a status that is known to the -`HealthAggregator`, an `UNKNOWN` status is used. +`HealthContributors` and you can also write your own. -TIP: The `HealthIndicatorRegistry` can be used to register and unregister health +A `HealthContributor` can either be a `HealthIndicator` or a `CompositeHealthContributor`. +A `HealthIndicator` provides actual health information, including a `Status`. A +`CompositeHealthContributor` provides a composite of other `HealthContributors`. Taken +together, contributor for a tree structure to represent the overall system heath. + +By default, the final system health is derived by a `StatusAggregator` which sorts the +statuses from each `HealthIndicator` based on an ordered list of statuses. The first +status in the sorted list is used as the overall health status. If no `HealthIndicator` +returns a status that is known to the `StatusAggregator`, an `UNKNOWN` status is used. + +TIP: The `HealthContributorRegistry` can be used to register and unregister health indicators at runtime. @@ -760,7 +766,7 @@ The following `HealthIndicators` are auto-configured by Spring Boot when appropr |{sc-spring-boot-actuator}/jdbc/DataSourceHealthIndicator.{sc-ext}[`DataSourceHealthIndicator`] |Checks that a connection to `DataSource` can be obtained. -|{sc-spring-boot-actuator}/elasticsearch/ElasticsearchHealthIndicator.{sc-ext}[`ElasticsearchHealthIndicator`] +|{sc-spring-boot-actuator}/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.{sc-ext}[`ElasticSearchRestHealthContributorAutoConfiguration`] |Checks that an Elasticsearch cluster is up. |{sc-spring-boot-actuator}/hazelcast/HazelcastHealthIndicator.{sc-ext}[`HazelcastHealthIndicator`] @@ -833,9 +839,9 @@ In addition to Spring Boot's predefined {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for `Health` to return a custom `Status` that represents a new system state. In such cases, a custom implementation of the -{sc-spring-boot-actuator}/health/HealthAggregator.{sc-ext}[`HealthAggregator`] interface +{sc-spring-boot-actuator}/health/StatusAggregator.{sc-ext}[`StatusAggregator`] interface also needs to be provided, or the default implementation has to be configured by using -the `management.health.status.order` configuration property. +the `management.endpoint.health.status.order` configuration property. For example, assume a new `Status` with code `FATAL` is being used in one of your `HealthIndicator` implementations. To configure the severity order, add the following @@ -843,7 +849,7 @@ property to your application properties: [source,properties,indent=0] ---- - management.health.status.order=FATAL, DOWN, OUT_OF_SERVICE, UNKNOWN, UP + management.endpoint.health.status.order=fatal,down,out-of-service,unknown,up ---- The HTTP status code in the response reflects the overall health status (for example, @@ -853,10 +859,10 @@ the following property maps `FATAL` to 503 (service unavailable): [source,properties,indent=0] ---- - management.health.status.http-mapping.FATAL=503 + management.endpoint.health.status.http-mapping.fatal=503 ---- -TIP: If you need more control, you can define your own `HealthStatusHttpMapper` bean. +TIP: If you need more control, you can define your own `HttpCodeStatusMapper` bean. The following table shows the default status mappings for the built-in statuses: @@ -881,18 +887,18 @@ The following table shows the default status mappings for the built-in statuses: [[reactive-health-indicators]] ==== Reactive Health Indicators -For reactive applications, such as those using Spring WebFlux, `ReactiveHealthIndicator` +For reactive applications, such as those using Spring WebFlux, `ReactiveHealthContributor` provides a non-blocking contract for getting application health. Similar to a traditional -`HealthIndicator`, health information is collected from the content of a -{sc-spring-boot-actuator}/health/ReactiveHealthIndicatorRegistry.{sc-ext}[ -`ReactiveHealthIndicatorRegistry`] (by default all -{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] and -{sc-spring-boot-actuator}/health/ReactiveHealthIndicator.{sc-ext}[ -`ReactiveHealthIndicator`] instances defined in your `ApplicationContext`. Regular -`HealthIndicator` that do not check against a reactive API are executed on the elastic +`HealthContributor`, health information is collected from the content of a +{sc-spring-boot-actuator}/health/ReactiveHealthContributorRegistry.{sc-ext}[ +`ReactiveHealthContributorRegistry`] (by default all +{sc-spring-boot-actuator}/health/HealthContributor.{sc-ext}[`HealthContributor`] and +{sc-spring-boot-actuator}/health/ReactiveHealthContributor.{sc-ext}[ +`ReactiveHealthContributor`] instances defined in your `ApplicationContext`). Regular +`HealthContributors` that do not check against a reactive API are executed on the elastic scheduler. -TIP: In a reactive application, The `ReactiveHealthIndicatorRegistry` can be used to +TIP: In a reactive application, The `ReactiveHealthContributorRegistry` can be used to register and unregister health indicators at runtime. To provide custom health information from a reactive API, you can register Spring beans