Fix ConditionalOnAvailableEndpoint dashed matching

Update `ConditionalOnAvailableEndpoint` so that it now uses the same
matching code as the endpoint filter. This allows the condition to
match endpoint IDs that contain a dash.

In order to share logic, the `ExposeExcludePropertyEndpointFilter` class
has been deprecated and its logic moved to a new `expose` package
under `IncludExcludeEndpointFilter`. This filter is used by both the
`OnAvailableEndpointCondition` and the auto-configuration classes.

Fixes gh-21044
This commit is contained in:
Phillip Webb 2020-04-20 12:03:25 -07:00
parent 439d9beecb
commit df26e24605
11 changed files with 521 additions and 180 deletions

View File

@ -16,21 +16,12 @@
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.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
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 include} and
@ -39,108 +30,20 @@ import org.springframework.util.Assert;
* @param <E> the endpoint type
* @author Phillip Webb
* @since 2.0.0
* @deprecated since 2.2.7 in favor of {@link IncludExcludeEndpointFilter}
*/
public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>> implements EndpointFilter<E> {
private final Class<E> endpointType;
private final EndpointPatterns include;
private final EndpointPatterns exclude;
private final EndpointPatterns exposeDefaults;
@Deprecated
public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>>
extends IncludExcludeEndpointFilter<E> {
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
String... exposeDefaults) {
Assert.notNull(endpointType, "EndpointType 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.endpointType = endpointType;
this.include = new EndpointPatterns(bind(binder, prefix + ".include"));
this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude"));
this.exposeDefaults = new EndpointPatterns(exposeDefaults);
super(endpointType, environment, prefix, exposeDefaults);
}
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType, Collection<String> include,
Collection<String> exclude, String... exposeDefaults) {
Assert.notNull(endpointType, "EndpointType Type must not be null");
this.endpointType = endpointType;
this.include = new EndpointPatterns(include);
this.exclude = new EndpointPatterns(exclude);
this.exposeDefaults = new EndpointPatterns(exposeDefaults);
}
private List<String> bind(Binder binder, String name) {
return binder.bind(name, Bindable.listOf(String.class)).orElseGet(ArrayList::new);
}
@Override
public boolean match(E endpoint) {
if (this.endpointType.isInstance(endpoint)) {
return isExposed(endpoint) && !isExcluded(endpoint);
}
return true;
}
private boolean isExposed(ExposableEndpoint<?> endpoint) {
if (this.include.isEmpty()) {
return this.exposeDefaults.matchesAll() || this.exposeDefaults.matches(endpoint);
}
return this.include.matchesAll() || this.include.matches(endpoint);
}
private boolean isExcluded(ExposableEndpoint<?> endpoint) {
if (this.exclude.isEmpty()) {
return false;
}
return this.exclude.matchesAll() || this.exclude.matches(endpoint);
}
/**
* A set of endpoint patterns used to match IDs.
*/
private static class EndpointPatterns {
private final boolean empty;
private final boolean matchesAll;
private final Set<EndpointId> endpointIds;
EndpointPatterns(String[] patterns) {
this((patterns != null) ? Arrays.asList(patterns) : (Collection<String>) null);
}
EndpointPatterns(Collection<String> patterns) {
patterns = (patterns != null) ? patterns : Collections.emptySet();
boolean matchesAll = false;
Set<EndpointId> endpointIds = new LinkedHashSet<>();
for (String pattern : patterns) {
if ("*".equals(pattern)) {
matchesAll = true;
}
else {
endpointIds.add(EndpointId.fromPropertyValue(pattern));
}
}
this.empty = patterns.isEmpty();
this.matchesAll = matchesAll;
this.endpointIds = endpointIds;
}
boolean isEmpty() {
return this.empty;
}
boolean matchesAll() {
return this.matchesAll;
}
boolean matches(ExposableEndpoint<?> endpoint) {
return this.endpointIds.contains(endpoint.getEndpointId());
}
super(endpointType, include, exclude, exposeDefaults);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,20 +16,18 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter.DefaultIncludes;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ConcurrentReferenceHashMap;
@ -39,13 +37,14 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*
* @author Brian Clozel
* @author Stephane Nicoll
* @author Phillip Webb
* @see ConditionalOnAvailableEndpoint
*/
class OnAvailableEndpointCondition extends AbstractEndpointCondition {
private static final String JMX_ENABLED_KEY = "spring.jmx.enabled";
private static final ConcurrentReferenceHashMap<Environment, Set<ExposureInformation>> endpointExposureCache = new ConcurrentReferenceHashMap<>();
private static final Map<Environment, Set<Exposure>> exposuresCache = new ConcurrentReferenceHashMap<>();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
@ -60,79 +59,51 @@ class OnAvailableEndpointCondition extends AbstractEndpointCondition {
return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("application is running on Cloud Foundry"));
}
AnnotationAttributes attributes = getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context,
metadata);
EndpointId id = EndpointId.of(environment, attributes.getString("id"));
Set<ExposureInformation> exposureInformations = getExposureInformation(environment);
for (ExposureInformation exposureInformation : exposureInformations) {
if (exposureInformation.isExposed(id)) {
EndpointId id = EndpointId.of(environment,
getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, metadata).getString("id"));
Set<Exposure> exposures = getExposures(environment);
for (Exposure exposure : exposures) {
if (exposure.isExposed(id)) {
return new ConditionOutcome(true,
message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("marked as exposed by a 'management.endpoints."
+ exposureInformation.getPrefix() + ".exposure' property"));
.because("marked as exposed by a 'management.endpoints." + exposure.getPrefix()
+ ".exposure' property"));
}
}
return new ConditionOutcome(false, message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("no 'management.endpoints' property marked it as exposed"));
}
private Set<ExposureInformation> getExposureInformation(Environment environment) {
Set<ExposureInformation> exposureInformations = endpointExposureCache.get(environment);
if (exposureInformations == null) {
exposureInformations = new HashSet<>(2);
Binder binder = Binder.get(environment);
private Set<Exposure> getExposures(Environment environment) {
Set<Exposure> exposures = exposuresCache.get(environment);
if (exposures == null) {
exposures = new HashSet<>(2);
if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) {
exposureInformations.add(new ExposureInformation(binder, "jmx", "*"));
exposures.add(new Exposure(environment, "jmx", DefaultIncludes.JMX));
}
exposureInformations.add(new ExposureInformation(binder, "web", "info", "health"));
endpointExposureCache.put(environment, exposureInformations);
exposures.add(new Exposure(environment, "web", DefaultIncludes.WEB));
exposuresCache.put(environment, exposures);
}
return exposureInformations;
return exposures;
}
static class ExposureInformation {
static class Exposure extends IncludExcludeEndpointFilter<ExposableEndpoint<?>> {
private final String prefix;
private final Set<String> include;
private final Set<String> exclude;
private final Set<String> exposeDefaults;
ExposureInformation(Binder binder, String prefix, String... exposeDefaults) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Exposure(Environment environment, String prefix, DefaultIncludes defaultIncludes) {
super((Class) ExposableEndpoint.class, environment, "management.endpoints." + prefix + ".exposure",
defaultIncludes);
this.prefix = prefix;
this.include = bind(binder, "management.endpoints." + prefix + ".exposure.include");
this.exclude = bind(binder, "management.endpoints." + prefix + ".exposure.exclude");
this.exposeDefaults = new HashSet<>(Arrays.asList(exposeDefaults));
}
private Set<String> bind(Binder binder, String name) {
List<String> values = binder.bind(name, Bindable.listOf(String.class)).orElse(Collections.emptyList());
Set<String> result = new HashSet<>(values.size());
for (String value : values) {
result.add("*".equals(value) ? "*" : EndpointId.fromPropertyValue(value).toLowerCaseString());
}
return result;
}
String getPrefix() {
return this.prefix;
}
boolean isExposed(EndpointId endpointId) {
String id = endpointId.toLowerCaseString();
if (!this.exclude.isEmpty()) {
if (this.exclude.contains("*") || this.exclude.contains(id)) {
return false;
}
}
if (this.include.isEmpty()) {
if (this.exposeDefaults.contains("*") || this.exposeDefaults.contains(id)) {
return true;
}
}
return this.include.contains("*") || this.include.contains(id);
boolean isExposed(EndpointId id) {
return super.match(id);
}
}

View File

@ -0,0 +1,237 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.expose;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
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 include} and
* {@code exclude} patterns.
*
* @param <E> the endpoint type
* @author Phillip Webb
* @since 2.2.7
*/
public class IncludExcludeEndpointFilter<E extends ExposableEndpoint<?>> implements EndpointFilter<E> {
private final Class<E> endpointType;
private final EndpointPatterns include;
private final EndpointPatterns defaultIncludes;
private final EndpointPatterns exclude;
/**
* Create a new {@link IncludExcludeEndpointFilter} with include/exclude rules bound
* from the {@link Environment}.
* @param endpointType the endpoint type that should be considered (other types always
* match)
* @param environment the environment containing the properties
* @param prefix the property prefix to bind
* @param defaultIncludes the default {@code includes} to use when none are specified.
*/
public IncludExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
String... defaultIncludes) {
this(endpointType, environment, prefix, new EndpointPatterns(defaultIncludes));
}
/**
* Create a new {@link IncludExcludeEndpointFilter} with include/exclude rules bound
* from the {@link Environment}.
* @param endpointType the endpoint type that should be considered (other types always
* match)
* @param environment the environment containing the properties
* @param prefix the property prefix to bind
* @param defaultIncludes the default {@code includes} to use when none are specified.
*/
public IncludExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
DefaultIncludes defaultIncludes) {
this(endpointType, environment, prefix, DefaultIncludes.patterns(defaultIncludes));
}
private IncludExcludeEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
EndpointPatterns defaultIncludes) {
Assert.notNull(endpointType, "EndpointType must not be null");
Assert.notNull(environment, "Environment must not be null");
Assert.hasText(prefix, "Prefix must not be empty");
Assert.notNull(defaultIncludes, "DefaultIncludes must not be null");
Binder binder = Binder.get(environment);
this.endpointType = endpointType;
this.include = new EndpointPatterns(bind(binder, prefix + ".include"));
this.defaultIncludes = defaultIncludes;
this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude"));
}
/**
* Create a new {@link IncludExcludeEndpointFilter} with specific include/exclude
* rules.
* @param endpointType the endpoint type that should be considered (other types always
* match)
* @param include the include patterns
* @param exclude the exclude patterns
* @param defaultIncludes the default {@code includes} to use when none are specified.
*/
public IncludExcludeEndpointFilter(Class<E> endpointType, Collection<String> include, Collection<String> exclude,
String... defaultIncludes) {
this(endpointType, include, exclude, new EndpointPatterns(defaultIncludes));
}
/**
* Create a new {@link IncludExcludeEndpointFilter} with specific include/exclude
* rules.
* @param endpointType the endpoint type that should be considered (other types always
* match)
* @param include the include patterns
* @param exclude the exclude patterns
* @param defaultIncludes the default {@code includes} to use when none are specified.
*/
public IncludExcludeEndpointFilter(Class<E> endpointType, Collection<String> include, Collection<String> exclude,
DefaultIncludes defaultIncludes) {
this(endpointType, include, exclude, DefaultIncludes.patterns(defaultIncludes));
}
private IncludExcludeEndpointFilter(Class<E> endpointType, Collection<String> include, Collection<String> exclude,
EndpointPatterns defaultIncludes) {
Assert.notNull(endpointType, "EndpointType Type must not be null");
Assert.notNull(defaultIncludes, "DefaultIncludes must not be null");
this.endpointType = endpointType;
this.include = new EndpointPatterns(include);
this.defaultIncludes = defaultIncludes;
this.exclude = new EndpointPatterns(exclude);
}
private List<String> bind(Binder binder, String name) {
return binder.bind(name, Bindable.listOf(String.class)).orElseGet(ArrayList::new);
}
@Override
public boolean match(E endpoint) {
if (!this.endpointType.isInstance(endpoint)) {
// Leave non-matching types for other filters
return true;
}
return match(endpoint.getEndpointId());
}
/**
* Return {@code true} if the filter matches.
* @param endpointId the endpoint ID to check
* @return {@code true} if the filter matches
*/
protected final boolean match(EndpointId endpointId) {
return isIncluded(endpointId) && !isExcluded(endpointId);
}
private boolean isIncluded(EndpointId endpointId) {
if (this.include.isEmpty()) {
return this.defaultIncludes.matches(endpointId);
}
return this.include.matches(endpointId);
}
private boolean isExcluded(EndpointId endpointId) {
if (this.exclude.isEmpty()) {
return false;
}
return this.exclude.matches(endpointId);
}
/**
* Default include patterns that can be used.
*/
public enum DefaultIncludes {
/**
* The default set of include patterns used for JMX.
*/
JMX("*"),
/**
* The default set of include patterns used for web.
*/
WEB("info", "health");
private final EndpointPatterns patterns;
DefaultIncludes(String... patterns) {
this.patterns = new EndpointPatterns(patterns);
}
static EndpointPatterns patterns(DefaultIncludes defaultIncludes) {
return (defaultIncludes != null) ? defaultIncludes.patterns : (EndpointPatterns) null;
}
}
/**
* A set of endpoint patterns used to match IDs.
*/
private static class EndpointPatterns {
private final boolean empty;
private final boolean matchesAll;
private final Set<EndpointId> endpointIds;
EndpointPatterns(String[] patterns) {
this((patterns != null) ? Arrays.asList(patterns) : (Collection<String>) null);
}
EndpointPatterns(Collection<String> patterns) {
patterns = (patterns != null) ? patterns : Collections.emptySet();
boolean matchesAll = false;
Set<EndpointId> endpointIds = new LinkedHashSet<>();
for (String pattern : patterns) {
if ("*".equals(pattern)) {
matchesAll = true;
}
else {
endpointIds.add(EndpointId.fromPropertyValue(pattern));
}
}
this.empty = patterns.isEmpty();
this.matchesAll = matchesAll;
this.endpointIds = endpointIds;
}
boolean isEmpty() {
return this.empty;
}
boolean matches(EndpointId endpointId) {
return this.matchesAll || this.endpointIds.contains(endpointId);
}
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Endpoint exposure logic used for auto-configuration and conditions.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.expose;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -23,7 +23,7 @@ import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
@ -97,9 +97,9 @@ public class JmxEndpointAutoConfiguration {
}
@Bean
public ExposeExcludePropertyEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() {
public IncludExcludeEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() {
JmxEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(),
return new IncludExcludeEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(),
exposure.getExclude(), "*");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint;
import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar;
@ -47,10 +47,10 @@ import org.springframework.web.servlet.DispatcherServlet;
public class ServletEndpointManagementContextConfiguration {
@Bean
public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(
public IncludExcludeEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(
WebEndpointProperties properties) {
WebEndpointProperties.Exposure exposure = properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(),
return new IncludExcludeEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(),
exposure.getExclude());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -22,7 +22,8 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter.DefaultIncludes;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -112,16 +113,16 @@ public class WebEndpointAutoConfiguration {
}
@Bean
public ExposeExcludePropertyEndpointFilter<ExposableWebEndpoint> webExposeExcludePropertyEndpointFilter() {
public IncludExcludeEndpointFilter<ExposableWebEndpoint> webExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(),
exposure.getExclude(), "info", "health");
return new IncludExcludeEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(),
exposure.getExclude(), DefaultIncludes.WEB);
}
@Bean
public ExposeExcludePropertyEndpointFilter<ExposableControllerEndpoint> controllerExposeExcludePropertyEndpointFilter() {
public IncludExcludeEndpointFilter<ExposableControllerEndpoint> controllerExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(),
return new IncludExcludeEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(),
exposure.getExclude());
}

View File

@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock;
*
* @author Phillip Webb
*/
@Deprecated
class ExposeExcludePropertyEndpointFilterTests {
private ExposeExcludePropertyEndpointFilter<?> filter;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -182,6 +182,20 @@ class ConditionalOnAvailableEndpointTests {
(context) -> assertThat(context).hasBean("info").hasBean("health").hasBean("spring").hasBean("test"));
}
@Test // gh-21044
void outcomeWhenIncludeAllShouldMatchDashedEndpoint() throws Exception {
this.contextRunner.withUserConfiguration(DashedEndpointConfiguration.class)
.withPropertyValues("management.endpoints.web.exposure.include=*")
.run((context) -> assertThat(context).hasSingleBean(DashedEndpoint.class));
}
@Test // gh-21044
void outcomeWhenIncludeDashedShouldMatchDashedEndpoint() throws Exception {
this.contextRunner.withUserConfiguration(DashedEndpointConfiguration.class)
.withPropertyValues("management.endpoints.web.exposure.include=test-dashed")
.run((context) -> assertThat(context).hasSingleBean(DashedEndpoint.class));
}
@Endpoint(id = "health")
static class HealthEndpoint {
@ -207,6 +221,11 @@ class ConditionalOnAvailableEndpointTests {
}
@Endpoint(id = "test-dashed")
static class DashedEndpoint {
}
@EndpointExtension(endpoint = SpringEndpoint.class, filter = TestFilter.class)
static class SpringEndpointExtension {
@ -284,4 +303,15 @@ class ConditionalOnAvailableEndpointTests {
}
@Configuration(proxyBeanMethods = false)
static class DashedEndpointConfiguration {
@Bean
@ConditionalOnAvailableEndpoint
DashedEndpoint dashedEndpoint() {
return new DashedEndpoint();
}
}
}

View File

@ -0,0 +1,178 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.expose;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link IncludExcludeEndpointFilter}.
*
* @author Phillip Webb
*/
class IncludExcludeEndpointFilterTests {
private IncludExcludeEndpointFilter<?> filter;
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
void createWhenEndpointTypeIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new IncludExcludeEndpointFilter<>(null, new MockEnvironment(), "foo"))
.withMessageContaining("EndpointType must not be null");
}
@Test
void createWhenEnvironmentIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new IncludExcludeEndpointFilter<>(ExposableEndpoint.class, null, "foo"))
.withMessageContaining("Environment must not be null");
}
@Test
void createWhenPrefixIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(
() -> new IncludExcludeEndpointFilter<>(ExposableEndpoint.class, new MockEnvironment(), null))
.withMessageContaining("Prefix must not be empty");
}
@Test
void createWhenPrefixIsEmptyShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new IncludExcludeEndpointFilter<>(ExposableEndpoint.class, new MockEnvironment(), ""))
.withMessageContaining("Prefix must not be empty");
}
@Test
void matchWhenExposeIsEmptyAndExcludeIsEmptyAndInDefaultShouldMatch() {
setupFilter("", "");
assertThat(match(EndpointId.of("def"))).isTrue();
}
@Test
void matchWhenExposeIsEmptyAndExcludeIsEmptyAndNotInDefaultShouldNotMatch() {
setupFilter("", "");
assertThat(match(EndpointId.of("bar"))).isFalse();
}
@Test
void matchWhenExposeMatchesAndExcludeIsEmptyShouldMatch() {
setupFilter("bar", "");
assertThat(match(EndpointId.of("bar"))).isTrue();
}
@Test
void matchWhenExposeDoesNotMatchAndExcludeIsEmptyShouldNotMatch() {
setupFilter("bar", "");
assertThat(match(EndpointId.of("baz"))).isFalse();
}
@Test
void matchWhenExposeMatchesAndExcludeMatchesShouldNotMatch() {
setupFilter("bar,baz", "baz");
assertThat(match(EndpointId.of("baz"))).isFalse();
}
@Test
void matchWhenExposeMatchesAndExcludeDoesNotMatchShouldMatch() {
setupFilter("bar,baz", "buz");
assertThat(match(EndpointId.of("baz"))).isTrue();
}
@Test
void matchWhenExposeMatchesWithDifferentCaseShouldMatch() {
setupFilter("bar", "");
assertThat(match(EndpointId.of("bAr"))).isTrue();
}
@Test
void matchWhenDiscovererDoesNotMatchShouldMatch() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("foo.include", "bar");
environment.setProperty("foo.exclude", "");
this.filter = new IncludExcludeEndpointFilter<>(DifferentTestExposableWebEndpoint.class, environment, "foo");
assertThat(match(EndpointId.of("baz"))).isTrue();
}
@Test
void matchWhenIncludeIsAsteriskShouldMatchAll() {
setupFilter("*", "buz");
assertThat(match(EndpointId.of("bar"))).isTrue();
assertThat(match(EndpointId.of("baz"))).isTrue();
assertThat(match(EndpointId.of("buz"))).isFalse();
}
@Test
void matchWhenExcludeIsAsteriskShouldMatchNone() {
setupFilter("bar,baz,buz", "*");
assertThat(match(EndpointId.of("bar"))).isFalse();
assertThat(match(EndpointId.of("baz"))).isFalse();
assertThat(match(EndpointId.of("buz"))).isFalse();
}
@Test
void matchWhenMixedCaseShouldMatch() {
setupFilter("foo-bar", "");
assertThat(match(EndpointId.of("fooBar"))).isTrue();
}
@Test // gh-20997
void matchWhenDashInName() throws Exception {
setupFilter("bus-refresh", "");
assertThat(match(EndpointId.of("bus-refresh"))).isTrue();
}
private void setupFilter(String include, String exclude) {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("foo.include", include);
environment.setProperty("foo.exclude", exclude);
this.filter = new IncludExcludeEndpointFilter<>(TestExposableWebEndpoint.class, environment, "foo", "def");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private boolean match(EndpointId id) {
ExposableEndpoint<?> endpoint = mock(TestExposableWebEndpoint.class);
given(endpoint.getEndpointId()).willReturn(id);
return ((EndpointFilter) this.filter).match(endpoint);
}
abstract static class TestExposableWebEndpoint implements ExposableWebEndpoint {
}
abstract static class DifferentTestExposableWebEndpoint implements ExposableWebEndpoint {
}
}

View File

@ -24,7 +24,7 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
@ -104,7 +104,7 @@ class WebEndpointAutoConfigurationTests {
@Test
void webApplicationConfiguresExposeExcludePropertyEndpointFilter() {
this.contextRunner
.run((context) -> assertThat(context).getBeans(ExposeExcludePropertyEndpointFilter.class).containsKeys(
.run((context) -> assertThat(context).getBeans(IncludExcludeEndpointFilter.class).containsKeys(
"webExposeExcludePropertyEndpointFilter", "controllerExposeExcludePropertyEndpointFilter"));
}