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.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
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.logging.LoggersEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -63,9 +63,9 @@ public class AuditEventsEndpointAutoConfiguration {
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(AuditEventsEndpoint.class)
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension(
public AuditEventsEndpointWebExtension auditEventsWebEndpointExtension(
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.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.http.HttpMethod;
@ -81,7 +81,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
}
@Override
protected void registerMappingForOperation(WebEndpointOperation operation) {
protected void registerMappingForOperation(WebOperation operation) {
OperationType operationType = operation.getType();
OperationInvoker operationInvoker = operation.getInvoker();
if (operation.isBlocking()) {
@ -135,7 +135,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
* @param securityInterceptor the Security Interceptor
*/
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
Collection<EndpointInfo<WebOperation>> webEndpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);

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

@ -18,9 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
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.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.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
@ -71,17 +70,17 @@ public class CloudFoundryActuatorAutoConfiguration {
@Bean
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
ParameterMapper parameterMapper,
DefaultCachingConfigurationFactory cachingConfigurationFactory,
EndpointMediaTypes endpointMediaTypes, Environment environment,
RestTemplateBuilder builder) {
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
RestTemplateBuilder restTemplateBuilder) {
WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, parameterMapper, cachingConfigurationFactory,
endpointMediaTypes, EndpointPathResolver.useEndpointId());
this.applicationContext, parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), null, null);
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
restTemplateBuilder, this.applicationContext.getEnvironment());
return new CloudFoundryWebEndpointServletHandlerMapping(
new EndpointMapping("/cloudfoundryapplication"),
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
getCorsConfiguration(), getSecurityInterceptor(builder, environment));
getCorsConfiguration(), securityInterceptor);
}
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.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.Link;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.http.HttpMethod;
@ -78,7 +78,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
Collection<EndpointInfo<WebOperation>> webEndpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
CloudFoundrySecurityInterceptor securityInterceptor) {
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
@ -125,7 +125,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
}
@Override
protected void registerMappingForOperation(WebEndpointOperation operation) {
protected void registerMappingForOperation(WebOperation operation) {
registerMapping(createRequestMappingInfo(operation),
new OperationHandler(operation.getInvoker(), operation.getId(),
this.securityInterceptor),

@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll
* @since 2.0.0
*/
@ConfigurationProperties("endpoints.configprops")
@ConfigurationProperties("management.endpoint.configprops")
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;
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.cache.CachingConfigurationFactory;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
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.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.Configuration;
import org.springframework.core.env.Environment;
@ -53,50 +42,12 @@ public class EndpointAutoConfiguration {
return new ConversionServiceParameterMapper();
}
@Bean
@ConditionalOnMissingBean(CachingConfigurationFactory.class)
public DefaultCachingConfigurationFactory endpointCacheConfigurationFactory(
Environment environment) {
return new DefaultCachingConfigurationFactory(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);
}
public CachingOperationInvokerAdvisor endpointCachingOperationInvokerAdvisor(
Environment environment) {
return new CachingOperationInvokerAdvisor(
new EndpointIdTimeToLivePropertyFunction(environment));
}
}

@ -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.Target;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
/**
* {@link Conditional} that checks whether an endpoint is enabled or not. Matches
* according to the {@code defaultEnablement} and {@code types} flag that the
* {@link Endpoint} may be restricted to.
* <p>
* 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.
* according to the endpoints specific {@link Environment} property, falling back to
* {@code management.endpoints.enabled-by-default} or failing that
* {@link Endpoint#enableByDefault()}.
*
* @author Stephane Nicoll
* @since 2.0.0

@ -16,45 +16,62 @@
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.jmx.annotation.JmxEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Bean;
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.MethodMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* A condition that checks if an endpoint is enabled.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
* @see ConditionalOnEnabledEndpoint
*/
class OnEnabledEndpointCondition extends SpringBootCondition {
private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default";
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
EndpointAttributes attributes = getEndpointAttributes(context, metadata);
EndpointEnablement endpointEnablement = attributes
.getEnablement(new EndpointEnablementProvider(context.getEnvironment()));
return new ConditionOutcome(endpointEnablement.isEnabled(),
AnnotationAttributes attributes = getEndpointAttributes(context, metadata);
String id = attributes.getString("id");
String key = "management.endpoint." + id + ".enabled";
Boolean userDefinedEnabled = context.getEnvironment().getProperty(key,
Boolean.class);
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(endpointEnablement.getReason()));
.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) {
Assert.state(
metadata instanceof MethodMetadata
@ -63,77 +80,35 @@ class OnEnabledEndpointCondition extends SpringBootCondition {
return getEndpointAttributes(context, (MethodMetadata) metadata);
}
private EndpointAttributes getEndpointAttributes(ConditionContext context,
MethodMetadata methodMetadata) {
private AnnotationAttributes getEndpointAttributes(ConditionContext context,
MethodMetadata metadata) {
// We should be safe to load at this point since we are in the REGISTER_BEAN phase
try {
// We should be safe to load at this point since we are in the
// REGISTER_BEAN phase
Class<?> returnType = ClassUtils.forName(methodMetadata.getReturnTypeName(),
Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(),
context.getClassLoader());
return extractEndpointAttributes(returnType);
return getEndpointAttributes(returnType);
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to extract endpoint id for "
+ methodMetadata.getDeclaringClassName() + "."
+ methodMetadata.getMethodName(), ex);
+ metadata.getDeclaringClassName() + "." + metadata.getMethodName(),
ex);
}
}
protected EndpointAttributes extractEndpointAttributes(Class<?> type) {
EndpointAttributes attributes = extractEndpointAttributesFromEndpoint(type);
protected AnnotationAttributes getEndpointAttributes(Class<?> type) {
AnnotationAttributes attributes = AnnotatedElementUtils
.findMergedAnnotationAttributes(type, Endpoint.class, true, true);
if (attributes == null) {
attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type,
EndpointExtension.class, false, true);
if (attributes != null) {
return attributes;
}
JmxEndpointExtension jmxExtension = AnnotationUtils.findAnnotation(type,
JmxEndpointExtension.class);
if (jmxExtension != null) {
return extractEndpointAttributes(jmxExtension.endpoint());
return getEndpointAttributes(attributes.getClass("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 {
private final String id;
private final DefaultEnablement defaultEnablement;
private final EndpointExposure exposure;
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 {
private final JmxEndpointExporterProperties properties;
private final JmxEndpointProperties properties;
private final MBeanServer mBeanServer;
private final String contextId;
DefaultEndpointObjectNameFactory(JmxEndpointExporterProperties properties,
DefaultEndpointObjectNameFactory(JmxEndpointProperties properties,
MBeanServer mBeanServer, String contextId) {
this.properties = properties;
this.mBeanServer = mBeanServer;

@ -16,21 +16,25 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
import java.util.Collection;
import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.DefaultCachingConfigurationFactory;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.ParameterMapper;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
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.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
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.jmx.JmxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -47,36 +51,49 @@ import org.springframework.util.ObjectUtils;
* @since 2.0.0
*/
@AutoConfigureAfter(JmxAutoConfiguration.class)
@EnableConfigurationProperties(JmxEndpointExporterProperties.class)
@EnableConfigurationProperties(JmxEndpointProperties.class)
@ConditionalOnProperty(name = "management.endpoints.jmx.enabled", matchIfMissing = true)
public class JmxEndpointAutoConfiguration {
private final ApplicationContext applicationContext;
public JmxEndpointAutoConfiguration(ApplicationContext applicationContext) {
private final JmxEndpointProperties properties;
public JmxEndpointAutoConfiguration(ApplicationContext applicationContext,
JmxEndpointProperties properties) {
this.applicationContext = applicationContext;
this.properties = properties;
}
@Bean
public JmxAnnotationEndpointDiscoverer jmxEndpointDiscoverer(
public JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer(
ParameterMapper parameterMapper,
DefaultCachingConfigurationFactory cachingConfigurationFactory) {
Collection<OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<JmxOperation>> filters) {
return new JmxAnnotationEndpointDiscoverer(this.applicationContext,
parameterMapper, cachingConfigurationFactory);
parameterMapper, invokerAdvisors, filters);
}
@ConditionalOnSingleCandidate(MBeanServer.class)
@Bean
public JmxEndpointExporter jmxMBeanExporter(JmxEndpointExporterProperties properties,
@ConditionalOnSingleCandidate(MBeanServer.class)
public JmxEndpointExporter jmxMBeanExporter(
JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer,
MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer,
ObjectProvider<ObjectMapper> objectMapper) {
EndpointProvider<JmxEndpointOperation> endpointProvider = new EndpointProvider<>(
this.applicationContext.getEnvironment(), endpointDiscoverer,
EndpointExposure.JMX);
EndpointMBeanRegistrar endpointMBeanRegistrar = new EndpointMBeanRegistrar(
mBeanServer, new DefaultEndpointObjectNameFactory(properties, mBeanServer,
ObjectUtils.getIdentityHexString(this.applicationContext)));
return new JmxEndpointExporter(endpointProvider, endpointMBeanRegistrar,
EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(
this.properties, mBeanServer,
ObjectUtils.getIdentityHexString(this.applicationContext));
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(mBeanServer,
objectNameFactory);
return new JmxEndpointExporter(jmxAnnotationEndpointDiscoverer, registrar,
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.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.management.MBeanServer;
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.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.jmx.EndpointMBean;
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar;
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.annotation.JmxAnnotationEndpointDiscoverer;
/**
* 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 {
private final EndpointProvider<JmxEndpointOperation> endpointProvider;
private final JmxAnnotationEndpointDiscoverer endpointDiscoverer;
private final EndpointMBeanRegistrar endpointMBeanRegistrar;
@ -52,9 +53,9 @@ class JmxEndpointExporter implements InitializingBean, DisposableBean {
private Collection<ObjectName> registeredObjectNames;
JmxEndpointExporter(EndpointProvider<JmxEndpointOperation> endpointProvider,
JmxEndpointExporter(JmxAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMBeanRegistrar endpointMBeanRegistrar, ObjectMapper objectMapper) {
this.endpointProvider = endpointProvider;
this.endpointDiscoverer = endpointDiscoverer;
this.endpointMBeanRegistrar = endpointMBeanRegistrar;
DataConverter dataConverter = new DataConverter(objectMapper);
this.mBeanFactory = new JmxEndpointMBeanFactory(dataConverter);
@ -65,21 +66,19 @@ class JmxEndpointExporter implements InitializingBean, DisposableBean {
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
public void destroy() throws Exception {
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) {
objectNames.forEach(this.endpointMBeanRegistrar::unregisterEndpointMbean);

@ -16,7 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
@ -29,7 +31,22 @@ import org.springframework.util.StringUtils;
* @since 2.0.0
*/
@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.
@ -47,13 +64,37 @@ public class JmxEndpointExporterProperties {
*/
private final Properties staticNames = new Properties();
public JmxEndpointExporterProperties(Environment environment) {
public JmxEndpointProperties(Environment environment) {
String defaultDomain = environment.getProperty("spring.jmx.default-domain");
if (StringUtils.hasText(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() {
return this.domain;
}

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

@ -14,7 +14,9 @@
* 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.core.env.Environment;
@ -27,16 +29,15 @@ import org.springframework.core.env.Environment;
*/
class DefaultEndpointPathResolver implements EndpointPathResolver {
private final Environment environment;
private final Map<String, String> pathMapping;
DefaultEndpointPathResolver(Environment environment) {
this.environment = environment;
DefaultEndpointPathResolver(Map<String, String> pathMapping) {
this.pathMapping = pathMapping;
}
@Override
public String resolvePath(String endpointId) {
String key = String.format("endpoints.%s.web.path", endpointId);
return this.environment.getProperty(key, String.class, endpointId);
return this.pathMapping.getOrDefault(endpointId, 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;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for web management endpoints.
*
* @author Madhura Bhave
* @author Phillip Webb
* @since 2.0.0
*/
@ConfigurationProperties(prefix = "management.endpoints.web")
public class WebEndpointProperties {
/**
* Whether web endpoints are enabled.
*/
private boolean enabled;
/**
* The base-path for the web endpoints. Relative to `server.context-path` or
* `management.server.context-path`, if `management.server.port` is different.
*/
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() {
return this.basePath;
}
@ -41,4 +75,28 @@ public class WebEndpointProperties {
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.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.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -49,27 +46,19 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(ResourceConfig.class)
@ConditionalOnBean(ResourceConfig.class)
@ConditionalOnBean({ ResourceConfig.class, WebAnnotationEndpointDiscoverer.class })
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
class JerseyWebEndpointManagementContextConfiguration {
@Bean
public ResourceConfigCustomizer webEndpointRegistrar(
EndpointProvider<WebEndpointOperation> provider,
WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
WebEndpointProperties webEndpointProperties) {
return (resourceConfig) -> resourceConfig.registerResources(
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
new EndpointMapping(webEndpointProperties.getBasePath()),
provider.getEndpoints(), endpointMediaTypes)));
}
@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
WebEndpointProperties webEndpointProperties) {
return new DefaultEndpointPathProvider(provider, webEndpointProperties);
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes)));
}
}

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

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

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

@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll
* @since 2.0.0
*/
@ConfigurationProperties("endpoints.env")
@ConfigurationProperties("management.endpoint.env")
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.HealthAggregator;
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.HealthReactiveWebEndpointExtension;
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.ReactiveHealthEndpointWebExtension;
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.StatusReactiveWebEndpointExtension;
import org.springframework.boot.actuate.health.StatusWebEndpointExtension;
import org.springframework.boot.actuate.health.StatusEndpointWebExtension;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@ -84,9 +84,9 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class)
public HealthReactiveWebEndpointExtension healthWebEndpointExtension(
public ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
HealthStatusHttpMapper healthStatusHttpMapper) {
return new HealthReactiveWebEndpointExtension(this.reactiveHealthIndicator,
return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator,
healthStatusHttpMapper);
}
@ -94,9 +94,9 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(StatusEndpoint.class)
public StatusReactiveWebEndpointExtension statusWebEndpointExtension(
public ReactiveStatusEndpointWebExtension reactiveStatusEndpointWebExtension(
HealthStatusHttpMapper healthStatusHttpMapper) {
return new StatusReactiveWebEndpointExtension(this.reactiveHealthIndicator,
return new ReactiveStatusEndpointWebExtension(this.reactiveHealthIndicator,
healthStatusHttpMapper);
}
@ -110,18 +110,18 @@ public class HealthWebEndpointManagementContextConfiguration {
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class)
public HealthWebEndpointExtension healthWebEndpointExtension(
public HealthEndpointWebExtension healthEndpointWebExtension(
HealthEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) {
return new HealthWebEndpointExtension(delegate, healthStatusHttpMapper);
return new HealthEndpointWebExtension(delegate, healthStatusHttpMapper);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(StatusEndpoint.class)
public StatusWebEndpointExtension statusWebEndpointExtension(
public StatusEndpointWebExtension statusEndpointWebExtension(
StatusEndpoint delegate, HealthStatusHttpMapper healthStatusHttpMapper) {
return new StatusWebEndpointExtension(delegate, healthStatusHttpMapper);
return new StatusEndpointWebExtension(delegate, healthStatusHttpMapper);
}
}

@ -71,10 +71,11 @@ public class LogFileWebEndpointManagementContextConfiguration {
return ConditionOutcome
.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)) {
return ConditionOutcome.match(
message.found("endpoints.logfile.external-file").items(config));
return ConditionOutcome
.match(message.found("management.endpoint.logfile.external-file")
.items(config));
}
return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll());
}

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

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

@ -13,6 +13,7 @@ org.springframework.boot.actuate.autoconfigure.couchbase.CouchbaseHealthIndicato
org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticsearchHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\
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.flyway.FlywayEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,\

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

@ -44,8 +44,9 @@ public class BeansEndpointAutoConfigurationTests {
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception {
this.contextRunner.withPropertyValues("endpoints.beans.enabled:false").run(
(context) -> assertThat(context).doesNotHaveBean(BeansEndpoint.class));
this.contextRunner.withPropertyValues("management.endpoint.beans.enabled:false")
.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.CloudFoundryAuthorizationException;
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.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
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.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
@ -217,7 +218,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
@Bean
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
ReactiveCloudFoundrySecurityInterceptor interceptor) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
@ -236,8 +237,8 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0),
endpointMediaTypes, EndpointPathResolver.useEndpointId());
parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), null, null);
}
@Bean

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

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.After;
@ -24,13 +25,14 @@ import org.junit.Before;
import org.junit.Test;
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.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
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.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@ -79,7 +81,7 @@ public class CloudFoundryActuatorAutoConfigurationTests {
RestTemplateAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
CloudFoundryActuatorAutoConfiguration.class);
}
@ -208,30 +210,34 @@ public class CloudFoundryActuatorAutoConfigurationTests {
}
@Test
public void allEndpointsAvailableUnderCloudFoundryWithoutEnablingWeb()
public void allEndpointsAvailableUnderCloudFoundryWithoutExposeAllOnWeb()
throws Exception {
this.context.register(TestConfiguration.class);
this.context.refresh();
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
List<EndpointInfo<WebEndpointOperation>> endpoints = (List<EndpointInfo<WebEndpointOperation>>) handlerMapping
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
.getEndpoints();
assertThat(endpoints.size()).isEqualTo(1);
assertThat(endpoints.get(0).getId()).isEqualTo("test");
assertThat(endpoints.stream()
.filter((candidate) -> "test".equals(candidate.getId())).findFirst())
.isNotEmpty();
}
@Test
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);
this.context.register(TestConfiguration.class);
this.context.refresh();
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
List<EndpointInfo<WebEndpointOperation>> endpoints = (List<EndpointInfo<WebEndpointOperation>>) handlerMapping
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
.getEndpoints();
assertThat(endpoints.size()).isEqualTo(1);
assertThat(endpoints.get(0).getOperations()).hasSize(1);
assertThat(endpoints.get(0).getOperations().iterator().next()
.getRequestPredicate().getPath()).isEqualTo("test");
EndpointInfo<WebOperation> endpoint = endpoints.stream()
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
.get();
Collection<WebOperation> operations = endpoint.getOperations();
assertThat(operations).hasSize(1);
assertThat(operations.iterator().next().getRequestPredicate().getPath())
.isEqualTo("test");
}
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.CloudFoundryAuthorizationException;
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.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
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.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -203,7 +204,7 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
@Bean
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
WebAnnotationEndpointDiscoverer webEndpointDiscoverer,
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
CloudFoundrySecurityInterceptor interceptor) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
@ -222,8 +223,8 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0),
endpointMediaTypes, EndpointPathResolver.useEndpointId());
parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), null, null);
}
@Bean

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

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

@ -53,7 +53,8 @@ public class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception {
this.contextRunner.withPropertyValues("endpoints.configprops.enabled:false")
this.contextRunner
.withPropertyValues("management.endpoint.configprops.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(ConfigurationPropertiesReportEndpoint.class));
}
@ -62,7 +63,7 @@ public class ConfigurationPropertiesReportEndpointAutoConfigurationTests {
public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception {
this.contextRunner.withUserConfiguration(Config.class)
.withPropertyValues(
"endpoints.configprops.keys-to-sanitize: .*pass.*, property")
"management.endpoint.configprops.keys-to-sanitize: .*pass.*, property")
.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;
import java.util.function.Function;
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 static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultCachingConfigurationFactory}.
* Tests for {@link EndpointIdTimeToLivePropertyFunction}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class DefaultCachingConfigurationFactoryTests {
public class EndpointIdTimeToLivePropertyFunctionTests {
private final MockEnvironment environment = new MockEnvironment();
private final CachingConfigurationFactory factory = new DefaultCachingConfigurationFactory(
private final Function<String, Long> timeToLive = new EndpointIdTimeToLivePropertyFunction(
this.environment);
@Test
public void defaultConfiguration() {
CachingConfiguration configuration = this.factory.getCachingConfiguration("test");
assertThat(configuration).isNotNull();
assertThat(configuration.getTimeToLive()).isEqualTo(0);
Long result = this.timeToLive.apply("test");
assertThat(result).isNull();
}
@Test
public void userConfiguration() {
this.environment.setProperty("endpoints.test.cache.time-to-live", "500");
CachingConfiguration configuration = this.factory.getCachingConfiguration("test");
assertThat(configuration).isNotNull();
assertThat(configuration.getTimeToLive()).isEqualTo(500);
this.environment.setProperty("management.endpoint.test.cache.time-to-live",
"500");
Long result = this.timeToLive.apply("test");
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.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointExposure;
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.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExtension;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -40,324 +41,158 @@ public class ConditionalOnEnabledEndpointTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
@Test
public void enabledByDefault() {
this.contextRunner.withUserConfiguration(FooConfig.class)
public void outcomeWhenEndpointEnabledPropertyIsTrueShouldMatch() throws Exception {
this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=true")
.withUserConfiguration(
FooEndpointEnabledByDefaultFalseConfiguration.class)
.run((context) -> assertThat(context).hasBean("foo"));
}
@Test
public void disabledViaSpecificProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.foo.enabled=false")
public void outcomeWhenEndpointEnabledPropertyIsFalseShouldNotMatch()
throws Exception {
this.contextRunner.withPropertyValues("management.endpoint.foo.enabled=false")
.withUserConfiguration(FooEndpointEnabledByDefaultTrueConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo"));
}
@Test
public void disabledViaGeneralProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.default.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("foo"));
}
@Test
public void enabledOverrideViaSpecificProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.foo.enabled=true")
public void outcomeWhenNoEndpointPropertyAndUserDefinedDefaultIsTrueShouldMatch()
throws Exception {
this.contextRunner
.withPropertyValues("management.endpoints.enabled-by-default=true")
.withUserConfiguration(
FooEndpointEnabledByDefaultFalseConfiguration.class)
.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"));
}
@Test
public void enabledOverrideViaGeneralWebProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.default.web.enabled=true")
.run((context) -> assertThat(context).hasBean("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"));
public void outcomeWhenNoEndpointPropertyAndUserDefinedDefaultIsFalseShouldNotMatch()
throws Exception {
this.contextRunner
.withPropertyValues("management.endpoints.enabled-by-default=false")
.withUserConfiguration(FooEndpointEnabledByDefaultTrueConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo"));
}
@Test
public void enabledOverrideViaGeneralAnyProperty() {
this.contextRunner.withUserConfiguration(FooConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.default.web.enabled=false",
"endpoints.default.jmx.enabled=true")
public void outcomeWhenNoPropertiesAndAnnotationIsEnabledByDefaultShouldMatch()
throws Exception {
this.contextRunner
.withUserConfiguration(FooEndpointEnabledByDefaultTrueConfiguration.class)
.run((context) -> assertThat(context).hasBean("foo"));
}
@Test
public void disabledEvenWithEnabledGeneralProperties() {
this.contextRunner.withUserConfiguration(FooConfig.class).withPropertyValues(
"endpoints.default.enabled=true", "endpoints.default.web.enabled=true",
"endpoints.default.jmx.enabled=true", "endpoints.foo.enabled=false")
public void outcomeWhenNoPropertiesAndAnnotationIsNotEnabledByDefaultShouldNotMatch()
throws Exception {
this.contextRunner
.withUserConfiguration(
FooEndpointEnabledByDefaultFalseConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo"));
}
@Test
public void disabledByDefaultWithAnnotationFlag() {
this.contextRunner.withUserConfiguration(BarConfig.class)
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
@Test
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"));
public void outcomeWhenNoPropertiesAndExtensionAnnotationIsEnabledByDefaultShouldMatch()
throws Exception {
this.contextRunner
.withUserConfiguration(
FooEndpointAndExtensionEnabledByDefaultTrueConfiguration.class)
.run((context) -> assertThat(context).hasBean("foo").hasBean("fooExt"));
}
@Test
public void enabledOnlyWebByDefault() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.default.web.enabled=true")
.run((context) -> assertThat(context).hasBean("onlyweb"));
public void outcomeWhenNoPropertiesAndExtensionAnnotationIsNotEnabledByDefaultShouldNotMatch()
throws Exception {
this.contextRunner
.withUserConfiguration(
FooEndpointAndExtensionEnabledByDefaultFalseConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("foo")
.doesNotHaveBean("fooExt"));
}
@Test
public void disabledOnlyWebViaEndpointProperty() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.onlyweb.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
}
@Endpoint(id = "foo", enableByDefault = true)
static class FooEndpointEnabledByDefaultTrue {
@Test
public void disabledOnlyWebViaSpecificTechProperty() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.onlyweb.web.enabled=false")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
}
@Test
public void enableOverridesOnlyWebViaGeneralJmxPropertyHasNoEffect() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.default.jmx.enabled=true")
.run((context) -> assertThat(context).doesNotHaveBean("onlyweb"));
}
@Endpoint(id = "foo", enableByDefault = false)
static class FooEndpointEnabledByDefaultFalse {
@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
public void enableOverridesOnlyWebViaSpecificWebProperty() {
this.contextRunner.withUserConfiguration(OnlyWebConfig.class)
.withPropertyValues("endpoints.default.enabled=false",
"endpoints.onlyweb.web.enabled=true")
.run((context) -> assertThat(context).hasBean("onlyweb"));
}
@EndpointExtension(endpoint = FooEndpointEnabledByDefaultTrue.class, filter = TestFilter.class)
static class FooEndpointExtensionEnabledByDefaultTrue {
@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
public void contextFailIfEndpointTypeIsNotDetected() {
this.contextRunner.withUserConfiguration(NonEndpointBeanConfig.class)
.run((context) -> assertThat(context).hasFailed());
}
@EndpointExtension(endpoint = FooEndpointEnabledByDefaultFalse.class, filter = TestFilter.class)
static class FooEndpointExtensionEnabledByDefaultFalse {
@Test
public void webExtensionWithEnabledByDefaultEndpoint() {
this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class)
.run((context) -> assertThat(context)
.hasSingleBean(FooWebEndpointExtension.class));
}
@Test
public void webExtensionWithEnabledByDefaultEndpointCanBeDisabled() {
this.contextRunner.withUserConfiguration(FooWebExtensionConfig.class)
.withPropertyValues("endpoints.foo.enabled=false")
.run((context) -> assertThat(context)
.doesNotHaveBean(FooWebEndpointExtension.class));
}
static class TestFilter implements EndpointFilter<Operation> {
@Test
public void jmxExtensionWithEnabledByDefaultEndpoint() {
this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class)
.run((context) -> assertThat(context)
.hasSingleBean(FooJmxEndpointExtension.class));
@Override
public boolean match(EndpointInfo<Operation> info,
EndpointDiscoverer<Operation> discoverer) {
return true;
}
@Test
public void jmxExtensionWithEnabledByDefaultEndpointCanBeDisabled() {
this.contextRunner.withUserConfiguration(FooJmxExtensionConfig.class)
.withPropertyValues("endpoints.foo.enabled=false")
.run((context) -> assertThat(context)
.doesNotHaveBean(FooJmxEndpointExtension.class));
}
@Configuration
static class FooConfig {
static class FooEndpointEnabledByDefaultTrueConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public FooEndpoint foo() {
return new FooEndpoint();
public FooEndpointEnabledByDefaultTrue foo() {
return new FooEndpointEnabledByDefaultTrue();
}
}
@Endpoint(id = "foo")
static class FooEndpoint {
}
@Configuration
static class BarConfig {
static class FooEndpointEnabledByDefaultFalseConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public BarEndpoint bar() {
return new BarEndpoint();
public FooEndpointEnabledByDefaultFalse foo() {
return new FooEndpointEnabledByDefaultFalse();
}
}
@Endpoint(id = "bar", exposure = { EndpointExposure.WEB,
EndpointExposure.JMX }, defaultEnablement = DefaultEnablement.DISABLED)
static class BarEndpoint {
}
@Configuration
static class OnlyWebConfig {
static class FooEndpointAndExtensionEnabledByDefaultTrueConfiguration {
@Bean(name = "onlyweb")
@Bean
@ConditionalOnEnabledEndpoint
public OnlyWebEndpoint onlyWeb() {
return new OnlyWebEndpoint();
}
public FooEndpointEnabledByDefaultTrue foo() {
return new FooEndpointEnabledByDefaultTrue();
}
@Endpoint(id = "onlyweb", exposure = EndpointExposure.WEB)
static class OnlyWebEndpoint {
}
@Configuration
static class NonEndpointBeanConfig {
@Bean
@ConditionalOnEnabledEndpoint
public String foo() {
return "endpoint type cannot be detected";
public FooEndpointExtensionEnabledByDefaultTrue fooExt() {
return new FooEndpointExtensionEnabledByDefaultTrue();
}
}
@JmxEndpointExtension(endpoint = FooEndpoint.class)
static class FooJmxEndpointExtension {
}
@Configuration
static class FooJmxExtensionConfig {
static class FooEndpointAndExtensionEnabledByDefaultFalseConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
FooJmxEndpointExtension fooJmxEndpointExtension() {
return new FooJmxEndpointExtension();
}
public FooEndpointEnabledByDefaultFalse foo() {
return new FooEndpointEnabledByDefaultFalse();
}
@WebEndpointExtension(endpoint = FooEndpoint.class)
static class FooWebEndpointExtension {
}
@Configuration
static class FooWebExtensionConfig {
@Bean
@ConditionalOnEnabledEndpoint
FooWebEndpointExtension fooJmxEndpointExtension() {
return new FooWebEndpointExtension();
public FooEndpointExtensionEnabledByDefaultFalse fooExt() {
return new FooEndpointExtensionEnabledByDefaultFalse();
}
}

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

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

@ -14,12 +14,13 @@
* 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.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@ -30,20 +31,18 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class DefaultEndpointPathResolverTests {
private final MockEnvironment environment = new MockEnvironment();
private final EndpointPathResolver resolver = new DefaultEndpointPathResolver(
this.environment);
@Test
public void defaultConfiguration() {
assertThat(this.resolver.resolvePath("test")).isEqualTo("test");
EndpointPathResolver resolver = new DefaultEndpointPathResolver(
Collections.emptyMap());
assertThat(resolver.resolvePath("test")).isEqualTo("test");
}
@Test
public void userConfiguration() {
this.environment.setProperty("endpoints.test.web.path", "custom");
assertThat(this.resolver.resolvePath("test")).isEqualTo("custom");
EndpointPathResolver resolver = new DefaultEndpointPathResolver(
Collections.singletonMap("test", "custom"));
assertThat(resolver.resolvePath("test")).isEqualTo("custom");
}
}

@ -14,10 +14,11 @@
* 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.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
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;
/**
* Tests for {@link EndpointAutoConfiguration}.
* Tests for {@link WebEndpointAutoConfiguration}.
*
* @author Andy Wilkinson
*/
public class EndpointAutoConfigurationTests {
public class WebEndpointAutoConfigurationTests {
@Test
public void webApplicationConfiguresEndpointMediaTypes() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class))
new WebApplicationContextRunner().withConfiguration(AutoConfigurations
.of(EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class))
.run((context) -> {
EndpointMediaTypes endpointMediaTypes = context
.getBean(EndpointMediaTypes.class);

@ -51,7 +51,7 @@ public class EnvironmentEndpointAutoConfigurationTests {
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception {
this.contextRunner.withPropertyValues("endpoints.env.enabled:false")
this.contextRunner.withPropertyValues("management.endpoint.env.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(EnvironmentEndpoint.class));
}
@ -59,7 +59,7 @@ public class EnvironmentEndpointAutoConfigurationTests {
@Test
public void keysToSanitizeCanBeConfiguredViaTheEnvironment() throws Exception {
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"));
}

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

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

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

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

@ -37,21 +37,23 @@ public class InfoEndpointAutoConfigurationTests {
@Test
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));
}
@Test
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));
}
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
throws Exception {
this.contextRunner.withPropertyValues("endpoints.info.enabled:false").run(
(context) -> assertThat(context).doesNotHaveBean(InfoEndpoint.class));
this.contextRunner.withPropertyValues("management.endpoint.info.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(InfoEndpoint.class));
}
}

@ -61,8 +61,8 @@ public class JmxEndpointIntegrationTests {
}
@Test
public void jmxEndpointsCanBeDisabled() {
this.contextRunner.withPropertyValues("endpoints.default.jmx.enabled:false")
public void jmxEndpointsCanBeExcluded() {
this.contextRunner.withPropertyValues("management.endpoints.jmx.exclude:*")
.run((context) -> {
MBeanServer mBeanServer = context.getBean(MBeanServer.class);
checkEndpointMBeans(mBeanServer, new String[0],
@ -74,9 +74,9 @@ public class JmxEndpointIntegrationTests {
}
@Test
public void singleJmxEndpointCanBeEnabled() {
this.contextRunner.withPropertyValues("endpoints.default.jmx.enabled=false",
"endpoints.beans.jmx.enabled=true").run((context) -> {
public void singleJmxEndpointCanBeExposed() {
this.contextRunner.withPropertyValues("management.endpoints.jmx.expose=beans")
.run((context) -> {
MBeanServer mBeanServer = context.getBean(MBeanServer.class);
checkEndpointMBeans(mBeanServer, new String[] { "beans" },
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.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.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
@ -103,6 +104,7 @@ public class JolokiaManagementContextConfigurationIntegrationTests {
@MinimalWebConfiguration
@Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class })

@ -19,10 +19,10 @@ package org.springframework.boot.actuate.autoconfigure.integrationtest;
import org.junit.Test;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.actuate.health.HealthReactiveWebEndpointExtension;
import org.springframework.boot.actuate.health.HealthWebEndpointExtension;
import org.springframework.boot.actuate.health.StatusReactiveWebEndpointExtension;
import org.springframework.boot.actuate.health.StatusWebEndpointExtension;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveStatusEndpointWebExtension;
import org.springframework.boot.actuate.health.StatusEndpointWebExtension;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;
@ -59,25 +59,25 @@ public class WebEndpointsAutoConfigurationIntegrationTests {
servletWebRunner()
.run((context) -> context.getBean(WebEndpointTestApplication.class));
servletWebRunner().run((context) -> assertThat(context)
.hasSingleBean(HealthWebEndpointExtension.class));
.hasSingleBean(HealthEndpointWebExtension.class));
}
@Test
public void statusEndpointWebExtensionIsAutoConfigured() {
servletWebRunner().run((context) -> assertThat(context)
.hasSingleBean(StatusWebEndpointExtension.class));
.hasSingleBean(StatusEndpointWebExtension.class));
}
@Test
public void healthEndpointReactiveWebExtensionIsAutoConfigured() {
reactiveWebRunner().run((context) -> assertThat(context)
.hasSingleBean(HealthReactiveWebEndpointExtension.class));
.hasSingleBean(ReactiveHealthEndpointWebExtension.class));
}
@Test
public void statusEndpointReactiveWebExtensionIsAutoConfigured() {
reactiveWebRunner().run((context) -> assertThat(context)
.hasSingleBean(StatusReactiveWebEndpointExtension.class));
.hasSingleBean(ReactiveStatusEndpointWebExtension.class));
}
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.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.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
@ -65,10 +66,11 @@ public class WebMvcEndpointCorsIntegrationTests {
this.context.register(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
EndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
BeansEndpointAutoConfiguration.class);
TestPropertyValues.of("endpoints.default.web.enabled:true").applyTo(this.context);
TestPropertyValues.of("management.endpoints.web.expose:*").applyTo(this.context);
}
@Test
@ -83,7 +85,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
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);
createMockMvc()
.perform(options("/application/beans").header("Origin", "bar.example.com")
@ -94,7 +97,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
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);
performAcceptedCorsRequest()
.andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800"));
@ -102,15 +106,18 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
public void maxAgeCanBeConfigured() throws Exception {
TestPropertyValues.of("management.endpoints.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.max-age: 2400").applyTo(this.context);
TestPropertyValues
.of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.max-age: 2400")
.applyTo(this.context);
performAcceptedCorsRequest()
.andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400"));
}
@Test
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);
createMockMvc()
.perform(options("/application/beans").header("Origin", "foo.example.com")
@ -122,8 +129,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
public void allowedHeadersCanBeConfigured() throws Exception {
TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allowed-headers:Alpha,Bravo")
.of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allowed-headers:Alpha,Bravo")
.applyTo(this.context);
createMockMvc()
.perform(options("/application/beans").header("Origin", "foo.example.com")
@ -135,7 +142,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
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);
createMockMvc()
.perform(options("/application/health")
@ -147,8 +155,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
public void allowedMethodsCanBeConfigured() throws Exception {
TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allowed-methods:GET,HEAD")
.of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allowed-methods:GET,HEAD")
.applyTo(this.context);
createMockMvc()
.perform(options("/application/beans")
@ -161,8 +169,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
public void credentialsCanBeAllowed() throws Exception {
TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allow-credentials:true")
.of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allow-credentials:true")
.applyTo(this.context);
performAcceptedCorsRequest().andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"));
@ -171,8 +179,8 @@ public class WebMvcEndpointCorsIntegrationTests {
@Test
public void credentialsCanBeDisabled() throws Exception {
TestPropertyValues
.of("management.endpoints.cors.allowed-origins:foo.example.com",
"management.endpoints.cors.allow-credentials:false")
.of("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allow-credentials:false")
.applyTo(this.context);
performAcceptedCorsRequest().andExpect(
header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.integrationtest;
import org.junit.Test;
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.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -52,7 +53,7 @@ public class WebMvcEndpointExposureIntegrationTests {
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
@ -79,9 +80,9 @@ public class WebMvcEndpointExposureIntegrationTests {
}
@Test
public void webEndpointsCanBeEnabled() {
public void webEndpointsCanBeExposed() {
WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues("endpoints.default.web.enabled=true");
.withPropertyValues("management.endpoints.web.expose=*");
contextRunner.run((context) -> {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue();
@ -99,26 +100,46 @@ public class WebMvcEndpointExposureIntegrationTests {
}
@Test
public void singleWebEndpointCanBeEnabled() {
WebApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
"endpoints.default.web.enabled=false",
"endpoints.beans.web.enabled=true");
public void singleWebEndpointCanBeExposed() {
WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues("management.endpoints.web.expose=beans");
contextRunner.run((context) -> {
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, "conditions")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isFalse();
assertThat(isExposed(mvc, HttpMethod.GET, "env")).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.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, "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)
throws Exception {
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.beans.BeansEndpointAutoConfiguration;
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.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -91,7 +92,7 @@ public class WebMvcEndpointIntegrationTests {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class);
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.perform(get("/management/beans")).andExpect(status().isOk());
}
@ -112,6 +113,7 @@ public class WebMvcEndpointIntegrationTests {
@ImportAutoConfiguration({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,

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

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

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

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

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

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

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

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

@ -22,13 +22,12 @@ import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -136,12 +135,12 @@ public class RequestMappingEndpointTests {
OperationRequestPredicate requestPredicate = new OperationRequestPredicate("test",
WebEndpointHttpMethod.GET, 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");
WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping(
new EndpointMapping("application"),
Collections.singleton(new EndpointInfo<>("test",
DefaultEnablement.ENABLED, Collections.singleton(operation))),
Collections.singleton(new EndpointInfo<>("test", true,
Collections.singleton(operation))),
new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"),
Arrays.asList("application/vnd.test+json")));
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.endpoint.annotation.ReadOperation;
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;
/**
* {@link WebEndpointExtension} for the {@link AuditEventsEndpoint}.
* {@link EndpointWebExtension} for the {@link AuditEventsEndpoint}.
*
* @author Vedran Pavic
* @since 2.0.0
*/
@WebEndpointExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsWebEndpointExtension {
@EndpointWebExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsEndpointWebExtension {
private final AuditEventsEndpoint delegate;
public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) {
public AuditEventsEndpointWebExtension(AuditEventsEndpoint 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.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}.
@ -29,7 +29,7 @@ import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExten
* @author Andy Wilkinson
* @since 2.0.0
*/
@JmxEndpointExtension(endpoint = AuditEventsEndpoint.class)
@EndpointJmxExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsJmxEndpointExtension {
private final AuditEventsEndpoint delegate;

@ -20,7 +20,6 @@ import java.util.Collections;
import java.util.Map;
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.WriteOperation;
import org.springframework.context.ApplicationContext;
@ -35,7 +34,7 @@ import org.springframework.context.ConfigurableApplicationContext;
* @author Andy Wilkinson
* @since 2.0.0
*/
@Endpoint(id = "shutdown", defaultEnablement = DefaultEnablement.DISABLED)
@Endpoint(id = "shutdown", enableByDefault = false)
public class ShutdownEndpoint implements ApplicationContextAware {
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;
/**
* 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
*/
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,
/**
* The endpoint is disabled unless explicitly enabled.
*/
DISABLED,
/**
* The endpoint's enablement defaults to the "default" settings.
*/
NEUTRAL
boolean match(EndpointInfo<T> info, EndpointDiscoverer<T> discoverer);
}

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

@ -33,14 +33,14 @@ public class Operation {
/**
* Creates a new {@code EndpointOperation} for an operation of the given {@code type}.
* The operation can be performed using the given {@code operationInvoker}.
* @param type the type of the operation
* @param operationInvoker used to perform the operation
* @param operationType the type of the operation
* @param invoker used to perform the 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) {
this.type = type;
this.invoker = operationInvoker;
this.type = operationType;
this.invoker = invoker;
this.blocking = blocking;
}

@ -16,139 +16,178 @@
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.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.Map.Entry;
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.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.OperationType;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
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.AnnotationAttributes;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
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.
*
* @param <T> the type of the operation
* @param <K> the type of the operation key
* @param <T> the type of the operation
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
implements EndpointDiscoverer<T> {
private final ApplicationContext applicationContext;
private final EndpointOperationFactory<T> operationFactory;
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,
EndpointOperationFactory<T> operationFactory,
Function<T, K> operationKeyFactory,
CachingConfigurationFactory cachingConfigurationFactory) {
OperationFactory<T> operationFactory, Function<T, K> operationKeyFactory,
ParameterMapper parameterMapper,
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.operationFactory = operationFactory;
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.
* @param extensionType the annotation type of the extension
* @param exposure the {@link EndpointExposure} that should be considered
* @return the list of {@link EndpointInfo EndpointInfos} that describes the
* discovered endpoints matching the specified {@link EndpointExposure}
* Return the operation type being discovered. By default this method will resolve the
* class generic "{@code <T>}".
* @return the operation type
*/
protected Collection<EndpointInfoDescriptor<T, K>> discoverEndpoints(
Class<? extends Annotation> extensionType, EndpointExposure exposure) {
Map<Class<?>, EndpointInfo<T>> endpoints = discoverEndpoints(exposure);
Map<Class<?>, EndpointExtensionInfo<T>> extensions = discoverExtensions(endpoints,
extensionType, exposure);
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;
@SuppressWarnings("unchecked")
protected Class<T> getOperationType() {
return (Class<T>) ResolvableType
.forClass(AnnotationEndpointDiscoverer.class, getClass())
.resolveGeneric(1);
}
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(
this.applicationContext, Endpoint.class);
Map<Class<?>, EndpointInfo<T>> endpoints = new LinkedHashMap<>();
Map<String, EndpointInfo<T>> endpointsById = new LinkedHashMap<>();
for (String beanName : beanNames) {
Class<?> beanType = this.applicationContext.getType(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);
}
addEndpoint(endpoints, endpointsById, beanName);
}
return endpoints;
}
private EndpointInfo<T> createEndpointInfo(String beanName, Class<?> beanType,
AnnotationAttributes attributes) {
String id = attributes.getString("id");
DefaultEnablement defaultEnablement = (DefaultEnablement) attributes
.get("defaultEnablement");
Map<Method, T> operations = discoverOperations(id, beanName, beanType);
return new EndpointInfo<>(id, defaultEnablement, operations.values());
private void addEndpoint(Map<Class<?>, DiscoveredEndpoint> endpoints,
Map<String, DiscoveredEndpoint> endpointsById, String beanName) {
Class<?> endpointType = this.applicationContext.getType(beanName);
Object target = this.applicationContext.getBean(beanName);
DiscoveredEndpoint endpoint = createEndpoint(target, endpointType);
String id = endpoint.getInfo().getId();
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 DiscoveredEndpoint createEndpoint(Object target, Class<?> endpointType) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.findMergedAnnotationAttributes(endpointType, Endpoint.class, true, true);
String id = annotationAttributes.getString("id");
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<?>, EndpointExtensionInfo<T>> discoverExtensions(
Map<Class<?>, EndpointInfo<T>> endpoints,
Class<? extends Annotation> extensionType, EndpointExposure exposure) {
if (extensionType == null) {
return Collections.emptyMap();
}
private Map<Class<?>, DiscoveredExtension> getExtensions(Class<T> operationType,
Map<Class<?>, DiscoveredEndpoint> endpoints) {
Map<Class<?>, DiscoveredExtension> extensions = new LinkedHashMap<>();
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
this.applicationContext, extensionType);
Map<Class<?>, EndpointExtensionInfo<T>> extensions = new HashMap<>();
this.applicationContext, EndpointExtension.class);
for (String beanName : beanNames) {
Class<?> beanType = this.applicationContext.getType(beanName);
Class<?> endpointType = getEndpointType(extensionType, beanType);
AnnotationAttributes endpointAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(endpointType, Endpoint.class);
Assert.state(isExposedOver(endpointAttributes, exposure),
() -> "Invalid extension " + beanType.getName() + "': endpoint '"
addExtension(endpoints, extensions, beanName);
}
return extensions;
}
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()
+ "' does not support such extension");
EndpointInfo<T> info = getEndpointInfo(endpoints, beanType, endpointType);
Map<Method, T> operations = discoverOperations(info.getId(), beanName,
beanType);
EndpointExtensionInfo<T> extension = new EndpointExtensionInfo<>(beanType,
Object target = this.applicationContext.getBean(beanName);
Map<Method, T> operations = this.operationsFactory
.createOperations(endpoint.getInfo().getId(), target, extensionType);
DiscoveredExtension extension = new DiscoveredExtension(extensionType,
operations.values());
EndpointExtensionInfo<T> previous = extensions.putIfAbsent(endpointType,
DiscoveredExtension previous = extensions.putIfAbsent(endpointType,
extension);
Assert.state(previous == null,
() -> "Found two extensions for the same endpoint '"
@ -156,237 +195,285 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
+ extension.getExtensionType().getName() + " and "
+ 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,
Class<?> beanType, Class<?> endpointClass) {
EndpointInfo<T> endpoint = endpoints.get(endpointClass);
private DiscoveredEndpoint getExtendingEndpoint(
Map<Class<?>, DiscoveredEndpoint> endpoints, Class<?> extensionType,
Class<?> endpointType) {
DiscoveredEndpoint endpoint = endpoints.get(endpointType);
Assert.state(endpoint != null,
() -> "Invalid extension '" + beanType.getName()
+ "': no endpoint found with type '" + endpointClass.getName()
() -> "Invalid extension '" + extensionType.getName()
+ "': no endpoint found with type '" + endpointType.getName()
+ "'");
return endpoint;
}
private Class<?> getEndpointType(Class<? extends Annotation> extensionType,
Class<?> beanType) {
AnnotationAttributes attributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(beanType, extensionType);
return (Class<?>) attributes.get("endpoint");
}
private EndpointInfoDescriptor<T, K> createDescriptor(Class<?> type,
EndpointInfo<T> info, EndpointExtensionInfo<T> extension) {
Map<OperationKey<K>, List<T>> operations = indexOperations(info.getId(), type,
info.getOperations());
if (extension != null) {
operations.putAll(indexOperations(info.getId(), extension.getExtensionType(),
extension.getOperations()));
return new EndpointInfoDescriptor<>(mergeEndpoint(info, extension),
operations);
private boolean isEndpointExposed(Class<?> endpointType,
EndpointInfo<T> endpointInfo) {
if (isEndpointFiltered(endpointInfo)) {
return false;
}
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(endpointType, FilteredEndpoint.class);
if (annotationAttributes == null) {
return true;
}
return new EndpointInfoDescriptor<>(info, operations);
Class<?> filterClass = annotationAttributes.getClass("value");
return isFilterMatch(filterClass, endpointInfo);
}
private EndpointInfo<T> mergeEndpoint(EndpointInfo<T> endpoint,
EndpointExtensionInfo<T> extension) {
Map<K, T> operations = new HashMap<>();
Consumer<T> consumer = (operation) -> operations
.put(this.operationKeyFactory.apply(operation), operation);
endpoint.getOperations().forEach(consumer);
extension.getOperations().forEach(consumer);
return new EndpointInfo<>(endpoint.getId(), endpoint.getDefaultEnablement(),
operations.values());
private boolean isEndpointFiltered(EndpointInfo<T> endpointInfo) {
for (EndpointFilter<T> filter : this.filters) {
if (!isFilterMatch(filter, endpointInfo)) {
return true;
}
}
return false;
}
private Map<OperationKey<K>, List<T>> indexOperations(String endpointId,
Class<?> target, Collection<T> operations) {
LinkedMultiValueMap<OperationKey<K>, T> result = new LinkedMultiValueMap<>();
operations.forEach((operation) -> {
K key = this.operationKeyFactory.apply(operation);
result.add(new OperationKey<>(endpointId, target, key), operation);
});
return result;
private boolean isExtensionExposed(Class<?> extensionType,
EndpointInfo<T> endpointInfo) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
Class<?> filterClass = annotationAttributes.getClass("filter");
return isFilterMatch(filterClass, endpointInfo);
}
private boolean isExposedOver(AnnotationAttributes attributes,
EndpointExposure exposure) {
if (exposure == null) {
return true;
@SuppressWarnings("unchecked")
private boolean isFilterMatch(Class<?> filterClass, EndpointInfo<T> endpointInfo) {
Class<?> generic = ResolvableType.forClass(EndpointFilter.class, filterClass)
.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 ObjectUtils.isEmpty(supported)
|| ObjectUtils.containsElement(supported, exposure);
return false;
}
private Map<Method, T> discoverOperations(String id, String name, Class<?> type) {
return MethodIntrospector.selectMethods(type,
(MethodIntrospector.MetadataLookup<T>) (
method) -> createOperationIfPossible(id, name, method));
private boolean isFilterMatch(EndpointFilter<T> filter,
EndpointInfo<T> endpointInfo) {
try {
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;
catch (ClassCastException ex) {
String msg = ex.getMessage();
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;
}
operation = createWriteOperationIfPossible(endpointId, beanName, method);
if (operation != null) {
return operation;
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,
Method method) {
return createOperationIfPossible(endpointId, beanName, method,
WriteOperation.class, OperationType.WRITE);
private Collection<DiscoveredEndpoint> mergeExposed(
Map<Class<?>, DiscoveredEndpoint> endpoints,
Map<Class<?>, DiscoveredExtension> extensions) {
List<DiscoveredEndpoint> result = new ArrayList<>();
endpoints.forEach((endpointClass, endpoint) -> {
if (endpoint.isExposed()) {
DiscoveredExtension extension = extensions.remove(endpointClass);
result.add(endpoint.merge(extension));
}
private T createDeleteOperationIfPossible(String endpointId, String beanName,
Method method) {
return createOperationIfPossible(endpointId, beanName, method,
DeleteOperation.class, OperationType.DELETE);
});
return result;
}
private T createOperationIfPossible(String endpointId, String beanName, Method method,
Class<? extends Annotation> operationAnnotation,
OperationType operationType) {
AnnotationAttributes operationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(method, operationAnnotation);
if (operationAttributes == null) {
return null;
/**
* Allows subclasses to verify that the descriptors are correctly configured.
* @param exposedEndpoints the discovered endpoints to verify before exposing
*/
protected void verify(Collection<DiscoveredEndpoint> exposedEndpoints) {
}
CachingConfiguration cachingConfiguration = this.cachingConfigurationFactory
.getCachingConfiguration(endpointId);
return this.operationFactory.createOperation(endpointId, operationAttributes,
this.applicationContext.getBean(beanName), method, operationType,
determineTimeToLive(cachingConfiguration, operationType, method));
/**
* A discovered endpoint (which may not be valid and might not ultimately be exposed).
*/
protected final class DiscoveredEndpoint {
private final EndpointInfo<T> info;
private final boolean exposed;
private final Map<OperationKey, List<T>> operations;
private DiscoveredEndpoint(Class<?> type, EndpointInfo<T> info, boolean exposed) {
Assert.notNull(info, "Info must not be null");
this.info = info;
this.exposed = exposed;
this.operations = indexEndpointOperations(type, info);
}
private long determineTimeToLive(CachingConfiguration cachingConfiguration,
OperationType operationType, Method method) {
if (cachingConfiguration != null && cachingConfiguration.getTimeToLive() > 0
&& operationType == OperationType.READ
&& method.getParameters().length == 0) {
return cachingConfiguration.getTimeToLive();
private Map<OperationKey, List<T>> indexEndpointOperations(Class<?> endpointType,
EndpointInfo<T> info) {
return Collections.unmodifiableMap(
indexOperations(info.getId(), endpointType, info.getOperations()));
}
return 0;
private DiscoveredEndpoint(EndpointInfo<T> info, boolean exposed,
Map<OperationKey, List<T>> operations) {
Assert.notNull(info, "Info must not be null");
this.info = info;
this.exposed = exposed;
this.operations = operations;
}
/**
* An {@code EndpointOperationFactory} creates an {@link Operation} for an operation
* on an endpoint.
*
* @param <T> the {@link Operation} type
* Return the {@link EndpointInfo} for the discovered endpoint.
* @return the endpoint info
*/
@FunctionalInterface
protected interface EndpointOperationFactory<T extends Operation> {
public EndpointInfo<T> getInfo() {
return this.info;
}
/**
* Creates an {@code EndpointOperation} for an operation on an endpoint.
* @param endpointId the id of the endpoint
* @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
* Return {@code true} if the endpoint is exposed.
* @return if the is exposed
*/
T createOperation(String endpointId, AnnotationAttributes operationAttributes,
Object target, Method operationMethod, OperationType operationType,
long timeToLive);
private boolean isExposed() {
return this.exposed;
}
/**
* Return all operation that were discovered. These might be different to the ones
* that are in {@link #getInfo()}.
* @return the endpoint operations
*/
public Map<OperationKey, List<T>> getOperations() {
return this.operations;
}
/**
* Describes a tech-specific extension of an endpoint.
* @param <T> the type of the operation
* Find any duplicate operations.
* @return any duplicate operations
*/
private static final class EndpointExtensionInfo<T extends Operation> {
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 Class<?> extensionType;
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 final Collection<T> operations;
private Map<OperationKey, List<T>> mergeOperations(
DiscoveredExtension extension) {
MultiValueMap<OperationKey, T> operations = new LinkedMultiValueMap<>(
this.operations);
operations.addAll(indexOperations(getInfo().getId(),
extension.getExtensionType(), extension.getOperations()));
return Collections.unmodifiableMap(operations);
}
private EndpointExtensionInfo(Class<?> extensionType, Collection<T> operations) {
this.extensionType = extensionType;
this.operations = operations;
private Map<K, T> flatten(Map<OperationKey, List<T>> operations) {
Map<K, T> flattened = new LinkedHashMap<>();
operations.forEach((operationKey, value) -> flattened
.put(operationKey.getKey(), getLastValue(value)));
return Collections.unmodifiableMap(flattened);
}
private Class<?> getExtensionType() {
return this.extensionType;
private T getLastValue(List<T> value) {
return value.get(value.size() - 1);
}
private Collection<T> getOperations() {
return this.operations;
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.
*
* @param <T> the type of the operation
* @param <K> the type of the operation key
* A discovered extension.
*/
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,
Map<OperationKey<K>, List<T>> operations) {
this.endpointInfo = endpointInfo;
private DiscoveredExtension(Class<?> extensionType, Collection<T> operations) {
this.extensionType = extensionType;
this.operations = operations;
}
public EndpointInfo<T> getEndpointInfo() {
return this.endpointInfo;
public Class<?> getExtensionType() {
return this.extensionType;
}
public Map<OperationKey<K>, List<T>> findDuplicateOperations() {
Map<OperationKey<K>, List<T>> duplicateOperations = new HashMap<>();
this.operations.forEach((k, list) -> {
if (list.size() > 1) {
duplicateOperations.put(k, list);
public Collection<T> getOperations() {
return this.operations;
}
});
return duplicateOperations;
@Override
public String toString() {
return this.extensionType.getName();
}
}
/**
* 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 Class<?> endpointType;
private final Class<?> target;
private final K key;
public OperationKey(String endpointId, Class<?> endpointType, K key) {
public OperationKey(String endpointId, Class<?> target, K key) {
this.endpointId = endpointId;
this.endpointType = endpointType;
this.target = target;
this.key = key;
}
public K getKey() {
return this.key;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (o == this) {
return true;
@ -394,10 +481,10 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
if (o == null || getClass() != o.getClass()) {
return false;
}
OperationKey<?> other = (OperationKey<?>) o;
OperationKey other = (OperationKey) o;
Boolean result = true;
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);
return result;
}
@ -405,11 +492,17 @@ public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
@Override
public int hashCode() {
int result = this.endpointId.hashCode();
result = 31 * result + this.endpointType.hashCode();
result = 31 * result + this.target.hashCode();
result = 31 * result + this.key.hashCode();
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.
*
* @return the media type
*/
String[] produces() default {};

@ -22,14 +22,28 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
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 Phillip Webb
* @since 2.0.0
* @see EndpointExtension
* @see FilteredEndpoint
* @see AnnotationEndpointDiscoverer
*/
@Target(ElementType.TYPE)
@ -41,20 +55,12 @@ public @interface Endpoint {
* The id of the endpoint.
* @return the id
*/
String id();
/**
* 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 {};
String id() default "";
/**
* Defines the {@link DefaultEnablement} of the endpoint. By default, the endpoint's
* enablement defaults to the "default" settings.
* @return the default enablement
* If the endpoint should be enabled or disabled by default.
* @return {@code true} if the endpoint is enabled by default
*/
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.
*
* @return the media type
*/
String[] produces() default {};

@ -35,7 +35,6 @@ public @interface WriteOperation {
/**
* The media type of the result of the operation.
*
* @return the media type
*/
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;
import org.springframework.boot.actuate.endpoint.ParameterMapper;
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;

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

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

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

@ -50,11 +50,11 @@ public class JmxEndpointMBeanFactory {
* @return the MBeans
*/
public Collection<EndpointMBean> createMBeans(
Collection<EndpointInfo<JmxEndpointOperation>> endpoints) {
Collection<EndpointInfo<JmxOperation>> endpoints) {
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
.createEndpointMBeanInfo(endpointInfo);
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.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.core.style.ToStringCreator;
/**
* An operation on a JMX endpoint.
@ -30,7 +31,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
* @author Andy Wilkinson
* @since 2.0.0
*/
public class JmxEndpointOperation extends Operation {
public class JmxOperation extends Operation {
private final String operationName;
@ -50,7 +51,7 @@ public class JmxEndpointOperation extends Operation {
* @param description the description 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,
List<JmxEndpointOperationParameterInfo> parameters) {
super(type, invoker, true);
@ -92,4 +93,11 @@ public class JmxEndpointOperation extends Operation {
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 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}.
@ -34,12 +36,14 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JmxEndpointExtension {
@EndpointExtension(filter = JmxEndpointFilter.class)
public @interface EndpointJmxExtension {
/**
* The {@link Endpoint endpoint} class to which this JMX extension relates.
* @return the endpoint class
*/
@AliasFor(annotation = EndpointExtension.class)
Class<?> endpoint();
}

@ -16,78 +16,56 @@
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.Collection;
import java.util.Collections;
import java.util.Date;
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.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.EndpointFilter;
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperation;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperationParameterInfo;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
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
* {@link JmxEndpointExtension JMX extensions} applied to them.
* {@link EndpointJmxExtension JMX extensions} applied to them.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 2.0.0
*/
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
* {@link Endpoint endpoints} and {@link JmxEndpointExtension jmx extensions} using
* {@link Endpoint endpoints} and {@link EndpointJmxExtension jmx extensions} using
* the given {@link ApplicationContext}.
* @param applicationContext the application context
* @param parameterMapper the {@link ParameterMapper} used to convert arguments when
* 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,
ParameterMapper parameterMapper,
CachingConfigurationFactory cachingConfigurationFactory) {
super(applicationContext, new JmxEndpointOperationFactory(parameterMapper),
JmxEndpointOperation::getOperationName, cachingConfigurationFactory);
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
Collection<? extends EndpointFilter<JmxOperation>> filters) {
super(applicationContext, new JmxEndpointOperationFactory(),
JmxOperation::getOperationName, parameterMapper, invokerAdvisors,
filters);
}
@Override
public Collection<EndpointInfo<JmxEndpointOperation>> discoverEndpoints() {
Collection<EndpointInfoDescriptor<JmxEndpointOperation, String>> endpointDescriptors = discoverEndpoints(
JmxEndpointExtension.class, EndpointExposure.JMX);
verifyThatOperationsHaveDistinctName(endpointDescriptors);
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()));
protected void verify(Collection<DiscoveredEndpoint> exposedEndpoints) {
List<List<JmxOperation>> clashes = new ArrayList<>();
exposedEndpoints.forEach((exposedEndpoint) -> clashes
.addAll(exposedEndpoint.findDuplicateOperations().values()));
if (!clashes.isEmpty()) {
StringBuilder message = new StringBuilder();
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