Polish health indicators

Align reactive and non-reactive web extensions and update `showDetails`
so that it only applies to web exposure.

See gh-11113
See gh-11192
This commit is contained in:
Phillip Webb
2017-11-28 18:39:45 -08:00
parent 9e95483645
commit 2c959b8e2a
23 changed files with 280 additions and 172 deletions

View File

@@ -23,13 +23,15 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.Cloud
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -39,7 +41,9 @@ import org.springframework.context.annotation.Configuration;
* @author Madhura Bhave
*/
@Configuration
@AutoConfigureBefore({ ReactiveCloudFoundryActuatorAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class })
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
@AutoConfigureBefore({ ReactiveCloudFoundryActuatorAutoConfiguration.class,
CloudFoundryActuatorAutoConfiguration.class })
@AutoConfigureAfter(HealthEndpointAutoConfiguration.class)
public class CloudFoundryHealthWebEndpointManagementContextConfiguration {
@@ -50,11 +54,10 @@ public class CloudFoundryHealthWebEndpointManagementContextConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnBean({ HealthEndpoint.class, HealthEndpointWebExtension.class })
public CloudFoundryHealthEndpointWebExtension cloudFoundryHealthEndpointWebExtension(
HealthEndpoint healthEndpoint, HealthStatusHttpMapper healthStatusHttpMapper) {
HealthEndpoint delegate = new HealthEndpoint(healthEndpoint.getHealthIndicator(), true);
return new CloudFoundryHealthEndpointWebExtension(delegate, healthStatusHttpMapper);
HealthEndpointWebExtension healthEndpointWebExtension) {
return new CloudFoundryHealthEndpointWebExtension(healthEndpointWebExtension);
}
}
@@ -66,12 +69,12 @@ public class CloudFoundryHealthWebEndpointManagementContextConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnBean({ HealthEndpoint.class,
ReactiveHealthEndpointWebExtension.class })
public CloudFoundryReactiveHealthEndpointWebExtension cloudFoundryReactiveHealthEndpointWebExtension(
ReactiveHealthIndicator reactiveHealthIndicator,
HealthStatusHttpMapper healthStatusHttpMapper) {
return new CloudFoundryReactiveHealthEndpointWebExtension(reactiveHealthIndicator,
healthStatusHttpMapper);
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension) {
return new CloudFoundryReactiveHealthEndpointWebExtension(
reactiveHealthEndpointWebExtension);
}
}

View File

