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.
Original file line number Diff line number Diff line change
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);
}

}
Original file line number Diff line number Diff line change
@@ -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.