Separate endpoint concerns

Update endpoint code to provide cleaner separation of concerns.
Specifically, the top level endpoint package is no longer aware of
the fact that JMX and HTTP are ultimately used to expose endpoints.
Caching concerns have also been abstracted behind a general purpose
`OperationMethodInvokerAdvisor` interface.

Configuration properties have been refined to further enforce
separation. The `management.endpoint.<name>` prefix provides
configuration for a  single endpoint (including enable and cache
time-to-live). These  properties are now technology agnostic (they
don't include `web` or `jmx` sub properties).

The `management.endpoints.<technology>` prefix provide exposure specific
configuration. For example, `management.endpoints.web.path-mapping`
allow endpoint URLs to be changed.

Endpoint enabled/disabled logic has been simplified so that endpoints
can't be disabled per exposure technology. Instead a filter based
approach is used to allow refinement of what endpoints are exposed over
a given technology.

Fixes gh-10176
pull/10948/merge
Phillip Webb 7 years ago
parent d24709c696
commit fd5c43cdc9

@ -18,8 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.audit;
import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint; import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
import org.springframework.boot.actuate.audit.AuditEventsJmxEndpointExtension; import org.springframework.boot.actuate.audit.AuditEventsJmxEndpointExtension;
import org.springframework.boot.actuate.audit.AuditEventsWebEndpointExtension;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.logging.LoggersEndpoint; import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -63,9 +63,9 @@ public class AuditEventsEndpointAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(AuditEventsEndpoint.class) @ConditionalOnBean(AuditEventsEndpoint.class)
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension( public AuditEventsEndpointWebExtension auditEventsWebEndpointExtension(
AuditEventsEndpoint auditEventsEndpoint) { AuditEventsEndpoint auditEventsEndpoint) {
return new AuditEventsWebEndpointExtension(auditEventsEndpoint); return new AuditEventsEndpointWebExtension(auditEventsEndpoint);
} }
} }

@ -32,13 +32,13 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityRespo
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationInvoker; import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.ParameterMappingException; import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException; import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link; import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping;
import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -81,7 +81,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
} }
@Override @Override
protected void registerMappingForOperation(WebEndpointOperation operation) { protected void registerMappingForOperation(WebOperation operation) {
OperationType operationType = operation.getType(); OperationType operationType = operation.getType();
OperationInvoker operationInvoker = operation.getInvoker(); OperationInvoker operationInvoker = operation.getInvoker();
if (operation.isBlocking()) { if (operation.isBlocking()) {
@ -135,7 +135,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
* @param securityInterceptor the Security Interceptor * @param securityInterceptor the Security Interceptor
*/ */
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping, CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints, Collection<EndpointInfo<WebOperation>> webEndpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration, EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) { ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration); super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);

@ -21,10 +21,9 @@ import java.util.Collections;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.endpoint.DefaultCachingConfigurationFactory; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.endpoint.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -68,17 +67,16 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
@Bean @Bean
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping( public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping(
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
WebClient.Builder webClientBuilder, Environment environment, WebClient.Builder webClientBuilder) {
DefaultCachingConfigurationFactory cachingConfigurationFactory,
WebEndpointProperties webEndpointProperties) {
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer( WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, cachingConfigurationFactory, this.applicationContext, parameterMapper, endpointMediaTypes,
endpointMediaTypes, (id) -> id); EndpointPathResolver.useEndpointId(), null, null);
ReactiveCloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
webClientBuilder, this.applicationContext.getEnvironment());
return new CloudFoundryWebFluxEndpointHandlerMapping( return new CloudFoundryWebFluxEndpointHandlerMapping(
new EndpointMapping("/cloudfoundryapplication"), new EndpointMapping("/cloudfoundryapplication"),
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes, endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
getCorsConfiguration(), getCorsConfiguration(), securityInterceptor);
getSecurityInterceptor(webClientBuilder, environment));
} }
private ReactiveCloudFoundrySecurityInterceptor getSecurityInterceptor( private ReactiveCloudFoundrySecurityInterceptor getSecurityInterceptor(

@ -18,9 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
import java.util.Arrays; import java.util.Arrays;
import org.springframework.boot.actuate.autoconfigure.endpoint.DefaultCachingConfigurationFactory;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.ParameterMapper; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
@ -71,17 +70,17 @@ public class CloudFoundryActuatorAutoConfiguration {
@Bean @Bean
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
ParameterMapper parameterMapper, ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
DefaultCachingConfigurationFactory cachingConfigurationFactory, RestTemplateBuilder restTemplateBuilder) {
EndpointMediaTypes endpointMediaTypes, Environment environment,
RestTemplateBuilder builder) {
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer( WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, cachingConfigurationFactory, this.applicationContext, parameterMapper, endpointMediaTypes,
endpointMediaTypes, EndpointPathResolver.useEndpointId()); EndpointPathResolver.useEndpointId(), null, null);
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
restTemplateBuilder, this.applicationContext.getEnvironment());
return new CloudFoundryWebEndpointServletHandlerMapping( return new CloudFoundryWebEndpointServletHandlerMapping(
new EndpointMapping("/cloudfoundryapplication"), new EndpointMapping("/cloudfoundryapplication"),
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes, endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
getCorsConfiguration(), getSecurityInterceptor(builder, environment)); getCorsConfiguration(), securityInterceptor);
} }
private CloudFoundrySecurityInterceptor getSecurityInterceptor( private CloudFoundrySecurityInterceptor getSecurityInterceptor(

@ -35,13 +35,13 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationInvoker; import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.ParameterMappingException; import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException; import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link; import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping;
import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -78,7 +78,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver(); private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping, CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints, Collection<EndpointInfo<WebOperation>> webEndpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration, EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
CloudFoundrySecurityInterceptor securityInterceptor) { CloudFoundrySecurityInterceptor securityInterceptor) {
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration); super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
@ -125,7 +125,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
} }
@Override @Override
protected void registerMappingForOperation(WebEndpointOperation operation) { protected void registerMappingForOperation(WebOperation operation) {
registerMapping(createRequestMappingInfo(operation), registerMapping(createRequestMappingInfo(operation),
new OperationHandler(operation.getInvoker(), operation.getId(), new OperationHandler(operation.getInvoker(), operation.getId(),
this.securityInterceptor), this.securityInterceptor),

@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties("endpoints.configprops") @ConfigurationProperties("management.endpoint.configprops")
public class ConfigurationPropertiesReportEndpointProperties { public class ConfigurationPropertiesReportEndpointProperties {
/** /**

@ -1,48 +0,0 @@
/*
* 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.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.core.env.Environment;
/**
* Default {@link CachingConfigurationFactory} implementation that use the
* {@link Environment} to extract the caching settings of each endpoint.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class DefaultCachingConfigurationFactory implements CachingConfigurationFactory {
private final Environment environment;
/**
* Create a new instance with the {@link Environment} to use.
* @param environment the environment
*/
DefaultCachingConfigurationFactory(Environment environment) {
this.environment = environment;
}
@Override
public CachingConfiguration getCachingConfiguration(String endpointId) {
String key = String.format("endpoints.%s.cache.time-to-live", endpointId);
Long timeToLive = this.environment.getProperty(key, Long.class, 0L);
return new CachingConfiguration(timeToLive);
}
}

@ -16,23 +16,12 @@
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.ParameterMapper;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory; import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper; import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -54,49 +43,11 @@ public class EndpointAutoConfiguration {
} }
@Bean @Bean
@ConditionalOnMissingBean(CachingConfigurationFactory.class) @ConditionalOnMissingBean
public DefaultCachingConfigurationFactory endpointCacheConfigurationFactory( public CachingOperationInvokerAdvisor endpointCachingOperationInvokerAdvisor(
Environment environment) { Environment environment) {
return new DefaultCachingConfigurationFactory(environment); return new CachingOperationInvokerAdvisor(
} new EndpointIdTimeToLivePropertyFunction(environment));
@Configuration
@ConditionalOnWebApplication
static class EndpointWebConfiguration {
private static final List<String> MEDIA_TYPES = Arrays
.asList(ActuatorMediaType.V2_JSON, "application/json");
private final ApplicationContext applicationContext;
EndpointWebConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
@Bean
@ConditionalOnMissingBean
public EndpointPathResolver endpointPathResolver(Environment environment) {
return new DefaultEndpointPathResolver(environment);
}
@Bean
public EndpointProvider<WebEndpointOperation> webEndpointProvider(
ParameterMapper parameterMapper,
DefaultCachingConfigurationFactory cachingConfigurationFactory,
EndpointPathResolver endpointPathResolver) {
Environment environment = this.applicationContext.getEnvironment();
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, cachingConfigurationFactory,
endpointMediaTypes(), endpointPathResolver);
return new EndpointProvider<>(environment, endpointDiscoverer,
EndpointExposure.WEB);
}
} }
} }

@ -1,57 +0,0 @@
/*
* 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;
/**
* Determines if an endpoint is enabled or not.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public final class EndpointEnablement {
private final boolean enabled;
private final String reason;
/**
* Creates a new instance.
* @param enabled whether or not the endpoint is enabled
* @param reason a human readable reason of the decision
*/
EndpointEnablement(boolean enabled, String reason) {
this.enabled = enabled;
this.reason = reason;
}
/**
* Return whether or not the endpoint is enabled.
* @return {@code true} if the endpoint is enabled, {@code false} otherwise
*/
public boolean isEnabled() {
return this.enabled;
}
/**
* Return a human readable reason of the decision.
* @return the reason of the endpoint's enablement
*/
public String getReason() {
return this.reason;
}
}

@ -1,213 +0,0 @@
/*
* 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.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* Determines an endpoint's enablement based on the current {@link Environment}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class EndpointEnablementProvider {
private final Environment environment;
/**
* Creates a new instance with the {@link Environment} to use.
* @param environment the environment
*/
public EndpointEnablementProvider(Environment environment) {
this.environment = environment;
}
/**
* Return the {@link EndpointEnablement} of an endpoint with no specific tech
* exposure.
* @param endpointId the id of the endpoint
* @param defaultEnablement the {@link DefaultEnablement} of the endpoint
* @return the {@link EndpointEnablement} of that endpoint
*/
public EndpointEnablement getEndpointEnablement(String endpointId,
DefaultEnablement defaultEnablement) {
return getEndpointEnablement(endpointId, defaultEnablement, null);
}
/**
* Return the {@link EndpointEnablement} of an endpoint for a specific tech exposure.
* @param endpointId the id of the endpoint
* @param defaultEnablement the {@link DefaultEnablement} of the endpoint
* @param exposure the requested {@link EndpointExposure}
* @return the {@link EndpointEnablement} of that endpoint for the specified
* {@link EndpointExposure}
*/
public EndpointEnablement getEndpointEnablement(String endpointId,
DefaultEnablement defaultEnablement, EndpointExposure exposure) {
Assert.hasText(endpointId, "Endpoint id must have a value");
Assert.isTrue(!endpointId.equals("default"),
"Endpoint id 'default' is a reserved "
+ "value and cannot be used by an endpoint");
EndpointEnablement result = findEnablement(endpointId, exposure);
if (result != null) {
return result;
}
result = findEnablement(getKey(endpointId, "enabled"));
if (result != null) {
return result;
}
// All endpoints specific attributes have been looked at. Checking default value
// for the endpoint
if (defaultEnablement != DefaultEnablement.NEUTRAL) {
return getDefaultEndpointEnablement(endpointId,
(defaultEnablement == DefaultEnablement.ENABLED), exposure);
}
return getGlobalEndpointEnablement(endpointId, defaultEnablement, exposure);
}
private EndpointEnablement findEnablement(String endpointId,
EndpointExposure exposure) {
if (exposure != null) {
return findEnablement(getKey(endpointId, exposure));
}
return findEnablementForAnyExposureTechnology(endpointId);
}
private EndpointEnablement getGlobalEndpointEnablement(String endpointId,
DefaultEnablement defaultEnablement, EndpointExposure exposure) {
EndpointEnablement result = findGlobalEndpointEnablement(exposure);
if (result != null) {
return result;
}
result = findEnablement(getKey("default", "enabled"));
if (result != null) {
return result;
}
boolean enablement = determineGlobalDefaultEnablement(defaultEnablement,
exposure);
String message = determineGlobalDefaultMessage(endpointId, enablement, exposure,
defaultEnablement);
return new EndpointEnablement(enablement, message);
}
private boolean determineGlobalDefaultEnablement(DefaultEnablement defaultEnablement,
EndpointExposure exposure) {
if (defaultEnablement == DefaultEnablement.NEUTRAL) {
return exposure == null || exposure.isEnabledByDefault();
}
return (defaultEnablement == DefaultEnablement.ENABLED);
}
private String determineGlobalDefaultMessage(String endpointId, boolean enablement,
EndpointExposure exposure, DefaultEnablement defaultEnablement) {
StringBuilder message = new StringBuilder();
message.append(String.format("endpoint '%s' ", endpointId));
if (exposure != null) {
message.append(String.format("(%s) ", exposure.name().toLowerCase()));
}
message.append(String.format("is %s ", (enablement ? "enabled" : "disabled")));
if (defaultEnablement == DefaultEnablement.NEUTRAL) {
if (exposure != null) {
message.append(String.format("(default for %s endpoints)",
exposure.name().toLowerCase()));
}
else {
message.append("(default)");
}
}
else {
message.append("by default");
}
return message.toString();
}
private EndpointEnablement findGlobalEndpointEnablement(EndpointExposure exposure) {
if (exposure != null) {
EndpointEnablement result = findEnablement(getKey("default", exposure));
if (result != null) {
return result;
}
if (!exposure.isEnabledByDefault()) {
return getDefaultEndpointEnablement("default", false, exposure);
}
return null;
}
return findEnablementForAnyExposureTechnology("default");
}
private EndpointEnablement findEnablementForAnyExposureTechnology(String endpointId) {
for (EndpointExposure candidate : EndpointExposure.values()) {
EndpointEnablement result = findEnablementForExposureTechnology(endpointId,
candidate);
if (result != null && result.isEnabled()) {
return result;
}
}
return null;
}
private EndpointEnablement findEnablementForExposureTechnology(String endpointId,
EndpointExposure exposure) {
String endpointTypeKey = getKey(endpointId, exposure);
return findEnablement(endpointTypeKey);
}
private EndpointEnablement getDefaultEndpointEnablement(String endpointId,
boolean enabledByDefault, EndpointExposure exposure) {
return new EndpointEnablement(enabledByDefault,
createDefaultEnablementMessage(endpointId, enabledByDefault, exposure));
}
private String createDefaultEnablementMessage(String endpointId,
boolean enabledByDefault, EndpointExposure exposure) {
StringBuilder message = new StringBuilder();
message.append(String.format("endpoint '%s' ", endpointId));
if (exposure != null) {
message.append(String.format("(%s) ", exposure.name().toLowerCase()));
}
message.append(String.format("is %s by default",
(enabledByDefault ? "enabled" : "disabled")));
return message.toString();
}
private String getKey(String endpointId, EndpointExposure exposure) {
return getKey(endpointId, exposure.name().toLowerCase() + ".enabled");
}
private String getKey(String endpointId, String suffix) {
return "endpoints." + endpointId + "." + suffix;
}
/**
* Return an {@link EndpointEnablement} for the specified key if it is set or
* {@code null} if the key is not present in the environment.
* @param key the key to check
* @return the outcome or {@code null} if the key is no set
*/
private EndpointEnablement findEnablement(String key) {
if (this.environment.containsProperty(key)) {
boolean match = this.environment.getProperty(key, Boolean.class, true);
return new EndpointEnablement(match, String.format("found property %s", key));
}
return null;
}
}

@ -0,0 +1,49 @@
/*
* 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 java.util.function.Function;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
import org.springframework.core.env.PropertyResolver;
/**
* Function for use with {@link CachingOperationInvokerAdvisor} that extracts caching
* time-to-live from a {@link PropertyResolver resolved property}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class EndpointIdTimeToLivePropertyFunction implements Function<String, Long> {
private final PropertyResolver propertyResolver;
/**
* Create a new instance with the {@link PropertyResolver} to use.
* @param propertyResolver the environment
*/
EndpointIdTimeToLivePropertyFunction(PropertyResolver propertyResolver) {
this.propertyResolver = propertyResolver;
}
@Override
public Long apply(String endpointId) {
String key = String.format("management.endpoint.%s.cache.time-to-live",
endpointId);
return this.propertyResolver.getProperty(key, Long.class);
}
}

@ -1,67 +0,0 @@
/*
* 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 java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.core.env.Environment;
/**
* Provides the endpoints that are enabled according to an {@link EndpointDiscoverer} and
* the current {@link Environment}.
*
* @param <T> the endpoint operation type
* @author Stephane Nicoll
* @since 2.0.0
*/
public class EndpointProvider<T extends Operation> {
private final EndpointDiscoverer<T> discoverer;
private final EndpointEnablementProvider endpointEnablementProvider;
private final EndpointExposure exposure;
/**
* Creates a new instance.
* @param environment the environment to use to check the endpoints that are enabled
* @param discoverer the discoverer to get the initial set of endpoints
* @param exposure the exposure technology for the endpoint
*/
public EndpointProvider(Environment environment, EndpointDiscoverer<T> discoverer,
EndpointExposure exposure) {
this.discoverer = discoverer;
this.endpointEnablementProvider = new EndpointEnablementProvider(environment);
this.exposure = exposure;
}
public Collection<EndpointInfo<T>> getEndpoints() {
return this.discoverer.discoverEndpoints().stream().filter(this::isEnabled)
.collect(Collectors.toList());
}
private boolean isEnabled(EndpointInfo<?> endpoint) {
return this.endpointEnablementProvider.getEndpointEnablement(endpoint.getId(),
endpoint.getDefaultEnablement(), this.exposure).isEnabled();
}
}

@ -0,0 +1,119 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* {@link EndpointFilter} that will filter endpoints based on {@code expose} and
* {@code exclude} properties.
*
* @param <T> The operation type
* @author Phillip Webb
* @since 2.0.0
*/
public class ExposeExcludePropertyEndpointFilter<T extends Operation>
implements EndpointFilter<T> {
private final Class<? extends EndpointDiscoverer<T>> discovererType;
private final Set<String> expose;
private final Set<String> exclude;
private final Set<String> exposeDefaults;
public ExposeExcludePropertyEndpointFilter(
Class<? extends EndpointDiscoverer<T>> discovererType,
Environment environment, String prefix, String... exposeDefaults) {
Assert.notNull(discovererType, "Discoverer Type must not be null");
Assert.notNull(environment, "Environment must not be null");
Assert.hasText(prefix, "Prefix must not be empty");
Binder binder = Binder.get(environment);
this.discovererType = discovererType;
this.expose = bind(binder, prefix + ".expose");
this.exclude = bind(binder, prefix + ".exclude");
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
}
public ExposeExcludePropertyEndpointFilter(
Class<? extends EndpointDiscoverer<T>> discovererType,
Collection<String> expose, Collection<String> exclude,
String... exposeDefaults) {
Assert.notNull(discovererType, "Discoverer Type must not be null");
this.discovererType = discovererType;
this.expose = asSet(expose);
this.exclude = asSet(exclude);
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
}
private Set<String> bind(Binder binder, String name) {
return asSet(binder.bind(name, Bindable.listOf(String.class))
.orElseGet(ArrayList::new));
}
private Set<String> asSet(Collection<String> items) {
if (items == null) {
return Collections.emptySet();
}
return items.stream().map(String::toLowerCase)
.collect(Collectors.toCollection(HashSet::new));
}
@Override
public boolean match(EndpointInfo<T> info, EndpointDiscoverer<T> discoverer) {
if (this.discovererType.isInstance(discoverer)) {
return isExposed(info) && !isExcluded(info);
}
return true;
}
private boolean isExposed(EndpointInfo<T> info) {
if (this.expose.isEmpty()) {
return this.exposeDefaults.contains("*")
|| contains(this.exposeDefaults, info);
}
return this.expose.contains("*") || contains(this.expose, info);
}
private boolean isExcluded(EndpointInfo<T> info) {
if (this.exclude.isEmpty()) {
return false;
}
return this.exclude.contains("*") || contains(this.exclude, info);
}
private boolean contains(Set<String> items, EndpointInfo<T> info) {
return items.contains(info.getId().toLowerCase());
}
}