@@ -17,9 +17,9 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
import java.util.Collection;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
@@ -30,34 +30,36 @@ import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.context.ApplicationContext;
/**
* {@link WebAnnotationEndpointDiscoverer} for Cloud Foundry that uses Cloud Foundry specific
* extensions for the {@link HealthEndpoint}.
* {@link WebAnnotationEndpointDiscoverer} for Cloud Foundry that uses Cloud Foundry
* specific extensions for the {@link HealthEndpoint}.
*
* @author Madhura Bhave
*/
public class CloudFoundryWebAnnotationEndpointDiscoverer extends WebAnnotationEndpointDiscoverer {
private final ApplicationContext applicationContext;
public class CloudFoundryWebAnnotationEndpointDiscoverer
extends WebAnnotationEndpointDiscoverer {
private final Class<?> requiredExtensionType;
public CloudFoundryWebAnnotationEndpointDiscoverer(ApplicationContext applicationContext, ParameterMapper parameterMapper,
EndpointMediaTypes endpointMediaTypes, EndpointPathResolver endpointPathResolver,
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors, Collection<? extends EndpointFilter<WebOperation>> filters, Class<?> requiredExtensionType) {
super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters);
this.applicationContext = applicationContext;
public CloudFoundryWebAnnotationEndpointDiscoverer(
ApplicationContext applicationContext, ParameterMapper parameterMapper,
EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver,
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<? extends EndpointFilter<WebOperation>> filters,
Class<?> requiredExtensionType) {
super(applicationContext, parameterMapper, endpointMediaTypes,
endpointPathResolver, invokerAdvisors, filters);
this.requiredExtensionType = requiredExtensionType;
}
@Override
protected void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints, Map<Class<?>, DiscoveredExtension> extensions, String beanName) {
Class<?> extensionType = this.applicationContext.getType(beanName);
Class<?> endpointType = getEndpointType(extensionType);
if (HealthEndpoint.class.equals(endpointType) && !this.requiredExtensionType.equals(extensionType)) {
return;
protected boolean isExtensionExposed(Class<?> endpointType, Class<?> extensionType,
EndpointInfo<WebOperation> endpointInfo) {
if (HealthEndpoint.class.equals(endpointType)
&& !this.requiredExtensionType.equals(extensionType)) {
return false;
}
super.addExtension(endpoints, extensions, beanName);
return super.isExtensionExposed(endpointType, extensionType, endpointInfo);
}
}

View File

@@ -25,36 +25,28 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
/**
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint}
* that always exposes full health details.
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint} that always
* exposes full health details.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
public class CloudFoundryReactiveHealthEndpointWebExtension {
private final ReactiveHealthIndicator delegate;
private final ReactiveHealthEndpointWebExtension delegate;
private final HealthStatusHttpMapper statusHttpMapper;
public CloudFoundryReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
HealthStatusHttpMapper statusHttpMapper) {
public CloudFoundryReactiveHealthEndpointWebExtension(
ReactiveHealthEndpointWebExtension delegate) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
}
@ReadOperation
public Mono<WebEndpointResponse<Health>> health() {
return this.delegate.health().map((health) -> {
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
});
return this.delegate.health(true);
}
}

View File

@@ -70,7 +70,8 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
WebClient.Builder webClientBuilder) {
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), null, null, CloudFoundryReactiveHealthEndpointWebExtension.class);
EndpointPathResolver.useEndpointId(), null, null,
CloudFoundryReactiveHealthEndpointWebExtension.class);
ReactiveCloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
webClientBuilder, this.applicationContext.getEnvironment());
return new CloudFoundryWebFluxEndpointHandlerMapping(

View File

@@ -74,7 +74,8 @@ public class CloudFoundryActuatorAutoConfiguration {
RestTemplateBuilder restTemplateBuilder) {
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), null, null, CloudFoundryHealthEndpointWebExtension.class);
EndpointPathResolver.useEndpointId(), null, null,
CloudFoundryHealthEndpointWebExtension.class);
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
restTemplateBuilder, this.applicationContext.getEnvironment());
return new CloudFoundryWebEndpointServletHandlerMapping(

View File

@@ -23,33 +23,27 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
/**
* {@link EndpointWebExtension} for the {@link HealthEndpoint}
* that always exposes full health details.
* {@link EndpointWebExtension} for the {@link HealthEndpoint} that always exposes full
* health details.
*
* @author Madhura Bhave
* @since 2.0.0
*/
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
public class CloudFoundryHealthEndpointWebExtension {
private final HealthEndpoint delegate;
private final HealthEndpointWebExtension delegate;
private final HealthStatusHttpMapper statusHttpMapper;
public CloudFoundryHealthEndpointWebExtension(HealthEndpoint delegate,
HealthStatusHttpMapper statusHttpMapper) {
public CloudFoundryHealthEndpointWebExtension(HealthEndpointWebExtension delegate) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
}
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
Health health = this.delegate.health();
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
return this.delegate.getHealth(true);
}
}

View File

@@ -16,24 +16,14 @@
package org.springframework.boot.actuate.autoconfigure.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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;
import org.springframework.util.ClassUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthEndpoint}.
@@ -47,46 +37,11 @@ import org.springframework.util.ClassUtils;
@EnableConfigurationProperties(HealthEndpointProperties.class)
public class HealthEndpointAutoConfiguration {
private final HealthIndicator healthIndicator;
public HealthEndpointAutoConfiguration(ApplicationContext applicationContext,
ObjectProvider<HealthAggregator> healthAggregator) {
this.healthIndicator = getHealthIndicator(applicationContext,
healthAggregator.getIfAvailable(OrderedHealthAggregator::new));
}
private HealthIndicator getHealthIndicator(ApplicationContext applicationContext,
HealthAggregator healthAggregator) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext)
.forEach(indicators::putIfAbsent);
}
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
return factory.createHealthIndicator(healthAggregator, indicators);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint(HealthEndpointProperties properties) {
return new HealthEndpoint(this.healthIndicator, properties.isShowDetails());
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> 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();
}
public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) {
return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext));
}
}

View File

