diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroups.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroups.java index 52024639dd7..d368942286a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroups.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroups.java @@ -16,14 +16,20 @@ package org.springframework.boot.actuate.autoconfigure.availability; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath; +import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.util.Assert; @@ -35,7 +41,7 @@ import org.springframework.util.Assert; * @author Brian Clozel * @author Madhura Bhave */ -class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups { +class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups, AdditionalPathsMapper { private final HealthEndpointGroups groups; @@ -107,4 +113,23 @@ class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups { return name.equals(LIVENESS) || name.equals(READINESS); } + @Override + public List getAdditionalPaths(EndpointId endpointId, WebServerNamespace webServerNamespace) { + if (!HealthEndpoint.ID.equals(endpointId)) { + return null; + } + List additionalPaths = new ArrayList<>(); + if (this.groups instanceof AdditionalPathsMapper additionalPathsMapper) { + additionalPaths.addAll(additionalPathsMapper.getAdditionalPaths(endpointId, webServerNamespace)); + } + additionalPaths.addAll(this.probeGroups.values() + .stream() + .map(HealthEndpointGroup::getAdditionalPath) + .filter(Objects::nonNull) + .filter((additionalPath) -> additionalPath.hasNamespace(webServerNamespace)) + .map(AdditionalHealthEndpointPath::getValue) + .toList()); + return additionalPaths; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroupsPostProcessorTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroupsPostProcessorTests.java index b522bec9722..162927c8b0d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroupsPostProcessorTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/availability/AvailabilityProbesHealthEndpointGroupsPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -17,10 +17,15 @@ package org.springframework.boot.actuate.autoconfigure.availability; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper; +import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.mock.env.MockEnvironment; @@ -103,6 +108,39 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests { assertThat(readiness.getAdditionalPath()).hasToString("server:/readyz"); } + @Test + void delegatesAdditionalPathMappingToOriginalBean() { + HealthEndpointGroups groups = mock(HealthEndpointGroups.class, + Mockito.withSettings().extraInterfaces(AdditionalPathsMapper.class)); + given(((AdditionalPathsMapper) groups).getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER)) + .willReturn(List.of("/one", "/two", "/three")); + MockEnvironment environment = new MockEnvironment(); + AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( + environment); + HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups); + assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class); + AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed; + assertThat(additionalPathsMapper.getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER)) + .containsExactly("/one", "/two", "/three"); + } + + @Test + void whenAddAdditionalPathsIsTrueThenIncludesOwnAdditionalPathsInGetAdditionalPathsResult() { + HealthEndpointGroups groups = mock(HealthEndpointGroups.class, + Mockito.withSettings().extraInterfaces(AdditionalPathsMapper.class)); + given(((AdditionalPathsMapper) groups).getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER)) + .willReturn(List.of("/one", "/two", "/three")); + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true"); + AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor( + environment); + HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups); + assertThat(postProcessed).isInstanceOf(AdditionalPathsMapper.class); + AdditionalPathsMapper additionalPathsMapper = (AdditionalPathsMapper) postProcessed; + assertThat(additionalPathsMapper.getAdditionalPaths(EndpointId.of("health"), WebServerNamespace.SERVER)) + .containsExactly("/one", "/two", "/three", "/livez", "/readyz"); + } + private HealthEndpointGroups getPostProcessed(String value) { MockEnvironment environment = new MockEnvironment(); environment.setProperty("management.endpoint.health.probes.add-additional-paths", value);