@ -22,63 +22,15 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
/** /**
* {@link Conditional} that checks whether an endpoint is enabled or not. Matches * {@link Conditional} that checks whether an endpoint is enabled or not. Matches
* according to the {@code defaultEnablement} and {@code types} flag that the * according to the endpoints specific {@link Environment} property, falling back to
* {@link Endpoint} may be restricted to. * {@code management.endpoints.enabled-by-default} or failing that
* <p> * {@link Endpoint#enableByDefault()}.
* When an endpoint uses {@link DefaultEnablement#DISABLED}, it will only be enabled if
* {@code endpoint.<name>.enabled}, {@code endpoint.<name>.jmx.enabled} or
* {@code endpoint.<name>.web.enabled} is {@code true}.
* <p>
* When an endpoint uses {@link DefaultEnablement#ENABLED}, it will be enabled unless
* {@code endpoint.<name>.enabled}, {@code endpoint.<name>.jmx.enabled} or
* {@code endpoint.<name>.web.enabled} is {@code false}.
* <p>
* When an endpoint uses {@link DefaultEnablement#NEUTRAL}, it will be enabled if
* {@code endpoint.default.enabled}, {@code endpoint.default.jmx.enabled} or
* {@code endpoint.default.web.enabled} is {@code true} and
* {@code endpoint.<name>.enabled}, {@code endpoint.<name>.jmx.enabled} or
* {@code endpoint.<name>.web.enabled} has not been set to {@code false}.
* <p>
* If any properties are set, they are evaluated from most to least specific, e.g.
* considering a web endpoint with id {@code foo}:
* <ol>
* <li>endpoints.foo.web.enabled</li>
* <li>endpoints.foo.enabled</li>
* <li>endpoints.default.web.enabled</li>
* <li>endpoints.default.enabled</li>
* </ol>
* For instance if {@code endpoints.default.enabled} is {@code false} but
* {@code endpoints.<name>.enabled} is {@code true}, the condition will match.
* <p>
* This condition must be placed on a {@code @Bean} method producing an endpoint as its id
* and other attributes are inferred from the {@link Endpoint} annotation set on the
* return type of the factory method. Consider the following valid example:
*
* <pre class="code">
* &#064;Configuration
* public class MyAutoConfiguration {
*
* &#064;ConditionalOnEnabledEndpoint
* &#064;Bean
* public MyEndpoint myEndpoint() {
* ...
* }
*
* &#064;Endpoint(id = "my", defaultEnablement = DefaultEnablement.DISABLED)
* static class MyEndpoint { ... }
*
* }</pre>
* <p>
*
* In the sample above the condition will be evaluated with the attributes specified on
* {@code MyEndpoint}. In particular, in the absence of any property in the environment,
* the condition will not match as this endpoint is disabled by default.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0

@ -16,45 +16,62 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.condition; package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointEnablement;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointEnablementProvider;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExtension; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/** /**
* A condition that checks if an endpoint is enabled. * A condition that checks if an endpoint is enabled.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
* @see ConditionalOnEnabledEndpoint
*/ */
class OnEnabledEndpointCondition extends SpringBootCondition { class OnEnabledEndpointCondition extends SpringBootCondition {
private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default";
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
EndpointAttributes attributes = getEndpointAttributes(context, metadata); AnnotationAttributes attributes = getEndpointAttributes(context, metadata);
EndpointEnablement endpointEnablement = attributes String id = attributes.getString("id");
.getEnablement(new EndpointEnablementProvider(context.getEnvironment())); String key = "management.endpoint." + id + ".enabled";
return new ConditionOutcome(endpointEnablement.isEnabled(), Boolean userDefinedEnabled = context.getEnvironment().getProperty(key,
ConditionMessage.forCondition(ConditionalOnEnabledEndpoint.class) Boolean.class);
.because(endpointEnablement.getReason())); if (userDefinedEnabled != null) {
return new ConditionOutcome(userDefinedEnabled,
ConditionMessage.forCondition(ConditionalOnEnabledEndpoint.class)
.because("found property " + key + " with value "
+ userDefinedEnabled));
}
Boolean userDefinedDefault = context.getEnvironment()
.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class);
if (userDefinedDefault != null) {
return new ConditionOutcome(userDefinedDefault,
ConditionMessage.forCondition(ConditionalOnEnabledEndpoint.class)
.because("no property " + key
+ " found so using user defined default from "
+ ENABLED_BY_DEFAULT_KEY));
}
boolean endpointDefault = attributes.getBoolean("enableByDefault");
return new ConditionOutcome(endpointDefault,
ConditionMessage.forCondition(ConditionalOnEnabledEndpoint.class).because(
"no property " + key + " found so using endpoint default"));
} }
private EndpointAttributes getEndpointAttributes(ConditionContext context, private AnnotationAttributes getEndpointAttributes(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
Assert.state( Assert.state(
metadata instanceof MethodMetadata metadata instanceof MethodMetadata
@ -63,77 +80,35 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
return getEndpointAttributes(context, (MethodMetadata) metadata); return getEndpointAttributes(context, (MethodMetadata) metadata);
} }
private EndpointAttributes getEndpointAttributes(ConditionContext context, private AnnotationAttributes getEndpointAttributes(ConditionContext context,
MethodMetadata methodMetadata) { MethodMetadata metadata) {
// We should be safe to load at this point since we are in the REGISTER_BEAN phase
try { try {
// We should be safe to load at this point since we are in the Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(),
// REGISTER_BEAN phase
Class<?> returnType = ClassUtils.forName(methodMetadata.getReturnTypeName(),
context.getClassLoader()); context.getClassLoader());
return extractEndpointAttributes(returnType); return getEndpointAttributes(returnType);
} }
catch (Throwable ex) { catch (Throwable ex) {
throw new IllegalStateException("Failed to extract endpoint id for " throw new IllegalStateException("Failed to extract endpoint id for "
+ methodMetadata.getDeclaringClassName() + "." + metadata.getDeclaringClassName() + "." + metadata.getMethodName(),
+ methodMetadata.getMethodName(), ex); ex);
}
}
protected EndpointAttributes extractEndpointAttributes(Class<?> type) {
EndpointAttributes attributes = extractEndpointAttributesFromEndpoint(type);
if (attributes != null) {
return attributes;
}
JmxEndpointExtension jmxExtension = AnnotationUtils.findAnnotation(type,
JmxEndpointExtension.class);
if (jmxExtension != null) {
return extractEndpointAttributes(jmxExtension.endpoint());
}
WebEndpointExtension webExtension = AnnotationUtils.findAnnotation(type,
WebEndpointExtension.class);
if (webExtension != null) {
return extractEndpointAttributes(webExtension.endpoint());
}
throw new IllegalStateException(
"OnEnabledEndpointCondition may only be used on @Bean methods that return"
+ " @Endpoint, @JmxEndpointExtension, or @WebEndpointExtension");
}
private EndpointAttributes extractEndpointAttributesFromEndpoint(
Class<?> endpointClass) {
Endpoint endpoint = AnnotationUtils.findAnnotation(endpointClass, Endpoint.class);
if (endpoint == null) {
return null;
} }
// If both types are set, all exposure technologies are exposed
EndpointExposure[] exposures = endpoint.exposure();
return new EndpointAttributes(endpoint.id(), endpoint.defaultEnablement(),
(exposures.length == 1 ? exposures[0] : null));
} }
private static class EndpointAttributes { protected AnnotationAttributes getEndpointAttributes(Class<?> type) {
AnnotationAttributes attributes = AnnotatedElementUtils
private final String id; .findMergedAnnotationAttributes(type, Endpoint.class, true, true);
if (attributes == null) {
private final DefaultEnablement defaultEnablement; attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type,
EndpointExtension.class, false, true);
private final EndpointExposure exposure; if (attributes != null) {
return getEndpointAttributes(attributes.getClass("endpoint"));
EndpointAttributes(String id, DefaultEnablement defaultEnablement,
EndpointExposure exposure) {
if (!StringUtils.hasText(id)) {
throw new IllegalStateException("Endpoint id could not be determined");
} }
this.id = id;
this.defaultEnablement = defaultEnablement;
this.exposure = exposure;
}
public EndpointEnablement getEnablement(EndpointEnablementProvider provider) {
return provider.getEndpointEnablement(this.id, this.defaultEnablement,
this.exposure);
} }
Assert.state(attributes != null,
"OnEnabledEndpointCondition may only be used on @Bean methods that "
+ "return an @Endpoint or and @EndpointExtension");
return attributes;
} }
} }

@ -36,13 +36,13 @@ import org.springframework.util.StringUtils;
*/ */
class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
private final JmxEndpointExporterProperties properties; private final JmxEndpointProperties properties;
private final MBeanServer mBeanServer; private final MBeanServer mBeanServer;
private final String contextId; private final String contextId;
DefaultEndpointObjectNameFactory(JmxEndpointExporterProperties properties, DefaultEndpointObjectNameFactory(JmxEndpointProperties properties,
MBeanServer mBeanServer, String contextId) { MBeanServer mBeanServer, String contextId) {
this.properties = properties; this.properties = properties;
this.mBeanServer = mBeanServer; this.mBeanServer = mBeanServer;

@ -16,21 +16,25 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx; package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
import java.util.Collection;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.DefaultCachingConfigurationFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider; import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.ParameterMapper;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar; import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperation; import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -47,36 +51,49 @@ import org.springframework.util.ObjectUtils;
* @since 2.0.0 * @since 2.0.0
*/ */
@AutoConfigureAfter(JmxAutoConfiguration.class) @AutoConfigureAfter(JmxAutoConfiguration.class)
@EnableConfigurationProperties(JmxEndpointExporterProperties.class) @EnableConfigurationProperties(JmxEndpointProperties.class)
@ConditionalOnProperty(name = "management.endpoints.jmx.enabled", matchIfMissing = true)
public class JmxEndpointAutoConfiguration { public class JmxEndpointAutoConfiguration {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
public JmxEndpointAutoConfiguration(ApplicationContext applicationContext) { private final JmxEndpointProperties properties;
public JmxEndpointAutoConfiguration(ApplicationContext applicationContext,
JmxEndpointProperties properties) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.properties = properties;
} }
@Bean @Bean
public JmxAnnotationEndpointDiscoverer jmxEndpointDiscoverer( public JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer(
ParameterMapper parameterMapper, ParameterMapper parameterMapper,
DefaultCachingConfigurationFactory cachingConfigurationFactory) { Collection<OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<JmxOperation>> filters) {
return new JmxAnnotationEndpointDiscoverer(this.applicationContext, return new JmxAnnotationEndpointDiscoverer(this.applicationContext,
parameterMapper, cachingConfigurationFactory); parameterMapper, invokerAdvisors, filters);
} }
@ConditionalOnSingleCandidate(MBeanServer.class)
@Bean @Bean
public JmxEndpointExporter jmxMBeanExporter(JmxEndpointExporterProperties properties, @ConditionalOnSingleCandidate(MBeanServer.class)
public JmxEndpointExporter jmxMBeanExporter(
JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer,
MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer, MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer,
ObjectProvider<ObjectMapper> objectMapper) { ObjectProvider<ObjectMapper> objectMapper) {
EndpointProvider<JmxEndpointOperation> endpointProvider = new EndpointProvider<>( EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(
this.applicationContext.getEnvironment(), endpointDiscoverer, this.properties, mBeanServer,
EndpointExposure.JMX); ObjectUtils.getIdentityHexString(this.applicationContext));
EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar( EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(mBeanServer,
mBeanServer, new DefaultEndpointObjectNameFactory(properties, mBeanServer, objectNameFactory);
ObjectUtils.getIdentityHexString(this.applicationContext))); return new JmxEndpointExporter(jmxAnnotationEndpointDiscoverer, registrar,
return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar,
objectMapper.getIfAvailable(ObjectMapper::new)); objectMapper.getIfAvailable(ObjectMapper::new));
} }
@Bean
public ExposeExcludePropertyEndpointFilter<JmxOperation> jmxIncludeExcludePropertyEndpointFilter() {
return new ExposeExcludePropertyEndpointFilter<>(
JmxAnnotationEndpointDiscoverer.class, this.properties.getExpose(),
this.properties.getExclude(), "*");
}
} }

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
@ -29,13 +30,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBean;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar; import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointMBeanFactory; import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointMBeanFactory;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperation; import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper; import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer;
/** /**
* Exports all available {@link Endpoint} to a configurable {@link MBeanServer}. * Exports all available {@link Endpoint} to a configurable {@link MBeanServer}.
@ -44,7 +45,7 @@ import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper;
*/ */
class JmxEndpointExporter implements InitializingBean, DisposableBean { class JmxEndpointExporter implements InitializingBean, DisposableBean {
private final EndpointProvider<JmxEndpointOperation> endpointProvider; private final JmxAnnotationEndpointDiscoverer endpointDiscoverer;
private final EndpointMBeanRegistrar endpointMBeanRegistrar; private final EndpointMBeanRegistrar endpointMBeanRegistrar;
@ -52,9 +53,9 @@ class JmxEndpointExporter implements InitializingBean, DisposableBean {
private Collection<ObjectName> registeredObjectNames; private Collection<ObjectName> registeredObjectNames;
JmxEndpointExporter(EndpointProvider<JmxEndpointOperation> endpointProvider, JmxEndpointExporter(JmxAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMBeanRegistrar endpointMBeanRegistrar, ObjectMapper objectMapper) { EndpointMBeanRegistrar endpointMBeanRegistrar, ObjectMapper objectMapper) {
this.endpointProvider = endpointProvider; this.endpointDiscoverer = endpointDiscoverer;
this.endpointMBeanRegistrar = endpointMBeanRegistrar; this.endpointMBeanRegistrar = endpointMBeanRegistrar;
DataConverter dataConverter = new DataConverter(objectMapper); DataConverter dataConverter = new DataConverter(objectMapper);
this.mBeanFactory = new JmxEndpointMBeanFactory(dataConverter); this.mBeanFactory = new JmxEndpointMBeanFactory(dataConverter);
@ -65,21 +66,19 @@ class JmxEndpointExporter implements InitializingBean, DisposableBean {
this.registeredObjectNames = registerEndpointMBeans(); this.registeredObjectNames = registerEndpointMBeans();
} }
private Collection<ObjectName> registerEndpointMBeans() {
Collection<EndpointInfo<JmxOperation>> endpoints = this.endpointDiscoverer
.discoverEndpoints();
return this.mBeanFactory.createMBeans(endpoints).stream()
.map(this.endpointMBeanRegistrar::registerEndpointMBean)
.collect(Collectors.toCollection(ArrayList::new));
}
@Override @Override
public void destroy() throws Exception { public void destroy() throws Exception {
unregisterEndpointMBeans(this.registeredObjectNames); unregisterEndpointMBeans(this.registeredObjectNames);
} }
private Collection<ObjectName> registerEndpointMBeans() {
List<ObjectName> objectNames = new ArrayList<>();
Collection<EndpointMBean> mBeans = this.mBeanFactory
.createMBeans(this.endpointProvider.getEndpoints());
for (EndpointMBean mBean : mBeans) {
objectNames.add(this.endpointMBeanRegistrar.registerEndpointMBean(mBean));
}
return objectNames;
}
private void unregisterEndpointMBeans(Collection<ObjectName> objectNames) { private void unregisterEndpointMBeans(Collection<ObjectName> objectNames) {
objectNames.forEach(this.endpointMBeanRegistrar::unregisterEndpointMbean); objectNames.forEach(this.endpointMBeanRegistrar::unregisterEndpointMbean);

@ -16,7 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx; package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
import java.util.LinkedHashSet;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -29,7 +31,22 @@ import org.springframework.util.StringUtils;
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties("management.endpoints.jmx") @ConfigurationProperties("management.endpoints.jmx")
public class JmxEndpointExporterProperties { public class JmxEndpointProperties {
/**
* Whether JMX endpoints are enabled.
*/
private boolean enabled;
/**
* The IDs of endpoints that should be exposed or '*' for all.
*/
private Set<String> expose = new LinkedHashSet<>();
/**
* The IDs of endpoints that should be excluded.
*/
private Set<String> exclude = new LinkedHashSet<>();
/** /**
* Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set. * Endpoints JMX domain name. Fallback to 'spring.jmx.default-domain' if set.
@ -47,13 +64,37 @@ public class JmxEndpointExporterProperties {
*/ */
private final Properties staticNames = new Properties(); private final Properties staticNames = new Properties();
public JmxEndpointExporterProperties(Environment environment) { public JmxEndpointProperties(Environment environment) {
String defaultDomain = environment.getProperty("spring.jmx.default-domain"); String defaultDomain = environment.getProperty("spring.jmx.default-domain");
if (StringUtils.hasText(defaultDomain)) { if (StringUtils.hasText(defaultDomain)) {
this.domain = defaultDomain; this.domain = defaultDomain;
} }
} }
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<String> getExpose() {
return this.expose;
}
public void setExpose(Set<String> expose) {
this.expose = expose;
}
public Set<String> getExclude() {
return this.exclude;
}
public void setExclude(Set<String> exclude) {
this.exclude = exclude;
}
public String getDomain() { public String getDomain() {
return this.domain; return this.domain;
} }

@ -16,13 +16,13 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web; package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider; import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -33,29 +33,34 @@ import org.springframework.util.Assert;
*/ */
public class DefaultEndpointPathProvider implements EndpointPathProvider { public class DefaultEndpointPathProvider implements EndpointPathProvider {
private final Collection<EndpointInfo<WebEndpointOperation>> endpoints;
private final String basePath; private final String basePath;
public DefaultEndpointPathProvider(EndpointProvider<WebEndpointOperation> provider, private final EndpointDiscoverer<WebOperation> endpointDiscoverer;
public DefaultEndpointPathProvider(
EndpointDiscoverer<WebOperation> endpointDiscoverer,
WebEndpointProperties webEndpointProperties) { WebEndpointProperties webEndpointProperties) {
this.endpoints = provider.getEndpoints(); this.endpointDiscoverer = endpointDiscoverer;
this.basePath = webEndpointProperties.getBasePath(); this.basePath = webEndpointProperties.getBasePath();
} }
@Override @Override
public List<String> getPaths() { public List<String> getPaths() {
return this.endpoints.stream().map(this::getPath).collect(Collectors.toList()); return getEndpoints().map(this::getPath).collect(Collectors.toList());
} }
@Override @Override
public String getPath(String id) { public String getPath(String id) {
Assert.notNull(id, "ID must not be null"); Assert.notNull(id, "ID must not be null");
return this.endpoints.stream().filter((info) -> id.equals(info.getId())) return getEndpoints().filter((info) -> id.equals(info.getId())).findFirst()
.findFirst().map(this::getPath).orElse(null); .map(this::getPath).orElse(null);
}
private Stream<EndpointInfo<WebOperation>> getEndpoints() {
return this.endpointDiscoverer.discoverEndpoints().stream();
} }
private String getPath(EndpointInfo<WebEndpointOperation> endpointInfo) { private String getPath(EndpointInfo<WebOperation> endpointInfo) {
return this.basePath + "/" + endpointInfo.getId(); return this.basePath + "/" + endpointInfo.getId();
} }

@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -27,16 +29,15 @@ import org.springframework.core.env.Environment;
*/ */
class DefaultEndpointPathResolver implements EndpointPathResolver { class DefaultEndpointPathResolver implements EndpointPathResolver {
private final Environment environment; private final Map<String, String> pathMapping;
DefaultEndpointPathResolver(Environment environment) { DefaultEndpointPathResolver(Map<String, String> pathMapping) {
this.environment = environment; this.pathMapping = pathMapping;
} }
@Override @Override
public String resolvePath(String endpointId) { public String resolvePath(String endpointId) {
String key = String.format("endpoints.%s.web.path", endpointId); return this.pathMapping.getOrDefault(endpointId, endpointId);
return this.environment.getProperty(key, String.class, endpointId);
} }
} }

@ -0,0 +1,111 @@
/*
* 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.web;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for web {@link Endpoint} support.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
@Configuration
@ConditionalOnWebApplication
@AutoConfigureAfter(EndpointAutoConfiguration.class)
@EnableConfigurationProperties(WebEndpointProperties.class)
@ConditionalOnProperty(name = "management.endpoints.web.enabled", matchIfMissing = true)
public class WebEndpointAutoConfiguration {
private static final List<String> MEDIA_TYPES = Arrays
.asList(ActuatorMediaType.V2_JSON, "application/json");
private final ApplicationContext applicationContext;
private final WebEndpointProperties properties;
public WebEndpointAutoConfiguration(ApplicationContext applicationContext,
WebEndpointProperties properties) {
this.applicationContext = applicationContext;
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public EndpointPathResolver endpointPathResolver() {
return new DefaultEndpointPathResolver(this.properties.getPathMapping());
}
@Bean
@ConditionalOnMissingBean
public WebAnnotationEndpointDiscoverer webAnnotationEndpointDiscoverer(
ParameterMapper parameterMapper, EndpointPathResolver endpointPathResolver,
Collection<OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<WebOperation>> filters) {
return new WebAnnotationEndpointDiscoverer(this.applicationContext,
parameterMapper, endpointMediaTypes(), endpointPathResolver,
invokerAdvisors, filters);
}
@Bean
@ConditionalOnMissingBean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointDiscoverer<WebOperation> endpointDiscoverer,
WebEndpointProperties webEndpointProperties) {
return new DefaultEndpointPathProvider(endpointDiscoverer, webEndpointProperties);
}
@Bean
public ExposeExcludePropertyEndpointFilter<WebOperation> webIncludeExcludePropertyEndpointFilter() {
return new ExposeExcludePropertyEndpointFilter<>(
WebAnnotationEndpointDiscoverer.class, this.properties.getExpose(),
this.properties.getExclude(), "info", "status");
}
}

@ -16,23 +16,57 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web; package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* Configuration properties for web management endpoints. * Configuration properties for web management endpoints.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties(prefix = "management.endpoints.web") @ConfigurationProperties(prefix = "management.endpoints.web")
public class WebEndpointProperties { public class WebEndpointProperties {
/**
* Whether web endpoints are enabled.
*/
private boolean enabled;
/** /**
* The base-path for the web endpoints. Relative to `server.context-path` or * The base-path for the web endpoints. Relative to `server.context-path` or
* `management.server.context-path`, if `management.server.port` is different. * `management.server.context-path`, if `management.server.port` is different.
*/ */
private String basePath = "/application"; private String basePath = "/application";
/**
* The IDs of endpoints that should be exposed or '*' for all.
*/
private Set<String> expose = new LinkedHashSet<>();
/**
* The IDs of endpoints that should be excluded.
*/
private Set<String> exclude = new LinkedHashSet<>();
/**
* Mapping between endpoint IDs and the path that should expose them.
*/
private Map<String, String> pathMapping = new LinkedHashMap<>();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBasePath() { public String getBasePath() {
return this.basePath; return this.basePath;
} }
@ -41,4 +75,28 @@ public class WebEndpointProperties {
this.basePath = basePath; this.basePath = basePath;
} }
public Set<String> getExpose() {
return this.expose;
}
public void setExpose(Set<String> expose) {
this.expose = expose;
}
public Set<String> getExclude() {
return this.exclude;
}
public void setExclude(Set<String> exclude) {
this.exclude = exclude;
}
public Map<String, String> getPathMapping() {
return this.pathMapping;
}
public void setPathMapping(Map<String, String> pathMapping) {
this.pathMapping = pathMapping;
}
} }

@ -20,14 +20,11 @@ import java.util.HashSet;
import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.DefaultEndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; 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.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory; import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -49,27 +46,19 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(ResourceConfig.class) @ConditionalOnClass(ResourceConfig.class)
@ConditionalOnBean(ResourceConfig.class) @ConditionalOnBean({ ResourceConfig.class, WebAnnotationEndpointDiscoverer.class })
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet") @ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
class JerseyWebEndpointManagementContextConfiguration { class JerseyWebEndpointManagementContextConfiguration {
@Bean @Bean
public ResourceConfigCustomizer webEndpointRegistrar( public ResourceConfigCustomizer webEndpointRegistrar(
EndpointProvider<WebEndpointOperation> provider, WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes, EndpointMediaTypes endpointMediaTypes,
WebEndpointProperties webEndpointProperties) { WebEndpointProperties webEndpointProperties) {
return (resourceConfig) -> resourceConfig.registerResources( return (resourceConfig) -> resourceConfig.registerResources(
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources( new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
new EndpointMapping(webEndpointProperties.getBasePath()), new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints(), endpointMediaTypes))); endpointDiscoverer.discoverEndpoints(), endpointMediaTypes)));
}
@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
WebEndpointProperties webEndpointProperties) {
return new DefaultEndpointPathProvider(provider, webEndpointProperties);
} }
} }

@ -16,15 +16,13 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive; package org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.DefaultEndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; 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.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
@ -40,25 +38,18 @@ import org.springframework.context.annotation.Bean;
*/ */
@ManagementContextConfiguration @ManagementContextConfiguration
@ConditionalOnWebApplication(type = Type.REACTIVE) @ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnBean(WebAnnotationEndpointDiscoverer.class)
public class WebFluxEndpointManagementContextConfiguration { public class WebFluxEndpointManagementContextConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping( public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
EndpointProvider<WebEndpointOperation> provider, WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes, EndpointMediaTypes endpointMediaTypes,
WebEndpointProperties webEndpointProperties) { WebEndpointProperties webEndpointProperties) {
return new WebFluxEndpointHandlerMapping( return new WebFluxEndpointHandlerMapping(
new EndpointMapping(webEndpointProperties.getBasePath()), new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints(), endpointMediaTypes); endpointDiscoverer.discoverEndpoints(), endpointMediaTypes);
}
@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
WebEndpointProperties webEndpointProperties) {
return new DefaultEndpointPathProvider(provider, webEndpointProperties);
} }
} }