@@ -28,7 +28,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class HealthEndpointProperties {
/**
* Whether to show full health details instead of just the status.
* Whether to show full health details instead of just the status when exposed over a
* potentially insecure connection.
*/
private boolean showDetails;

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2012-2017 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
*
* http://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.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory;
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.ReactiveHealthIndicator;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;
/**
* Creates a {@link CompositeHealthIndicator} from beans in the
* {@link ApplicationContext}.
*
* @author Phillip Webb
*/
final class HealthIndicatorBeansComposite {
private HealthIndicatorBeansComposite() {
}
public static HealthIndicator get(ApplicationContext applicationContext) {
HealthAggregator healthAggregator = getHealthAggregator(applicationContext);
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext)
.forEach(indicators::putIfAbsent);
}
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
return factory.createHealthIndicator(healthAggregator, indicators);
}
private static HealthAggregator getHealthAggregator(
ApplicationContext applicationContext) {
try {
return applicationContext.getBean(HealthAggregator.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new OrderedHealthAggregator();
}
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> 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();
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2017 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
*
* http://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.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicator;
import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicatorFactory;
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.ReactiveHealthIndicator;
import org.springframework.context.ApplicationContext;
/**
* Creates a {@link CompositeReactiveHealthIndicator} from beans in the
* {@link ApplicationContext}.
*
* @author Phillip Webb
*/
final class HealthIndicatorBeansReactiveComposite {
private HealthIndicatorBeansReactiveComposite() {
}
public static ReactiveHealthIndicator get(ApplicationContext applicationContext) {
HealthAggregator healthAggregator = getHealthAggregator(applicationContext);
return new CompositeReactiveHealthIndicatorFactory()
.createReactiveHealthIndicator(healthAggregator,
applicationContext.getBeansOfType(ReactiveHealthIndicator.class),
applicationContext.getBeansOfType(HealthIndicator.class));
}
private static HealthAggregator getHealthAggregator(
ApplicationContext applicationContext) {
try {
return applicationContext.getBean(HealthAggregator.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new OrderedHealthAggregator();
}
}
}

View File

@@ -36,6 +36,7 @@ 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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -65,7 +66,8 @@ public class HealthWebEndpointManagementContextConfiguration {
static class ReactiveWebHealthConfiguration {
@Bean
public ReactiveHealthIndicator reactiveHealthIndicator(ObjectProvider<HealthAggregator> healthAggregator,
public ReactiveHealthIndicator reactiveHealthIndicator(
ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<Map<String, ReactiveHealthIndicator>> reactiveHealthIndicators,
ObjectProvider<Map<String, HealthIndicator>> healthIndicators) {
return new CompositeReactiveHealthIndicatorFactory()
@@ -99,8 +101,12 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class)
public HealthEndpointWebExtension healthEndpointWebExtension(
HealthEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) {
return new HealthEndpointWebExtension(delegate, healthStatusHttpMapper);
ApplicationContext applicationContext,
HealthStatusHttpMapper healthStatusHttpMapper,
HealthEndpointProperties properties) {
return new HealthEndpointWebExtension(
HealthIndicatorBeansComposite.get(applicationContext),
healthStatusHttpMapper, properties.isShowDetails());
}
}

View File

@@ -49,20 +49,27 @@ public class CloudFoundryEndpointFilterTests {
@Test
public void matchIfDiscovererCloudFoundryShouldReturnFalse() throws Exception {
CloudFoundryWebAnnotationEndpointDiscoverer discoverer = Mockito.mock(CloudFoundryWebAnnotationEndpointDiscoverer.class);
CloudFoundryWebAnnotationEndpointDiscoverer discoverer = Mockito
.mock(CloudFoundryWebAnnotationEndpointDiscoverer.class);
assertThat(this.filter.match(null, discoverer)).isTrue();
}
@Test
public void matchIfDiscovererNotCloudFoundryShouldReturnFalse() throws Exception {
WebAnnotationEndpointDiscoverer discoverer = Mockito.mock(WebAnnotationEndpointDiscoverer.class);
WebAnnotationEndpointDiscoverer discoverer = Mockito
.mock(WebAnnotationEndpointDiscoverer.class);
assertThat(this.filter.match(null, discoverer)).isFalse();
}
static class TestEndpointDiscoverer extends WebAnnotationEndpointDiscoverer {
TestEndpointDiscoverer(ApplicationContext applicationContext, ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, EndpointPathResolver endpointPathResolver, Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors, Collection<? extends EndpointFilter<WebOperation>> filters) {
super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters);
TestEndpointDiscoverer(ApplicationContext applicationContext,
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver,
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<? extends EndpointFilter<WebOperation>> filters) {
super(applicationContext, parameterMapper, endpointMediaTypes,
endpointPathResolver, invokerAdvisors, filters);
}
}

View File

@@ -50,7 +50,8 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
@Test
public void discovererShouldAddSuppliedExtensionForHealthEndpoint() throws Exception {
load(TestConfiguration.class, endpointDiscoverer -> {
Collection<EndpointInfo<WebOperation>> endpoints = endpointDiscoverer.discoverEndpoints();
Collection<EndpointInfo<WebOperation>> endpoints = endpointDiscoverer
.discoverEndpoints();
assertThat(endpoints.size()).isEqualTo(2);
});
}
@@ -92,7 +93,7 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
@Bean
public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(null, true);
return new HealthEndpoint(null);
}
@Bean

View File

@@ -229,14 +229,19 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
@Test
public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() throws Exception {
setupContextWithCloudEnabled();
this.context.register(HealthEndpointAutoConfiguration.class, HealthWebEndpointManagementContextConfiguration.class,
this.context.register(HealthEndpointAutoConfiguration.class,
HealthWebEndpointManagementContextConfiguration.class,
CloudFoundryHealthWebEndpointManagementContextConfiguration.class);
this.context.refresh();
Collection<EndpointInfo<WebOperation>> endpoints = getHandlerMapping().getEndpoints();
Collection<EndpointInfo<WebOperation>> endpoints = getHandlerMapping()
.getEndpoints();
EndpointInfo endpointInfo = (EndpointInfo) (endpoints.toArray()[0]);
WebOperation webOperation = (WebOperation) endpointInfo.getOperations().toArray()[0];
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation.getInvoker();
assertThat(ReflectionTestUtils.getField(invoker, "target")).isInstanceOf(CloudFoundryReactiveHealthEndpointWebExtension.class);
WebOperation webOperation = (WebOperation) endpointInfo.getOperations()
.toArray()[0];
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation
.getInvoker();
assertThat(ReflectionTestUtils.getField(invoker, "target"))
.isInstanceOf(CloudFoundryReactiveHealthEndpointWebExtension.class);
}
private void setupContextWithCloudEnabled() {

View File

@@ -251,15 +251,21 @@ public class CloudFoundryActuatorAutoConfigurationTests {
.of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",
"vcap.application.cf_api:http://my-cloud-controller.com")
.applyTo(this.context);
this.context.register(HealthEndpointAutoConfiguration.class, HealthWebEndpointManagementContextConfiguration.class,
this.context.register(HealthEndpointAutoConfiguration.class,
HealthWebEndpointManagementContextConfiguration.class,
CloudFoundryHealthWebEndpointManagementContextConfiguration.class);
this.context.refresh();
Collection<EndpointInfo<WebOperation>> endpoints = this.context.getBean("cloudFoundryWebEndpointServletHandlerMapping",
CloudFoundryWebEndpointServletHandlerMapping.class).getEndpoints();
Collection<EndpointInfo<WebOperation>> endpoints = this.context
.getBean("cloudFoundryWebEndpointServletHandlerMapping",
CloudFoundryWebEndpointServletHandlerMapping.class)
.getEndpoints();
EndpointInfo endpointInfo = (EndpointInfo) (endpoints.toArray()[0]);
WebOperation webOperation = (WebOperation) endpointInfo.getOperations().toArray()[0];
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation.getInvoker();
assertThat(ReflectionTestUtils.getField(invoker, "target")).isInstanceOf(CloudFoundryHealthEndpointWebExtension.class);
WebOperation webOperation = (WebOperation) endpointInfo.getOperations()
.toArray()[0];
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation
.getInvoker();
assertThat(ReflectionTestUtils.getField(invoker, "target"))
.isInstanceOf(CloudFoundryHealthEndpointWebExtension.class);
}
private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping() {

View File

@@ -74,7 +74,7 @@ public class HealthEndpointDocumentationTests extends AbstractEndpointDocumentat
@Bean
public HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(new CompositeHealthIndicator(
new OrderedHealthAggregator(), healthIndicators), true);
new OrderedHealthAggregator(), healthIndicators));
}
@Bean

View File

@@ -57,7 +57,7 @@ public class HealthEndpointAutoConfigurationTests {
verify(indicator, times(0)).health();
Health health = context.getBean(HealthEndpoint.class).health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).isEmpty();
assertThat(health.getDetails()).isNotEmpty();
verify(indicator, times(1)).health();
});
}

View File

@@ -173,13 +173,13 @@ public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
return extensions;
}
protected void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints,
private void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints,
Map<Class<?>, DiscoveredExtension> extensions, String beanName) {
Class<?> extensionType = this.applicationContext.getType(beanName);
Class<?> endpointType = getEndpointType(extensionType);
DiscoveredEndpoint endpoint = getExtendingEndpoint(endpoints, extensionType,
endpointType);
if (isExtensionExposed(extensionType, endpoint.getInfo())) {
if (isExtensionExposed(endpointType, extensionType, endpoint.getInfo())) {
Assert.state(endpoint.isExposed() || isEndpointFiltered(endpoint.getInfo()),
() -> "Invalid extension " + extensionType.getName() + "': endpoint '"
+ endpointType.getName()
@@ -199,7 +199,7 @@ public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
}
}
protected Class<?> getEndpointType(Class<?> extensionType) {
private Class<?> getEndpointType(Class<?> extensionType) {
AnnotationAttributes attributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
Class<?> endpointType = attributes.getClass("endpoint");
@@ -242,7 +242,14 @@ public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
return false;
}
private boolean isExtensionExposed(Class<?> extensionType,
/**
* Determines if an extension is exposed.
* @param endpointType the endpoint type
* @param extensionType the extension type
* @param endpointInfo the endpoint info
* @return if the extension is exposed
*/
protected boolean isExtensionExposed(Class<?> endpointType, Class<?> extensionType,
EndpointInfo<T> endpointInfo) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);

View File

@@ -32,29 +32,17 @@ public class HealthEndpoint {
private final HealthIndicator healthIndicator;
private final boolean showDetails;
/**
* Create a new {@link HealthEndpoint} instance.
* @param healthIndicator the health indicator
* @param showDetails if full details should be returned instead of just the status
*/
public HealthEndpoint(HealthIndicator healthIndicator, boolean showDetails) {
public HealthEndpoint(HealthIndicator healthIndicator) {
this.healthIndicator = healthIndicator;
this.showDetails = showDetails;
}
@ReadOperation
public Health health() {
Health health = this.healthIndicator.health();
if (this.showDetails) {
return health;
}
return Health.status(health.getStatus()).build();
}
public HealthIndicator getHealthIndicator() {
return this.healthIndicator;
return this.healthIndicator.health();
}
}

View File

@@ -34,20 +34,30 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten
@EndpointWebExtension(endpoint = HealthEndpoint.class)
public class HealthEndpointWebExtension {
private final HealthEndpoint delegate;
private final HealthIndicator delegate;
private final HealthStatusHttpMapper statusHttpMapper;
public HealthEndpointWebExtension(HealthEndpoint delegate,
HealthStatusHttpMapper statusHttpMapper) {
private final boolean showDetails;
public HealthEndpointWebExtension(HealthIndicator delegate,
HealthStatusHttpMapper statusHttpMapper, boolean showDetails) {
this.delegate = delegate;
this.statusHttpMapper = statusHttpMapper;
this.showDetails = showDetails;
}
@ReadOperation
public WebEndpointResponse<Health> getHealth() {
return getHealth(this.showDetails);
}
public WebEndpointResponse<Health> getHealth(boolean showDetails) {
Health health = this.delegate.health();
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
if (!showDetails) {
health = Health.status(health.getStatus()).build();
}
return new WebEndpointResponse<>(health, status);
}

View File

@@ -46,9 +46,13 @@ public class ReactiveHealthEndpointWebExtension {
@ReadOperation
public Mono<WebEndpointResponse<Health>> health() {
return health(this.showDetails);
}
public Mono<WebEndpointResponse<Health>> health(boolean showDetails) {
return this.delegate.health().map((health) -> {
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
if (!this.showDetails) {
if (!showDetails) {
health = Health.status(health.getStatus()).build();
}
return new WebEndpointResponse<>(health, status);

View File

@@ -41,7 +41,7 @@ public class HealthEndpointTests {
healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP)
.withDetail("second", "2").build());
HealthEndpoint endpoint = new HealthEndpoint(
createHealthIndicator(healthIndicators), true);
createHealthIndicator(healthIndicators));
Health health = endpoint.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain");
@@ -51,20 +51,6 @@ public class HealthEndpointTests {
assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2"));
}
@Test
public void onlyStatusIsExposed() {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("up", () -> new Health.Builder().status(Status.UP)
.withDetail("first", "1").build());
healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP)
.withDetail("second", "2").build());
HealthEndpoint endpoint = new HealthEndpoint(
createHealthIndicator(healthIndicators), false);
Health health = endpoint.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).isEmpty();
}
private HealthIndicator createHealthIndicator(
Map<String, HealthIndicator> healthIndicators) {
return new CompositeHealthIndicatorFactory()

View File

@@ -66,14 +66,16 @@ public class HealthEndpointWebIntegrationTests {
Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(
new CompositeHealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators),
true);
new OrderedHealthAggregator(), healthIndicators));
}
@Bean
public HealthEndpointWebExtension healthWebEndpointExtension(
HealthEndpoint delegate) {
return new HealthEndpointWebExtension(delegate, new HealthStatusHttpMapper());
Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpointWebExtension(
new CompositeHealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators),
new HealthStatusHttpMapper(), true);
}
@Bean