From 9e9548364504dc6508820919461d38104e4ee982 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 28 Nov 2017 18:48:33 -0800 Subject: [PATCH] Add cloudfoundry health extensions Fixes gh-11192 --- .../CloudFoundryEndpointFilter.java | 38 +++++ ...ndpointManagementContextConfiguration.java | 79 +++++++++ ...oundryWebAnnotationEndpointDiscoverer.java | 63 +++++++ ...dryReactiveHealthEndpointWebExtension.java | 60 +++++++ ...CloudFoundryActuatorAutoConfiguration.java | 6 +- ...CloudFoundryActuatorAutoConfiguration.java | 6 +- ...loudFoundryHealthEndpointWebExtension.java | 55 +++++++ ...ndpointManagementContextConfiguration.java | 10 +- .../main/resources/META-INF/spring.factories | 1 + .../CloudFoundryEndpointFilterTests.java | 70 ++++++++ ...yWebAnnotationEndpointDiscovererTests.java | 155 ++++++++++++++++++ ...FoundryActuatorAutoConfigurationTests.java | 18 ++ ...FoundryActuatorAutoConfigurationTests.java | 21 +++ .../AnnotationEndpointDiscoverer.java | 4 +- .../boot/actuate/health/HealthEndpoint.java | 4 + 15 files changed, 577 insertions(+), 13 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilter.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryHealthWebEndpointManagementContextConfiguration.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscoverer.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilterTests.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscovererTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilter.java new file mode 100644 index 00000000000..8cdf608b6c7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilter.java @@ -0,0 +1,38 @@ +/* + * 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.cloudfoundry; + +import org.springframework.boot.actuate.endpoint.EndpointDiscoverer; +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointInfo; +import org.springframework.boot.actuate.endpoint.web.WebOperation; + +/** + * {@link EndpointFilter} for endpoints discovered by + * {@link CloudFoundryWebAnnotationEndpointDiscoverer}. + * + * @author Madhura Bhave + */ +public class CloudFoundryEndpointFilter implements EndpointFilter { + + @Override + public boolean match(EndpointInfo info, EndpointDiscoverer discoverer) { + return (discoverer instanceof CloudFoundryWebAnnotationEndpointDiscoverer); + } + +} + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryHealthWebEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryHealthWebEndpointManagementContextConfiguration.java new file mode 100644 index 00000000000..86f027cbc2e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryHealthWebEndpointManagementContextConfiguration.java @@ -0,0 +1,79 @@ +/* + * 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.cloudfoundry; + +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.CloudFoundryReactiveHealthEndpointWebExtension; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryHealthEndpointWebExtension; +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.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration for Cloud Foundry Health endpoint extensions. + * + * @author Madhura Bhave + */ +@Configuration +@AutoConfigureBefore({ ReactiveCloudFoundryActuatorAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class }) +@AutoConfigureAfter(HealthEndpointAutoConfiguration.class) +public class CloudFoundryHealthWebEndpointManagementContextConfiguration { + + @Configuration + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + static class ServletWebHealthConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(HealthEndpoint.class) + public CloudFoundryHealthEndpointWebExtension cloudFoundryHealthEndpointWebExtension( + HealthEndpoint healthEndpoint, HealthStatusHttpMapper healthStatusHttpMapper) { + HealthEndpoint delegate = new HealthEndpoint(healthEndpoint.getHealthIndicator(), true); + return new CloudFoundryHealthEndpointWebExtension(delegate, healthStatusHttpMapper); + } + + } + + @Configuration + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + static class ReactiveWebHealthConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + @ConditionalOnBean(HealthEndpoint.class) + public CloudFoundryReactiveHealthEndpointWebExtension cloudFoundryReactiveHealthEndpointWebExtension( + ReactiveHealthIndicator reactiveHealthIndicator, + HealthStatusHttpMapper healthStatusHttpMapper) { + return new CloudFoundryReactiveHealthEndpointWebExtension(reactiveHealthIndicator, + healthStatusHttpMapper); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscoverer.java new file mode 100644 index 00000000000..68d167435d2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscoverer.java @@ -0,0 +1,63 @@ +/* + * 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.cloudfoundry; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor; +import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper; +import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; +import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; +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}. + * + * @author Madhura Bhave + */ +public class CloudFoundryWebAnnotationEndpointDiscoverer extends WebAnnotationEndpointDiscoverer { + + private final ApplicationContext applicationContext; + + private final Class requiredExtensionType; + + public CloudFoundryWebAnnotationEndpointDiscoverer(ApplicationContext applicationContext, ParameterMapper parameterMapper, + EndpointMediaTypes endpointMediaTypes, EndpointPathResolver endpointPathResolver, + Collection invokerAdvisors, Collection> filters, Class requiredExtensionType) { + super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters); + this.applicationContext = applicationContext; + this.requiredExtensionType = requiredExtensionType; + } + + @Override + protected void addExtension(Map, DiscoveredEndpoint> endpoints, Map, DiscoveredExtension> extensions, String beanName) { + Class extensionType = this.applicationContext.getType(beanName); + Class endpointType = getEndpointType(extensionType); + if (HealthEndpoint.class.equals(endpointType) && !this.requiredExtensionType.equals(extensionType)) { + return; + } + super.addExtension(endpoints, extensions, beanName); + } + +} + 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 new file mode 100644 index 00000000000..7f9aa5b2c36 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtension.java @@ -0,0 +1,60 @@ +/* + * 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.cloudfoundry.reactive; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryEndpointFilter; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +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; + +/** + * Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint} + * that always exposes full health details. + * + * @author Madhura Bhave + */ +@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class) +public class CloudFoundryReactiveHealthEndpointWebExtension { + + private final ReactiveHealthIndicator delegate; + + private final HealthStatusHttpMapper statusHttpMapper; + + public CloudFoundryReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate, + HealthStatusHttpMapper statusHttpMapper) { + this.delegate = delegate; + this.statusHttpMapper = statusHttpMapper; + } + + @ReadOperation + public Mono> health() { + return this.delegate.health().map((health) -> { + Integer status = this.statusHttpMapper.mapStatus(health.getStatus()); + return new WebEndpointResponse<>(health, status); + }); + } + +} + + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java index ffa8ef73fd2..16e3abd8773 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfiguration.java @@ -21,10 +21,10 @@ import java.util.Collections; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; -import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; @@ -68,9 +68,9 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration { public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping( ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, WebClient.Builder webClientBuilder) { - WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer( + CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer( this.applicationContext, parameterMapper, endpointMediaTypes, - EndpointPathResolver.useEndpointId(), null, null); + EndpointPathResolver.useEndpointId(), null, null, CloudFoundryReactiveHealthEndpointWebExtension.class); ReactiveCloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor( webClientBuilder, this.applicationContext.getEnvironment()); return new CloudFoundryWebFluxEndpointHandlerMapping( diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java index c048d7872be..4ac21b1a851 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfiguration.java @@ -18,11 +18,11 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; import java.util.Arrays; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; -import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -72,9 +72,9 @@ public class CloudFoundryActuatorAutoConfiguration { public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, RestTemplateBuilder restTemplateBuilder) { - WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer( + CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer( this.applicationContext, parameterMapper, endpointMediaTypes, - EndpointPathResolver.useEndpointId(), null, null); + EndpointPathResolver.useEndpointId(), null, null, CloudFoundryHealthEndpointWebExtension.class); CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor( restTemplateBuilder, this.applicationContext.getEnvironment()); return new CloudFoundryWebEndpointServletHandlerMapping( 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 new file mode 100644 index 00000000000..b9418c0cb07 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryHealthEndpointWebExtension.java @@ -0,0 +1,55 @@ +/* + * 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.cloudfoundry.servlet; + +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryEndpointFilter; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +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; + +/** + * {@link EndpointWebExtension} for the {@link HealthEndpoint} + * that always exposes full health details. + * + * @author Madhura Bhave + */ +@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class) +public class CloudFoundryHealthEndpointWebExtension { + + private final HealthEndpoint delegate; + + private final HealthStatusHttpMapper statusHttpMapper; + + public CloudFoundryHealthEndpointWebExtension(HealthEndpoint delegate, + HealthStatusHttpMapper statusHttpMapper) { + this.delegate = delegate; + this.statusHttpMapper = statusHttpMapper; + } + + @ReadOperation + public WebEndpointResponse getHealth() { + Health health = this.delegate.health(); + Integer status = this.statusHttpMapper.mapStatus(health.getStatus()); + return new WebEndpointResponse<>(health, status); + } + +} + diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthWebEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthWebEndpointManagementContextConfiguration.java index f08a47186ea..752ffaa864c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthWebEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthWebEndpointManagementContextConfiguration.java @@ -64,12 +64,11 @@ public class HealthWebEndpointManagementContextConfiguration { @ConditionalOnWebApplication(type = Type.REACTIVE) static class ReactiveWebHealthConfiguration { - private final ReactiveHealthIndicator reactiveHealthIndicator; - - ReactiveWebHealthConfiguration(ObjectProvider healthAggregator, + @Bean + public ReactiveHealthIndicator reactiveHealthIndicator(ObjectProvider healthAggregator, ObjectProvider> reactiveHealthIndicators, ObjectProvider> healthIndicators) { - this.reactiveHealthIndicator = new CompositeReactiveHealthIndicatorFactory() + return new CompositeReactiveHealthIndicatorFactory() .createReactiveHealthIndicator( healthAggregator.getIfAvailable(OrderedHealthAggregator::new), reactiveHealthIndicators @@ -82,9 +81,10 @@ public class HealthWebEndpointManagementContextConfiguration { @ConditionalOnEnabledEndpoint @ConditionalOnBean(HealthEndpoint.class) public ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension( + ReactiveHealthIndicator reactiveHealthIndicator, HealthStatusHttpMapper healthStatusHttpMapper, HealthEndpointProperties properties) { - return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator, + return new ReactiveHealthEndpointWebExtension(reactiveHealthIndicator, healthStatusHttpMapper, properties.isShowDetails()); } 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 6b24efad9be..213bba4b497 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 @@ -6,6 +6,7 @@ org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfigurat org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryHealthWebEndpointManagementContextConfiguration,\ org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.context.properties.ConfigurationPropertiesReportEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.context.ShutdownEndpointAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilterTests.java new file mode 100644 index 00000000000..2d3e84f2942 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryEndpointFilterTests.java @@ -0,0 +1,70 @@ +/* + * 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.cloudfoundry; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor; +import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper; +import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; +import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CloudFoundryEndpointFilter}. + * + * @author Madhura Bhave + */ +public class CloudFoundryEndpointFilterTests { + + private CloudFoundryEndpointFilter filter; + + @Before + public void setUp() throws Exception { + this.filter = new CloudFoundryEndpointFilter(); + } + + @Test + public void matchIfDiscovererCloudFoundryShouldReturnFalse() throws Exception { + 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); + assertThat(this.filter.match(null, discoverer)).isFalse(); + } + + static class TestEndpointDiscoverer extends WebAnnotationEndpointDiscoverer { + + TestEndpointDiscoverer(ApplicationContext applicationContext, ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, EndpointPathResolver endpointPathResolver, Collection invokerAdvisors, Collection> filters) { + super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscovererTests.java new file mode 100644 index 00000000000..d436a9c4e70 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscovererTests.java @@ -0,0 +1,155 @@ +/* + * 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.cloudfoundry; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.junit.Test; + +import org.springframework.boot.actuate.endpoint.EndpointInfo; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor; +import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper; +import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; +import org.springframework.boot.actuate.endpoint.web.WebOperation; +import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; +import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.support.DefaultConversionService; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CloudFoundryWebAnnotationEndpointDiscoverer}. + * + * @author Madhura Bhave + */ +public class CloudFoundryWebAnnotationEndpointDiscovererTests { + + @Test + public void discovererShouldAddSuppliedExtensionForHealthEndpoint() throws Exception { + load(TestConfiguration.class, endpointDiscoverer -> { + Collection> endpoints = endpointDiscoverer.discoverEndpoints(); + assertThat(endpoints.size()).isEqualTo(2); + }); + } + + private void load(Class configuration, + Consumer consumer) { + this.load((id) -> null, (id) -> id, configuration, consumer); + } + + private void load(Function timeToLive, + EndpointPathResolver endpointPathResolver, Class configuration, + Consumer consumer) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + configuration); + try { + ConversionServiceParameterMapper parameterMapper = new ConversionServiceParameterMapper( + DefaultConversionService.getSharedInstance()); + EndpointMediaTypes mediaTypes = new EndpointMediaTypes( + Collections.singletonList("application/json"), + Collections.singletonList("application/json")); + CloudFoundryWebAnnotationEndpointDiscoverer discoverer = new CloudFoundryWebAnnotationEndpointDiscoverer( + context, parameterMapper, mediaTypes, endpointPathResolver, + Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), + null, HealthWebEndpointExtension.class); + consumer.accept(discoverer); + } + finally { + context.close(); + } + } + + @Configuration + static class TestConfiguration { + + @Bean + public TestEndpoint testEndpoint() { + return new TestEndpoint(); + } + + @Bean + public HealthEndpoint healthEndpoint() { + return new HealthEndpoint(null, true); + } + + @Bean + public TestWebEndpointExtension testEndpointExtension() { + return new TestWebEndpointExtension(); + } + + @Bean + public HealthWebEndpointExtension healthEndpointExtension() { + return new HealthWebEndpointExtension(); + } + + @Bean + public OtherHealthWebEndpointExtension otherHealthEndpointExtension() { + return new OtherHealthWebEndpointExtension(); + } + + } + + @EndpointWebExtension(endpoint = TestEndpoint.class) + static class TestWebEndpointExtension { + + @ReadOperation + public Object getAll() { + return null; + } + + } + + @Endpoint(id = "test") + static class TestEndpoint { + + @ReadOperation + public Object getAll() { + return null; + } + + } + + @EndpointWebExtension(endpoint = HealthEndpoint.class) + static class HealthWebEndpointExtension { + + @ReadOperation + public Object getAll() { + return null; + } + + } + + @EndpointWebExtension(endpoint = HealthEndpoint.class) + static class OtherHealthWebEndpointExtension { + + @ReadOperation + public Object getAll() { + return null; + } + + } + +} 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 d0d523a0b48..55d19506a70 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 @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -24,13 +25,17 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryHealthWebEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthWebEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; +import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -221,6 +226,19 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { assertThat(operation.getRequestPredicate().getPath()).isEqualTo("test"); } + @Test + public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() throws Exception { + setupContextWithCloudEnabled(); + this.context.register(HealthEndpointAutoConfiguration.class, HealthWebEndpointManagementContextConfiguration.class, + CloudFoundryHealthWebEndpointManagementContextConfiguration.class); + this.context.refresh(); + Collection> 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); + } + private void setupContextWithCloudEnabled() { TestPropertyValues .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", 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 c91c7e586b9..804986bb388 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 @@ -24,14 +24,18 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryHealthWebEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthWebEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; +import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; @@ -241,6 +245,23 @@ public class CloudFoundryActuatorAutoConfigurationTests { .isEqualTo("test"); } + @Test + public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() throws Exception { + TestPropertyValues + .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, + CloudFoundryHealthWebEndpointManagementContextConfiguration.class); + this.context.refresh(); + Collection> 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); + } + private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping() { TestPropertyValues .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AnnotationEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AnnotationEndpointDiscoverer.java index 52a22ff6a04..666ebd62c9a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AnnotationEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AnnotationEndpointDiscoverer.java @@ -173,7 +173,7 @@ public abstract class AnnotationEndpointDiscoverer return extensions; } - private void addExtension(Map, DiscoveredEndpoint> endpoints, + protected void addExtension(Map, DiscoveredEndpoint> endpoints, Map, DiscoveredExtension> extensions, String beanName) { Class extensionType = this.applicationContext.getType(beanName); Class endpointType = getEndpointType(extensionType); @@ -199,7 +199,7 @@ public abstract class AnnotationEndpointDiscoverer } } - private Class getEndpointType(Class extensionType) { + protected Class getEndpointType(Class extensionType) { AnnotationAttributes attributes = AnnotatedElementUtils .getMergedAnnotationAttributes(extensionType, EndpointExtension.class); Class endpointType = attributes.getClass("endpoint"); 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 e242c43ed28..032f55ba3f1 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 @@ -53,4 +53,8 @@ public class HealthEndpoint { return Health.status(health.getStatus()).build(); } + public HealthIndicator getHealthIndicator() { + return this.healthIndicator; + } + }