Add cloudfoundry health extensions

Fixes gh-11192
This commit is contained in:
Madhura Bhave 2017-11-28 18:48:33 -08:00
parent 1bdd42e379
commit 9e95483645
15 changed files with 577 additions and 13 deletions

View File

@ -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<WebOperation> {
@Override
public boolean match(EndpointInfo<WebOperation> info, EndpointDiscoverer<WebOperation> discoverer) {
return (discoverer instanceof CloudFoundryWebAnnotationEndpointDiscoverer);
}
}

View File

@ -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);
}
}
}

View File

@ -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<? extends OperationMethodInvokerAdvisor> invokerAdvisors, Collection<? extends EndpointFilter<WebOperation>> filters, Class<?> requiredExtensionType) {
super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters);
this.applicationContext = applicationContext;
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;
}
super.addExtension(endpoints, extensions, beanName);
}
}

View File

@ -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<WebEndpointResponse<Health>> health() {
return this.delegate.health().map((health) -> {
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
});
}
}

View File

@ -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(

View File

@ -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(

View File

@ -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<Health> getHealth() {
Health health = this.delegate.health();
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
return new WebEndpointResponse<>(health, status);
}
}

View File

@ -64,12 +64,11 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnWebApplication(type = Type.REACTIVE)
static class ReactiveWebHealthConfiguration {
private final ReactiveHealthIndicator reactiveHealthIndicator;
ReactiveWebHealthConfiguration(ObjectProvider<HealthAggregator> healthAggregator,
@Bean
public ReactiveHealthIndicator reactiveHealthIndicator(ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<Map<String, ReactiveHealthIndicator>> reactiveHealthIndicators,
ObjectProvider<Map<String, HealthIndicator>> 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());
}

View File

@ -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,\

View File

@ -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<? extends OperationMethodInvokerAdvisor> invokerAdvisors, Collection<? extends EndpointFilter<WebOperation>> filters) {
super(applicationContext, parameterMapper, endpointMediaTypes, endpointPathResolver, invokerAdvisors, filters);
}
}
}

View File

@ -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<EndpointInfo<WebOperation>> endpoints = endpointDiscoverer.discoverEndpoints();
assertThat(endpoints.size()).isEqualTo(2);
});
}
private void load(Class<?> configuration,
Consumer<CloudFoundryWebAnnotationEndpointDiscoverer> consumer) {
this.load((id) -> null, (id) -> id, configuration, consumer);
}
private void load(Function<String, Long> timeToLive,
EndpointPathResolver endpointPathResolver, Class<?> configuration,
Consumer<CloudFoundryWebAnnotationEndpointDiscoverer> 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;
}
}
}

View File

@ -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<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);
}
private void setupContextWithCloudEnabled() {
TestPropertyValues
.of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",

View File

@ -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<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);
}
private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping() {
TestPropertyValues
.of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",

View File

@ -173,7 +173,7 @@ public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
return extensions;
}
private void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints,
protected void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints,
Map<Class<?>, DiscoveredExtension> extensions, String beanName) {
Class<?> extensionType = this.applicationContext.getType(beanName);
Class<?> endpointType = getEndpointType(extensionType);
@ -199,7 +199,7 @@ public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
}
}
private Class<?> getEndpointType(Class<?> extensionType) {
protected Class<?> getEndpointType(Class<?> extensionType) {
AnnotationAttributes attributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
Class<?> endpointType = attributes.getClass("endpoint");

View File

@ -53,4 +53,8 @@ public class HealthEndpoint {
return Health.status(health.getStatus()).build();
}
public HealthIndicator getHealthIndicator() {
return this.healthIndicator;
}
}