@ -27,7 +27,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties(prefix = "management.endpoints.cors") @ConfigurationProperties(prefix = "management.endpoints.web.cors")
public class CorsEndpointProperties { public class CorsEndpointProperties {
/** /**

@ -16,15 +16,11 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet; package org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.DefaultEndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; 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.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; 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.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -45,35 +41,27 @@ import org.springframework.web.servlet.DispatcherServlet;
* @author Phillip Webb * @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
*/ */
@ManagementContextConfiguration @ManagementContextConfiguration
@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class) @ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean(DispatcherServlet.class) @ConditionalOnBean({ DispatcherServlet.class, WebAnnotationEndpointDiscoverer.class })
@EnableConfigurationProperties({ CorsEndpointProperties.class, @EnableConfigurationProperties(CorsEndpointProperties.class)
WebEndpointProperties.class, ManagementServerProperties.class })
public class WebMvcEndpointManagementContextConfiguration { public class WebMvcEndpointManagementContextConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping( public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
EndpointProvider<WebEndpointOperation> provider, WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) { WebEndpointProperties webEndpointProperties) {
WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping( WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping(
new EndpointMapping(webEndpointProperties.getBasePath()), new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints(), endpointMediaTypes, endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
getCorsConfiguration(corsProperties)); getCorsConfiguration(corsProperties));
return handlerMapping; return handlerMapping;
} }
@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
WebEndpointProperties webEndpointProperties) {
return new DefaultEndpointPathProvider(provider, webEndpointProperties);
}
private CorsConfiguration getCorsConfiguration(CorsEndpointProperties properties) { private CorsConfiguration getCorsConfiguration(CorsEndpointProperties properties) {
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) { if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
return null; return null;

@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.env;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.env.EnvironmentEndpoint; import org.springframework.boot.actuate.env.EnvironmentEndpoint;
import org.springframework.boot.actuate.env.EnvironmentWebEndpointExtension; import org.springframework.boot.actuate.env.EnvironmentEndpointWebExtension;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -61,9 +61,9 @@ public class EnvironmentEndpointAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(EnvironmentEndpoint.class) @ConditionalOnBean(EnvironmentEndpoint.class)
public EnvironmentWebEndpointExtension environmentWebEndpointExtension( public EnvironmentEndpointWebExtension environmentWebEndpointExtension(
EnvironmentEndpoint environmentEndpoint) { EnvironmentEndpoint environmentEndpoint) {
return new EnvironmentWebEndpointExtension(environmentEndpoint); return new EnvironmentEndpointWebExtension(environmentEndpoint);
} }
} }

@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties("endpoints.env") @ConfigurationProperties("management.endpoint.env")
public class EnvironmentEndpointProperties { public class EnvironmentEndpointProperties {
/** /**

@ -25,15 +25,15 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfi
import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicatorFactory; import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicatorFactory;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint; 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.HealthIndicator;
import org.springframework.boot.actuate.health.HealthReactiveWebEndpointExtension;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HealthWebEndpointExtension;
import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveStatusEndpointWebExtension;
import org.springframework.boot.actuate.health.StatusEndpoint; import org.springframework.boot.actuate.health.StatusEndpoint;
import org.springframework.boot.actuate.health.StatusReactiveWebEndpointExtension; import org.springframework.boot.actuate.health.StatusEndpointWebExtension;
import org.springframework.boot.actuate.health.StatusWebEndpointExtension;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@ -84,9 +84,9 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class) @ConditionalOnBean(HealthEndpoint.class)
public HealthReactiveWebEndpointExtension healthWebEndpointExtension( public ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
HealthStatusHttpMapper healthStatusHttpMapper) { HealthStatusHttpMapper healthStatusHttpMapper) {
return new HealthReactiveWebEndpointExtension(this.reactiveHealthIndicator, return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator,
healthStatusHttpMapper); healthStatusHttpMapper);
} }
@ -94,9 +94,9 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(StatusEndpoint.class) @ConditionalOnBean(StatusEndpoint.class)
public StatusReactiveWebEndpointExtension statusWebEndpointExtension( public ReactiveStatusEndpointWebExtension reactiveStatusEndpointWebExtension(
HealthStatusHttpMapper healthStatusHttpMapper) { HealthStatusHttpMapper healthStatusHttpMapper) {
return new StatusReactiveWebEndpointExtension(this.reactiveHealthIndicator, return new ReactiveStatusEndpointWebExtension(this.reactiveHealthIndicator,
healthStatusHttpMapper); healthStatusHttpMapper);
} }
@ -110,18 +110,18 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class) @ConditionalOnBean(HealthEndpoint.class)
public HealthWebEndpointExtension healthWebEndpointExtension( public HealthEndpointWebExtension healthEndpointWebExtension(
HealthEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) { HealthEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) {
return new HealthWebEndpointExtension(delegate, healthStatusHttpMapper); return new HealthEndpointWebExtension(delegate, healthStatusHttpMapper);
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(StatusEndpoint.class) @ConditionalOnBean(StatusEndpoint.class)
public StatusWebEndpointExtension statusWebEndpointExtension( public StatusEndpointWebExtension statusEndpointWebExtension(
StatusEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) { StatusEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) {
return new StatusWebEndpointExtension(delegate, healthStatusHttpMapper); return new StatusEndpointWebExtension(delegate, healthStatusHttpMapper);
} }
} }

@ -71,10 +71,11 @@ public class LogFileWebEndpointManagementContextConfiguration {
return ConditionOutcome return ConditionOutcome
.match(message.found("logging.path").items(config)); .match(message.found("logging.path").items(config));
} }
config = environment.getProperty("endpoints.logfile.external-file"); config = environment.getProperty("management.endpoint.logfile.external-file");
if (StringUtils.hasText(config)) { if (StringUtils.hasText(config)) {
return ConditionOutcome.match( return ConditionOutcome
message.found("endpoints.logfile.external-file").items(config)); .match(message.found("management.endpoint.logfile.external-file")
.items(config));
} }
return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll()); return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll());
} }

@ -27,7 +27,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
@ConfigurationProperties(prefix = "endpoints.logfile") @ConfigurationProperties(prefix = "management.endpoint.logfile")
public class LogFileWebEndpointProperties { public class LogFileWebEndpointProperties {
/** /**

@ -1,6 +1,6 @@
{"properties": [ {"properties": [
{ {
"name": "endpoints.configprops.keys-to-sanitize", "name": "management.endpoint.configprops.keys-to-sanitize",
"defaultValue": [ "defaultValue": [
"password", "password",
"secret", "secret",
@ -11,25 +11,12 @@
] ]
}, },
{ {
"name": "endpoints.default.enabled", "name": "management.endpoints.enabled-by-default",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "Enable all endpoints by default.", "description": "Enable or disable all endpoints by default."
"defaultValue": true
},
{
"name": "endpoints.default.jmx.enabled",
"type": "java.lang.Boolean",
"description": "Enable all endpoints as JMX MBeans by default.",
"defaultValue": true
},
{
"name": "endpoints.default.web.enabled",
"type": "java.lang.Boolean",
"description": "Enable all endpoints as Web endpoints by default.",
"defaultValue": false
}, },
{ {
"name": "endpoints.env.keys-to-sanitize", "name": "management.endpoint.env.keys-to-sanitize",
"defaultValue": [ "defaultValue": [
"password", "password",
"secret", "secret",
@ -40,7 +27,7 @@
] ]
}, },
{ {
"name": "endpoints.trace.filter.enabled", "name": "management.endpoint.trace.filter.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "Enable the trace servlet filter.", "description": "Enable the trace servlet filter.",
"defaultValue": true, "defaultValue": true,
@ -321,7 +308,7 @@
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "Set whether credentials are supported. When not set, credentials are not supported.", "description": "Set whether credentials are supported. When not set, credentials are not supported.",
"deprecation": { "deprecation": {
"replacement": "management.endpoints.cors.allow-credentials", "replacement": "management.endpoints.web.cors.allow-credentials",
"level": "error" "level": "error"
} }
}, },
@ -330,7 +317,7 @@
"type": "java.util.List<java.lang.String>", "type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of headers to allow in a request. '*' allows all headers.", "description": "Comma-separated list of headers to allow in a request. '*' allows all headers.",
"deprecation": { "deprecation": {
"replacement": "management.endpoints.cors.allowed-headers", "replacement": "management.endpoints.web.cors.allowed-headers",
"level": "error" "level": "error"
} }
}, },
@ -339,7 +326,7 @@
"type": "java.util.List<java.lang.String>", "type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of methods to allow. '*' allows all methods. When not set,\n defaults to GET.", "description": "Comma-separated list of methods to allow. '*' allows all methods. When not set,\n defaults to GET.",
"deprecation": { "deprecation": {
"replacement": "management.endpoints.cors.allowed-methods", "replacement": "management.endpoints.web.cors.allowed-methods",
"level": "error" "level": "error"
} }
}, },
@ -348,7 +335,7 @@
"type": "java.util.List<java.lang.String>", "type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of origins to allow. '*' allows all origins. When not set,\n CORS support is disabled.", "description": "Comma-separated list of origins to allow. '*' allows all origins. When not set,\n CORS support is disabled.",
"deprecation": { "deprecation": {
"replacement": "management.endpoints.cors.allowed-origins", "replacement": "management.endpoints.web.cors.allowed-origins",
"level": "error" "level": "error"
} }
}, },
@ -357,7 +344,7 @@
"type": "java.util.List<java.lang.String>", "type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of headers to include in a response.", "description": "Comma-separated list of headers to include in a response.",
"deprecation": { "deprecation": {
"replacement": "management.endpoints.cors.exposed-headers", "replacement": "management.endpoints.web.cors.exposed-headers",
"level": "error" "level": "error"
} }
}, },
@ -367,7 +354,7 @@
"description": "How long, in seconds, the response from a pre-flight request can be cached by\n clients.", "description": "How long, in seconds, the response from a pre-flight request can be cached by\n clients.",
"defaultValue": 1800, "defaultValue": 1800,
"deprecation": { "deprecation": {
"replacement": "management.endpoints.cors.max-age", "replacement": "management.endpoints.web.cors.max-age",
"level": "error" "level": "error"
} }
}, },
@ -1202,7 +1189,7 @@
} }
],"hints": [ ],"hints": [
{ {
"name": "management.endpoints.cors.allowed-headers", "name": "management.endpoints.web.cors.allowed-headers",
"values": [ "values": [
{ {
"value": "*" "value": "*"
@ -1215,7 +1202,7 @@
] ]
}, },
{ {
"name": "management.endpoints.cors.allowed-methods", "name": "management.endpoints.web.cors.allowed-methods",
"values": [ "values": [
{ {
"value": "*" "value": "*"
@ -1228,7 +1215,7 @@
] ]
}, },
{ {
"name": "management.endpoints.cors.allowed-origins", "name": "management.endpoints.web.cors.allowed-origins",
"values": [ "values": [
{ {
"value": "*" "value": "*"

@ -13,6 +13,7 @@ org.springframework.boot.actuate.autoconfigure.couchbase.CouchbaseHealthIndicato
org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,\

@ -19,8 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.audit;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint; import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
import org.springframework.boot.actuate.audit.AuditEventsJmxEndpointExtension; import org.springframework.boot.actuate.audit.AuditEventsJmxEndpointExtension;
import org.springframework.boot.actuate.audit.AuditEventsWebEndpointExtension;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -54,17 +54,18 @@ public class AuditEventsEndpointAutoConfigurationTests {
@Test @Test
public void runShouldHaveWebExtensionBean() { public void runShouldHaveWebExtensionBean() {
this.contextRunner.run((context) -> assertThat(context) this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(AuditEventsWebEndpointExtension.class)); .hasSingleBean(AuditEventsEndpointWebExtension.class));
} }
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointOrExtensionBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointOrExtensionBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.auditevents.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.auditevents.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(AuditEventsEndpoint.class) .doesNotHaveBean(AuditEventsEndpoint.class)
.doesNotHaveBean(AuditEventsJmxEndpointExtension.class) .doesNotHaveBean(AuditEventsJmxEndpointExtension.class)
.doesNotHaveBean(AuditEventsWebEndpointExtension.class)); .doesNotHaveBean(AuditEventsEndpointWebExtension.class));
} }
} }

@ -44,8 +44,9 @@ public class BeansEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.beans.enabled:false").run( this.contextRunner.withPropertyValues("management.endpoint.beans.enabled:false")
(context) -> assertThat(context).doesNotHaveBean(BeansEndpoint.class)); .run((context) -> assertThat(context)
.doesNotHaveBean(BeansEndpoint.class));
} }
} }

@ -28,15 +28,16 @@ import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.actuate.endpoint.ParameterMapper; import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper; import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
@ -217,7 +218,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
@Bean @Bean
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
WebAnnotationEndpointDiscoverer webEndpointDiscoverer, EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
EndpointMediaTypes endpointMediaTypes, EndpointMediaTypes endpointMediaTypes,
ReactiveCloudFoundrySecurityInterceptor interceptor) { ReactiveCloudFoundrySecurityInterceptor interceptor) {
CorsConfiguration corsConfiguration = new CorsConfiguration(); CorsConfiguration corsConfiguration = new CorsConfiguration();
@ -236,8 +237,8 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
ParameterMapper parameterMapper = new ConversionServiceParameterMapper( ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
DefaultConversionService.getSharedInstance()); DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext, return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0), parameterMapper, endpointMediaTypes,
endpointMediaTypes, EndpointPathResolver.useEndpointId()); EndpointPathResolver.useEndpointId(), null, null);
} }
@Bean @Bean

@ -18,18 +18,20 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@ -190,16 +192,17 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
} }
@Test @Test
public void allEndpointsAvailableUnderCloudFoundryWithoutEnablingWeb() public void allEndpointsAvailableUnderCloudFoundryWithoutEnablingWebInclues()
throws Exception { throws Exception {
setupContextWithCloudEnabled(); setupContextWithCloudEnabled();
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(); CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
List<EndpointInfo<WebEndpointOperation>> endpoints = (List<EndpointInfo<WebEndpointOperation>>) handlerMapping List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
.getEndpoints(); .getEndpoints();
assertThat(endpoints.size()).isEqualTo(1); List<String> endpointIds = endpoints.stream().map(EndpointInfo::getId)
assertThat(endpoints.get(0).getId()).isEqualTo("test"); .collect(Collectors.toList());
assertThat(endpointIds).contains("test");
} }
@Test @Test
@ -208,12 +211,14 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping(); CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
List<EndpointInfo<WebEndpointOperation>> endpoints = (List<EndpointInfo<WebEndpointOperation>>) handlerMapping List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
.getEndpoints(); .getEndpoints();
assertThat(endpoints.size()).isEqualTo(1); EndpointInfo<WebOperation> endpoint = endpoints.stream()
assertThat(endpoints.get(0).getOperations()).hasSize(1); .filter((candidate) -> "test".equals(candidate.getId())).findFirst()
assertThat(endpoints.get(0).getOperations().iterator().next() .get();
.getRequestPredicate().getPath()).isEqualTo("test"); assertThat(endpoint.getOperations()).hasSize(1);
WebOperation operation = endpoint.getOperations().iterator().next();
assertThat(operation.getRequestPredicate().getPath()).isEqualTo("test");
} }
private void setupContextWithCloudEnabled() { private void setupContextWithCloudEnabled() {
@ -231,6 +236,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, WebClientCustomizerConfig.class, WebClientAutoConfiguration.class,
ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ReactiveCloudFoundryActuatorAutoConfiguration.class); ReactiveCloudFoundryActuatorAutoConfiguration.class);
} }

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.junit.After; import org.junit.After;
@ -24,13 +25,14 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@ -79,7 +81,7 @@ public class CloudFoundryActuatorAutoConfigurationTests {
RestTemplateAutoConfiguration.class, RestTemplateAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
CloudFoundryActuatorAutoConfiguration.class); CloudFoundryActuatorAutoConfiguration.class);
} }
@ -208,30 +210,34 @@ public class CloudFoundryActuatorAutoConfigurationTests {
} }
@Test @Test
public void allEndpointsAvailableUnderCloudFoundryWithoutEnablingWeb() public void allEndpointsAvailableUnderCloudFoundryWithoutExposeAllOnWeb()
throws Exception { throws Exception {
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping(); CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
List<EndpointInfo<WebEndpointOperation>> endpoints = (List<EndpointInfo<WebEndpointOperation>>) handlerMapping List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
.getEndpoints(); .getEndpoints();
assertThat(endpoints.size()).isEqualTo(1); assertThat(endpoints.stream()
assertThat(endpoints.get(0).getId()).isEqualTo("test"); .filter((candidate) -> "test".equals(candidate.getId())).findFirst())
.isNotEmpty();
} }
@Test @Test
public void endpointPathCustomizationIsNotApplied() throws Exception { public void endpointPathCustomizationIsNotApplied() throws Exception {
TestPropertyValues.of("endpoints.test.web.path=another/custom") TestPropertyValues.of("management.endpoints.web.path-mapping.test=custom")
.applyTo(this.context); .applyTo(this.context);
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping(); CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
List<EndpointInfo<WebEndpointOperation>> endpoints = (List<EndpointInfo<WebEndpointOperation>>) handlerMapping List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
.getEndpoints(); .getEndpoints();
assertThat(endpoints.size()).isEqualTo(1); EndpointInfo<WebOperation> endpoint = endpoints.stream()
assertThat(endpoints.get(0).getOperations()).hasSize(1); .filter((candidate) -> "test".equals(candidate.getId())).findFirst()
assertThat(endpoints.get(0).getOperations().iterator().next() .get();
.getRequestPredicate().getPath()).isEqualTo("test"); Collection<WebOperation> operations = endpoint.getOperations();
assertThat(operations).hasSize(1);
assertThat(operations.iterator().next().getRequestPredicate().getPath())
.isEqualTo("test");
} }
private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping() { private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping() {

@ -27,15 +27,16 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.actuate.endpoint.ParameterMapper; import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper; import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -203,7 +204,7 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
@Bean @Bean
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
WebAnnotationEndpointDiscoverer webEndpointDiscoverer, EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
EndpointMediaTypes endpointMediaTypes, EndpointMediaTypes endpointMediaTypes,
CloudFoundrySecurityInterceptor interceptor) { CloudFoundrySecurityInterceptor interceptor) {
CorsConfiguration corsConfiguration = new CorsConfiguration(); CorsConfiguration corsConfiguration = new CorsConfiguration();
@ -222,8 +223,8 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
ParameterMapper parameterMapper = new ConversionServiceParameterMapper( ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
DefaultConversionService.getSharedInstance()); DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext, return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0), parameterMapper, endpointMediaTypes,
endpointMediaTypes, EndpointPathResolver.useEndpointId()); EndpointPathResolver.useEndpointId(), null, null);
} }
@Bean @Bean

@ -43,7 +43,8 @@ public class ConditionsReportEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.conditions.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.conditions.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(ConditionsReportEndpoint.class)); .doesNotHaveBean(ConditionsReportEndpoint.class));
} }

@ -37,15 +37,18 @@ public class ShutdownEndpointAutoConfigurationTests {
@Test @Test
public void runShouldHaveEndpointBean() { public void runShouldHaveEndpointBean() {
this.contextRunner.withPropertyValues("endpoints.shutdown.enabled:true").run( this.contextRunner.withPropertyValues("management.endpoint.shutdown.enabled:true")
(context) -> assertThat(context).hasSingleBean(ShutdownEndpoint.class)); .run((context) -> assertThat(context)
.hasSingleBean(ShutdownEndpoint.class));
} }
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.shutdown.enabled:false").run( this.contextRunner
(context) -> assertThat(context).doesNotHaveBean(ShutdownEndpoint.class)); .withPropertyValues("management.endpoint.shutdown.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(ShutdownEndpoint.class));
} }
} }

@ -53,7 +53,8 @@ public class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.configprops.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.configprops.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(ConfigurationPropertiesReportEndpoint.class)); .doesNotHaveBean(ConfigurationPropertiesReportEndpoint.class));
} }
@ -62,7 +63,7 @@ public class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception { public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception {
this.contextRunner.withUserConfiguration(Config.class) this.contextRunner.withUserConfiguration(Config.class)
.withPropertyValues( .withPropertyValues(
"endpoints.configprops.keys-to-sanitize: .*pass.*, property") "management.endpoint.configprops.keys-to-sanitize: .*pass.*, property")
.run(validateTestProperties("******", "******")); .run(validateTestProperties("******", "******"));
} }

@ -1,263 +0,0 @@
/*
* 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.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EndpointEnablementProvider}.
*
* @author Stephane Nicoll
*/
public class EndpointEnablementProviderTests {
@Test
public void defaultEnablementDisabled() {
EndpointEnablement enablement = getEndpointEnablement("foo",
DefaultEnablement.DISABLED);
validate(enablement, false, "endpoint 'foo' is disabled by default");
}
@Test
public void defaultEnablementDisabledWithGeneralEnablement() {
EndpointEnablement enablement = getEndpointEnablement("foo",
DefaultEnablement.DISABLED, "endpoints.default.enabled=true");
validate(enablement, false, "endpoint 'foo' is disabled by default");
}
@Test
public void defaultEnablementDisabledWithGeneralTechEnablement() {
EndpointEnablement enablement = getEndpointEnablement("foo",
DefaultEnablement.DISABLED, EndpointExposure.WEB,
"endpoints.default.web.enabled=true");
validate(enablement, false, "endpoint 'foo' (web) is disabled by default");
}
@Test
public void defaultEnablementDisabledWithOverride() {
EndpointEnablement enablement = getEndpointEnablement("foo",
DefaultEnablement.DISABLED, "endpoints.foo.enabled=true");
validate(enablement, true, "found property endpoints.foo.enabled");
}
@Test
public void defaultEnablementDisabledWithTechOverride() {
EndpointEnablement enablement = getEndpointEnablement("foo",
DefaultEnablement.DISABLED, EndpointExposure.WEB,
"endpoints.foo.web.enabled=true");
validate(enablement, true, "found property endpoints.foo.web.enabled");
}
@Test
public void defaultEnablementDisabledWithIrrelevantTechOverride() {
EndpointEnablement enablement = getEndpointEnablement("foo",
DefaultEnablement.DISABLED, EndpointExposure.WEB,
"endpoints.foo.jmx.enabled=true");
validate(enablement, false, "endpoint 'foo' (web) is disabled by default");
}
@Test
public void defaultEnablementEnabled() {
EndpointEnablement enablement = getEndpointEnablement("bar",
DefaultEnablement.ENABLED);
validate(enablement, true, "endpoint 'bar' is enabled by default");
}
@Test
public void defaultEnablementEnabledWithGeneralDisablement() {
EndpointEnablement enablement = getEndpointEnablement("bar",
DefaultEnablement.ENABLED, "endpoints.default.enabled=false");
validate(enablement, true, "endpoint 'bar' is enabled by default");
}
@Test
public void defaultEnablementEnabledWithGeneralTechDisablement() {
EndpointEnablement enablement = getEndpointEnablement("bar",
DefaultEnablement.ENABLED, EndpointExposure.JMX,
"endpoints.default.jmx.enabled=false");
validate(enablement, true, "endpoint 'bar' (jmx) is enabled by default");
}
@Test
public void defaultEnablementEnabledWithOverride() {
EndpointEnablement enablement = getEndpointEnablement("bar",
DefaultEnablement.ENABLED, "endpoints.bar.enabled=false");
validate(enablement, false, "found property endpoints.bar.enabled");
}
@Test
public void defaultEnablementEnabledWithTechOverride() {
EndpointEnablement enablement = getEndpointEnablement("bar",
DefaultEnablement.ENABLED, EndpointExposure.JMX,
"endpoints.bar.jmx.enabled=false");
validate(enablement, false, "found property endpoints.bar.jmx.enabled");
}
@Test
public void defaultEnablementEnabledWithIrrelevantTechOverride() {
EndpointEnablement enablement = getEndpointEnablement("bar",
DefaultEnablement.ENABLED, EndpointExposure.JMX,
"endpoints.bar.web.enabled=false");
validate(enablement, true, "endpoint 'bar' (jmx) is enabled by default");
}
@Test
public void defaultEnablementNeutral() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL);
validate(enablement, true, "endpoint 'biz' is enabled (default)");
}
@Test
public void defaultEnablementNeutralWeb() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.WEB);
validate(enablement, false, "endpoint 'default' (web) is disabled by default");
}
@Test
public void defaultEnablementNeutralJmx() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX);
validate(enablement, true,
"endpoint 'biz' (jmx) is enabled (default for jmx endpoints)");
}
@Test
public void defaultEnablementNeutralWithGeneralDisablement() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, "endpoints.default.enabled=false");
validate(enablement, false, "found property endpoints.default.enabled");
}
@Test
public void defaultEnablementNeutralJmxWithTechDisablement() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.default.jmx.enabled=false");
validate(enablement, false, "found property endpoints.default.jmx.enabled");
}
@Test
public void defaultEnablementNeutralTechTakesPrecedence() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.default.enabled=true", "endpoints.default.jmx.enabled=false");
validate(enablement, false, "found property endpoints.default.jmx.enabled");
}
@Test
public void defaultEnablementNeutralWebWithTechEnablement() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.WEB,
"endpoints.default.web.enabled=true");
validate(enablement, true, "found property endpoints.default.web.enabled");
}
@Test
public void defaultEnablementNeutralJmxWithUnrelatedTechDisablement() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.default.web.enabled=false");
validate(enablement, true,
"endpoint 'biz' (jmx) is enabled (default for jmx endpoints)");
}
@Test
public void defaultEnablementNeutralWithOverride() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, "endpoints.biz.enabled=false");
validate(enablement, false, "found property endpoints.biz.enabled");
}
@Test
public void defaultEnablementNeutralWebWithOverride() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.WEB,
"endpoints.biz.web.enabled=true");
validate(enablement, true, "found property endpoints.biz.web.enabled");
}
@Test
public void defaultEnablementNeutralJmxWithOverride() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.biz.jmx.enabled=false");
validate(enablement, false, "found property endpoints.biz.jmx.enabled");
}
@Test
public void defaultEnablementNeutralTechTakesPrecedenceOnEverything() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.default.enabled=false", "endpoints.default.jmx.enabled=false",
"endpoints.biz.enabled=false", "endpoints.biz.jmx.enabled=true");
validate(enablement, true, "found property endpoints.biz.jmx.enabled");
}
@Test
public void defaultEnablementNeutralSpecificTakesPrecedenceOnDefaults() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.default.enabled=false", "endpoints.default.jmx.enabled=false",
"endpoints.biz.enabled=true");
validate(enablement, true, "found property endpoints.biz.enabled");
}
@Test
public void defaultEnablementNeutralDefaultTechTakesPrecedenceOnGeneralDefault() {
EndpointEnablement enablement = getEndpointEnablement("biz",
DefaultEnablement.NEUTRAL, EndpointExposure.JMX,
"endpoints.default.enabled=false", "endpoints.default.jmx.enabled=true");
validate(enablement, true, "found property endpoints.default.jmx.enabled");
}
private EndpointEnablement getEndpointEnablement(String id,
DefaultEnablement defaultEnablement, String... environment) {
return getEndpointEnablement(id, defaultEnablement, null, environment);
}
private EndpointEnablement getEndpointEnablement(String id,
DefaultEnablement defaultEnablement, EndpointExposure exposure,
String... environment) {
MockEnvironment env = new MockEnvironment();
TestPropertyValues.of(environment).applyTo(env);
EndpointEnablementProvider provider = new EndpointEnablementProvider(env);
if (exposure != null) {
return provider.getEndpointEnablement(id, defaultEnablement, exposure);
}
return provider.getEndpointEnablement(id, defaultEnablement);
}
private void validate(EndpointEnablement enablement, boolean enabled,
String... messages) {
assertThat(enablement).isNotNull();
assertThat(enablement.isEnabled()).isEqualTo(enabled);
if (!ObjectUtils.isEmpty(messages)) {
assertThat(enablement.getReason()).contains(messages);
}
}
}

