Commit df26e246 authored by Phillip Webb's avatar Phillip Webb

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
parent 439d9bee
...@@ -16,21 +16,12 @@ ...@@ -16,21 +16,12 @@
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; 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.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint; 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.core.env.Environment;
import org.springframework.util.Assert;
/** /**
* {@link EndpointFilter} that will filter endpoints based on {@code include} and * {@link EndpointFilter} that will filter endpoints based on {@code include} and
...@@ -39,108 +30,20 @@ import org.springframework.util.Assert; ...@@ -39,108 +30,20 @@ import org.springframework.util.Assert;
* @param <E> the endpoint type * @param <E> the endpoint type
* @author Phillip Webb * @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.7 in favor of {@link IncludExcludeEndpointFilter}
*/ */
public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>> implements EndpointFilter<E> { @Deprecated
public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>>
private final Class<E> endpointType; extends IncludExcludeEndpointFilter<E> {
private final EndpointPatterns include;
private final EndpointPatterns exclude;
private final EndpointPatterns exposeDefaults;
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType, Environment environment, String prefix, public ExposeExcludePropertyEndpointFilter(Class<E> endpointType, Environment environment, String prefix,
String... exposeDefaults) { String... exposeDefaults) {
Assert.notNull(endpointType, "EndpointType must not be null"); super(endpointType, environment, prefix, exposeDefaults);
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);
} }
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType, Collection<String> include, public ExposeExcludePropertyEndpointFilter(Class<E> endpointType, Collection<String> include,
Collection<String> exclude, String... exposeDefaults) { Collection<String> exclude, String... exposeDefaults) {
Assert.notNull(endpointType, "EndpointType Type must not be null"); super(endpointType, include, exclude, exposeDefaults);
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());
}
} }
} }
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,20 +16,18 @@ ...@@ -16,20 +16,18 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.condition; package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.Map;
import java.util.Set; 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.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.cloud.CloudPlatform; 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.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
...@@ -39,13 +37,14 @@ import org.springframework.util.ConcurrentReferenceHashMap; ...@@ -39,13 +37,14 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* *
* @author Brian Clozel * @author Brian Clozel
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @see ConditionalOnAvailableEndpoint * @see ConditionalOnAvailableEndpoint
*/ */
class OnAvailableEndpointCondition extends AbstractEndpointCondition { class OnAvailableEndpointCondition extends AbstractEndpointCondition {
private static final String JMX_ENABLED_KEY = "spring.jmx.enabled"; 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 @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
...@@ -60,79 +59,51 @@ class OnAvailableEndpointCondition extends AbstractEndpointCondition { ...@@ -60,79 +59,51 @@ class OnAvailableEndpointCondition extends AbstractEndpointCondition {
return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class) return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("application is running on Cloud Foundry")); .because("application is running on Cloud Foundry"));
} }
AnnotationAttributes attributes = getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, EndpointId id = EndpointId.of(environment,
metadata); getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, metadata).getString("id"));
EndpointId id = EndpointId.of(environment, attributes.getString("id")); Set<Exposure> exposures = getExposures(environment);
Set<ExposureInformation> exposureInformations = getExposureInformation(environment); for (Exposure exposure : exposures) {
for (ExposureInformation exposureInformation : exposureInformations) { if (exposure.isExposed(id)) {
if (exposureInformation.isExposed(id)) {
return new ConditionOutcome(true, return new ConditionOutcome(true,
message.andCondition(ConditionalOnAvailableEndpoint.class) message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("marked as exposed by a 'management.endpoints." .because("marked as exposed by a 'management.endpoints." + exposure.getPrefix()
+ exposureInformation.getPrefix() + ".exposure' property")); + ".exposure' property"));
} }
} }
return new ConditionOutcome(false, message.andCondition(ConditionalOnAvailableEndpoint.class) return new ConditionOutcome(false, message.andCondition(ConditionalOnAvailableEndpoint.class)
.because("no 'management.endpoints' property marked it as exposed")); .because("no 'management.endpoints' property marked it as exposed"));
} }
private Set<ExposureInformation> getExposureInformation(Environment environment) { private Set<Exposure> getExposures(Environment environment) {
Set<ExposureInformation> exposureInformations = endpointExposureCache.get(environment); Set<Exposure> exposures = exposuresCache.get(environment);
if (exposureInformations == null) { if (exposures == null) {
exposureInformations = new HashSet<>(2); exposures = new HashSet<>(2);
Binder binder = Binder.get(environment);
if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) { 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")); exposures.add(new Exposure(environment, "web", DefaultIncludes.WEB));
endpointExposureCache.put(environment, exposureInformations); exposuresCache.put(environment, exposures);
} }
return exposureInformations; return exposures;
} }
static class ExposureInformation { static class Exposure extends IncludExcludeEndpointFilter<ExposableEndpoint<?>> {
private final String prefix; private final String prefix;
private final Set<String> include; @SuppressWarnings({ "rawtypes", "unchecked" })
Exposure(Environment environment, String prefix, DefaultIncludes defaultIncludes) {
private final Set<String> exclude; super((Class) ExposableEndpoint.class, environment, "management.endpoints." + prefix + ".exposure",
defaultIncludes);
private final Set<String> exposeDefaults;
ExposureInformation(Binder binder, String prefix, String... exposeDefaults) {
this.prefix = prefix; 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() { String getPrefix() {
return this.prefix; return this.prefix;
} }
boolean isExposed(EndpointId endpointId) { boolean isExposed(EndpointId id) {
String id = endpointId.toLowerCaseString(); return super.match(id);
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);
} }
} }
......
/*
* 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);
}
}
}
/*
* 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;
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -23,7 +23,7 @@ import javax.management.MBeanServer; ...@@ -23,7 +23,7 @@ import javax.management.MBeanServer;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludExcludeEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointFilter; import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
...@@ -97,9 +97,9 @@ public class JmxEndpointAutoConfiguration { ...@@ -97,9 +97,9 @@ public class JmxEndpointAutoConfiguration {
} }
@Bean @Bean
public ExposeExcludePropertyEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() { public IncludExcludeEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() {
JmxEndpointProperties.Exposure exposure = this.properties.getExposure(); JmxEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(), return new IncludExcludeEndpointFilter<>(ExposableJmxEndpoint.class, exposure.getInclude(),
exposure.getExclude(), "*"); exposure.getExclude(), "*");
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; ...@@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import org.glassfish.jersey.server.ResourceConfig; 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.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint;
import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar;
...@@ -47,10 +47,10 @@ import org.springframework.web.servlet.DispatcherServlet; ...@@ -47,10 +47,10 @@ import org.springframework.web.servlet.DispatcherServlet;
public class ServletEndpointManagementContextConfiguration { public class ServletEndpointManagementContextConfiguration {
@Bean @Bean
public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter( public IncludExcludeEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(
WebEndpointProperties properties) { WebEndpointProperties properties) {
WebEndpointProperties.Exposure exposure = properties.getExposure(); WebEndpointProperties.Exposure exposure = properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(), return new IncludExcludeEndpointFilter<>(ExposableServletEndpoint.class, exposure.getInclude(),
exposure.getExclude()); exposure.getExclude());
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,7 +22,8 @@ import java.util.stream.Collectors; ...@@ -22,7 +22,8 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; 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.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
...@@ -112,16 +113,16 @@ public class WebEndpointAutoConfiguration { ...@@ -112,16 +113,16 @@ public class WebEndpointAutoConfiguration {
} }
@Bean @Bean
public ExposeExcludePropertyEndpointFilter<ExposableWebEndpoint> webExposeExcludePropertyEndpointFilter() { public IncludExcludeEndpointFilter<ExposableWebEndpoint> webExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure(); WebEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(), return new IncludExcludeEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(),
exposure.getExclude(), "info", "health"); exposure.getExclude(), DefaultIncludes.WEB);
} }
@Bean @Bean
public ExposeExcludePropertyEndpointFilter<ExposableControllerEndpoint> controllerExposeExcludePropertyEndpointFilter() { public IncludExcludeEndpointFilter<ExposableControllerEndpoint> controllerExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure(); WebEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(), return new IncludExcludeEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(),
exposure.getExclude()); exposure.getExclude());
} }
......
...@@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock; ...@@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
@Deprecated
class ExposeExcludePropertyEndpointFilterTests { class ExposeExcludePropertyEndpointFilterTests {
private ExposeExcludePropertyEndpointFilter<?> filter; private ExposeExcludePropertyEndpointFilter<?> filter;
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -182,6 +182,20 @@ class ConditionalOnAvailableEndpointTests { ...@@ -182,6 +182,20 @@ class ConditionalOnAvailableEndpointTests {
(context) -> assertThat(context).hasBean("info").hasBean("health").hasBean("spring").hasBean("test")); (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") @Endpoint(id = "health")
static class HealthEndpoint { static class HealthEndpoint {
...@@ -207,6 +221,11 @@ class ConditionalOnAvailableEndpointTests { ...@@ -207,6 +221,11 @@ class ConditionalOnAvailableEndpointTests {
} }
@Endpoint(id = "test-dashed")
static class DashedEndpoint {
}
@EndpointExtension(endpoint = SpringEndpoint.class, filter = TestFilter.class) @EndpointExtension(endpoint = SpringEndpoint.class, filter = TestFilter.class)
static class SpringEndpointExtension { static class SpringEndpointExtension {
...@@ -284,4 +303,15 @@ class ConditionalOnAvailableEndpointTests { ...@@ -284,4 +303,15 @@ class ConditionalOnAvailableEndpointTests {
} }
@Configuration(proxyBeanMethods = false)
static class DashedEndpointConfiguration {
@Bean
@ConditionalOnAvailableEndpoint
DashedEndpoint dashedEndpoint() {
return new DashedEndpoint();
}
}
} }
/*
* 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 {
}
}
...@@ -24,7 +24,7 @@ import java.util.stream.Collectors; ...@@ -24,7 +24,7 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; 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.EndpointId;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
...@@ -104,7 +104,7 @@ class WebEndpointAutoConfigurationTests { ...@@ -104,7 +104,7 @@ class WebEndpointAutoConfigurationTests {
@Test @Test
void webApplicationConfiguresExposeExcludePropertyEndpointFilter() { void webApplicationConfiguresExposeExcludePropertyEndpointFilter() {
this.contextRunner this.contextRunner
.run((context) -> assertThat(context).getBeans(ExposeExcludePropertyEndpointFilter.class).containsKeys( .run((context) -> assertThat(context).getBeans(IncludExcludeEndpointFilter.class).containsKeys(
"webExposeExcludePropertyEndpointFilter", "controllerExposeExcludePropertyEndpointFilter")); "webExposeExcludePropertyEndpointFilter", "controllerExposeExcludePropertyEndpointFilter"));
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment