Ensure media types are used consistently across endpoint mappings
Previously, the media types that are consumed and produced by endpoints were configured in the web stack-specific configuration. Furthermore, these configured media types were not used for the discovery "endpoint" that links to all the available endpoints. This commit introduces EndpointMediaTypes that is configred in a single, central location and then used to configure the consumed and produced media types for endpoints exposed via WebFlux, Web MVC, and Jersey as well as the discovery "endpoint" provided by each. Closes gh-10659
This commit is contained in:
@@ -20,6 +20,7 @@ import java.util.Arrays;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -67,11 +68,12 @@ public class CloudFoundryActuatorAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||
EndpointProvider<WebEndpointOperation> provider, Environment environment,
|
||||
EndpointProvider<WebEndpointOperation> provider,
|
||||
EndpointMediaTypes endpointMediaTypes, Environment environment,
|
||||
RestTemplateBuilder builder) {
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||
new EndpointMapping("/cloudfoundryapplication"),
|
||||
provider.getEndpoints(), getCorsConfiguration(),
|
||||
provider.getEndpoints(), endpointMediaTypes, getCorsConfiguration(),
|
||||
getSecurityInterceptor(builder, environment));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
@@ -76,9 +77,9 @@ class CloudFoundryWebEndpointServletHandlerMapping
|
||||
|
||||
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
super(endpointMapping, webEndpoints, corsConfiguration);
|
||||
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -71,6 +72,11 @@ public class EndpointAutoConfiguration {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EndpointMediaTypes endpointMediaTypes() {
|
||||
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EndpointProvider<WebEndpointOperation> webEndpointProvider(
|
||||
OperationParameterMapper parameterMapper,
|
||||
@@ -78,7 +84,7 @@ public class EndpointAutoConfiguration {
|
||||
Environment environment = this.applicationContext.getEnvironment();
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
|
||||
this.applicationContext, parameterMapper, cachingConfigurationFactory,
|
||||
MEDIA_TYPES, MEDIA_TYPES);
|
||||
endpointMediaTypes());
|
||||
return new EndpointProvider<>(environment, endpointDiscoverer,
|
||||
EndpointExposure.WEB);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathP
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -55,11 +56,12 @@ class JerseyWebEndpointManagementContextConfiguration {
|
||||
@Bean
|
||||
public ResourceConfigCustomizer webEndpointRegistrar(
|
||||
EndpointProvider<WebEndpointOperation> provider,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return (resourceConfig) -> resourceConfig.registerResources(
|
||||
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
provider.getEndpoints())));
|
||||
provider.getEndpoints(), endpointMediaTypes)));
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathP
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -45,10 +46,11 @@ public class WebFluxEndpointManagementContextConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
|
||||
EndpointProvider<WebEndpointOperation> provider,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return new WebFluxEndpointHandlerMapping(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
provider.getEndpoints());
|
||||
provider.getEndpoints(), endpointMediaTypes);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointPr
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -56,11 +57,12 @@ public class WebMvcEndpointManagementContextConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
|
||||
EndpointProvider<WebEndpointOperation> provider,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
CorsEndpointProperties corsProperties,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
provider.getEndpoints(), getCorsConfiguration(corsProperties));
|
||||
provider.getEndpoints(), endpointMediaTypes, getCorsConfiguration(corsProperties));
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.junit.Test;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
@@ -40,11 +41,15 @@ import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
|
||||
/**
|
||||
* Tests for {@link CloudFoundryActuatorAutoConfiguration}.
|
||||
@@ -92,6 +97,18 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||
Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloudfoundryapplicationProducesActuatorMediaType() 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.refresh();
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
mockMvc.perform(get("/cloudfoundryapplication")).andExpect(header()
|
||||
.string("Content-Type", ActuatorMediaType.V2_JSON + ";charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloudFoundryPlatformActiveSetsApplicationId() throws Exception {
|
||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
@@ -190,28 +191,35 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
|
||||
"app-id");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EndpointMediaTypes EndpointMediaTypes() {
|
||||
return new EndpointMediaTypes(Collections.singletonList("application/json"),
|
||||
Collections.singletonList("application/json"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
CloudFoundrySecurityInterceptor interceptor) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||
new EndpointMapping("/cfApplication"),
|
||||
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration,
|
||||
interceptor);
|
||||
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
corsConfiguration, interceptor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
|
||||
ApplicationContext applicationContext) {
|
||||
ApplicationContext applicationContext,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
OperationParameterMapper parameterMapper = new ConversionServiceOperationParameterMapper(
|
||||
DefaultConversionService.getSharedInstance());
|
||||
return new WebAnnotationEndpointDiscoverer(applicationContext,
|
||||
parameterMapper, (id) -> new CachingConfiguration(0),
|
||||
Collections.singletonList("application/json"),
|
||||
Collections.singletonList("application/json"));
|
||||
endpointMediaTypes);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.endpoint;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link EndpointAutoConfiguration}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class EndpointAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void webApplicationConfiguresEndpointMediaTypes() {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
EndpointMediaTypes endpointMediaTypes = context
|
||||
.getBean(EndpointMediaTypes.class);
|
||||
assertThat(endpointMediaTypes.getConsumed()).containsExactly(
|
||||
ActuatorMediaType.V2_JSON, "application/json");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.web.servlet;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,6 +25,7 @@ import org.junit.Test;
|
||||
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
@@ -142,7 +144,9 @@ public class RequestMappingEndpointTests {
|
||||
WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping(
|
||||
new EndpointMapping("application"),
|
||||
Collections.singleton(new EndpointInfo<>("test",
|
||||
DefaultEnablement.ENABLED, Collections.singleton(operation))));
|
||||
DefaultEnablement.ENABLED, Collections.singleton(operation))),
|
||||
new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"),
|
||||
Arrays.asList("application/vnd.test+json")));
|
||||
mapping.setApplicationContext(new StaticApplicationContext());
|
||||
mapping.afterPropertiesSet();
|
||||
return mapping;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.endpoint.web;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Media types that are, by default, produced and consumed by an endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class EndpointMediaTypes {
|
||||
|
||||
private final List<String> produced;
|
||||
|
||||
private final List<String> consumed;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EndpointMediaTypes} with the given {@code produced} and
|
||||
* {@code consumed} media types.
|
||||
* @param produced the default media types that are produced by an endpoint. Must not
|
||||
* be {@code null}.
|
||||
* @param consumed the default media types that are consumed by an endpoint. Must not
|
||||
*/
|
||||
public EndpointMediaTypes(List<String> produced, List<String> consumed) {
|
||||
Assert.notNull(produced, () -> "Produced must not be null");
|
||||
Assert.notNull(consumed, () -> "Consumed must not be null");
|
||||
this.produced = Collections.unmodifiableList(produced);
|
||||
this.consumed = Collections.unmodifiableList(consumed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media types produced by an endpoint.
|
||||
*
|
||||
* @return the produced media types
|
||||
*/
|
||||
public List<String> getProduced() {
|
||||
return this.produced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media types consumed by an endpoint.
|
||||
*
|
||||
* @return the consumed media types
|
||||
*/
|
||||
public List<String> getConsumed() {
|
||||
return this.consumed;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
@@ -68,17 +69,16 @@ public class WebAnnotationEndpointDiscoverer extends
|
||||
* @param operationParameterMapper the {@link OperationParameterMapper} used to
|
||||
* convert arguments when an operation is invoked
|
||||
* @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use
|
||||
* @param consumedMediaTypes the media types consumed by web endpoint operations
|
||||
* @param producedMediaTypes the media types produced by web endpoint operations
|
||||
* @param endpointMediaTypes the media types produced and consumed by web endpoint
|
||||
* operations
|
||||
*/
|
||||
public WebAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
OperationParameterMapper operationParameterMapper,
|
||||
CachingConfigurationFactory cachingConfigurationFactory,
|
||||
Collection<String> consumedMediaTypes,
|
||||
Collection<String> producedMediaTypes) {
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
super(applicationContext,
|
||||
new WebEndpointOperationFactory(operationParameterMapper,
|
||||
consumedMediaTypes, producedMediaTypes),
|
||||
endpointMediaTypes),
|
||||
WebEndpointOperation::getRequestPredicate, cachingConfigurationFactory);
|
||||
}
|
||||
|
||||
@@ -119,16 +119,12 @@ public class WebAnnotationEndpointDiscoverer extends
|
||||
|
||||
private final OperationParameterMapper parameterMapper;
|
||||
|
||||
private final Collection<String> consumedMediaTypes;
|
||||
|
||||
private final Collection<String> producedMediaTypes;
|
||||
private final EndpointMediaTypes endpointMediaTypes;
|
||||
|
||||
private WebEndpointOperationFactory(OperationParameterMapper parameterMapper,
|
||||
Collection<String> consumedMediaTypes,
|
||||
Collection<String> producedMediaTypes) {
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
this.parameterMapper = parameterMapper;
|
||||
this.consumedMediaTypes = consumedMediaTypes;
|
||||
this.producedMediaTypes = producedMediaTypes;
|
||||
this.endpointMediaTypes = endpointMediaTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,7 +168,7 @@ public class WebAnnotationEndpointDiscoverer extends
|
||||
private Collection<String> determineConsumedMediaTypes(
|
||||
WebEndpointHttpMethod httpMethod, Method method) {
|
||||
if (WebEndpointHttpMethod.POST == httpMethod && consumesRequestBody(method)) {
|
||||
return this.consumedMediaTypes;
|
||||
return this.endpointMediaTypes.getConsumed();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -189,7 +185,7 @@ public class WebAnnotationEndpointDiscoverer extends
|
||||
if (producesResourceResponseBody(method)) {
|
||||
return Collections.singletonList("application/octet-stream");
|
||||
}
|
||||
return this.producedMediaTypes;
|
||||
return this.endpointMediaTypes.getProduced();
|
||||
}
|
||||
|
||||
private boolean producesResourceResponseBody(Method method) {
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.function.Function;
|
||||
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
@@ -43,6 +42,7 @@ import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
@@ -68,18 +68,20 @@ public class JerseyEndpointResourceFactory {
|
||||
* {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @return the resources for the operations
|
||||
*/
|
||||
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
webEndpoints.stream()
|
||||
.flatMap((endpointInfo) -> endpointInfo.getOperations().stream())
|
||||
.map((operation) -> createResource(endpointMapping, operation))
|
||||
.forEach(resources::add);
|
||||
if (StringUtils.hasText(endpointMapping.getPath())) {
|
||||
resources.add(
|
||||
createEndpointLinksResource(endpointMapping.getPath(), webEndpoints));
|
||||
resources.add(createEndpointLinksResource(endpointMapping.getPath(),
|
||||
webEndpoints, endpointMediaTypes));
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
@@ -102,10 +104,14 @@ public class JerseyEndpointResourceFactory {
|
||||
}
|
||||
|
||||
private Resource createEndpointLinksResource(String endpointPath,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointPath);
|
||||
resourceBuilder.addMethod("GET").produces(MediaType.APPLICATION_JSON).handledBy(
|
||||
new EndpointLinksInflector(webEndpoints, this.endpointLinksResolver));
|
||||
resourceBuilder.addMethod("GET")
|
||||
.produces(endpointMediaTypes.getProduced()
|
||||
.toArray(new String[endpointMediaTypes.getProduced().size()]))
|
||||
.handledBy(new EndpointLinksInflector(webEndpoints,
|
||||
this.endpointLinksResolver));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
@@ -87,6 +88,8 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
|
||||
|
||||
private final EndpointMediaTypes endpointMediaTypes;
|
||||
|
||||
private final CorsConfiguration corsConfiguration;
|
||||
|
||||
/**
|
||||
@@ -94,10 +97,12 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
*/
|
||||
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointMapping, collection, null);
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
this(endpointMapping, collection, endpointMediaTypes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,13 +110,15 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the path beneath which all endpoints should be mapped
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration) {
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.webEndpoints = webEndpoints;
|
||||
this.endpointMediaTypes = endpointMediaTypes;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
setOrder(-100);
|
||||
}
|
||||
@@ -127,11 +134,18 @@ public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapp
|
||||
}
|
||||
|
||||
private void registerLinksMapping() {
|
||||
registerMapping(new RequestMappingInfo(
|
||||
new PatternsRequestCondition(
|
||||
pathPatternParser.parse(this.endpointMapping.getPath())),
|
||||
new RequestMethodsRequestCondition(RequestMethod.GET), null, null, null,
|
||||
null, null), this, this.links);
|
||||
registerMapping(
|
||||
new RequestMappingInfo(
|
||||
new PatternsRequestCondition(
|
||||
pathPatternParser.parse(this.endpointMapping.getPath())),
|
||||
new RequestMethodsRequestCondition(RequestMethod.GET), null, null,
|
||||
null,
|
||||
new ProducesRequestCondition(
|
||||
this.endpointMediaTypes.getProduced()
|
||||
.toArray(new String[this.endpointMediaTypes
|
||||
.getProduced().size()])),
|
||||
null),
|
||||
this, this.links);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
@@ -56,6 +57,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
|
||||
|
||||
private final EndpointMediaTypes endpointMediaTypes;
|
||||
|
||||
private final CorsConfiguration corsConfiguration;
|
||||
|
||||
/**
|
||||
@@ -63,10 +66,12 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints operations
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
*/
|
||||
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointMapping, collection, null);
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
this(endpointMapping, collection, endpointMediaTypes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,13 +79,15 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration) {
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.webEndpoints = webEndpoints;
|
||||
this.endpointMediaTypes = endpointMediaTypes;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
setOrder(-100);
|
||||
}
|
||||
@@ -107,8 +114,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||
PatternsRequestCondition patterns = patternsRequestConditionForPattern("");
|
||||
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
|
||||
RequestMethod.GET);
|
||||
ProducesRequestCondition produces = new ProducesRequestCondition(
|
||||
this.endpointMediaTypes.getProduced().toArray(
|
||||
new String[this.endpointMediaTypes.getProduced().size()]));
|
||||
RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null,
|
||||
null, null, null);
|
||||
null, produces, null);
|
||||
registerMapping(mapping, this, getLinks());
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
@@ -65,10 +66,12 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints operations
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
*/
|
||||
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointMapping, collection, null);
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
this(endpointMapping, collection, endpointMediaTypes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,12 +79,13 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
super(endpointMapping, webEndpoints, corsConfiguration);
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration) {
|
||||
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -65,6 +66,10 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
|
||||
private static final Duration TIMEOUT = Duration.ofMinutes(6);
|
||||
|
||||
private static final String ACTUATOR_MEDIA_TYPE_PATTERN = "application/vnd.test\\+json(;charset=UTF-8)?";
|
||||
|
||||
private static final String JSON_MEDIA_TYPE_PATTERN = "application/json(;charset=UTF-8)?";
|
||||
|
||||
private final Class<?> exporterConfiguration;
|
||||
|
||||
protected AbstractWebEndpointIntegrationTests(Class<?> exporterConfiguration) {
|
||||
@@ -74,41 +79,36 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
@Test
|
||||
public void readOperation() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/test").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody().jsonPath("All")
|
||||
.isEqualTo(true));
|
||||
(client) -> client.get().uri("/test").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("All").isEqualTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithEndpointsMappedToTheRoot() {
|
||||
load(TestEndpointConfiguration.class, "",
|
||||
(client) -> client.get().uri("/test").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody().jsonPath("All")
|
||||
.isEqualTo(true));
|
||||
(client) -> client.get().uri("/test").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("All").isEqualTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithSelector() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/test/one")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("part").isEqualTo("one"));
|
||||
(client) -> client.get().uri("/test/one").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("part").isEqualTo("one"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithSelectorContainingADot() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/test/foo.bar")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
(client) -> client.get().uri("/test/foo.bar").exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("part").isEqualTo("foo.bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksToOtherEndpointsAreProvided() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("_links.length()").isEqualTo(3)
|
||||
(client) -> client.get().uri("").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("_links.length()").isEqualTo(3)
|
||||
.jsonPath("_links.self.href").isNotEmpty()
|
||||
.jsonPath("_links.self.templated").isEqualTo(false)
|
||||
.jsonPath("_links.test.href").isNotEmpty()
|
||||
@@ -120,48 +120,45 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
@Test
|
||||
public void linksMappingIsDisabledWhenEndpointPathIsEmpty() {
|
||||
load(TestEndpointConfiguration.class, "",
|
||||
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isNotFound());
|
||||
(client) -> client.get().uri("").exchange().expectStatus().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithSingleQueryParameters() {
|
||||
load(QueryEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/query?one=1&two=2")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("query").isEqualTo("1 2"));
|
||||
(client) -> client.get().uri("/query?one=1&two=2").exchange()
|
||||
.expectStatus().isOk().expectBody().jsonPath("query")
|
||||
.isEqualTo("1 2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithSingleQueryParametersAndMultipleValues() {
|
||||
load(QueryEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/query?one=1&one=1&two=2")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("query").isEqualTo("1,1 2"));
|
||||
(client) -> client.get().uri("/query?one=1&one=1&two=2").exchange()
|
||||
.expectStatus().isOk().expectBody().jsonPath("query")
|
||||
.isEqualTo("1,1 2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithListQueryParameterAndSingleValue() {
|
||||
load(QueryWithListEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/query?one=1&two=2")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("query").isEqualTo("1 [2]"));
|
||||
(client) -> client.get().uri("/query?one=1&two=2").exchange()
|
||||
.expectStatus().isOk().expectBody().jsonPath("query")
|
||||
.isEqualTo("1 [2]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithListQueryParameterAndMultipleValues() {
|
||||
load(QueryWithListEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/query?one=1&two=2&two=2")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("query").isEqualTo("1 [2, 2]"));
|
||||
(client) -> client.get().uri("/query?one=1&two=2&two=2").exchange()
|
||||
.expectStatus().isOk().expectBody().jsonPath("query")
|
||||
.isEqualTo("1 [2, 2]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readOperationWithMappingFailureProducesBadRequestResponse() {
|
||||
load(QueryEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/query?two=two")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isBadRequest());
|
||||
load(QueryEndpointConfiguration.class, (client) -> client.get()
|
||||
.uri("/query?two=two").exchange().expectStatus().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,16 +167,16 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("foo", "one");
|
||||
body.put("bar", "two");
|
||||
client.post().uri("/test").syncBody(body).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isNoContent().expectBody().isEmpty();
|
||||
client.post().uri("/test").syncBody(body).exchange().expectStatus()
|
||||
.isNoContent().expectBody().isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeOperationWithVoidResponse() {
|
||||
load(VoidWriteResponseEndpointConfiguration.class, (context, client) -> {
|
||||
client.post().uri("/voidwrite").accept(MediaType.APPLICATION_JSON).exchange()
|
||||
.expectStatus().isNoContent().expectBody().isEmpty();
|
||||
client.post().uri("/voidwrite").exchange().expectStatus().isNoContent()
|
||||
.expectBody().isEmpty();
|
||||
verify(context.getBean(EndpointDelegate.class)).write();
|
||||
});
|
||||
}
|
||||
@@ -187,16 +184,15 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
@Test
|
||||
public void deleteOperation() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.delete().uri("/test/one")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
(client) -> client.delete().uri("/test/one").exchange().expectStatus()
|
||||
.isOk().expectBody().jsonPath("part").isEqualTo("one"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteOperationWithVoidResponse() {
|
||||
load(VoidDeleteResponseEndpointConfiguration.class, (context, client) -> {
|
||||
client.delete().uri("/voiddelete").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isNoContent().expectBody().isEmpty();
|
||||
client.delete().uri("/voiddelete").exchange().expectStatus().isNoContent()
|
||||
.expectBody().isEmpty();
|
||||
verify(context.getBean(EndpointDelegate.class)).delete();
|
||||
});
|
||||
}
|
||||
@@ -206,8 +202,8 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
load(TestEndpointConfiguration.class, (context, client) -> {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("foo", "one");
|
||||
client.post().uri("/test").syncBody(body).accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isNoContent().expectBody().isEmpty();
|
||||
client.post().uri("/test").syncBody(body).exchange().expectStatus()
|
||||
.isNoContent().expectBody().isEmpty();
|
||||
verify(context.getBean(EndpointDelegate.class)).write("one", null);
|
||||
});
|
||||
}
|
||||
@@ -215,35 +211,28 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
@Test
|
||||
public void nullsArePassedToTheOperationWhenPostRequestHasNoBody() {
|
||||
load(TestEndpointConfiguration.class, (context, client) -> {
|
||||
client.post().uri("/test").contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isNoContent().expectBody().isEmpty();
|
||||
client.post().uri("/test").contentType(MediaType.APPLICATION_JSON).exchange()
|
||||
.expectStatus().isNoContent().expectBody().isEmpty();
|
||||
verify(context.getBean(EndpointDelegate.class)).write(null, null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullResponseFromReadOperationResultsInNotFoundResponseStatus() {
|
||||
load(NullReadResponseEndpointConfiguration.class,
|
||||
(context, client) -> client.get().uri("/nullread")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isNotFound());
|
||||
load(NullReadResponseEndpointConfiguration.class, (context, client) -> client
|
||||
.get().uri("/nullread").exchange().expectStatus().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullResponseFromDeleteOperationResultsInNoContentResponseStatus() {
|
||||
load(NullDeleteResponseEndpointConfiguration.class,
|
||||
(context, client) -> client.delete().uri("/nulldelete")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isNoContent());
|
||||
load(NullDeleteResponseEndpointConfiguration.class, (context, client) -> client
|
||||
.delete().uri("/nulldelete").exchange().expectStatus().isNoContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullResponseFromWriteOperationResultsInNoContentResponseStatus() {
|
||||
load(NullWriteResponseEndpointConfiguration.class,
|
||||
(context, client) -> client.post().uri("/nullwrite")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||
.isNoContent());
|
||||
load(NullWriteResponseEndpointConfiguration.class, (context, client) -> client
|
||||
.post().uri("/nullwrite").exchange().expectStatus().isNoContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -272,9 +261,8 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
@Test
|
||||
public void readOperationWithMonoResponse() {
|
||||
load(MonoResponseEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/mono").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectBody().jsonPath("a")
|
||||
.isEqualTo("alpha"));
|
||||
(client) -> client.get().uri("/mono").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("a").isEqualTo("alpha"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -298,6 +286,38 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
.uri("/requiredparameters?foo=hello").exchange().expectStatus().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointsProducePrimaryMediaTypeByDefault() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/test").exchange().expectStatus().isOk()
|
||||
.expectHeader()
|
||||
.valueMatches("Content-Type", ACTUATOR_MEDIA_TYPE_PATTERN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointsProduceSecondaryMediaTypeWhenRequested() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/test").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectHeader()
|
||||
.valueMatches("Content-Type", JSON_MEDIA_TYPE_PATTERN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksProducesPrimaryMediaTypeByDefault() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("").exchange().expectStatus().isOk()
|
||||
.expectHeader()
|
||||
.valueMatches("Content-Type", ACTUATOR_MEDIA_TYPE_PATTERN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksProducesSecondaryMediaTypeWhenRequested() {
|
||||
load(TestEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().expectStatus().isOk().expectHeader()
|
||||
.valueMatches("Content-Type", JSON_MEDIA_TYPE_PATTERN));
|
||||
}
|
||||
|
||||
protected abstract T createApplicationContext(Class<?>... config);
|
||||
|
||||
protected abstract int getPort(T context);
|
||||
@@ -351,6 +371,13 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EndpointMediaTypes endpointMediaTypes() {
|
||||
List<String> mediaTypes = Arrays.asList("application/vnd.test+json",
|
||||
"application/json");
|
||||
return new EndpointMediaTypes(mediaTypes, mediaTypes);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
|
||||
ApplicationContext applicationContext) {
|
||||
@@ -358,8 +385,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
DefaultConversionService.getSharedInstance());
|
||||
return new WebAnnotationEndpointDiscoverer(applicationContext,
|
||||
parameterMapper, (id) -> new CachingConfiguration(0),
|
||||
Collections.singletonList("application/json"),
|
||||
Collections.singletonList("application/json"));
|
||||
endpointMediaTypes());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -247,12 +247,14 @@ public class WebAnnotationEndpointDiscovererTests {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
configuration);
|
||||
try {
|
||||
consumer.accept(new WebAnnotationEndpointDiscoverer(context,
|
||||
new ConversionServiceOperationParameterMapper(
|
||||
DefaultConversionService.getSharedInstance()),
|
||||
cachingConfigurationFactory,
|
||||
Collections.singletonList("application/json"),
|
||||
Collections.singletonList("application/json")));
|
||||
consumer.accept(
|
||||
new WebAnnotationEndpointDiscoverer(context,
|
||||
new ConversionServiceOperationParameterMapper(
|
||||
DefaultConversionService.getSharedInstance()),
|
||||
cachingConfigurationFactory,
|
||||
new EndpointMediaTypes(
|
||||
Collections.singletonList("application/json"),
|
||||
Collections.singletonList("application/json"))));
|
||||
}
|
||||
finally {
|
||||
context.close();
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
@@ -80,12 +81,13 @@ public class JerseyWebEndpointIntegrationTests extends
|
||||
|
||||
@Bean
|
||||
public ResourceConfig resourceConfig(Environment environment,
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer) {
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
Collection<Resource> resources = new JerseyEndpointResourceFactory()
|
||||
.createEndpointResources(
|
||||
new EndpointMapping(environment.getProperty("endpointPath")),
|
||||
endpointDiscoverer.discoverEndpoints());
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes);
|
||||
resourceConfig.registerResources(new HashSet<>(resources));
|
||||
resourceConfig.register(JacksonFeature.class);
|
||||
resourceConfig.register(new ObjectMapperContextResolver(new ObjectMapper()),
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
@@ -110,13 +111,15 @@ public class WebFluxEndpointIntegrationTests
|
||||
@Bean
|
||||
public WebFluxEndpointHandlerMapping webEndpointHandlerMapping(
|
||||
Environment environment,
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer) {
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new WebFluxEndpointHandlerMapping(
|
||||
new EndpointMapping(environment.getProperty("endpointPath")),
|
||||
endpointDiscoverer.discoverEndpoints(), corsConfiguration);
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
corsConfiguration);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
@@ -104,13 +105,15 @@ public class MvcWebEndpointIntegrationTests extends
|
||||
@Bean
|
||||
public WebMvcEndpointHandlerMapping webEndpointHandlerMapping(
|
||||
Environment environment,
|
||||
WebAnnotationEndpointDiscoverer webEndpointDiscoverer) {
|
||||
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new WebMvcEndpointHandlerMapping(
|
||||
new EndpointMapping(environment.getProperty("endpointPath")),
|
||||
webEndpointDiscoverer.discoverEndpoints(), corsConfiguration);
|
||||
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
corsConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.junit.runners.BlockJUnit4ClassRunner;
|
||||
@@ -28,6 +30,7 @@ import org.junit.runners.model.InitializationError;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
@@ -41,7 +44,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* {@link BlockJUnit4ClassRunner} for Jersey.
|
||||
@@ -90,15 +92,17 @@ class JerseyEndpointsRunner extends AbstractWebEndpointRunner {
|
||||
}
|
||||
|
||||
private void customize(ResourceConfig config) {
|
||||
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
|
||||
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON,
|
||||
ActuatorMediaType.V2_JSON);
|
||||
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
|
||||
mediaTypes);
|
||||
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
|
||||
this.applicationContext,
|
||||
new ConversionServiceOperationParameterMapper(), (id) -> null,
|
||||
mediaTypes, mediaTypes);
|
||||
endpointMediaTypes);
|
||||
Collection<Resource> resources = new JerseyEndpointResourceFactory()
|
||||
.createEndpointResources(new EndpointMapping("/application"),
|
||||
discoverer.discoverEndpoints());
|
||||
discoverer.discoverEndpoints(), endpointMediaTypes);
|
||||
config.registerResources(new HashSet<>(resources));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.runners.model.InitializationError;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
@@ -99,12 +100,15 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner {
|
||||
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping() {
|
||||
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
|
||||
ActuatorMediaType.V2_JSON);
|
||||
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
|
||||
mediaTypes);
|
||||
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
|
||||
this.applicationContext,
|
||||
new ConversionServiceOperationParameterMapper(), (id) -> null,
|
||||
mediaTypes, mediaTypes);
|
||||
endpointMediaTypes);
|
||||
return new WebFluxEndpointHandlerMapping(new EndpointMapping("/application"),
|
||||
discoverer.discoverEndpoints(), new CorsConfiguration());
|
||||
discoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
new CorsConfiguration());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.runners.model.InitializationError;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
@@ -82,12 +83,15 @@ class WebMvcEndpointRunner extends AbstractWebEndpointRunner {
|
||||
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping() {
|
||||
List<String> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
|
||||
ActuatorMediaType.V2_JSON);
|
||||
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
|
||||
mediaTypes);
|
||||
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
|
||||
this.applicationContext,
|
||||
new ConversionServiceOperationParameterMapper(), (id) -> null,
|
||||
mediaTypes, mediaTypes);
|
||||
endpointMediaTypes);
|
||||
return new WebMvcEndpointHandlerMapping(new EndpointMapping("/application"),
|
||||
discoverer.discoverEndpoints(), new CorsConfiguration());
|
||||
discoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
new CorsConfiguration());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user