@ -16,39 +16,39 @@
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.util.function.Function;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link DefaultCachingConfigurationFactory}. * Tests for {@link EndpointIdTimeToLivePropertyFunction}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
*/ */
public class DefaultCachingConfigurationFactoryTests { public class EndpointIdTimeToLivePropertyFunctionTests {
private final MockEnvironment environment = new MockEnvironment(); private final MockEnvironment environment = new MockEnvironment();
private final CachingConfigurationFactory factory = new DefaultCachingConfigurationFactory( private final Function<String, Long> timeToLive = new EndpointIdTimeToLivePropertyFunction(
this.environment); this.environment);
@Test @Test
public void defaultConfiguration() { public void defaultConfiguration() {
CachingConfiguration configuration = this.factory.getCachingConfiguration("test"); Long result = this.timeToLive.apply("test");
assertThat(configuration).isNotNull(); assertThat(result).isNull();
assertThat(configuration.getTimeToLive()).isEqualTo(0);
} }
@Test @Test
public void userConfiguration() { public void userConfiguration() {
this.environment.setProperty("endpoints.test.cache.time-to-live", "500"); this.environment.setProperty("management.endpoint.test.cache.time-to-live",
CachingConfiguration configuration = this.factory.getCachingConfiguration("test"); "500");
assertThat(configuration).isNotNull(); Long result = this.timeToLive.apply("test");
assertThat(configuration.getTimeToLive()).isEqualTo(500); assertThat(result).isEqualTo(500L);
} }
} }

@ -0,0 +1,183 @@
/*
* 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 java.util.Collections;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ExposeExcludePropertyEndpointFilter}.
*
* @author Phillip Webb
*/
public class ExposeExcludePropertyEndpointFilterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private MockEnvironment environment = new MockEnvironment();
private EndpointFilter<Operation> filter;
@Mock
private TestEndpointDiscoverer discoverer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void createWhenDiscovererTypeIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Discoverer Type must not be null");
new ExposeExcludePropertyEndpointFilter<>(null, this.environment, "foo");
}
@Test
public void createWhenEnvironmentIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Environment must not be null");
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class, null,
"foo");
}
@Test
public void createWhenPrefixIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Prefix must not be empty");
new ExposeExcludePropertyEndpointFilter<Operation>(TestEndpointDiscoverer.class,
this.environment, null);
}
@Test
public void createWhenPrefixIsEmptyShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Prefix must not be empty");
new ExposeExcludePropertyEndpointFilter<Operation>(TestEndpointDiscoverer.class,
this.environment, "");
}
@Test
public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndInDefaultShouldMatch()
throws Exception {
setupFilter("", "");
assertThat(match("def")).isTrue();
}
@Test
public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndNotInDefaultShouldNotMatch()
throws Exception {
setupFilter("", "");
assertThat(match("bar")).isFalse();
}
@Test
public void matchWhenExposeMatchesAndExcludeIsEmptyShouldMatch() throws Exception {
setupFilter("bar", "");
assertThat(match("bar")).isTrue();
}
@Test
public void matchWhenExposeDoesNotMatchAndExcludeIsEmptyShouldNotMatch()
throws Exception {
setupFilter("bar", "");
assertThat(match("baz")).isFalse();
}
@Test
public void matchWhenExposeMatchesAndExcludeMatchesShouldNotMatch() throws Exception {
setupFilter("bar,baz", "baz");
assertThat(match("baz")).isFalse();
}
@Test
public void matchWhenExposeMatchesAndExcludeDoesNotMatchShouldMatch()
throws Exception {
setupFilter("bar,baz", "buz");
assertThat(match("baz")).isTrue();
}
@Test
public void matchWhenExposeMatchesWithDifferentCaseShouldMatch() throws Exception {
setupFilter("bar", "");
assertThat(match("bAr")).isTrue();
}
@Test
public void matchWhenDicovererDoesNotMatchShouldMatch() throws Exception {
this.environment.setProperty("foo.expose", "bar");
this.environment.setProperty("foo.exclude", "");
this.filter = new ExposeExcludePropertyEndpointFilter<>(
DifferentTestEndpointDiscoverer.class, this.environment, "foo");
assertThat(match("baz")).isTrue();
}
@Test
public void matchWhenIncludeIsAsteriskShouldMatchAll() throws Exception {
setupFilter("*", "buz");
assertThat(match("bar")).isTrue();
assertThat(match("baz")).isTrue();
assertThat(match("buz")).isFalse();
}
@Test
public void matchWhenExcludeIsAsteriskShouldMatchNone() throws Exception {
setupFilter("bar,baz,buz", "*");
assertThat(match("bar")).isFalse();
assertThat(match("baz")).isFalse();
assertThat(match("buz")).isFalse();
}
private void setupFilter(String expose, String exclude) {
this.environment.setProperty("foo.expose", expose);
this.environment.setProperty("foo.exclude", exclude);
this.filter = new ExposeExcludePropertyEndpointFilter<>(
TestEndpointDiscoverer.class, this.environment, "foo", "def");
}
private boolean match(String id) {
EndpointInfo<Operation> info = new EndpointInfo<>(id, true,
Collections.emptyList());
return this.filter.match(info, this.discoverer);
}
private abstract static class TestEndpointDiscoverer
implements EndpointDiscoverer<Operation> {
}
private abstract static class DifferentTestEndpointDiscoverer
implements EndpointDiscoverer<Operation> {
}
}

@ -18,11 +18,12 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.DefaultEnablement; import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExtension; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -40,324 +41,158 @@ public class ConditionalOnEnabledEndpointTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test @Test
public void enabledByDefault() { public void outcomeWhenEndpointEnabledPropertyIsTrueShouldMatch() throws Exception {
this.contextRunner.withUserConfiguration(FooConfig.class) this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=true")
.withUserConfiguration(
FooEndpointEnabledByDefaultFalseConfiguration.class)
.run((context) -> assertThat(context).hasBean("foo")); .run((context) -> assertThat(context).hasBean("foo"));
} }
@Test @Test
public void disabledViaSpecificProperty() { public void outcomeWhenEndpointEnabledPropertyIsFalseShouldNotMatch()
this.contextRunner.withUserConfiguration(FooConfig.class) throws Exception {
.withPropertyValues("endpoints.foo.enabled=false") this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=false")
.withUserConfiguration(FooEndpointEnabledByDefaultTrueConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo")); .run((context) -> assertThat(context).doesNotHaveBean("foo"));
} }
@Test @Test
public void disabledViaGeneralProperty() { public void outcomeWhenNoEndpointPropertyAndUserDefinedDefaultIsTrueShouldMatch()
this.contextRunner.withUserConfiguration(FooConfig.class) throws Exception {
.withPropertyValues("endpoints.default.enabled=false") this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean("foo")); .withPropertyValues("management.endpoints.enabled-by-default=true")
} .withUserConfiguration(
FooEndpointEnabledByDefaultFalseConfiguration.class)
@Test
public void enabledOverrideViaSpecificProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.foo.enabled=true")
.run((context) -> assertThat(context).hasBean("foo"));
}
@Test
public void enabledOverrideViaSpecificWebProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.foo.enabled=false",
"endpoints.foo.web.enabled=true")
.run((context) -> assertThat(context).hasBean("foo"));
}
@Test
public void enabledOverrideViaSpecificJmxProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.foo.enabled=false",
"endpoints.foo.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("foo"));
}
@Test
public void enabledOverrideViaSpecificAnyProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.foo.enabled=false",
"endpoints.foo.web.enabled=false",
"endpoints.foo.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("foo")); .run((context) -> assertThat(context).hasBean("foo"));
} }
@Test @Test
public void enabledOverrideViaGeneralWebProperty() { public void outcomeWhenNoEndpointPropertyAndUserDefinedDefaultIsFalseShouldNotMatch()
this.contextRunner.withUserConfiguration(FooConfig.class) throws Exception {
.withPropertyValues("endpoints.default.enabled=false", this.contextRunner
"endpoints.default.web.enabled=true") .withPropertyValues("management.endpoints.enabled-by-default=false")
.run((context) -> assertThat(context).hasBean("foo")); .withUserConfiguration(FooEndpointEnabledByDefaultTrueConfiguration.class)
} .run((context) -> assertThat(context).doesNotHaveBean("foo"));
@Test
public void enabledOverrideViaGeneralJmxProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.default.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("foo"));
} }
@Test @Test
public void enabledOverrideViaGeneralAnyProperty() { public void outcomeWhenNoPropertiesAndAnnotationIsEnabledByDefaultShouldMatch()
this.contextRunner.withUserConfiguration(FooConfig.class) throws Exception {
.withPropertyValues("endpoints.default.enabled=false", this.contextRunner
"endpoints.default.web.enabled=false", .withUserConfiguration(FooEndpointEnabledByDefaultTrueConfiguration.class)
"endpoints.default.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("foo")); .run((context) -> assertThat(context).hasBean("foo"));
} }
@Test @Test
public void disabledEvenWithEnabledGeneralProperties() { public void outcomeWhenNoPropertiesAndAnnotationIsNotEnabledByDefaultShouldNotMatch()
this.contextRunner.withUserConfiguration(FooConfig.class).withPropertyValues( throws Exception {
"endpoints.default.enabled=true", "endpoints.default.web.enabled=true", this.contextRunner
"endpoints.default.jmx.enabled=true", "endpoints.foo.enabled=false") .withUserConfiguration(
FooEndpointEnabledByDefaultFalseConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo")); .run((context) -> assertThat(context).doesNotHaveBean("foo"));
} }
@Test @Test
public void disabledByDefaultWithAnnotationFlag() { public void outcomeWhenNoPropertiesAndExtensionAnnotationIsEnabledByDefaultShouldMatch()
this.contextRunner.withUserConfiguration(BarConfig.class) throws Exception {
.run((context) -> assertThat(context).doesNotHaveBean("bar")); this.contextRunner
} .withUserConfiguration(
FooEndpointAndExtensionEnabledByDefaultTrueConfiguration.class)
@Test .run((context) -> assertThat(context).hasBean("foo").hasBean("fooExt"));
public void disabledByDefaultWithAnnotationFlagEvenWithGeneralProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.default.enabled=true")
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test
public void disabledByDefaultWithAnnotationFlagEvenWithGeneralWebProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.default.web.enabled=true")
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test
public void disabledByDefaultWithAnnotationFlagEvenWithGeneralJmxProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.default.jmx.enabled=true")
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test
public void enabledOverrideWithAndAnnotationFlagAndSpecificProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.bar.enabled=true")
.run((context) -> assertThat(context).hasBean("bar"));
}
@Test
public void enabledOverrideWithAndAnnotationFlagAndSpecificWebProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.bar.web.enabled=true")
.run((context) -> assertThat(context).hasBean("bar"));
}
@Test
public void enabledOverrideWithAndAnnotationFlagAndSpecificJmxProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.bar.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("bar"));
}
@Test
public void enabledOverrideWithAndAnnotationFlagAndAnyProperty() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.withPropertyValues("endpoints.bar.web.enabled=false",
"endpoints.bar.jmx.enabled=true")
.run((context) -> assertThat(context).hasBean("bar"));
} }
@Test @Test
public void enabledOnlyWebByDefault() { public void outcomeWhenNoPropertiesAndExtensionAnnotationIsNotEnabledByDefaultShouldNotMatch()
this.contextRunner.withUserConfiguration(OnlyWebConfig.class) throws Exception {
.withPropertyValues("endpoints.default.web.enabled=true") this.contextRunner
.run((context) -> assertThat(context).hasBean("onlyweb")); .withUserConfiguration(
FooEndpointAndExtensionEnabledByDefaultFalseConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo")
.doesNotHaveBean("fooExt"));
} }
@Test @Endpoint(id = "foo", enableByDefault = true)
public void disabledOnlyWebViaEndpointProperty() { static class FooEndpointEnabledByDefaultTrue {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.onlyweb.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
}
@Test
public void disabledOnlyWebViaSpecificTechProperty() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.onlyweb.web.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
} }
@Test @Endpoint(id = "foo", enableByDefault = false)
public void enableOverridesOnlyWebViaGeneralJmxPropertyHasNoEffect() { static class FooEndpointEnabledByDefaultFalse {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.default.jmx.enabled=true")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
}
@Test
public void enableOverridesOnlyWebViaSpecificJmxPropertyHasNoEffect() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.onlyweb.jmx.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
} }
@Test @EndpointExtension(endpoint = FooEndpointEnabledByDefaultTrue.class, filter = TestFilter.class)
public void enableOverridesOnlyWebViaSpecificWebProperty() { static class FooEndpointExtensionEnabledByDefaultTrue {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.onlyweb.web.enabled=true")
.run((context) -> assertThat(context).hasBean("onlyweb"));
}
@Test
public void disabledOnlyWebEvenWithEnabledGeneralProperties() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class).withPropertyValues(
"endpoints.default.enabled=true", "endpoints.default.web.enabled=true",
"endpoints.onlyweb.enabled=true", "endpoints.onlyweb.web.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("foo"));
} }
@Test @EndpointExtension(endpoint = FooEndpointEnabledByDefaultFalse.class, filter = TestFilter.class)
public void contextFailIfEndpointTypeIsNotDetected() { static class FooEndpointExtensionEnabledByDefaultFalse {
this.contextRunner.withUserConfiguration(NonEndpointBeanConfig.class)
.run((context) -> assertThat(context).hasFailed());
}
@Test
public void webExtensionWithEnabledByDefaultEndpoint() {
this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class)
.run((context) -> assertThat(context)
.hasSingleBean(FooWebEndpointExtension.class));
} }
@Test static class TestFilter implements EndpointFilter<Operation> {
public void webExtensionWithEnabledByDefaultEndpointCanBeDisabled() {
this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class)
.withPropertyValues("endpoints.foo.enabled=false")
.run((context) -> assertThat(context)
.doesNotHaveBean(FooWebEndpointExtension.class));
}
@Test @Override
public void jmxExtensionWithEnabledByDefaultEndpoint() { public boolean match(EndpointInfo<Operation> info,
this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class) EndpointDiscoverer<Operation> discoverer) {
.run((context) -> assertThat(context) return true;
.hasSingleBean(FooJmxEndpointExtension.class)); }
}
@Test
public void jmxExtensionWithEnabledByDefaultEndpointCanBeDisabled() {
this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class)
.withPropertyValues("endpoints.foo.enabled=false")
.run((context) -> assertThat(context)
.doesNotHaveBean(FooJmxEndpointExtension.class));
} }
@Configuration @Configuration
static class FooConfig { static class FooEndpointEnabledByDefaultTrueConfiguration {
@Bean @Bean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
public FooEndpoint foo() { public FooEndpointEnabledByDefaultTrue foo() {
return new FooEndpoint(); return new FooEndpointEnabledByDefaultTrue();
} }
} }
@Endpoint(id = "foo")
static class FooEndpoint {
}
@Configuration @Configuration
static class BarConfig { static class FooEndpointEnabledByDefaultFalseConfiguration {
@Bean @Bean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
public BarEndpoint bar() { public FooEndpointEnabledByDefaultFalse foo() {
return new BarEndpoint(); return new FooEndpointEnabledByDefaultFalse();
} }
} }
@Endpoint(id = "bar", exposure = { EndpointExposure.WEB,
EndpointExposure.JMX }, defaultEnablement = DefaultEnablement.DISABLED)
static class BarEndpoint {
}
@Configuration @Configuration
static class OnlyWebConfig { static class FooEndpointAndExtensionEnabledByDefaultTrueConfiguration {
@Bean(name = "onlyweb") @Bean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
public OnlyWebEndpoint onlyWeb() { public FooEndpointEnabledByDefaultTrue foo() {
return new OnlyWebEndpoint(); return new FooEndpointEnabledByDefaultTrue();
} }
}
@Endpoint(id = "onlyweb", exposure = EndpointExposure.WEB)
static class OnlyWebEndpoint {
}
@Configuration
static class NonEndpointBeanConfig {
@Bean @Bean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
public String foo() { public FooEndpointExtensionEnabledByDefaultTrue fooExt() {
return "endpoint type cannot be detected"; return new FooEndpointExtensionEnabledByDefaultTrue();
} }
} }
@JmxEndpointExtension(endpoint = FooEndpoint.class)
static class FooJmxEndpointExtension {
}
@Configuration @Configuration
static class FooJmxExtensionConfig { static class FooEndpointAndExtensionEnabledByDefaultFalseConfiguration {
@Bean @Bean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
FooJmxEndpointExtension fooJmxEndpointExtension() { public FooEndpointEnabledByDefaultFalse foo() {
return new FooJmxEndpointExtension(); return new FooEndpointEnabledByDefaultFalse();
} }
}
@WebEndpointExtension(endpoint = FooEndpoint.class)
static class FooWebEndpointExtension {
}
@Configuration
static class FooWebExtensionConfig {
@Bean @Bean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
FooWebEndpointExtension fooJmxEndpointExtension() { public FooEndpointExtensionEnabledByDefaultFalse fooExt() {
return new FooWebEndpointExtension(); return new FooEndpointExtensionEnabledByDefaultFalse();
} }
} }

