Skip to content

Commit

Permalink
Fix ConditionalOnAvailableEndpoint dashed matching
Browse files Browse the repository at this point in the history
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
  • Loading branch information
philwebb committed Apr 20, 2020
1 parent 439d9be commit df26e24
Show file tree
Hide file tree
Showing 11 changed files with 521 additions and 180 deletions.
Expand Up @@ -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
Expand All @@ -39,108 +30,20 @@
* @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);
}

}
@@ -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.
Expand All @@ -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;
Expand All @@ -39,13 +37,14 @@
*
* @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) {
Expand All @@ -60,79 +59,51 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM
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);
}

}
Expand Down

0 comments on commit df26e24

Please sign in to comment.