From 52f732920ba1a9606cfc7e0bad83ccbeceb8374d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 6 Jul 2023 13:34:43 +0100 Subject: [PATCH] Support multiple health groups with an additional path with Jersey This commit knowingly makes breaking API changes to JerseyHealthEndpointAdditionalPathResourceFactory. We considered other options but they all had the potential to be backwards incompatible in one way or another. Faced with that situation we concluded that the likelihood of anyone using the modified API directly is small enough to warrant making the breaking changes. If it becomes apparent that we have misjudged things we can revisit the changes in the future. Closes gh-36250 --- ...ndpointManagementContextConfiguration.java | 2 +- ...althEndpointWebExtensionConfiguration.java | 2 +- ...ndpointAdditionalPathIntegrationTests.java | 43 ++++++++++++++----- ...EndpointAdditionalPathResourceFactory.java | 32 +++++++++++--- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java index e0c1f42f54..75dab516eb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java @@ -184,7 +184,7 @@ class JerseyWebEndpointManagementContextConfiguration { JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory( WebServerNamespace.MANAGEMENT, this.groups); Collection endpointResources = resourceFactory - .createEndpointResources(mapping, Collections.singletonList(this.endpoint), null, null, false) + .createEndpointResources(mapping, Collections.singletonList(this.endpoint)) .stream() .filter(Objects::nonNull) .collect(Collectors.toList()); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java index 33e91ed0ff..2ef22a1023 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java @@ -163,7 +163,7 @@ class HealthEndpointWebExtensionConfiguration { JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory( WebServerNamespace.SERVER, this.groups); Collection endpointResources = resourceFactory - .createEndpointResources(mapping, Collections.singletonList(this.endpoint), null, null, false) + .createEndpointResources(mapping, Collections.singletonList(this.endpoint)) .stream() .filter(Objects::nonNull) .collect(Collectors.toList()); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/AbstractHealthEndpointAdditionalPathIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/AbstractHealthEndpointAdditionalPathIntegrationTests.java index 5e31018325..b0d41c2a9b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/AbstractHealthEndpointAdditionalPathIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/AbstractHealthEndpointAdditionalPathIntegrationTests.java @@ -27,6 +27,8 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; +import static org.assertj.core.api.Assertions.assertThatNoException; + /** * Abstract base class for health groups with an additional path. * @@ -52,6 +54,18 @@ abstract class AbstractHealthEndpointAdditionalPathIntegrationTests testResponses(client, "/alpha", "/bravo"), "local.server.port")); + } + @Test void groupIsAvailableAtAdditionalPathWithoutSlash() { this.runner @@ -125,17 +139,24 @@ abstract class AbstractHealthEndpointAdditionalPathIntegrationTests client.get() + .uri(path) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("status") + .isEqualTo("UP") + .jsonPath("components.diskSpace") + .exists()); + } } private ContextConsumer withWebTestClient(Consumer consumer, String property) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyHealthEndpointAdditionalPathResourceFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyHealthEndpointAdditionalPathResourceFactory.java index 3b7033753b..9e744585c2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyHealthEndpointAdditionalPathResourceFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyHealthEndpointAdditionalPathResourceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -16,11 +16,17 @@ package org.springframework.boot.actuate.endpoint.web.jersey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.glassfish.jersey.server.model.Resource; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; +import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; @@ -35,7 +41,9 @@ import org.springframework.boot.actuate.health.HealthEndpointGroups; * @author Madhura Bhave * @since 2.6.0 */ -public class JerseyHealthEndpointAdditionalPathResourceFactory extends JerseyEndpointResourceFactory { +public final class JerseyHealthEndpointAdditionalPathResourceFactory { + + private final JerseyEndpointResourceFactory delegate = new JerseyEndpointResourceFactory(); private final Set groups; @@ -47,20 +55,30 @@ public class JerseyHealthEndpointAdditionalPathResourceFactory extends JerseyEnd this.groups = groups.getAllWithAdditionalPath(serverNamespace); } - @Override - protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) { + public Collection createEndpointResources(EndpointMapping endpointMapping, + Collection endpoints) { + return endpoints.stream() + .flatMap((endpoint) -> endpoint.getOperations().stream()) + .flatMap((operation) -> createResources(endpointMapping, operation)) + .collect(Collectors.toList()); + } + + private Stream createResources(EndpointMapping endpointMapping, WebOperation operation) { WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate(); String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable(); if (matchAllRemainingPathSegmentsVariable != null) { + List resources = new ArrayList<>(); for (HealthEndpointGroup group : this.groups) { AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath(); if (additionalPath != null) { - return getResource(endpointMapping, operation, requestPredicate, additionalPath.getValue(), - this.serverNamespace, (data, pathSegmentsVariable) -> data.getUriInfo().getPath()); + resources.add(this.delegate.getResource(endpointMapping, operation, requestPredicate, + additionalPath.getValue(), this.serverNamespace, + (data, pathSegmentsVariable) -> data.getUriInfo().getPath())); } } + return resources.stream(); } - return null; + return Stream.empty(); } }