@ -41,7 +41,7 @@ public class DefaultEndpointObjectNameFactoryTests {
private final MockEnvironment environment = new MockEnvironment(); private final MockEnvironment environment = new MockEnvironment();
private final JmxEndpointExporterProperties properties = new JmxEndpointExporterProperties( private final JmxEndpointProperties properties = new JmxEndpointProperties(
this.environment); this.environment);
private final MBeanServer mBeanServer = mock(MBeanServer.class); private final MBeanServer mBeanServer = mock(MBeanServer.class);

@ -25,10 +25,9 @@ import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider; import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -41,7 +40,7 @@ import static org.mockito.BDDMockito.given;
public class DefaultEndpointPathProviderTests { public class DefaultEndpointPathProviderTests {
@Mock @Mock
private EndpointProvider<WebEndpointOperation> endpointProvider; private EndpointDiscoverer<WebOperation> discoverer;
@Before @Before
public void setup() { public void setup() {
@ -81,16 +80,13 @@ public class DefaultEndpointPathProviderTests {
} }
private DefaultEndpointPathProvider createProvider(String contextPath) { private DefaultEndpointPathProvider createProvider(String contextPath) {
Collection<EndpointInfo<WebEndpointOperation>> endpoints = new ArrayList<>(); Collection<EndpointInfo<WebOperation>> endpoints = new ArrayList<>();
endpoints.add(new EndpointInfo<>("foo", DefaultEnablement.ENABLED, endpoints.add(new EndpointInfo<>("foo", true, Collections.emptyList()));
Collections.emptyList())); endpoints.add(new EndpointInfo<>("bar", true, Collections.emptyList()));
endpoints.add(new EndpointInfo<>("bar", DefaultEnablement.ENABLED, given(this.discoverer.discoverEndpoints()).willReturn(endpoints);
Collections.emptyList()));
given(this.endpointProvider.getEndpoints()).willReturn(endpoints);
WebEndpointProperties webEndpointProperties = new WebEndpointProperties(); WebEndpointProperties webEndpointProperties = new WebEndpointProperties();
webEndpointProperties.setBasePath(contextPath); webEndpointProperties.setBasePath(contextPath);
return new DefaultEndpointPathProvider(this.endpointProvider, return new DefaultEndpointPathProvider(this.discoverer, webEndpointProperties);
webEndpointProperties);
} }
} }

@ -14,12 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -30,20 +31,18 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class DefaultEndpointPathResolverTests { public class DefaultEndpointPathResolverTests {
private final MockEnvironment environment = new MockEnvironment();
private final EndpointPathResolver resolver = new DefaultEndpointPathResolver(
this.environment);
@Test @Test
public void defaultConfiguration() { public void defaultConfiguration() {
assertThat(this.resolver.resolvePath("test")).isEqualTo("test"); EndpointPathResolver resolver = new DefaultEndpointPathResolver(
Collections.emptyMap());
assertThat(resolver.resolvePath("test")).isEqualTo("test");
} }
@Test @Test
public void userConfiguration() { public void userConfiguration() {
this.environment.setProperty("endpoints.test.web.path", "custom"); EndpointPathResolver resolver = new DefaultEndpointPathResolver(
assertThat(this.resolver.resolvePath("test")).isEqualTo("custom"); Collections.singletonMap("test", "custom"));
assertThat(resolver.resolvePath("test")).isEqualTo("custom");
} }
} }

@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -26,16 +27,16 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link EndpointAutoConfiguration}. * Tests for {@link WebEndpointAutoConfiguration}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class EndpointAutoConfigurationTests { public class WebEndpointAutoConfigurationTests {
@Test @Test
public void webApplicationConfiguresEndpointMediaTypes() { public void webApplicationConfiguresEndpointMediaTypes() {
new WebApplicationContextRunner() new WebApplicationContextRunner().withConfiguration(AutoConfigurations
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class)) .of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class))
.run((context) -> { .run((context) -> {
EndpointMediaTypes endpointMediaTypes = context EndpointMediaTypes endpointMediaTypes = context
.getBean(EndpointMediaTypes.class); .getBean(EndpointMediaTypes.class);

@ -51,7 +51,7 @@ public class EnvironmentEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.env.enabled:false") this.contextRunner.withPropertyValues("management.endpoint.env.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(EnvironmentEndpoint.class)); .doesNotHaveBean(EnvironmentEndpoint.class));
} }
@ -59,7 +59,7 @@ public class EnvironmentEndpointAutoConfigurationTests {
@Test @Test
public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception { public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception {
this.contextRunner.withSystemProperties("dbPassword=123456", "apiKey=123456") this.contextRunner.withSystemProperties("dbPassword=123456", "apiKey=123456")
.withPropertyValues("endpoints.env.keys-to-sanitize=.*pass.*") .withPropertyValues("management.endpoint.env.keys-to-sanitize=.*pass.*")
.run(validateSystemProperties("******", "123456")); .run(validateSystemProperties("******", "123456"));
} }

@ -49,8 +49,9 @@ public class FlywayEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.flyway.enabled:false").run( this.contextRunner.withPropertyValues("management.endpoint.flyway.enabled:false")
(context) -> assertThat(context).doesNotHaveBean(FlywayEndpoint.class)); .run((context) -> assertThat(context)
.doesNotHaveBean(FlywayEndpoint.class));
} }
@Configuration @Configuration

@ -84,15 +84,18 @@ public class HealthEndpointAutoConfigurationTests {
@Test @Test
public void runShouldHaveStatusEndpointBeanEvenIfDefaultIsDisabled() { public void runShouldHaveStatusEndpointBeanEvenIfDefaultIsDisabled() {
this.contextRunner.withPropertyValues("endpoints.default.enabled:false").run( // FIXME
(context) -> assertThat(context).hasSingleBean(StatusEndpoint.class)); this.contextRunner.withPropertyValues("management.endpoint.default.enabled:false")
.run((context) -> assertThat(context)
.hasSingleBean(StatusEndpoint.class));
} }
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveStatusEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveStatusEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.status.enabled:false").run( this.contextRunner.withPropertyValues("management.endpoint.status.enabled:false")
(context) -> assertThat(context).doesNotHaveBean(StatusEndpoint.class)); .run((context) -> assertThat(context)
.doesNotHaveBean(StatusEndpoint.class));
} }
@Configuration @Configuration

