Move Actuator Jersey infrastructure to spring-boot-jersey
This commit is contained in:
committed by
Phillip Webb
parent
92547def9d
commit
bf38d1176d
@@ -19,14 +19,17 @@ dependencies {
|
||||
|
||||
implementation("org.springframework:spring-web")
|
||||
|
||||
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
|
||||
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
|
||||
optional(project(":spring-boot-project:spring-boot-jackson"))
|
||||
optional("io.projectreactor:reactor-core")
|
||||
|
||||
testImplementation(project(":spring-boot-project:spring-boot-restclient"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-test"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-tomcat"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-web-server-test"))
|
||||
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-actuator-autoconfigure")))
|
||||
testImplementation("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
testRuntimeOnly("ch.qos.logback:logback-classic")
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyApplicationPath;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Jetty
|
||||
* endpoints.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@ManagementContextConfiguration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
|
||||
public class JerseyEndpointManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("removal")
|
||||
public org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar servletEndpointRegistrar(
|
||||
WebEndpointProperties properties,
|
||||
org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier,
|
||||
JerseyApplicationPath jerseyApplicationPath, EndpointAccessResolver endpointAccessResolver) {
|
||||
return new org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar(
|
||||
jerseyApplicationPath.getRelativePath(properties.getBasePath()),
|
||||
servletEndpointsSupplier.getEndpoints(), endpointAccessResolver);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.ws.rs.Priorities;
|
||||
import jakarta.ws.rs.ext.ContextResolver;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointId;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.jersey.actuate.autoconfigure.web.ManagementContextResourceConfigCustomizer;
|
||||
import org.springframework.boot.jersey.actuate.endpoint.web.JerseyEndpointResourceFactory;
|
||||
import org.springframework.boot.jersey.actuate.endpoint.web.JerseyHealthEndpointAdditionalPathResourceFactory;
|
||||
import org.springframework.boot.jersey.autoconfigure.ResourceConfigCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Jersey
|
||||
* {@link Endpoint @Endpoint} concerns.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Michael Simons
|
||||
* @author Madhura Bhave
|
||||
* @author HaiTao Zhang
|
||||
*/
|
||||
@ManagementContextConfiguration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass(ResourceConfig.class)
|
||||
@ConditionalOnBean(WebEndpointsSupplier.class)
|
||||
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
|
||||
class JerseyWebEndpointManagementContextConfiguration {
|
||||
|
||||
private static final EndpointId HEALTH_ENDPOINT_ID = EndpointId.of("health");
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("removal")
|
||||
JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Environment environment,
|
||||
WebEndpointsSupplier webEndpointsSupplier,
|
||||
org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier,
|
||||
EndpointMediaTypes endpointMediaTypes, WebEndpointProperties webEndpointProperties) {
|
||||
String basePath = webEndpointProperties.getBasePath();
|
||||
boolean shouldRegisterLinks = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
|
||||
return new JerseyWebEndpointsResourcesRegistrar(webEndpointsSupplier, servletEndpointsSupplier,
|
||||
endpointMediaTypes, basePath, shouldRegisterLinks);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
|
||||
JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar jerseyDifferentPortAdditionalHealthEndpointPathsResourcesRegistrar(
|
||||
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups healthEndpointGroups) {
|
||||
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
|
||||
ExposableWebEndpoint healthEndpoint = webEndpoints.stream()
|
||||
.filter((endpoint) -> endpoint.getEndpointId().equals(HEALTH_ENDPOINT_ID))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return new JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(healthEndpoint,
|
||||
healthEndpointGroups);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(EndpointObjectMapper.class)
|
||||
ResourceConfigCustomizer endpointObjectMapperResourceConfigCustomizer(EndpointObjectMapper endpointObjectMapper) {
|
||||
return (config) -> config.register(new EndpointObjectMapperContextResolver(endpointObjectMapper),
|
||||
ContextResolver.class);
|
||||
}
|
||||
|
||||
private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Environment environment,
|
||||
String basePath) {
|
||||
return properties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
|
||||
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register endpoints with the {@link ResourceConfig} for the management context.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
static class JerseyWebEndpointsResourcesRegistrar implements ManagementContextResourceConfigCustomizer {
|
||||
|
||||
private final WebEndpointsSupplier webEndpointsSupplier;
|
||||
|
||||
private final org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier;
|
||||
|
||||
private final EndpointMediaTypes mediaTypes;
|
||||
|
||||
private final String basePath;
|
||||
|
||||
private final boolean shouldRegisterLinks;
|
||||
|
||||
JerseyWebEndpointsResourcesRegistrar(WebEndpointsSupplier webEndpointsSupplier,
|
||||
org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier,
|
||||
EndpointMediaTypes endpointMediaTypes, String basePath, boolean shouldRegisterLinks) {
|
||||
this.webEndpointsSupplier = webEndpointsSupplier;
|
||||
this.servletEndpointsSupplier = servletEndpointsSupplier;
|
||||
this.mediaTypes = endpointMediaTypes;
|
||||
this.basePath = basePath;
|
||||
this.shouldRegisterLinks = shouldRegisterLinks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ResourceConfig config) {
|
||||
register(config);
|
||||
}
|
||||
|
||||
private void register(ResourceConfig config) {
|
||||
Collection<ExposableWebEndpoint> webEndpoints = this.webEndpointsSupplier.getEndpoints();
|
||||
Collection<org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint> servletEndpoints = this.servletEndpointsSupplier
|
||||
.getEndpoints();
|
||||
EndpointLinksResolver linksResolver = getLinksResolver(webEndpoints, servletEndpoints);
|
||||
EndpointMapping mapping = new EndpointMapping(this.basePath);
|
||||
Collection<Resource> endpointResources = new JerseyEndpointResourceFactory().createEndpointResources(
|
||||
mapping, webEndpoints, this.mediaTypes, linksResolver, this.shouldRegisterLinks);
|
||||
register(endpointResources, config);
|
||||
}
|
||||
|
||||
private EndpointLinksResolver getLinksResolver(Collection<ExposableWebEndpoint> webEndpoints,
|
||||
Collection<org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint> servletEndpoints) {
|
||||
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(webEndpoints.size() + servletEndpoints.size());
|
||||
endpoints.addAll(webEndpoints);
|
||||
endpoints.addAll(servletEndpoints);
|
||||
return new EndpointLinksResolver(endpoints, this.basePath);
|
||||
}
|
||||
|
||||
private void register(Collection<Resource> resources, ResourceConfig config) {
|
||||
config.registerResources(new HashSet<>(resources));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar
|
||||
implements ManagementContextResourceConfigCustomizer {
|
||||
|
||||
private final ExposableWebEndpoint healthEndpoint;
|
||||
|
||||
private final HealthEndpointGroups groups;
|
||||
|
||||
JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(ExposableWebEndpoint healthEndpoint,
|
||||
HealthEndpointGroups groups) {
|
||||
this.healthEndpoint = healthEndpoint;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ResourceConfig config) {
|
||||
if (this.healthEndpoint != null) {
|
||||
register(config);
|
||||
}
|
||||
}
|
||||
|
||||
private void register(ResourceConfig config) {
|
||||
EndpointMapping mapping = new EndpointMapping("");
|
||||
JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory(
|
||||
WebServerNamespace.MANAGEMENT, this.groups);
|
||||
Collection<Resource> endpointResources = resourceFactory
|
||||
.createEndpointResources(mapping, Collections.singletonList(this.healthEndpoint))
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
register(endpointResources, config);
|
||||
}
|
||||
|
||||
private void register(Collection<Resource> resources, ResourceConfig config) {
|
||||
config.registerResources(new HashSet<>(resources));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ContextResolver} used to obtain the {@link ObjectMapper} that should be used
|
||||
* for {@link OperationResponseBody} instances.
|
||||
*/
|
||||
@Priority(Priorities.USER - 100)
|
||||
private static final class EndpointObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
|
||||
|
||||
private final EndpointObjectMapper endpointObjectMapper;
|
||||
|
||||
private EndpointObjectMapperContextResolver(EndpointObjectMapper endpointObjectMapper) {
|
||||
this.endpointObjectMapper = endpointObjectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectMapper getContext(Class<?> type) {
|
||||
return OperationResponseBody.class.isAssignableFrom(type) ? this.endpointObjectMapper.get() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for exposing actuator web endpoints using Jersey.
|
||||
*/
|
||||
package org.springframework.boot.jersey.actuate.autoconfigure.endpoint.web;
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jersey.actuate.endpoint.web.JerseyHealthEndpointAdditionalPathResourceFactory;
|
||||
import org.springframework.boot.jersey.autoconfigure.DefaultJerseyApplicationPath;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyApplicationPath;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyProperties;
|
||||
import org.springframework.boot.jersey.autoconfigure.ResourceConfigCustomizer;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Auto-Configuration for {@link HealthEndpoint} Jersey extension.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 4.0.0
|
||||
* @see HealthEndpointAutoConfiguration
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
|
||||
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
|
||||
public class HealthEndpointJerseyExtensionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
JerseyAdditionalHealthEndpointPathsResourcesRegistrar jerseyAdditionalHealthEndpointPathsResourcesRegistrar(
|
||||
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups healthEndpointGroups) {
|
||||
ExposableWebEndpoint health = getHealthEndpoint(webEndpointsSupplier);
|
||||
return new JerseyAdditionalHealthEndpointPathsResourcesRegistrar(health, healthEndpointGroups);
|
||||
}
|
||||
|
||||
private static ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEndpointsSupplier) {
|
||||
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
|
||||
return webEndpoints.stream()
|
||||
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(ResourceConfig.class)
|
||||
@EnableConfigurationProperties(JerseyProperties.class)
|
||||
static class JerseyInfrastructureConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties, ResourceConfig config) {
|
||||
return new DefaultJerseyApplicationPath(properties.getApplicationPath(), config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig(ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
resourceConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
|
||||
return resourceConfig;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ServletRegistrationBean<ServletContainer> jerseyServletRegistration(JerseyApplicationPath jerseyApplicationPath,
|
||||
ResourceConfig resourceConfig) {
|
||||
return new ServletRegistrationBean<>(new ServletContainer(resourceConfig),
|
||||
jerseyApplicationPath.getUrlMapping());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class JerseyAdditionalHealthEndpointPathsResourcesRegistrar implements ResourceConfigCustomizer {
|
||||
|
||||
private final ExposableWebEndpoint endpoint;
|
||||
|
||||
private final HealthEndpointGroups groups;
|
||||
|
||||
JerseyAdditionalHealthEndpointPathsResourcesRegistrar(ExposableWebEndpoint endpoint,
|
||||
HealthEndpointGroups groups) {
|
||||
this.endpoint = endpoint;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ResourceConfig config) {
|
||||
register(config);
|
||||
}
|
||||
|
||||
private void register(ResourceConfig config) {
|
||||
EndpointMapping mapping = new EndpointMapping("");
|
||||
JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory(
|
||||
WebServerNamespace.SERVER, this.groups);
|
||||
Collection<Resource> endpointResources = resourceFactory
|
||||
.createEndpointResources(mapping,
|
||||
(this.endpoint != null) ? Collections.singletonList(this.endpoint) : Collections.emptyList())
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
register(endpointResources, config);
|
||||
}
|
||||
|
||||
private void register(Collection<Resource> resources, ResourceConfig config) {
|
||||
config.registerResources(new HashSet<>(resources));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for actuator health concerns.
|
||||
*/
|
||||
package org.springframework.boot.jersey.actuate.autoconfigure.health;
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.web;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyApplicationPath;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Jersey
|
||||
* infrastructure when a separate management context with a web server running on a
|
||||
* different port is required.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@ManagementContextConfiguration(value = ManagementContextType.CHILD, proxyBeanMethods = false)
|
||||
@Import(JerseyManagementContextConfiguration.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@ConditionalOnClass({ JerseyApplicationPath.class, ResourceConfig.class })
|
||||
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
|
||||
public class JerseyChildManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public JerseyApplicationPath jerseyApplicationPath() {
|
||||
return () -> "/";
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig(ObjectProvider<ManagementContextResourceConfigCustomizer> customizers) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
customizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
|
||||
return resourceConfig;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.web;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyApplicationPath;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Shared configuration for Jersey-based actuators regardless of management context type.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class JerseyManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
ServletRegistrationBean<ServletContainer> jerseyServletRegistration(JerseyApplicationPath jerseyApplicationPath,
|
||||
ResourceConfig resourceConfig) {
|
||||
return new ServletRegistrationBean<>(new ServletContainer(resourceConfig),
|
||||
jerseyApplicationPath.getUrlMapping());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.web;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jersey.autoconfigure.DefaultJerseyApplicationPath;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyApplicationPath;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyProperties;
|
||||
import org.springframework.boot.jersey.autoconfigure.ResourceConfigCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Jersey
|
||||
* infrastructure when the management context is the same as the main application context.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@ManagementContextConfiguration(value = ManagementContextType.SAME, proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(JerseyProperties.class)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@ConditionalOnClass({ JerseyApplicationPath.class, ResourceConfig.class })
|
||||
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
|
||||
public class JerseySameManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceConfigCustomizer managementResourceConfigCustomizerAdapter(
|
||||
ObjectProvider<ManagementContextResourceConfigCustomizer> customizers) {
|
||||
return (config) -> customizers.orderedStream().forEach((customizer) -> customizer.customize(config));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(JerseyManagementContextConfiguration.class)
|
||||
@ConditionalOnMissingBean(ResourceConfig.class)
|
||||
static class JerseyInfrastructureConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties, ResourceConfig config) {
|
||||
return new DefaultJerseyApplicationPath(properties.getApplicationPath(), config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig(ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
resourceConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
|
||||
return resourceConfig;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.web;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize Jersey's
|
||||
* {@link ResourceConfig} in the management context before it is used.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public interface ManagementContextResourceConfigCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the resource config.
|
||||
* @param config the {@link ResourceConfig} to customize
|
||||
*/
|
||||
void customize(ResourceConfig config);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration for a Jersey-based management context.
|
||||
*/
|
||||
package org.springframework.boot.jersey.actuate.autoconfigure.web;
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.endpoint.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.ws.rs.HttpMethod;
|
||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.glassfish.jersey.process.Inflector;
|
||||
import org.glassfish.jersey.server.ContainerRequest;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.server.model.Resource.Builder;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for {@link WebOperation web
|
||||
* endpoint operations}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JerseyEndpointResourceFactory {
|
||||
|
||||
/**
|
||||
* Creates {@link Resource Resources} for the operations of the given
|
||||
* {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param linksResolver resolver for determining links to available endpoints
|
||||
* @param shouldRegisterLinks should register links
|
||||
* @return the resources for the operations
|
||||
*/
|
||||
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
|
||||
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointLinksResolver linksResolver, boolean shouldRegisterLinks) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
endpoints.stream()
|
||||
.flatMap((endpoint) -> endpoint.getOperations().stream())
|
||||
.map((operation) -> createResource(endpointMapping, operation))
|
||||
.forEach(resources::add);
|
||||
if (shouldRegisterLinks) {
|
||||
Resource resource = createEndpointLinksResource(endpointMapping.getPath(), endpointMediaTypes,
|
||||
linksResolver);
|
||||
resources.add(resource);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String path = requestPredicate.getPath();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}",
|
||||
"{" + matchAllRemainingPathSegmentsVariable + ": .*}");
|
||||
}
|
||||
return getResource(endpointMapping, operation, requestPredicate, path, null, null);
|
||||
}
|
||||
|
||||
protected Resource getResource(EndpointMapping endpointMapping, WebOperation operation,
|
||||
WebOperationRequestPredicate requestPredicate, String path, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegmentProvider) {
|
||||
Builder resourceBuilder = Resource.builder()
|
||||
.path(endpointMapping.getPath())
|
||||
.path(endpointMapping.createSubPath(path));
|
||||
resourceBuilder.addMethod(requestPredicate.getHttpMethod().name())
|
||||
.consumes(StringUtils.toStringArray(requestPredicate.getConsumes()))
|
||||
.produces(StringUtils.toStringArray(requestPredicate.getProduces()))
|
||||
.handledBy(new OperationInflector(operation, !requestPredicate.getConsumes().isEmpty(), serverNamespace,
|
||||
remainingPathSegmentProvider));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
private Resource createEndpointLinksResource(String endpointPath, EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointLinksResolver linksResolver) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointPath);
|
||||
resourceBuilder.addMethod("GET")
|
||||
.produces(StringUtils.toStringArray(endpointMediaTypes.getProduced()))
|
||||
.handledBy(new EndpointLinksInflector(linksResolver));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Inflector} to invoke the {@link WebOperation}.
|
||||
*/
|
||||
private static final class OperationInflector implements Inflector<ContainerRequestContext, Object> {
|
||||
|
||||
private static final String PATH_SEPARATOR = AntPathMatcher.DEFAULT_PATH_SEPARATOR;
|
||||
|
||||
private static final List<Function<Object, Object>> BODY_CONVERTERS;
|
||||
|
||||
static {
|
||||
List<Function<Object, Object>> converters = new ArrayList<>();
|
||||
converters.add(new ResourceBodyConverter());
|
||||
if (ClassUtils.isPresent("reactor.core.publisher.Mono", OperationInflector.class.getClassLoader())) {
|
||||
converters.add(new FluxBodyConverter());
|
||||
converters.add(new MonoBodyConverter());
|
||||
}
|
||||
BODY_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
}
|
||||
|
||||
private final WebOperation operation;
|
||||
|
||||
private final boolean readBody;
|
||||
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
private final JerseyRemainingPathSegmentProvider remainingPathSegmentProvider;
|
||||
|
||||
private OperationInflector(WebOperation operation, boolean readBody, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegments) {
|
||||
this.operation = operation;
|
||||
this.readBody = readBody;
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.remainingPathSegmentProvider = remainingPathSegments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(ContainerRequestContext data) {
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
if (this.readBody) {
|
||||
arguments.putAll(extractBodyArguments(data));
|
||||
}
|
||||
arguments.putAll(extractPathParameters(data));
|
||||
arguments.putAll(extractQueryParameters(data));
|
||||
try {
|
||||
JerseySecurityContext securityContext = new JerseySecurityContext(data.getSecurityContext());
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> this.serverNamespace);
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver,
|
||||
new ProducibleOperationArgumentResolver(() -> data.getHeaders().get("Accept")));
|
||||
Object response = this.operation.invoke(invocationContext);
|
||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||
}
|
||||
catch (InvalidEndpointRequestException ex) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, String> extractBodyArguments(ContainerRequestContext data) {
|
||||
Map<String, String> entity = ((ContainerRequest) data).readEntity(Map.class,
|
||||
new ParameterizedTypeReference<Map<String, String>>() {
|
||||
}.getType());
|
||||
return (entity != null) ? entity : Collections.emptyMap();
|
||||
}
|
||||
|
||||
private Map<String, Object> extractPathParameters(ContainerRequestContext requestContext) {
|
||||
Map<String, Object> pathParameters = extract(requestContext.getUriInfo().getPathParameters());
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
String remainingPathSegments = getRemainingPathSegments(requestContext, pathParameters,
|
||||
matchAllRemainingPathSegmentsVariable);
|
||||
pathParameters.put(matchAllRemainingPathSegmentsVariable, tokenizePathSegments(remainingPathSegments));
|
||||
}
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private String getRemainingPathSegments(ContainerRequestContext requestContext,
|
||||
Map<String, Object> pathParameters, String matchAllRemainingPathSegmentsVariable) {
|
||||
if (this.remainingPathSegmentProvider != null) {
|
||||
return this.remainingPathSegmentProvider.get(requestContext, matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
return (String) pathParameters.get(matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String path) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(path, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Map<String, Object> extractQueryParameters(ContainerRequestContext requestContext) {
|
||||
return extract(requestContext.getUriInfo().getQueryParameters());
|
||||
}
|
||||
|
||||
private Map<String, Object> extract(MultivaluedMap<String, String> multivaluedMap) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
multivaluedMap.forEach((name, values) -> {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
result.put(name, (values.size() != 1) ? values : values.get(0));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Response convertToJaxRsResponse(Object response, String httpMethod) {
|
||||
if (response == null) {
|
||||
boolean isGet = HttpMethod.GET.equals(httpMethod);
|
||||
Status status = isGet ? Status.NOT_FOUND : Status.NO_CONTENT;
|
||||
return Response.status(status).build();
|
||||
}
|
||||
if (!(response instanceof WebEndpointResponse<?> webEndpointResponse)) {
|
||||
return Response.status(Status.OK).entity(convertIfNecessary(response)).build();
|
||||
}
|
||||
return Response.status(webEndpointResponse.getStatus())
|
||||
.header("Content-Type", webEndpointResponse.getContentType())
|
||||
.entity(convertIfNecessary(webEndpointResponse.getBody()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Object convertIfNecessary(Object body) {
|
||||
for (Function<Object, Object> converter : BODY_CONVERTERS) {
|
||||
body = converter.apply(body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Body converter from {@link org.springframework.core.io.Resource} to
|
||||
* {@link InputStream}.
|
||||
*/
|
||||
private static final class ResourceBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof org.springframework.core.io.Resource) {
|
||||
try {
|
||||
return ((org.springframework.core.io.Resource) body).getInputStream();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Body converter from {@link Mono} to {@link Mono#block()}.
|
||||
*/
|
||||
private static final class MonoBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof Mono) {
|
||||
return ((Mono<?>) body).block();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Body converter from {@link Flux} to {@link Flux#collectList Mono<List>}.
|
||||
*/
|
||||
private static final class FluxBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof Flux) {
|
||||
return ((Flux<?>) body).collectList();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Inflector} to for endpoint links.
|
||||
*/
|
||||
private static final class EndpointLinksInflector implements Inflector<ContainerRequestContext, Response> {
|
||||
|
||||
private final EndpointLinksResolver linksResolver;
|
||||
|
||||
private EndpointLinksInflector(EndpointLinksResolver linksResolver) {
|
||||
this.linksResolver = linksResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(ContainerRequestContext request) {
|
||||
Map<String, Link> links = this.linksResolver
|
||||
.resolveLinks(request.getUriInfo().getAbsolutePath().toString());
|
||||
Map<String, Map<String, Link>> entity = OperationResponseBody.of(Collections.singletonMap("_links", links));
|
||||
return Response.ok(entity).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class JerseySecurityContext implements SecurityContext {
|
||||
|
||||
private final jakarta.ws.rs.core.SecurityContext securityContext;
|
||||
|
||||
private JerseySecurityContext(jakarta.ws.rs.core.SecurityContext securityContext) {
|
||||
this.securityContext = securityContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return this.securityContext.getUserPrincipal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
return this.securityContext.isUserInRole(role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.endpoint.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
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;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for health groups with
|
||||
* additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public final class JerseyHealthEndpointAdditionalPathResourceFactory {
|
||||
|
||||
private final JerseyEndpointResourceFactory delegate = new JerseyEndpointResourceFactory();
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
public JerseyHealthEndpointAdditionalPathResourceFactory(WebServerNamespace serverNamespace,
|
||||
HealthEndpointGroups groups) {
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.groups = groups.getAllWithAdditionalPath(serverNamespace);
|
||||
}
|
||||
|
||||
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
|
||||
Collection<ExposableWebEndpoint> endpoints) {
|
||||
return endpoints.stream()
|
||||
.flatMap((endpoint) -> endpoint.getOperations().stream())
|
||||
.flatMap((operation) -> createResources(endpointMapping, operation))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Stream<Resource> createResources(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
resources.add(this.delegate.getResource(endpointMapping, operation, requestPredicate,
|
||||
additionalPath.getValue(), this.serverNamespace,
|
||||
(data, pathSegmentsVariable) -> data.getUriInfo().getPath()));
|
||||
}
|
||||
}
|
||||
return resources.stream();
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.endpoint.web;
|
||||
|
||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||
|
||||
/**
|
||||
* Strategy interface used to provide the remaining path segments for a Jersey actuator
|
||||
* endpoint.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
interface JerseyRemainingPathSegmentProvider {
|
||||
|
||||
String get(ContainerRequestContext requestContext, String matchAllRemainingPathSegmentsVariable);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jersey support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.jersey.actuate.endpoint.web;
|
||||
@@ -0,0 +1,5 @@
|
||||
org.springframework.boot.jersey.actuate.autoconfigure.endpoint.web.JerseyEndpointManagementContextConfiguration
|
||||
org.springframework.boot.jersey.actuate.autoconfigure.endpoint.web.JerseyWebEndpointManagementContextConfiguration
|
||||
org.springframework.boot.jersey.actuate.autoconfigure.web.JerseySameManagementContextConfiguration
|
||||
org.springframework.boot.jersey.actuate.autoconfigure.web.JerseyChildManagementContextConfiguration
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
org.springframework.boot.jersey.actuate.autoconfigure.health.HealthEndpointJerseyExtensionAutoConfiguration
|
||||
org.springframework.boot.jersey.autoconfigure.JerseyAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.endpoint.Access;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyApplicationPath;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JerseyEndpointManagementContextConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
class JerseyEndpointManagementContextConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withUserConfiguration(TestConfig.class);
|
||||
|
||||
@Test
|
||||
void contextShouldContainServletEndpointRegistrar() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ServletEndpointRegistrar.class);
|
||||
ServletEndpointRegistrar bean = context.getBean(ServletEndpointRegistrar.class);
|
||||
assertThat(bean).hasFieldOrPropertyWithValue("basePath", "/test/actuator");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextWhenNotServletBasedShouldNotContainServletEndpointRegistrar() {
|
||||
new ApplicationContextRunner().withUserConfiguration(TestConfig.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(ServletEndpointRegistrar.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(JerseyEndpointManagementContextConfiguration.class)
|
||||
@EnableConfigurationProperties(WebEndpointProperties.class)
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
ServletEndpointsSupplier servletEndpointsSupplier() {
|
||||
return Collections::emptyList;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JerseyApplicationPath jerseyApplicationPath() {
|
||||
return () -> "/test";
|
||||
}
|
||||
|
||||
@Bean
|
||||
EndpointAccessResolver endpointAccessResolver() {
|
||||
return (endpointId, defaultAccess) -> Access.UNRESTRICTED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.jersey.actuate.autoconfigure.web.JerseySameManagementContextConfiguration;
|
||||
import org.springframework.boot.jersey.autoconfigure.JerseyAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration;
|
||||
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for web endpoints running on Jersey.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JerseyWebEndpointIntegrationTests {
|
||||
|
||||
@Test
|
||||
void whenJerseyIsConfiguredToUseAFilterThenResourceRegistrationSucceeds() {
|
||||
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(JerseySameManagementContextConfiguration.class, JerseyAutoConfiguration.class,
|
||||
TomcatServletWebServerAutoConfiguration.class, EndpointAutoConfiguration.class,
|
||||
WebEndpointAutoConfiguration.class, JerseyWebEndpointManagementContextConfiguration.class))
|
||||
.withUserConfiguration(ResourceConfigConfiguration.class)
|
||||
.withPropertyValues("spring.jersey.type=filter", "server.port=0")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
Set<Resource> resources = context.getBean(ResourceConfig.class).getResources();
|
||||
assertThat(resources).hasSize(1);
|
||||
Resource resource = resources.iterator().next();
|
||||
assertThat(resource.getPath()).isEqualTo("/actuator");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ResourceConfigConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig() {
|
||||
return new ResourceConfig();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.Access;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.jersey.actuate.autoconfigure.endpoint.web.JerseyWebEndpointManagementContextConfiguration.JerseyWebEndpointsResourcesRegistrar;
|
||||
import org.springframework.boot.jersey.actuate.autoconfigure.web.JerseySameManagementContextConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JerseyWebEndpointManagementContextConfiguration}.
|
||||
*
|
||||
* @author Michael Simons
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class JerseyWebEndpointManagementContextConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class,
|
||||
JerseyWebEndpointManagementContextConfiguration.class))
|
||||
.withBean(WebEndpointsSupplier.class, () -> Collections::emptyList)
|
||||
.withBean(EndpointAccessResolver.class, () -> (endpointId, defaultAccess) -> Access.UNRESTRICTED);
|
||||
|
||||
@Test
|
||||
void jerseyWebEndpointsResourcesRegistrarForEndpointsIsAutoConfigured() {
|
||||
this.runner.run((context) -> assertThat(context).hasSingleBean(JerseyWebEndpointsResourcesRegistrar.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfigurationIsConditionalOnServletWebApplication() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JerseySameManagementContextConfiguration.class));
|
||||
contextRunner
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(JerseySameManagementContextConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfigurationIsConditionalOnClassResourceConfig() {
|
||||
this.runner.withClassLoader(new FilteredClassLoader(ResourceConfig.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(JerseySameManagementContextConfiguration.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.jersey.actuate.autoconfigure.health;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.WithTestEndpointOutcomeExposureContributor;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.jersey.actuate.autoconfigure.health.HealthEndpointJerseyExtensionAutoConfiguration.JerseyAdditionalHealthEndpointPathsResourcesRegistrar;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HealthEndpointJerseyExtensionAutoConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class HealthEndpointJerseyExtensionAutoConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withUserConfiguration(HealthIndicatorsConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
|
||||
HealthEndpointAutoConfiguration.class, HealthEndpointJerseyExtensionAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
@WithTestEndpointOutcomeExposureContributor
|
||||
void additionalJerseyHealthEndpointsPathsTolerateHealthEndpointThatIsNotWebExposed() {
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class))
|
||||
.withPropertyValues("management.endpoints.web.exposure.exclude=*",
|
||||
"management.endpoints.test.exposure.include=*")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasSingleBean(HealthEndpoint.class);
|
||||
assertThat(context).hasSingleBean(HealthEndpointWebExtension.class);
|
||||
assertThat(context.getBean(WebEndpointsSupplier.class).getEndpoints()).isEmpty();
|
||||
assertThat(context).hasSingleBean(JerseyAdditionalHealthEndpointPathsResourcesRegistrar.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class HealthIndicatorsConfiguration {
|
||||
|
||||
@Bean
|
||||
HealthIndicator simpleHealthIndicator() {
|
||||
return () -> Health.up().withDetail("counter", 42).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
HealthIndicator additionalHealthIndicator() {
|
||||
return () -> Health.up().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveHealthIndicator reactiveHealthIndicator() {
|
||||
return () -> Mono.just(Health.up().build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user