@ -20,9 +20,9 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.health.HealthReactiveWebEndpointExtension;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.StatusReactiveWebEndpointExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveStatusEndpointWebExtension;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
@ -46,33 +46,34 @@ public class HealthWebEndpointReactiveManagementContextConfigurationTests {
@Test @Test
public void runShouldCreateExtensionBeans() throws Exception { public void runShouldCreateExtensionBeans() throws Exception {
this.contextRunner.run((context) -> assertThat(context) this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(StatusReactiveWebEndpointExtension.class) .hasSingleBean(ReactiveStatusEndpointWebExtension.class)
.hasSingleBean(HealthReactiveWebEndpointExtension.class)); .hasSingleBean(ReactiveHealthEndpointWebExtension.class));
} }
@Test @Test
public void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans() public void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.health.enabled:false") this.contextRunner.withPropertyValues("management.endpoint.health.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(HealthReactiveWebEndpointExtension.class)); .doesNotHaveBean(ReactiveHealthEndpointWebExtension.class));
} }
@Test @Test
public void runWhenStatusEndpointIsDisabledShouldNotCreateExtensionBeans() public void runWhenStatusEndpointIsDisabledShouldNotCreateExtensionBeans()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.status.enabled:false") this.contextRunner.withPropertyValues("management.endpoint.status.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(StatusReactiveWebEndpointExtension.class)); .doesNotHaveBean(ReactiveStatusEndpointWebExtension.class));
} }
@Test @Test
public void runWithCustomHealthMappingShouldMapStatusCode() throws Exception { public void runWithCustomHealthMappingShouldMapStatusCode() throws Exception {
this.contextRunner this.contextRunner
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500") .withPropertyValues(
"management.health.status.http-mapping.CUSTOM=500")
.run((context) -> { .run((context) -> {
Object extension = context Object extension = context
.getBean(HealthReactiveWebEndpointExtension.class); .getBean(ReactiveHealthEndpointWebExtension.class);
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
.getField(extension, "statusHttpMapper"); .getField(extension, "statusHttpMapper");
Map<String, Integer> statusMappings = mapper.getStatusMapping(); Map<String, Integer> statusMappings = mapper.getStatusMapping();
@ -85,10 +86,11 @@ public class HealthWebEndpointReactiveManagementContextConfigurationTests {
@Test @Test
public void runWithCustomStatusMappingShouldMapStatusCode() throws Exception { public void runWithCustomStatusMappingShouldMapStatusCode() throws Exception {
this.contextRunner this.contextRunner
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500") .withPropertyValues(
"management.health.status.http-mapping.CUSTOM=500")
.run((context) -> { .run((context) -> {
Object extension = context Object extension = context
.getBean(StatusReactiveWebEndpointExtension.class); .getBean(ReactiveStatusEndpointWebExtension.class);
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
.getField(extension, "statusHttpMapper"); .getField(extension, "statusHttpMapper");
Map<String, Integer> statusMappings = mapper.getStatusMapping(); Map<String, Integer> statusMappings = mapper.getStatusMapping();

@ -20,9 +20,9 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper; import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HealthWebEndpointExtension; import org.springframework.boot.actuate.health.StatusEndpointWebExtension;
import org.springframework.boot.actuate.health.StatusWebEndpointExtension;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
@ -46,24 +46,24 @@ public class HealthWebEndpointServletManagementContextConfigurationTests {
@Test @Test
public void runShouldCreateExtensionBeans() throws Exception { public void runShouldCreateExtensionBeans() throws Exception {
this.contextRunner.run((context) -> assertThat(context) this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(StatusWebEndpointExtension.class) .hasSingleBean(StatusEndpointWebExtension.class)
.hasSingleBean(HealthWebEndpointExtension.class)); .hasSingleBean(HealthEndpointWebExtension.class));
} }
@Test @Test
public void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans() public void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.health.enabled:false") this.contextRunner.withPropertyValues("management.endpoint.health.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(HealthWebEndpointExtension.class)); .doesNotHaveBean(HealthEndpointWebExtension.class));
} }
@Test @Test
public void runWhenStatusEndpointIsDisabledShouldNotCreateExtensionBeans() public void runWhenStatusEndpointIsDisabledShouldNotCreateExtensionBeans()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.status.enabled:false") this.contextRunner.withPropertyValues("management.endpoint.status.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(StatusWebEndpointExtension.class)); .doesNotHaveBean(StatusEndpointWebExtension.class));
} }
@Test @Test
@ -71,7 +71,7 @@ public class HealthWebEndpointServletManagementContextConfigurationTests {
this.contextRunner this.contextRunner
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500") .withPropertyValues("management.health.status.http-mapping.CUSTOM=500")
.run((context) -> { .run((context) -> {
Object extension = context.getBean(HealthWebEndpointExtension.class); Object extension = context.getBean(HealthEndpointWebExtension.class);
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
.getField(extension, "statusHttpMapper"); .getField(extension, "statusHttpMapper");
Map<String, Integer> statusMappings = mapper.getStatusMapping(); Map<String, Integer> statusMappings = mapper.getStatusMapping();
@ -86,7 +86,7 @@ public class HealthWebEndpointServletManagementContextConfigurationTests {
this.contextRunner this.contextRunner
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500") .withPropertyValues("management.health.status.http-mapping.CUSTOM=500")
.run((context) -> { .run((context) -> {
Object extension = context.getBean(StatusWebEndpointExtension.class); Object extension = context.getBean(StatusEndpointWebExtension.class);
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
.getField(extension, "statusHttpMapper"); .getField(extension, "statusHttpMapper");
Map<String, Integer> statusMappings = mapper.getStatusMapping(); Map<String, Integer> statusMappings = mapper.getStatusMapping();

@ -37,21 +37,23 @@ public class InfoEndpointAutoConfigurationTests {
@Test @Test
public void runShouldHaveEndpointBean() { public void runShouldHaveEndpointBean() {
this.contextRunner.withPropertyValues("endpoints.shutdown.enabled:true") this.contextRunner.withPropertyValues("management.endpoint.shutdown.enabled:true")
.run((context) -> assertThat(context).hasSingleBean(InfoEndpoint.class)); .run((context) -> assertThat(context).hasSingleBean(InfoEndpoint.class));
} }
@Test @Test
public void runShouldHaveEndpointBeanEvenIfDefaultIsDisabled() { public void runShouldHaveEndpointBeanEvenIfDefaultIsDisabled() {
this.contextRunner.withPropertyValues("endpoints.default.enabled:false") // FIXME
this.contextRunner.withPropertyValues("management.endpoint.default.enabled:false")
.run((context) -> assertThat(context).hasSingleBean(InfoEndpoint.class)); .run((context) -> assertThat(context).hasSingleBean(InfoEndpoint.class));
} }
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.info.enabled:false").run( this.contextRunner.withPropertyValues("management.endpoint.info.enabled:false")
(context) -> assertThat(context).doesNotHaveBean(InfoEndpoint.class)); .run((context) -> assertThat(context)
.doesNotHaveBean(InfoEndpoint.class));
} }
} }

@ -61,8 +61,8 @@ public class JmxEndpointIntegrationTests {
} }
@Test @Test
public void jmxEndpointsCanBeDisabled() { public void jmxEndpointsCanBeExcluded() {
this.contextRunner.withPropertyValues("endpoints.default.jmx.enabled:false") this.contextRunner.withPropertyValues("management.endpoints.jmx.exclude:*")
.run((context) -> { .run((context) -> {
MBeanServer mBeanServer = context.getBean(MBeanServer.class); MBeanServer mBeanServer = context.getBean(MBeanServer.class);
checkEndpointMBeans(mBeanServer, new String[0], checkEndpointMBeans(mBeanServer, new String[0],
@ -74,9 +74,9 @@ public class JmxEndpointIntegrationTests {
} }
@Test @Test
public void singleJmxEndpointCanBeEnabled() { public void singleJmxEndpointCanBeExposed() {
this.contextRunner.withPropertyValues("endpoints.default.jmx.enabled=false", this.contextRunner.withPropertyValues("management.endpoints.jmx.expose=beans")
"endpoints.beans.jmx.enabled=true").run((context) -> { .run((context) -> {
MBeanServer mBeanServer = context.getBean(MBeanServer.class); MBeanServer mBeanServer = context.getBean(MBeanServer.class);
checkEndpointMBeans(mBeanServer, new String[] { "beans" }, checkEndpointMBeans(mBeanServer, new String[] { "beans" },
new String[] { "conditions", "configprops", "env", "health", new String[] { "conditions", "configprops", "env", "health",

@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
@ -103,6 +104,7 @@ public class JolokiaManagementContextConfigurationIntegrationTests {
@MinimalWebConfiguration @MinimalWebConfiguration
@Import({ JacksonAutoConfiguration.class, @Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class }) ServletManagementContextAutoConfiguration.class })

@ -19,10 +19,10 @@ package org.springframework.boot.actuate.autoconfigure.integrationtest;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.actuate.health.HealthReactiveWebEndpointExtension; import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthWebEndpointExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.StatusReactiveWebEndpointExtension; import org.springframework.boot.actuate.health.ReactiveStatusEndpointWebExtension;
import org.springframework.boot.actuate.health.StatusWebEndpointExtension; import org.springframework.boot.actuate.health.StatusEndpointWebExtension;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;
@ -59,25 +59,25 @@ public class WebEndpointsAutoConfigurationIntegrationTests {
servletWebRunner() servletWebRunner()
.run((context) -> context.getBean(WebEndpointTestApplication.class)); .run((context) -> context.getBean(WebEndpointTestApplication.class));
servletWebRunner().run((context) -> assertThat(context) servletWebRunner().run((context) -> assertThat(context)
.hasSingleBean(HealthWebEndpointExtension.class)); .hasSingleBean(HealthEndpointWebExtension.class));
} }
@Test @Test
public void statusEndpointWebExtensionIsAutoConfigured() { public void statusEndpointWebExtensionIsAutoConfigured() {
servletWebRunner().run((context) -> assertThat(context) servletWebRunner().run((context) -> assertThat(context)
.hasSingleBean(StatusWebEndpointExtension.class)); .hasSingleBean(StatusEndpointWebExtension.class));
} }
@Test @Test
public void healthEndpointReactiveWebExtensionIsAutoConfigured() { public void healthEndpointReactiveWebExtensionIsAutoConfigured() {
reactiveWebRunner().run((context) -> assertThat(context) reactiveWebRunner().run((context) -> assertThat(context)
.hasSingleBean(HealthReactiveWebEndpointExtension.class)); .hasSingleBean(ReactiveHealthEndpointWebExtension.class));
} }
@Test @Test
public void statusEndpointReactiveWebExtensionIsAutoConfigured() { public void statusEndpointReactiveWebExtensionIsAutoConfigured() {
reactiveWebRunner().run((context) -> assertThat(context) reactiveWebRunner().run((context) -> assertThat(context)
.hasSingleBean(StatusReactiveWebEndpointExtension.class)); .hasSingleBean(ReactiveStatusEndpointWebExtension.class));
} }
private WebApplicationContextRunner servletWebRunner() { private WebApplicationContextRunner servletWebRunner() {

@ -21,6 +21,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
@ -65,10 +66,11 @@ public class WebMvcEndpointCorsIntegrationTests {
this.context.register(JacksonAutoConfiguration.class, this.context.register(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
EndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
BeansEndpointAutoConfiguration.class); BeansEndpointAutoConfiguration.class);
TestPropertyValues.of("endpoints.default.web.enabled:true").applyTo(this.context); TestPropertyValues.of("management.endpoints.web.expose:*").applyTo(this.context);
} }
@Test @Test
@ -83,7 +85,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void settingAllowedOriginsEnablesCors() throws Exception { public void settingAllowedOriginsEnablesCors() throws Exception {
TestPropertyValues.of("management.endpoints.cors.allowed-origins:foo.example.com") TestPropertyValues
.of("management.endpoints.web.cors.allowed-origins:foo.example.com")
.applyTo(this.context); .applyTo(this.context);
createMockMvc() createMockMvc()
.perform(options("/application/beans").header("Origin", "bar.example.com") .perform(options("/application/beans").header("Origin", "bar.example.com")
@ -94,7 +97,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void maxAgeDefaultsTo30Minutes() throws Exception { public void maxAgeDefaultsTo30Minutes() throws Exception {
TestPropertyValues.of("management.endpoints.cors.allowed-origins:foo.example.com") TestPropertyValues
.of("management.endpoints.web.cors.allowed-origins:foo.example.com")
.applyTo(this.context); .applyTo(this.context);
performAcceptedCorsRequest() performAcceptedCorsRequest()
.andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800")); .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800"));
@ -102,15 +106,18 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void maxAgeCanBeConfigured() throws Exception { public void maxAgeCanBeConfigured() throws Exception {
TestPropertyValues.of("management.endpoints.cors.allowed-origins:foo.example.com", TestPropertyValues
"management.endpoints.cors.max-age: 2400").applyTo(this.context); .of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.max-age: 2400")
.applyTo(this.context);
performAcceptedCorsRequest() performAcceptedCorsRequest()
.andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400")); .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400"));
} }
@Test @Test
public void requestsWithDisallowedHeadersAreRejected() throws Exception { public void requestsWithDisallowedHeadersAreRejected() throws Exception {
TestPropertyValues.of("management.endpoints.cors.allowed-origins:foo.example.com") TestPropertyValues
.of("management.endpoints.web.cors.allowed-origins:foo.example.com")
.applyTo(this.context); .applyTo(this.context);
createMockMvc() createMockMvc()
.perform(options("/application/beans").header("Origin", "foo.example.com") .perform(options("/application/beans").header("Origin", "foo.example.com")
@ -122,8 +129,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void allowedHeadersCanBeConfigured() throws Exception { public void allowedHeadersCanBeConfigured() throws Exception {
TestPropertyValues TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com", .of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allowed-headers:Alpha,Bravo") "management.endpoints.web.cors.allowed-headers:Alpha,Bravo")
.applyTo(this.context); .applyTo(this.context);
createMockMvc() createMockMvc()
.perform(options("/application/beans").header("Origin", "foo.example.com") .perform(options("/application/beans").header("Origin", "foo.example.com")
@ -135,7 +142,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void requestsWithDisallowedMethodsAreRejected() throws Exception { public void requestsWithDisallowedMethodsAreRejected() throws Exception {
TestPropertyValues.of("management.endpoints.cors.allowed-origins:foo.example.com") TestPropertyValues
.of("management.endpoints.web.cors.allowed-origins:foo.example.com")
.applyTo(this.context); .applyTo(this.context);
createMockMvc() createMockMvc()
.perform(options("/application/health") .perform(options("/application/health")
@ -147,8 +155,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void allowedMethodsCanBeConfigured() throws Exception { public void allowedMethodsCanBeConfigured() throws Exception {
TestPropertyValues TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com", .of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allowed-methods:GET,HEAD") "management.endpoints.web.cors.allowed-methods:GET,HEAD")
.applyTo(this.context); .applyTo(this.context);
createMockMvc() createMockMvc()
.perform(options("/application/beans") .perform(options("/application/beans")
@ -161,8 +169,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void credentialsCanBeAllowed() throws Exception { public void credentialsCanBeAllowed() throws Exception {
TestPropertyValues TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com", .of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allow-credentials:true") "management.endpoints.web.cors.allow-credentials:true")
.applyTo(this.context); .applyTo(this.context);
performAcceptedCorsRequest().andExpect( performAcceptedCorsRequest().andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")); header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"));
@ -171,8 +179,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test @Test
public void credentialsCanBeDisabled() throws Exception { public void credentialsCanBeDisabled() throws Exception {
TestPropertyValues TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com", .of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allow-credentials:false") "management.endpoints.web.cors.allow-credentials:false")
.applyTo(this.context); .applyTo(this.context);
performAcceptedCorsRequest().andExpect( performAcceptedCorsRequest().andExpect(
header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)); header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.integrationtest;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -52,7 +53,7 @@ public class WebMvcEndpointExposureIntegrationTests {
JacksonAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
@ -79,9 +80,9 @@ public class WebMvcEndpointExposureIntegrationTests {
} }
@Test @Test
public void webEndpointsCanBeEnabled() { public void webEndpointsCanBeExposed() {
WebApplicationContextRunner contextRunner = this.contextRunner WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues("endpoints.default.web.enabled=true"); .withPropertyValues("management.endpoints.web.expose=*");
contextRunner.run((context) -> { contextRunner.run((context) -> {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue(); assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue();
@ -99,26 +100,46 @@ public class WebMvcEndpointExposureIntegrationTests {
} }
@Test @Test
public void singleWebEndpointCanBeEnabled() { public void singleWebEndpointCanBeExposed() {
WebApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues( WebApplicationContextRunner contextRunner = this.contextRunner
"endpoints.default.web.enabled=false", .withPropertyValues("management.endpoints.web.expose=beans");
"endpoints.beans.web.enabled=true");
contextRunner.run((context) -> { contextRunner.run((context) -> {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
assertThat(isExposed(mvc, HttpMethod.GET, "autoconfig")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue(); assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "conditions")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isFalse(); assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "env")).isFalse(); assertThat(isExposed(mvc, HttpMethod.GET, "env")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "health")).isFalse(); assertThat(isExposed(mvc, HttpMethod.GET, "health")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "info")).isTrue(); assertThat(isExposed(mvc, HttpMethod.GET, "info")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isFalse(); assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isFalse();
assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse(); assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "status")).isTrue(); assertThat(isExposed(mvc, HttpMethod.GET, "status")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isFalse(); assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "trace")).isFalse(); assertThat(isExposed(mvc, HttpMethod.GET, "trace")).isFalse();
}); });
} }
@Test
public void singleWebEndpointCanBeExcluded() {
WebApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
"management.endpoints.web.expose=*",
"management.endpoints.web.exclude=shutdown");
contextRunner.run((context) -> {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "conditions")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "env")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "health")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "info")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isTrue();
assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "status")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isTrue();
assertThat(isExposed(mvc, HttpMethod.GET, "trace")).isTrue();
});
}
private boolean isExposed(MockMvc mockMvc, HttpMethod method, String path) private boolean isExposed(MockMvc mockMvc, HttpMethod method, String path)
throws Exception { throws Exception {
path = "/application/" + path; path = "/application/" + path;

@ -22,6 +22,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -91,7 +92,7 @@ public class WebMvcEndpointIntegrationTests {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class); this.context.register(SecureConfiguration.class);
TestPropertyValues.of("management.endpoints.web.base-path:/management", TestPropertyValues.of("management.endpoints.web.base-path:/management",
"endpoints.default.web.enabled=true").applyTo(this.context); "management.endpoints.web.expose=*").applyTo(this.context);
MockMvc mockMvc = createSecureMockMvc(); MockMvc mockMvc = createSecureMockMvc();
mockMvc.perform(get("/management/beans")).andExpect(status().isOk()); mockMvc.perform(get("/management/beans")).andExpect(status().isOk());
} }
@ -112,6 +113,7 @@ public class WebMvcEndpointIntegrationTests {
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, @ImportAutoConfiguration({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class, ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,

@ -49,7 +49,8 @@ public class LiquibaseEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.liquibase.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.liquibase.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(LiquibaseEndpoint.class)); .doesNotHaveBean(LiquibaseEndpoint.class));
} }

@ -63,7 +63,8 @@ public class LogFileWebEndpointManagementContextConfigurationTests {
@Test @Test
public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() { public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() {
this.contextRunner this.contextRunner
.withPropertyValues("endpoints.logfile.external-file:external.log") .withPropertyValues(
"management.endpoint.logfile.external-file:external.log")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.hasSingleBean(LogFileWebEndpoint.class)); .hasSingleBean(LogFileWebEndpoint.class));
} }
@ -72,7 +73,7 @@ public class LogFileWebEndpointManagementContextConfigurationTests {
public void logFileWebEndpointCanBeDisabled() { public void logFileWebEndpointCanBeDisabled() {
this.contextRunner this.contextRunner
.withPropertyValues("logging.file:test.log", .withPropertyValues("logging.file:test.log",
"endpoints.logfile.enabled:false") "management.endpoint.logfile.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.hasSingleBean(LogFileWebEndpoint.class)); .hasSingleBean(LogFileWebEndpoint.class));
} }
@ -81,9 +82,8 @@ public class LogFileWebEndpointManagementContextConfigurationTests {
public void logFileWebEndpointUsesConfiguredExternalFile() throws IOException { public void logFileWebEndpointUsesConfiguredExternalFile() throws IOException {
File file = this.temp.newFile("logfile"); File file = this.temp.newFile("logfile");
FileCopyUtils.copy("--TEST--".getBytes(), file); FileCopyUtils.copy("--TEST--".getBytes(), file);
this.contextRunner this.contextRunner.withPropertyValues(
.withPropertyValues( "management.endpoint.logfile.external-file:" + file.getAbsolutePath())
"endpoints.logfile.external-file:" + file.getAbsolutePath())
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(LogFileWebEndpoint.class); assertThat(context).hasSingleBean(LogFileWebEndpoint.class);
LogFileWebEndpoint endpoint = context LogFileWebEndpoint endpoint = context

@ -49,8 +49,9 @@ public class LoggersEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.loggers.enabled:false").run( this.contextRunner.withPropertyValues("management.endpoint.loggers.enabled:false")
(context) -> assertThat(context).doesNotHaveBean(LoggersEndpoint.class)); .run((context) -> assertThat(context)
.doesNotHaveBean(LoggersEndpoint.class));
} }
@Configuration @Configuration

@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class HeapDumpWebEndpointManagementContextConfigurationTests { public class HeapDumpWebEndpointManagementContextConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withPropertyValues("endpoints.default.web.enabled:true") .withPropertyValues("management.endpoints.web.expose:*")
.withUserConfiguration( .withUserConfiguration(
HeapDumpWebEndpointManagementContextConfiguration.class); HeapDumpWebEndpointManagementContextConfiguration.class);
@ -43,7 +43,8 @@ public class HeapDumpWebEndpointManagementContextConfigurationTests {
@Test @Test
public void runWhenDisabledShouldNotCreateIndicator() throws Exception { public void runWhenDisabledShouldNotCreateIndicator() throws Exception {
this.contextRunner.withPropertyValues("endpoints.heapdump.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.heapdump.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(HeapDumpWebEndpoint.class)); .doesNotHaveBean(HeapDumpWebEndpoint.class));
} }

@ -44,7 +44,8 @@ public class ThreadDumpEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.threaddump.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.threaddump.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(ThreadDumpEndpoint.class)); .doesNotHaveBean(ThreadDumpEndpoint.class));
} }

@ -46,7 +46,8 @@ public class ScheduledTasksEndpointAutoConfigurationTests {
@Test @Test
public void endpointCanBeDisabled() { public void endpointCanBeDisabled() {
this.contextRunner.withPropertyValues("endpoints.scheduledtasks.enabled:false") this.contextRunner
.withPropertyValues("management.endpoint.scheduledtasks.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(ScheduledTasksEndpoint.class)); .doesNotHaveBean(ScheduledTasksEndpoint.class));
} }

@ -49,8 +49,10 @@ public class SessionsEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.sessions.enabled:false").run( this.contextRunner
(context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class)); .withPropertyValues("management.endpoint.sessions.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(SessionsEndpoint.class));
} }
@Configuration @Configuration

@ -44,8 +44,9 @@ public class TraceEndpointAutoConfigurationTests {
@Test @Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception { throws Exception {
this.contextRunner.withPropertyValues("endpoints.trace.enabled:false").run( this.contextRunner.withPropertyValues("management.endpoint.trace.enabled:false")
(context) -> assertThat(context).doesNotHaveBean(TraceEndpoint.class)); .run((context) -> assertThat(context)
.doesNotHaveBean(TraceEndpoint.class));
} }
} }

@ -22,13 +22,12 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod; import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.endpoint.web.EndpointMapping; import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -136,12 +135,12 @@ public class RequestMappingEndpointTests {
OperationRequestPredicate requestPredicate = new OperationRequestPredicate("test", OperationRequestPredicate requestPredicate = new OperationRequestPredicate("test",
WebEndpointHttpMethod.GET, Collections.singletonList("application/json"), WebEndpointHttpMethod.GET, Collections.singletonList("application/json"),
Collections.singletonList("application/json")); Collections.singletonList("application/json"));
WebEndpointOperation operation = new WebEndpointOperation(OperationType.READ, WebOperation operation = new WebOperation(OperationType.READ,
(arguments) -> "Invoked", true, requestPredicate, "test"); (arguments) -> "Invoked", true, requestPredicate, "test");
WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping( WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping(
new EndpointMapping("application"), new EndpointMapping("application"),
Collections.singleton(new EndpointInfo<>("test", Collections.singleton(new EndpointInfo<>("test", true,
DefaultEnablement.ENABLED, Collections.singleton(operation))), Collections.singleton(operation))),
new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"), new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"),
Arrays.asList("application/vnd.test+json"))); Arrays.asList("application/vnd.test+json")));
mapping.setApplicationContext(new StaticApplicationContext()); mapping.setApplicationContext(new StaticApplicationContext());

@ -21,21 +21,21 @@ import java.util.Date;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor; import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* {@link WebEndpointExtension} for the {@link AuditEventsEndpoint}. * {@link EndpointWebExtension} for the {@link AuditEventsEndpoint}.
* *
* @author Vedran Pavic * @author Vedran Pavic
* @since 2.0.0 * @since 2.0.0
*/ */
@WebEndpointExtension(endpoint = AuditEventsEndpoint.class) @EndpointWebExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsWebEndpointExtension { public class AuditEventsEndpointWebExtension {
private final AuditEventsEndpoint delegate; private final AuditEventsEndpoint delegate;
public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) { public AuditEventsEndpointWebExtension(AuditEventsEndpoint delegate) {
this.delegate = delegate; this.delegate = delegate;
} }

@ -20,7 +20,7 @@ import java.util.Date;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor; import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExtension; import org.springframework.boot.actuate.endpoint.jmx.annotation.EndpointJmxExtension;
/** /**
* JMX-specific extension of the {@link AuditEventsEndpoint}. * JMX-specific extension of the {@link AuditEventsEndpoint}.
@ -29,7 +29,7 @@ import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExten
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
*/ */
@JmxEndpointExtension(endpoint = AuditEventsEndpoint.class) @EndpointJmxExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsJmxEndpointExtension { public class AuditEventsJmxEndpointExtension {
private final AuditEventsEndpoint delegate; private final AuditEventsEndpoint delegate;

@ -20,7 +20,6 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -35,7 +34,7 @@ import org.springframework.context.ConfigurableApplicationContext;
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
*/ */
@Endpoint(id = "shutdown", defaultEnablement = DefaultEnablement.DISABLED) @Endpoint(id = "shutdown", enableByDefault = false)
public class ShutdownEndpoint implements ApplicationContextAware { public class ShutdownEndpoint implements ApplicationContextAware {
private static final Map<String, String> NO_CONTEXT_MESSAGE = Collections private static final Map<String, String> NO_CONTEXT_MESSAGE = Collections

@ -1,47 +0,0 @@
/*
* 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;
/**
* An enumeration of the available exposure technologies for an endpoint.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public enum EndpointExposure {
/**
* Expose the endpoint as a JMX MBean.
*/
JMX(true),
/**
* Expose the endpoint as a Web endpoint.
*/
WEB(false);
private final boolean enabledByDefault;
EndpointExposure(boolean enabledByDefault) {
this.enabledByDefault = enabledByDefault;
}
public boolean isEnabledByDefault() {
return this.enabledByDefault;
}
}

@ -17,26 +17,21 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
/** /**
* Enumerate the enablement options for an endpoint. * Strategy class that can be used to filter discovered endpoints.
* *
* @author Stephane Nicoll * @author Phillip Webb
* @param <T> the type of the endpoint's operations
* @since 2.0.0 * @since 2.0.0
*/ */
public enum DefaultEnablement { @FunctionalInterface
public interface EndpointFilter<T extends Operation> {
/** /**
* The endpoint is enabled unless explicitly disabled. * Return {@code true} if the filter matches.
* @param info the endpoint info
* @param discoverer the endpoint discoverer
* @return {@code true} if the filter matches
*/ */
ENABLED, boolean match(EndpointInfo<T> info, EndpointDiscoverer<T> discoverer);
/**
* The endpoint is disabled unless explicitly enabled.
*/
DISABLED,
/**
* The endpoint's enablement defaults to the "default" settings.
*/
NEUTRAL
} }

@ -17,6 +17,9 @@
package org.springframework.boot.actuate.endpoint; package org.springframework.boot.actuate.endpoint;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import org.springframework.util.Assert;
/** /**
* Information describing an endpoint. * Information describing an endpoint.
@ -29,7 +32,7 @@ public class EndpointInfo<T extends Operation> {
private final String id; private final String id;
private final DefaultEnablement defaultEnablement; private final boolean enableByDefault;
private final Collection<T> operations; private final Collection<T> operations;
@ -37,14 +40,15 @@ public class EndpointInfo<T extends Operation> {
* Creates a new {@code EndpointInfo} describing an endpoint with the given {@code id} * Creates a new {@code EndpointInfo} describing an endpoint with the given {@code id}
* and {@code operations}. * and {@code operations}.
* @param id the id of the endpoint * @param id the id of the endpoint
* @param defaultEnablement the {@link DefaultEnablement} of the endpoint * @param enableByDefault if the endpoint is enabled by default
* @param operations the operations of the endpoint * @param operations the operations of the endpoint
*/ */
public EndpointInfo(String id, DefaultEnablement defaultEnablement, public EndpointInfo(String id, boolean enableByDefault, Collection<T> operations) {
Collection<T> operations) { Assert.hasText(id, "ID must not be empty");
Assert.notNull(operations, "Operations must not be null");
this.id = id; this.id = id;
this.defaultEnablement = defaultEnablement; this.enableByDefault = enableByDefault;
this.operations = operations; this.operations = Collections.unmodifiableCollection(operations);
} }
/** /**
@ -56,11 +60,11 @@ public class EndpointInfo<T extends Operation> {
} }
/** /**
* Return the {@link DefaultEnablement} of the endpoint. * Returns if the endpoint is enabled by default.
* @return the default enablement * @return if the endpoint is enabled by default
*/ */
public DefaultEnablement getDefaultEnablement() { public boolean isEnableByDefault() {
return this.defaultEnablement; return this.enableByDefault;
} }
/** /**

@ -33,14 +33,14 @@ public class Operation {
/** /**
* Creates a new {@code EndpointOperation} for an operation of the given {@code type}. * Creates a new {@code EndpointOperation} for an operation of the given {@code type}.
* The operation can be performed using the given {@code operationInvoker}. * The operation can be performed using the given {@code operationInvoker}.
* @param type the type of the operation * @param operationType the type of the operation
* @param operationInvoker used to perform the operation * @param invoker used to perform the operation
* @param blocking whether or not this is a blocking operation * @param blocking whether or not this is a blocking operation
*/ */
public Operation(OperationType type, OperationInvoker operationInvoker, public Operation(OperationType operationType, OperationInvoker invoker,
boolean blocking) { boolean blocking) {
this.type = type; this.type = operationType;
this.invoker = operationInvoker; this.invoker = invoker;
this.blocking = blocking; this.blocking = blocking;
} }

@ -16,139 +16,178 @@
package org.springframework.boot.actuate.endpoint.annotation; package org.springframework.boot.actuate.endpoint.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.Map.Entry;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodIntrospector; import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.ObjectUtils; import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/** /**
* A base {@link EndpointDiscoverer} implementation that discovers {@link Endpoint} beans * A base {@link EndpointDiscoverer} implementation that discovers
* {@link Endpoint @Endpoint} beans and {@link EndpointExtension @EndpointExtension} beans
* in an application context. * in an application context.
* *
* @param <T> the type of the operation
* @param <K> the type of the operation key * @param <K> the type of the operation key
* @param <T> the type of the operation
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
*/ */
public abstract class AnnotationEndpointDiscoverer<T extends Operation, K> public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
implements EndpointDiscoverer<T> { implements EndpointDiscoverer<T> {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final EndpointOperationFactory<T> operationFactory;
private final Function<T, K> operationKeyFactory; private final Function<T, K> operationKeyFactory;
private final CachingConfigurationFactory cachingConfigurationFactory; private final OperationsFactory<T> operationsFactory;
private final List<EndpointFilter<T>> filters;
/**
* Create a new {@link AnnotationEndpointDiscoverer} instance.
* @param applicationContext the application context
* @param operationFactory a factory used to create operations
* @param operationKeyFactory a factory used to create a key for an operation
* @param parameterMapper the {@link ParameterMapper} used to convert arguments when
* an operation is invoked
* @param invokerAdvisors advisors used to add additional invoker advise
* @param filters filters that must match for an endpoint to be exposed
*/
protected AnnotationEndpointDiscoverer(ApplicationContext applicationContext, protected AnnotationEndpointDiscoverer(ApplicationContext applicationContext,
EndpointOperationFactory<T> operationFactory, OperationFactory<T> operationFactory, Function<T, K> operationKeyFactory,
Function<T, K> operationKeyFactory, ParameterMapper parameterMapper,
CachingConfigurationFactory cachingConfigurationFactory) { Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<? extends EndpointFilter<T>> filters) {
Assert.notNull(applicationContext, "Application Context must not be null");
Assert.notNull(operationFactory, "Operation Factory must not be null");
Assert.notNull(operationKeyFactory, "Operation Key Factory must not be null");
Assert.notNull(parameterMapper, "Parameter Mapper must not be null");
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.operationFactory = operationFactory;
this.operationKeyFactory = operationKeyFactory; this.operationKeyFactory = operationKeyFactory;
this.cachingConfigurationFactory = cachingConfigurationFactory; this.operationsFactory = new OperationsFactory<>(operationFactory,
parameterMapper, invokerAdvisors);
this.filters = (filters == null ? Collections.emptyList()
: new ArrayList<>(filters));
}
@Override
public final Collection<EndpointInfo<T>> discoverEndpoints() {
Class<T> operationType = getOperationType();
Map<Class<?>, DiscoveredEndpoint> endpoints = getEndpoints(operationType);
Map<Class<?>, DiscoveredExtension> extensions = getExtensions(operationType,
endpoints);
Collection<DiscoveredEndpoint> exposed = mergeExposed(endpoints, extensions);
verify(exposed);
return exposed.stream().map(DiscoveredEndpoint::getInfo)
.collect(Collectors.toCollection(ArrayList::new));
} }
/** /**
* Perform endpoint discovery, including discovery and merging of extensions. * Return the operation type being discovered. By default this method will resolve the
* @param extensionType the annotation type of the extension * class generic "{@code <T>}".
* @param exposure the {@link EndpointExposure} that should be considered * @return the operation type
* @return the list of {@link EndpointInfo EndpointInfos} that describes the
* discovered endpoints matching the specified {@link EndpointExposure}
*/ */
protected Collection<EndpointInfoDescriptor<T, K>> discoverEndpoints( @SuppressWarnings("unchecked")
Class<? extends Annotation> extensionType, EndpointExposure exposure) { protected Class<T> getOperationType() {
Map<Class<?>, EndpointInfo<T>> endpoints = discoverEndpoints(exposure); return (Class<T>) ResolvableType
Map<Class<?>, EndpointExtensionInfo<T>> extensions = discoverExtensions(endpoints, .forClass(AnnotationEndpointDiscoverer.class, getClass())
extensionType, exposure); .resolveGeneric(1);
Collection<EndpointInfoDescriptor<T, K>> result = new ArrayList<>();
endpoints.forEach((endpointClass, endpointInfo) -> {
EndpointExtensionInfo<T> extension = extensions.remove(endpointClass);
result.add(createDescriptor(endpointClass, endpointInfo, extension));
});
return result;
} }
private Map<Class<?>, EndpointInfo<T>> discoverEndpoints(EndpointExposure exposure) { private Map<Class<?>, DiscoveredEndpoint> getEndpoints(Class<T> operationType) {
Map<Class<?>, DiscoveredEndpoint> endpoints = new LinkedHashMap<>();
Map<String, DiscoveredEndpoint> endpointsById = new LinkedHashMap<>();
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
this.applicationContext, Endpoint.class); this.applicationContext, Endpoint.class);
Map<Class<?>, EndpointInfo<T>> endpoints = new LinkedHashMap<>();
Map<String, EndpointInfo<T>> endpointsById = new LinkedHashMap<>();
for (String beanName : beanNames) { for (String beanName : beanNames) {
Class<?> beanType = this.applicationContext.getType(beanName); addEndpoint(endpoints, endpointsById, beanName);
AnnotationAttributes attributes = AnnotatedElementUtils
.findMergedAnnotationAttributes(beanType, Endpoint.class, true, true);
if (isExposedOver(attributes, exposure)) {
EndpointInfo<T> info = createEndpointInfo(beanName, beanType, attributes);
EndpointInfo<T> previous = endpointsById.putIfAbsent(info.getId(), info);
Assert.state(previous == null, () -> "Found two endpoints with the id '"
+ info.getId() + "': " + info + " and " + previous);
endpoints.put(beanType, info);
}
} }
return endpoints; return endpoints;
} }
private EndpointInfo<T> createEndpointInfo(String beanName, Class<?> beanType, private void addEndpoint(Map<Class<?>, DiscoveredEndpoint> endpoints,
AnnotationAttributes attributes) { Map<String, DiscoveredEndpoint> endpointsById, String beanName) {
String id = attributes.getString("id"); Class<?> endpointType = this.applicationContext.getType(beanName);
DefaultEnablement defaultEnablement = (DefaultEnablement) attributes Object target = this.applicationContext.getBean(beanName);
.get("defaultEnablement"); DiscoveredEndpoint endpoint = createEndpoint(target, endpointType);
Map<Method, T> operations = discoverOperations(id, beanName, beanType); String id = endpoint.getInfo().getId();
return new EndpointInfo<>(id, defaultEnablement, operations.values()); DiscoveredEndpoint previous = endpointsById.putIfAbsent(id, endpoint);
Assert.state(previous == null, () -> "Found two endpoints with the id '" + id
+ "': " + endpoint + " and " + previous);
endpoints.put(endpointType, endpoint);
} }
private Map<Class<?>, EndpointExtensionInfo<T>> discoverExtensions( private DiscoveredEndpoint createEndpoint(Object target, Class<?> endpointType) {
Map<Class<?>, EndpointInfo<T>> endpoints, AnnotationAttributes annotationAttributes = AnnotatedElementUtils
Class<? extends Annotation> extensionType, EndpointExposure exposure) { .findMergedAnnotationAttributes(endpointType, Endpoint.class, true, true);
if (extensionType == null) { String id = annotationAttributes.getString("id");
return Collections.emptyMap(); Assert.state(StringUtils.hasText(id),
} "No @Endpoint id attribute specified for " + endpointType.getName());
boolean enabledByDefault = (Boolean) annotationAttributes.get("enableByDefault");
Collection<T> operations = this.operationsFactory
.createOperations(id, target, endpointType).values();
EndpointInfo<T> endpointInfo = new EndpointInfo<>(id, enabledByDefault,
operations);
boolean exposed = isEndpointExposed(endpointType, endpointInfo);
return new DiscoveredEndpoint(endpointType, endpointInfo, exposed);
}
private Map<Class<?>, DiscoveredExtension> getExtensions(Class<T> operationType,
Map<Class<?>, DiscoveredEndpoint> endpoints) {
Map<Class<?>, DiscoveredExtension> extensions = new LinkedHashMap<>();
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
this.applicationContext, extensionType); this.applicationContext, EndpointExtension.class);
Map<Class<?>, EndpointExtensionInfo<T>> extensions = new HashMap<>();
for (String beanName : beanNames) { for (String beanName : beanNames) {
Class<?> beanType = this.applicationContext.getType(beanName); addExtension(endpoints, extensions, beanName);
Class<?> endpointType = getEndpointType(extensionType, beanType); }
AnnotationAttributes endpointAttributes = AnnotatedElementUtils return extensions;
.getMergedAnnotationAttributes(endpointType, Endpoint.class); }
Assert.state(isExposedOver(endpointAttributes, exposure),
() -> "Invalid extension " + beanType.getName() + "': endpoint '" private void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints,
Map<Class<?>, DiscoveredExtension> extensions, String beanName) {
Class<?> extensionType = this.applicationContext.getType(beanName);
Class<?> endpointType = getEndpointType(extensionType);
DiscoveredEndpoint endpoint = getExtendingEndpoint(endpoints, extensionType,
endpointType);
if (isExtensionExposed(extensionType, endpoint.getInfo())) {
Assert.state(endpoint.isExposed() || isEndpointFiltered(endpoint.getInfo()),
() -> "Invalid extension " + extensionType.getName() + "': endpoint '"
+ endpointType.getName() + endpointType.getName()
+ "' does not support such extension"); + "' does not support such extension");
EndpointInfo<T> info = getEndpointInfo(endpoints, beanType, endpointType); Object target = this.applicationContext.getBean(beanName);
Map<Method, T> operations = discoverOperations(info.getId(), beanName, Map<Method, T> operations = this.operationsFactory
beanType); .createOperations(endpoint.getInfo().getId(), target, extensionType);
EndpointExtensionInfo<T> extension = new EndpointExtensionInfo<>(beanType, DiscoveredExtension extension = new DiscoveredExtension(extensionType,
operations.values()); operations.values());
EndpointExtensionInfo<T> previous = extensions.putIfAbsent(endpointType, DiscoveredExtension previous = extensions.putIfAbsent(endpointType,
extension); extension);
Assert.state(previous == null, Assert.state(previous == null,
() -> "Found two extensions for the same endpoint '" () -> "Found two extensions for the same endpoint '"
@ -156,237 +195,285 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
+ extension.getExtensionType().getName() + " and " + extension.getExtensionType().getName() + " and "
+ previous.getExtensionType().getName()); + previous.getExtensionType().getName());
} }
return extensions; }
private Class<?> getEndpointType(Class<?> extensionType) {
AnnotationAttributes attributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
Class<?> endpointType = attributes.getClass("endpoint");
Assert.state(!endpointType.equals(Void.class), () -> "Extension "
+ endpointType.getName() + " does not specify an endpoint");
return endpointType;
} }
private EndpointInfo<T> getEndpointInfo(Map<Class<?>, EndpointInfo<T>> endpoints, private DiscoveredEndpoint getExtendingEndpoint(
Class<?> beanType, Class<?> endpointClass) { Map<Class<?>, DiscoveredEndpoint> endpoints, Class<?> extensionType,
EndpointInfo<T> endpoint = endpoints.get(endpointClass); Class<?> endpointType) {
DiscoveredEndpoint endpoint = endpoints.get(endpointType);
Assert.state(endpoint != null, Assert.state(endpoint != null,
() -> "Invalid extension '" + beanType.getName() () -> "Invalid extension '" + extensionType.getName()
+ "': no endpoint found with type '" + endpointClass.getName() + "': no endpoint found with type '" + endpointType.getName()
+ "'"); + "'");
return endpoint; return endpoint;
} }
private Class<?> getEndpointType(Class<? extends Annotation> extensionType, private boolean isEndpointExposed(Class<?> endpointType,
Class<?> beanType) { EndpointInfo<T> endpointInfo) {
AnnotationAttributes attributes = AnnotatedElementUtils if (isEndpointFiltered(endpointInfo)) {
.getMergedAnnotationAttributes(beanType, extensionType); return false;
return (Class<?>) attributes.get("endpoint"); }
} AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(endpointType, FilteredEndpoint.class);
private EndpointInfoDescriptor<T, K> createDescriptor(Class<?> type, if (annotationAttributes == null) {
EndpointInfo<T> info, EndpointExtensionInfo<T> extension) { return true;
Map<OperationKey<K>, List<T>> operations = indexOperations(info.getId(), type, }
info.getOperations()); Class<?> filterClass = annotationAttributes.getClass("value");
if (extension != null) { return isFilterMatch(filterClass, endpointInfo);
operations.putAll(indexOperations(info.getId(), extension.getExtensionType(),
extension.getOperations()));
return new EndpointInfoDescriptor<>(mergeEndpoint(info, extension),
operations);
}
return new EndpointInfoDescriptor<>(info, operations);
} }
private EndpointInfo<T> mergeEndpoint(EndpointInfo<T> endpoint, private boolean isEndpointFiltered(EndpointInfo<T> endpointInfo) {
EndpointExtensionInfo<T> extension) { for (EndpointFilter<T> filter : this.filters) {
Map<K, T> operations = new HashMap<>(); if (!isFilterMatch(filter, endpointInfo)) {
Consumer<T> consumer = (operation) -> operations return true;
.put(this.operationKeyFactory.apply(operation), operation); }
endpoint.getOperations().forEach(consumer); }
extension.getOperations().forEach(consumer); return false;
return new EndpointInfo<>(endpoint.getId(), endpoint.getDefaultEnablement(),
operations.values());
} }
private Map<OperationKey<K>, List<T>> indexOperations(String endpointId, private boolean isExtensionExposed(Class<?> extensionType,
Class<?> target, Collection<T> operations) { EndpointInfo<T> endpointInfo) {
LinkedMultiValueMap<OperationKey<K>, T> result = new LinkedMultiValueMap<>(); AnnotationAttributes annotationAttributes = AnnotatedElementUtils
operations.forEach((operation) -> { .getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
K key = this.operationKeyFactory.apply(operation); Class<?> filterClass = annotationAttributes.getClass("filter");
result.add(new OperationKey<>(endpointId, target, key), operation); return isFilterMatch(filterClass, endpointInfo);
});
return result;
} }
private boolean isExposedOver(AnnotationAttributes attributes, @SuppressWarnings("unchecked")
EndpointExposure exposure) { private boolean isFilterMatch(Class<?> filterClass, EndpointInfo<T> endpointInfo) {
if (exposure == null) { Class<?> generic = ResolvableType.forClass(EndpointFilter.class, filterClass)
return true; .resolveGeneric(0);
if (generic == null || generic.isAssignableFrom(getOperationType())) {
EndpointFilter<T> filter = (EndpointFilter<T>) BeanUtils
.instantiateClass(filterClass);
return isFilterMatch(filter, endpointInfo);
} }
EndpointExposure[] supported = (EndpointExposure[]) attributes.get("exposure"); return false;
return ObjectUtils.isEmpty(supported)
|| ObjectUtils.containsElement(supported, exposure);
} }
private Map<Method, T> discoverOperations(String id, String name, Class<?> type) { private boolean isFilterMatch(EndpointFilter<T> filter,
return MethodIntrospector.selectMethods(type, EndpointInfo<T> endpointInfo) {
(MethodIntrospector.MetadataLookup<T>) ( try {
method) -> createOperationIfPossible(id, name, method)); return filter.match(endpointInfo, this);
}
private T createOperationIfPossible(String endpointId, String beanName,
Method method) {
T operation = createReadOperationIfPossible(endpointId, beanName, method);
if (operation != null) {
return operation;
} }
operation = createWriteOperationIfPossible(endpointId, beanName, method); catch (ClassCastException ex) {
if (operation != null) { String msg = ex.getMessage();
return operation; if (msg == null || msg.startsWith(endpointInfo.getClass().getName())) {
// Possibly a lambda-defined listener which we could not resolve the
// generic event type for
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching info type for lister: " + filter, ex);
}
return false;
}
else {
throw ex;
}
} }
return createDeleteOperationIfPossible(endpointId, beanName, method);
}
private T createReadOperationIfPossible(String endpointId, String beanName,
Method method) {
return createOperationIfPossible(endpointId, beanName, method,
ReadOperation.class, OperationType.READ);
} }
private T createWriteOperationIfPossible(String endpointId, String beanName, private Collection<DiscoveredEndpoint> mergeExposed(
Method method) { Map<Class<?>, DiscoveredEndpoint> endpoints,
return createOperationIfPossible(endpointId, beanName, method, Map<Class<?>, DiscoveredExtension> extensions) {
WriteOperation.class, OperationType.WRITE); List<DiscoveredEndpoint> result = new ArrayList<>();
endpoints.forEach((endpointClass, endpoint) -> {
if (endpoint.isExposed()) {
DiscoveredExtension extension = extensions.remove(endpointClass);
result.add(endpoint.merge(extension));
}
});
return result;
} }
private T createDeleteOperationIfPossible(String endpointId, String beanName, /**
Method method) { * Allows subclasses to verify that the descriptors are correctly configured.
return createOperationIfPossible(endpointId, beanName, method, * @param exposedEndpoints the discovered endpoints to verify before exposing
DeleteOperation.class, OperationType.DELETE); */
protected void verify(Collection<DiscoveredEndpoint> exposedEndpoints) {
} }
private T createOperationIfPossible(String endpointId, String beanName, Method method, /**
Class<? extends Annotation> operationAnnotation, * A discovered endpoint (which may not be valid and might not ultimately be exposed).
OperationType operationType) { */
AnnotationAttributes operationAttributes = AnnotatedElementUtils protected final class DiscoveredEndpoint {
.getMergedAnnotationAttributes(method, operationAnnotation);
if (operationAttributes == null) { private final EndpointInfo<T> info;
return null;
}
CachingConfiguration cachingConfiguration = this.cachingConfigurationFactory
.getCachingConfiguration(endpointId);
return this.operationFactory.createOperation(endpointId, operationAttributes,
this.applicationContext.getBean(beanName), method, operationType,
determineTimeToLive(cachingConfiguration, operationType, method));
}
private long determineTimeToLive(CachingConfiguration cachingConfiguration, private final boolean exposed;
OperationType operationType, Method method) {
if (cachingConfiguration != null && cachingConfiguration.getTimeToLive() > 0 private final Map<OperationKey, List<T>> operations;
&& operationType == OperationType.READ
&& method.getParameters().length == 0) { private DiscoveredEndpoint(Class<?> type, EndpointInfo<T> info, boolean exposed) {
return cachingConfiguration.getTimeToLive(); Assert.notNull(info, "Info must not be null");
this.info = info;
this.exposed = exposed;
this.operations = indexEndpointOperations(type, info);
} }
return 0;
}
/** private Map<OperationKey, List<T>> indexEndpointOperations(Class<?> endpointType,
* An {@code EndpointOperationFactory} creates an {@link Operation} for an operation EndpointInfo<T> info) {
* on an endpoint. return Collections.unmodifiableMap(
* indexOperations(info.getId(), endpointType, info.getOperations()));
* @param <T> the {@link Operation} type }
*/
@FunctionalInterface private DiscoveredEndpoint(EndpointInfo<T> info, boolean exposed,
protected interface EndpointOperationFactory<T extends Operation> { Map<OperationKey, List<T>> operations) {
Assert.notNull(info, "Info must not be null");
this.info = info;
this.exposed = exposed;
this.operations = operations;
}
/** /**
* Creates an {@code EndpointOperation} for an operation on an endpoint. * Return the {@link EndpointInfo} for the discovered endpoint.
* @param endpointId the id of the endpoint * @return the endpoint info
* @param operationAttributes the annotation attributes for the operation
* @param target the target that implements the operation
* @param operationMethod the method on the bean that implements the operation
* @param operationType the type of the operation
* @param timeToLive the caching period in milliseconds
* @return the operation info that describes the operation
*/ */
T createOperation(String endpointId, AnnotationAttributes operationAttributes, public EndpointInfo<T> getInfo() {
Object target, Method operationMethod, OperationType operationType, return this.info;
long timeToLive); }
} /**
* Return {@code true} if the endpoint is exposed.
* @return if the is exposed
*/
private boolean isExposed() {
return this.exposed;
}
/** /**
* Describes a tech-specific extension of an endpoint. * Return all operation that were discovered. These might be different to the ones
* @param <T> the type of the operation * that are in {@link #getInfo()}.
*/ * @return the endpoint operations
private static final class EndpointExtensionInfo<T extends Operation> { */
public Map<OperationKey, List<T>> getOperations() {
return this.operations;
}
private final Class<?> extensionType; /**
* Find any duplicate operations.
* @return any duplicate operations
*/
public Map<OperationKey, List<T>> findDuplicateOperations() {
return this.operations.entrySet().stream()
.filter((entry) -> entry.getValue().size() > 1)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (u, v) -> v,
LinkedHashMap::new));
}
private final Collection<T> operations; private DiscoveredEndpoint merge(DiscoveredExtension extension) {
if (extension == null) {
return this;
}
Map<OperationKey, List<T>> operations = mergeOperations(extension);
EndpointInfo<T> info = new EndpointInfo<>(this.info.getId(),
this.info.isEnableByDefault(), flatten(operations).values());
return new DiscoveredEndpoint(info, this.exposed, operations);
}
private EndpointExtensionInfo(Class<?> extensionType, Collection<T> operations) { private Map<OperationKey, List<T>> mergeOperations(
this.extensionType = extensionType; DiscoveredExtension extension) {
this.operations = operations; MultiValueMap<OperationKey, T> operations = new LinkedMultiValueMap<>(
this.operations);
operations.addAll(indexOperations(getInfo().getId(),
extension.getExtensionType(), extension.getOperations()));
return Collections.unmodifiableMap(operations);
} }
private Class<?> getExtensionType() { private Map<K, T> flatten(Map<OperationKey, List<T>> operations) {
return this.extensionType; Map<K, T> flattened = new LinkedHashMap<>();
operations.forEach((operationKey, value) -> flattened
.put(operationKey.getKey(), getLastValue(value)));
return Collections.unmodifiableMap(flattened);
} }
private Collection<T> getOperations() { private T getLastValue(List<T> value) {
return this.operations; return value.get(value.size() - 1);
}
private MultiValueMap<OperationKey, T> indexOperations(String endpointId,
Class<?> target, Collection<T> operations) {
LinkedMultiValueMap<OperationKey, T> result = new LinkedMultiValueMap<>();
operations.forEach((operation) -> {
K key = getOperationKey(operation);
result.add(new OperationKey(endpointId, target, key), operation);
});
return result;
}
private K getOperationKey(T operation) {
return AnnotationEndpointDiscoverer.this.operationKeyFactory.apply(operation);
}
@Override
public String toString() {
return getInfo().toString();
} }
} }
/** /**
* Describes an {@link EndpointInfo endpoint} and whether or not it is valid. * A discovered extension.
*
* @param <T> the type of the operation
* @param <K> the type of the operation key
*/ */
protected static class EndpointInfoDescriptor<T extends Operation, K> { protected final class DiscoveredExtension {
private final EndpointInfo<T> endpointInfo; private final Class<?> extensionType;
private final Map<OperationKey<K>, List<T>> operations; private final Collection<T> operations;
protected EndpointInfoDescriptor(EndpointInfo<T> endpointInfo, private DiscoveredExtension(Class<?> extensionType, Collection<T> operations) {
Map<OperationKey<K>, List<T>> operations) { this.extensionType = extensionType;
this.endpointInfo = endpointInfo;
this.operations = operations; this.operations = operations;
} }
public EndpointInfo<T> getEndpointInfo() { public Class<?> getExtensionType() {
return this.endpointInfo; return this.extensionType;
} }
public Map<OperationKey<K>, List<T>> findDuplicateOperations() { public Collection<T> getOperations() {
Map<OperationKey<K>, List<T>> duplicateOperations = new HashMap<>(); return this.operations;
this.operations.forEach((k, list) -> { }
if (list.size() > 1) {
duplicateOperations.put(k, list); @Override
} public String toString() {
}); return this.extensionType.getName();
return duplicateOperations;
} }
} }
/** /**
* Define the key of an operation in the context of an operation's implementation. * Define the key of an operation in the context of an operation's implementation.
*
* @param <K> the type of the key
*/ */
protected static final class OperationKey<K> { protected final class OperationKey {
private final String endpointId; private final String endpointId;
private final Class<?> endpointType; private final Class<?> target;
private final K key; private final K key;
public OperationKey(String endpointId, Class<?> endpointType, K key) { public OperationKey(String endpointId, Class<?> target, K key) {
this.endpointId = endpointId; this.endpointId = endpointId;
this.endpointType = endpointType; this.target = target;
this.key = key; this.key = key;
} }
public K getKey() {
return this.key;
}
@Override @Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == this) { if (o == this) {
return true; return true;
@ -394,10 +481,10 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
if (o == null || getClass() != o.getClass()) { if (o == null || getClass() != o.getClass()) {
return false; return false;
} }
OperationKey<?> other = (OperationKey<?>) o; OperationKey other = (OperationKey) o;
Boolean result = true; Boolean result = true;
result = result && this.endpointId.equals(other.endpointId); result = result && this.endpointId.equals(other.endpointId);
result = result && this.endpointType.equals(other.endpointType); result = result && this.target.equals(other.target);
result = result && this.key.equals(other.key); result = result && this.key.equals(other.key);
return result; return result;
} }
@ -405,11 +492,17 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
@Override @Override
public int hashCode() { public int hashCode() {
int result = this.endpointId.hashCode(); int result = this.endpointId.hashCode();
result = 31 * result + this.endpointType.hashCode(); result = 31 * result + this.target.hashCode();
result = 31 * result + this.key.hashCode(); result = 31 * result + this.key.hashCode();
return result; return result;
} }
@Override
public String toString() {
return new ToStringCreator(this).append("endpointId", this.endpointId)
.append("target", this.target).append("key", this.key).toString();
}
} }
} }

@ -36,7 +36,6 @@ public @interface DeleteOperation {
/** /**
* The media type of the result of the operation. * The media type of the result of the operation.
*
* @return the media type * @return the media type
*/ */
String[] produces() default {}; String[] produces() default {};

@ -22,14 +22,28 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
/** /**
* Identifies a type as being an endpoint. * Identifies a type as being an actuator endpoint that provides information about the
* running application. Endpoints can be exposed over a variety of technologies including
* JMX and HTTP.
* <p>
* Most {@code @Endpoint} classes will declare one or more
* {@link ReadOperation @ReadOperation}, {@link WriteOperation @WriteOperation},
* {@link DeleteOperation @DeleteOperation} annotated methods which will be automatically
* adapted to the exposing technology (JMX, Spring MVC, Spring WebFlux, Jersey etc.).
* <p>
* {@code @Endpoint} represents the lowest common denominator for endpoints and
* intentionally limits the sorts of operation methods that may be defined in order to
* support the broadest possible range of exposure technologies. If you need deeper
* support for a specific technology you can either write an endpoint that is
* {@link FilteredEndpoint filtered} to a certain technology, or provide
* {@link EndpointExtension extension} for the broader endpoint.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
* @see EndpointExtension
* @see FilteredEndpoint
* @see AnnotationEndpointDiscoverer * @see AnnotationEndpointDiscoverer
*/ */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@ -41,20 +55,12 @@ public @interface Endpoint {
* The id of the endpoint. * The id of the endpoint.
* @return the id * @return the id
*/ */
String id(); String id() default "";
/**
* Defines the {@link EndpointExposure technologies} over which the endpoint should be
* exposed. By default, all technologies are supported.
* @return the supported endpoint exposure technologies
*/
EndpointExposure[] exposure() default {};
/** /**
* Defines the {@link DefaultEnablement} of the endpoint. By default, the endpoint's * If the endpoint should be enabled or disabled by default.
* enablement defaults to the "default" settings. * @return {@code true} if the endpoint is enabled by default
* @return the default enablement
*/ */
DefaultEnablement defaultEnablement() default DefaultEnablement.NEUTRAL; boolean enableByDefault() default true;
} }

@ -0,0 +1,67 @@
/*
* 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.core.annotation.AliasFor;
/**
* Meta-annotation used to indicate that an annotation provides extension support for an
* endpoint. Extensions allow additional technology specific {@link Operation operations}
* to be added to an existing endpoint. For example, a web extension may offer variations
* of a read operation operation to support filtering based on a query parameter.
* <p>
* Extension annotations must provide an {@link EndpointFilter} to restrict when the
* extension applies. The {@code endpoint} attribute is usually re-declared using
* {@link AliasFor @AliasFor}. For example: <pre class="code">
* &#64;EndpointExtension(filter = WebEndpointFilter.class)
* public &#64;interface EndpointWebExtension {
*
* &#64;AliasFor(annotation = EndpointExtension.class, attribute = "endpoint")
* Class<?> endpoint();
*
* }
* </pre>
*
* @author Phillip Webb
* @since 2.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EndpointExtension {
/**
* The filter class used to determine when the extension applies.
* @return the filter class
*/
Class<? extends EndpointFilter<?>> filter();
/**
* The class of the endpoint to extend.
* @return the class endpoint to extend
*/
Class<?> endpoint() default Void.class;
}

@ -0,0 +1,56 @@
/*
* 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
/**
* Annotation that can be used on an {@link Endpoint @Endpoint} to implement implicit
* filtering. Often used as a meta-annotation on technology specific endpoint annotations,
* for example:<pre class="code">
* &#64;Endpoint
* &#64;FilteredEndpoint(WebEndpointFilter.class)
* public &#64;interface WebEndpoint {
*
* &#64;AliasFor(annotation = Endpoint.class, attribute = "id")
* String id();
*
* &#64;AliasFor(annotation = Endpoint.class, attribute = "enableByDefault")
* boolean enableByDefault() default true;
*
* } </pre>
* @author Phillip Webb
* @since 2.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FilteredEndpoint {
/**
* The filter class to use.
* @return the filter class
*/
Class<? extends EndpointFilter<?>> value();
}

@ -0,0 +1,46 @@
/*
* 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.annotation;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
/**
* Factory to creates an {@link Operation} for an annotated method on an
* {@link Endpoint @Endpoint}.
*
* @param <T> the {@link Operation} type
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
@FunctionalInterface
public interface OperationFactory<T extends Operation> {
/**
* Creates an {@link Operation} for an operation on an endpoint.
* @param endpointId the id of the endpoint
* @param target the target that implements the operation
* @param methodInfo the method on the bean that implements the operation
* @param invoker the invoker that should be used for the operation
* @return the operation info that describes the operation
*/
T createOperation(String endpointId, OperationMethodInfo methodInfo, Object target,
OperationInvoker invoker);
}

@ -0,0 +1,113 @@
/*
* 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.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.MethodIntrospector.MetadataLookup;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
/**
* Factory to creates an {@link Operation} for a annotated methods on an
* {@link Endpoint @Endpoint}.
*
* @param <T> The operation type
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
class OperationsFactory<T extends Operation> {
private static final Map<OperationType, Class<? extends Annotation>> OPERATION_TYPES;
static {
Map<OperationType, Class<? extends Annotation>> operationTypes = new LinkedHashMap<>();
operationTypes.put(OperationType.READ, ReadOperation.class);
operationTypes.put(OperationType.WRITE, WriteOperation.class);
operationTypes.put(OperationType.DELETE, DeleteOperation.class);
OPERATION_TYPES = Collections.unmodifiableMap(operationTypes);
}
private final OperationFactory<T> operationFactory;
private final ParameterMapper parameterMapper;
private final Collection<OperationMethodInvokerAdvisor> invokerAdvisors;
OperationsFactory(OperationFactory<T> operationFactory,
ParameterMapper parameterMapper,
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors) {
this.operationFactory = operationFactory;
this.parameterMapper = parameterMapper;
this.invokerAdvisors = (invokerAdvisors == null ? Collections.emptyList()
: new ArrayList<>(invokerAdvisors));
}
public Map<Method, T> createOperations(String id, Object target, Class<?> type) {
return MethodIntrospector.selectMethods(type,
(MetadataLookup<T>) (method) -> createOperation(id, target, method));
}
private T createOperation(String endpointId, Object target, Method method) {
return OPERATION_TYPES.entrySet().stream()
.map((entry) -> createOperation(endpointId, target, method,
entry.getKey(), entry.getValue()))
.filter(Objects::nonNull).findFirst().orElse(null);
}
private T createOperation(String endpointId, Object target, Method method,
OperationType operationType, Class<? extends Annotation> annotationType) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(method, annotationType);
if (annotationAttributes == null) {
return null;
}
OperationMethodInfo methodInfo = new OperationMethodInfo(method, operationType,
annotationAttributes);
OperationInvoker invoker = new ReflectiveOperationInvoker(target, methodInfo,
this.parameterMapper);
return this.operationFactory.createOperation(endpointId, methodInfo, target,
applyAdvisors(endpointId, methodInfo, invoker));
}
private OperationInvoker applyAdvisors(String endpointId,
OperationMethodInfo methodInfo, OperationInvoker invoker) {
if (this.invokerAdvisors != null) {
for (OperationMethodInvokerAdvisor advisor : this.invokerAdvisors) {
invoker = advisor.apply(endpointId, methodInfo, invoker);
}
}
return invoker;
}
}

@ -35,7 +35,6 @@ public @interface ReadOperation {
/** /**
* The media type of the result of the operation. * The media type of the result of the operation.
*
* @return the media type * @return the media type
*/ */
String[] produces() default {}; String[] produces() default {};

@ -35,7 +35,6 @@ public @interface WriteOperation {
/** /**
* The media type of the result of the operation. * The media type of the result of the operation.
*
* @return the media type * @return the media type
*/ */
String[] produces() default {}; String[] produces() default {};

@ -1,45 +0,0 @@
/*
* 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.cache;
/**
* The caching configuration of an endpoint.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class CachingConfiguration {
private final long timeToLive;
/**
* Create a new instance with the given {@code timeToLive}.
* @param timeToLive the time to live of an operation result in milliseconds
*/
public CachingConfiguration(long timeToLive) {
this.timeToLive = timeToLive;
}
/**
* Returns the time to live of a cached operation result.
* @return the time to live of an operation result
*/
public long getTimeToLive() {
return this.timeToLive;
}
}

@ -0,0 +1,54 @@
/*
* 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.cache;
import java.util.function.Function;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
/**
* {@link OperationMethodInvokerAdvisor} to optionally wrap an {@link OperationInvoker}
* with a {@link CachingOperationInvoker}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class CachingOperationInvokerAdvisor implements OperationMethodInvokerAdvisor {
private final Function<String, Long> endpointIdTimeToLive;
public CachingOperationInvokerAdvisor(Function<String, Long> endpointIdTimeToLive) {
this.endpointIdTimeToLive = endpointIdTimeToLive;
}
@Override
public OperationInvoker apply(String endpointId, OperationMethodInfo methodInfo,
OperationInvoker invoker) {
if (methodInfo.getOperationType() == OperationType.READ
&& methodInfo.getParameters().isEmpty()) {
Long timeToLive = this.endpointIdTimeToLive.apply(endpointId);
if (timeToLive != null && timeToLive > 0) {
return new CachingOperationInvoker(invoker, timeToLive);
}
}
return invoker;
}
}

@ -16,8 +16,8 @@
package org.springframework.boot.actuate.endpoint.convert; package org.springframework.boot.actuate.endpoint.convert;
import org.springframework.boot.actuate.endpoint.ParameterMapper; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.ParameterMappingException; import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService; import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.DefaultFormattingConversionService;

@ -33,8 +33,8 @@ import javax.management.ReflectionException;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.EndpointInfo; import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.ParameterMappingException; import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException; import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@ -76,7 +76,7 @@ public class EndpointMBean implements DynamicMBean {
@Override @Override
public Object invoke(String actionName, Object[] params, String[] signature) public Object invoke(String actionName, Object[] params, String[] signature)
throws MBeanException, ReflectionException { throws MBeanException, ReflectionException {
JmxEndpointOperation operation = this.endpointInfo.getOperations() JmxOperation operation = this.endpointInfo.getOperations()
.get(actionName); .get(actionName);
if (operation != null) { if (operation != null) {
Map<String, Object> arguments = getArguments(params, Map<String, Object> arguments = getArguments(params,

@ -36,10 +36,10 @@ public final class EndpointMBeanInfo {
private final MBeanInfo mBeanInfo; private final MBeanInfo mBeanInfo;
private final Map<String, JmxEndpointOperation> operations; private final Map<String, JmxOperation> operations;
public EndpointMBeanInfo(String endpointId, MBeanInfo mBeanInfo, public EndpointMBeanInfo(String endpointId, MBeanInfo mBeanInfo,
Map<String, JmxEndpointOperation> operations) { Map<String, JmxOperation> operations) {
this.endpointId = endpointId; this.endpointId = endpointId;
this.mBeanInfo = mBeanInfo; this.mBeanInfo = mBeanInfo;
this.operations = operations; this.operations = operations;
@ -53,7 +53,7 @@ public final class EndpointMBeanInfo {
return this.mBeanInfo; return this.mBeanInfo;
} }
public Map<String, JmxEndpointOperation> getOperations() { public Map<String, JmxOperation> getOperations() {
return this.operations; return this.operations;
} }

@ -54,12 +54,12 @@ class EndpointMBeanInfoAssembler {
* @return the mbean info for the endpoint * @return the mbean info for the endpoint
*/ */
EndpointMBeanInfo createEndpointMBeanInfo( EndpointMBeanInfo createEndpointMBeanInfo(
EndpointInfo<JmxEndpointOperation> endpointInfo) { EndpointInfo<JmxOperation> endpointInfo) {
Map<String, OperationInfos> operationsMapping = getOperationInfo(endpointInfo); Map<String, OperationInfos> operationsMapping = getOperationInfo(endpointInfo);
ModelMBeanOperationInfo[] operationsMBeanInfo = operationsMapping.values() ModelMBeanOperationInfo[] operationsMBeanInfo = operationsMapping.values()
.stream().map((t) -> t.mBeanOperationInfo).collect(Collectors.toList()) .stream().map((t) -> t.mBeanOperationInfo).collect(Collectors.toList())
.toArray(new ModelMBeanOperationInfo[] {}); .toArray(new ModelMBeanOperationInfo[] {});
Map<String, JmxEndpointOperation> operationsInfo = new LinkedHashMap<>(); Map<String, JmxOperation> operationsInfo = new LinkedHashMap<>();
operationsMapping.forEach((name, t) -> operationsInfo.put(name, t.operation)); operationsMapping.forEach((name, t) -> operationsInfo.put(name, t.operation));
MBeanInfo info = new ModelMBeanInfoSupport(EndpointMBean.class.getName(), MBeanInfo info = new ModelMBeanInfoSupport(EndpointMBean.class.getName(),
getDescription(endpointInfo), new ModelMBeanAttributeInfo[0], getDescription(endpointInfo), new ModelMBeanAttributeInfo[0],
@ -73,7 +73,7 @@ class EndpointMBeanInfoAssembler {
} }
private Map<String, OperationInfos> getOperationInfo( private Map<String, OperationInfos> getOperationInfo(
EndpointInfo<JmxEndpointOperation> endpointInfo) { EndpointInfo<JmxOperation> endpointInfo) {
Map<String, OperationInfos> operationInfos = new HashMap<>(); Map<String, OperationInfos> operationInfos = new HashMap<>();
endpointInfo.getOperations().forEach((operationInfo) -> { endpointInfo.getOperations().forEach((operationInfo) -> {
String name = operationInfo.getOperationName(); String name = operationInfo.getOperationName();
@ -88,7 +88,7 @@ class EndpointMBeanInfoAssembler {
return operationInfos; return operationInfos;
} }
private MBeanParameterInfo[] getMBeanParameterInfos(JmxEndpointOperation operation) { private MBeanParameterInfo[] getMBeanParameterInfos(JmxOperation operation) {
return operation.getParameters().stream() return operation.getParameters().stream()
.map((operationParameter) -> new MBeanParameterInfo( .map((operationParameter) -> new MBeanParameterInfo(
operationParameter.getName(), operationParameter.getName(),
@ -113,10 +113,10 @@ class EndpointMBeanInfoAssembler {
private final ModelMBeanOperationInfo mBeanOperationInfo; private final ModelMBeanOperationInfo mBeanOperationInfo;
private final JmxEndpointOperation operation; private final JmxOperation operation;
OperationInfos(ModelMBeanOperationInfo mBeanOperationInfo, OperationInfos(ModelMBeanOperationInfo mBeanOperationInfo,
JmxEndpointOperation operation) { JmxOperation operation) {
this.mBeanOperationInfo = mBeanOperationInfo; this.mBeanOperationInfo = mBeanOperationInfo;
this.operation = operation; this.operation = operation;
} }

@ -50,11 +50,11 @@ public class JmxEndpointMBeanFactory {
* @return the MBeans * @return the MBeans
*/ */
public Collection<EndpointMBean> createMBeans( public Collection<EndpointMBean> createMBeans(
Collection<EndpointInfo<JmxEndpointOperation>> endpoints) { Collection<EndpointInfo<JmxOperation>> endpoints) {
return endpoints.stream().map(this::createMBean).collect(Collectors.toList()); return endpoints.stream().map(this::createMBean).collect(Collectors.toList());
} }
private EndpointMBean createMBean(EndpointInfo<JmxEndpointOperation> endpointInfo) { private EndpointMBean createMBean(EndpointInfo<JmxOperation> endpointInfo) {
EndpointMBeanInfo endpointMBeanInfo = this.assembler EndpointMBeanInfo endpointMBeanInfo = this.assembler
.createEndpointMBeanInfo(endpointInfo); .createEndpointMBeanInfo(endpointInfo);
return new EndpointMBean(this.resultMapper::mapResponse, endpointMBeanInfo); return new EndpointMBean(this.resultMapper::mapResponse, endpointMBeanInfo);

@ -22,6 +22,7 @@ import java.util.List;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.OperationInvoker; import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.core.style.ToStringCreator;
/** /**
* An operation on a JMX endpoint. * An operation on a JMX endpoint.
@ -30,7 +31,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
*/ */
public class JmxEndpointOperation extends Operation { public class JmxOperation extends Operation {
private final String operationName; private final String operationName;
@ -50,7 +51,7 @@ public class JmxEndpointOperation extends Operation {
* @param description the description of the operation * @param description the description of the operation
* @param parameters the parameters of the operation * @param parameters the parameters of the operation
*/ */
public JmxEndpointOperation(OperationType type, OperationInvoker invoker, public JmxOperation(OperationType type, OperationInvoker invoker,
String operationName, Class<?> outputType, String description, String operationName, Class<?> outputType, String description,
List<JmxEndpointOperationParameterInfo> parameters) { List<JmxEndpointOperationParameterInfo> parameters) {
super(type, invoker, true); super(type, invoker, true);
@ -92,4 +93,11 @@ public class JmxEndpointOperation extends Operation {
return Collections.unmodifiableList(this.parameters); return Collections.unmodifiableList(this.parameters);
} }
@Override
public String toString() {
return new ToStringCreator(this).append("operationName", this.operationName)
.append("outputType", this.outputType)
.append("description", this.description).toString();
}
} }

@ -23,6 +23,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.core.annotation.AliasFor;
/** /**
* Identifies a type as being a JMX-specific extension of an {@link Endpoint}. * Identifies a type as being a JMX-specific extension of an {@link Endpoint}.
@ -34,12 +36,14 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface JmxEndpointExtension { @EndpointExtension(filter = JmxEndpointFilter.class)
public @interface EndpointJmxExtension {
/** /**
* The {@link Endpoint endpoint} class to which this JMX extension relates. * The {@link Endpoint endpoint} class to which this JMX extension relates.
* @return the endpoint class * @return the endpoint class
*/ */
@AliasFor(annotation = EndpointExtension.class)
Class<?> endpoint(); Class<?> endpoint();
} }

@ -16,78 +16,56 @@
package org.springframework.boot.actuate.endpoint.jmx.annotation; package org.springframework.boot.actuate.endpoint.jmx.annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.ParameterMapper;
import org.springframework.boot.actuate.endpoint.ReflectiveOperationInvoker;
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration; import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory; import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker; import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperation;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperationParameterInfo;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource; import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.metadata.ManagedOperation;
import org.springframework.jmx.export.metadata.ManagedOperationParameter;
import org.springframework.util.StringUtils;
/** /**
* Discovers the {@link Endpoint endpoints} in an {@link ApplicationContext} with * Discovers the {@link Endpoint endpoints} in an {@link ApplicationContext} with
* {@link JmxEndpointExtension JMX extensions} applied to them. * {@link EndpointJmxExtension JMX extensions} applied to them.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
*/ */
public class JmxAnnotationEndpointDiscoverer public class JmxAnnotationEndpointDiscoverer
extends AnnotationEndpointDiscoverer<JmxEndpointOperation, String> { extends AnnotationEndpointDiscoverer<String, JmxOperation> {
private static final AnnotationJmxAttributeSource jmxAttributeSource = new AnnotationJmxAttributeSource(); static final AnnotationJmxAttributeSource jmxAttributeSource = new AnnotationJmxAttributeSource();
/** /**
* Creates a new {@link JmxAnnotationEndpointDiscoverer} that will discover * Creates a new {@link JmxAnnotationEndpointDiscoverer} that will discover
* {@link Endpoint endpoints} and {@link JmxEndpointExtension jmx extensions} using * {@link Endpoint endpoints} and {@link EndpointJmxExtension jmx extensions} using
* the given {@link ApplicationContext}. * the given {@link ApplicationContext}.
* @param applicationContext the application context * @param applicationContext the application context
* @param parameterMapper the {@link ParameterMapper} used to convert arguments when * @param parameterMapper the {@link ParameterMapper} used to convert arguments when
* an operation is invoked * an operation is invoked
* @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use * @param invokerAdvisors advisors used to add additional invoker advise
* @param filters filters that must match for an endpoint to be exposed
*/ */
public JmxAnnotationEndpointDiscoverer(ApplicationContext applicationContext, public JmxAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
ParameterMapper parameterMapper, ParameterMapper parameterMapper,
CachingConfigurationFactory cachingConfigurationFactory) { Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
super(applicationContext, new JmxEndpointOperationFactory(parameterMapper), Collection<? extends EndpointFilter<JmxOperation>> filters) {
JmxEndpointOperation::getOperationName, cachingConfigurationFactory); super(applicationContext, new JmxEndpointOperationFactory(),
JmxOperation::getOperationName, parameterMapper, invokerAdvisors,
filters);
} }
@Override @Override
public Collection<EndpointInfo<JmxEndpointOperation>> discoverEndpoints() { protected void verify(Collection<DiscoveredEndpoint> exposedEndpoints) {
Collection<EndpointInfoDescriptor<JmxEndpointOperation, String>> endpointDescriptors = discoverEndpoints( List<List<JmxOperation>> clashes = new ArrayList<>();
JmxEndpointExtension.class, EndpointExposure.JMX); exposedEndpoints.forEach((exposedEndpoint) -> clashes
verifyThatOperationsHaveDistinctName(endpointDescriptors); .addAll(exposedEndpoint.findDuplicateOperations().values()));
return endpointDescriptors.stream().map(EndpointInfoDescriptor::getEndpointInfo)
.collect(Collectors.toList());
}
private void verifyThatOperationsHaveDistinctName(
Collection<EndpointInfoDescriptor<JmxEndpointOperation, String>> endpointDescriptors) {
List<List<JmxEndpointOperation>> clashes = new ArrayList<>();
endpointDescriptors.forEach((descriptor) -> clashes
.addAll(descriptor.findDuplicateOperations().values()));
if (!clashes.isEmpty()) { if (!clashes.isEmpty()) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.append( message.append(
@ -102,97 +80,4 @@ public class JmxAnnotationEndpointDiscoverer
} }
} }
private static class JmxEndpointOperationFactory
implements EndpointOperationFactory<JmxEndpointOperation> {
private final ParameterMapper parameterMapper;
JmxEndpointOperationFactory(ParameterMapper parameterMapper) {
this.parameterMapper = parameterMapper;
}
@Override
public JmxEndpointOperation createOperation(String endpointId,
AnnotationAttributes operationAttributes, Object target, Method method,
OperationType type, long timeToLive) {
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(target,
method, this.parameterMapper);
String operationName = method.getName();
Class<?> outputType = getJmxType(method.getReturnType());
String description = getDescription(method,
() -> "Invoke " + operationName + " for endpoint " + endpointId);
List<JmxEndpointOperationParameterInfo> parameters = getParameters(invoker);
return new JmxEndpointOperation(type,
CachingOperationInvoker.apply(invoker, timeToLive), operationName,
outputType, description, parameters);
}
private String getDescription(Method method, Supplier<String> fallback) {
ManagedOperation managedOperation = jmxAttributeSource
.getManagedOperation(method);
if (managedOperation != null
&& StringUtils.hasText(managedOperation.getDescription())) {
return managedOperation.getDescription();
}
return fallback.get();
}
private List<JmxEndpointOperationParameterInfo> getParameters(
ReflectiveOperationInvoker invoker) {
if (invoker.getMethod().getParameterCount() == 0) {
return Collections.emptyList();
}
ManagedOperationParameter[] operationParameters = jmxAttributeSource
.getManagedOperationParameters(invoker.getMethod());
if (operationParameters.length == 0) {
return invoker.getParameters(this::getParameter);
}
return mergeParameters(invoker.getMethod().getParameters(),
operationParameters);
}
private List<JmxEndpointOperationParameterInfo> mergeParameters(
Parameter[] methodParameters,
ManagedOperationParameter[] operationParameters) {
List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>();
for (int i = 0; i < operationParameters.length; i++) {
ManagedOperationParameter operationParameter = operationParameters[i];
Parameter methodParameter = methodParameters[i];
JmxEndpointOperationParameterInfo parameter = getParameter(
operationParameter.getName(), methodParameter,
operationParameter.getDescription());
parameters.add(parameter);
}
return parameters;
}
private JmxEndpointOperationParameterInfo getParameter(String name,
Parameter methodParameter) {
return getParameter(name, methodParameter, null);
}
private JmxEndpointOperationParameterInfo getParameter(String name,
Parameter methodParameter, String description) {
return new JmxEndpointOperationParameterInfo(name,
getJmxType(methodParameter.getType()), description);
}
private Class<?> getJmxType(Class<?> type) {
if (type.isEnum()) {
return String.class;
}
if (Date.class.isAssignableFrom(type)) {
return String.class;
}
if (type.getName().startsWith("java.")) {
return type;
}
if (type.equals(Void.TYPE)) {
return type;
}
return Object.class;
}
}
} }

@ -0,0 +1,59 @@
/*
* 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.jmx.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint;
import org.springframework.core.annotation.AliasFor;
/**
* Identifies a type as being an endpoint that is only exposed over JMX.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
* @see AnnotationEndpointDiscoverer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Endpoint
@FilteredEndpoint(JmxEndpointFilter.class)
public @interface JmxEndpoint {
/**
* The id of the endpoint.
* @return the id
*/
@AliasFor(annotation = Endpoint.class)
String id() default "";
/**
* If the endpoint should be enabled or disabled by default.
* @return {@code true} if the endpoint is enabled by default
*/
@AliasFor(annotation = Endpoint.class)
boolean enableByDefault() default true;
}

@ -0,0 +1,38 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.jmx.annotation;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
/**
* {@link EndpointFilter} for endpoints discovered by
* {@link JmxAnnotationEndpointDiscoverer}.
*
* @author Phillip Webb
*/
class JmxEndpointFilter implements EndpointFilter<JmxOperation> {
@Override
public boolean match(EndpointInfo<JmxOperation> info,
EndpointDiscoverer<JmxOperation> discoverer) {
return (discoverer instanceof JmxAnnotationEndpointDiscoverer);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save