Add HealthContributor and refactor HealthEndpoint

Overhaul `HealthEndpoint` support to make it easier to support health
groups. Prior to this commit the `HealthIndicator` interface was used
for both regular indicators and composite indicators. In addition the
`Health` result was used to both represent individual, system and
composite health. This design unfortunately means that all health
contributors need to be aware of the `HealthAggregator` and could not
easily support heath groups if per-group aggregation is required.

This commit reworks many aspects of the health support in order to
provide a cleaner separation between a `HealthIndicator`and a
composite. The following changes have been made:

- A `HealthContributor` interface has been introduced to represent
  the general concept of something that contributes health information.
  A contributor can either be a `HealthIndicator` or a
  `CompositeHealthContributor`.

- A `HealthComponent` class has been introduced to mirror the
  contributor arrangement. The component can be either
  `CompositeHealth` or `Health`.

- The `HealthAggregator` interface has been replaced with a more
  focused `StatusAggregator` interface which only deals with `Status`
  results.

- `CompositeHealthIndicator` has been replaced with
  `CompositeHealthContributor` which only provides access to other
  contributors. A composite can no longer directly return `Health`.

- `HealthIndicatorRegistry` has been replaced with
  `HealthContributorRegistry` and the default implementation now
  uses a copy-on-write strategy.

- `HealthEndpoint`, `HealthEndpointWebExtension` and
  `ReactiveHealthEndpointWebExtension` now extend a common
  `HealthEndpointSupport` class. They are now driven by a
  health contributor registry and `HealthEndpointSettings`.

- The `HealthStatusHttpMapper` class has been replaced by a
  `HttpCodeStatusMapper` interface.

- The `HealthWebEndpointResponseMapper` class has been replaced
  by a `HealthEndpointSettings` strategy. This allows us to move
  role related logic and `ShowDetails` to the auto-configure module.

- `SimpleHttpCodeStatusMapper` and `SimpleStatusAggregator`
  implementations have been added which are configured via constructor
  arguments rather than setters.

- Endpoint auto-configuration has been reworked and the
  `CompositeHealthIndicatorConfiguration` class has been replaced
  by `CompositeHealthContributorConfiguration`.

- The endpoint JSON has been changed make `details` distinct from
  `components`.

See gh-17926
pull/17939/head
Phillip Webb 5 years ago
parent 24b5b0d93e
commit 3c535e0de3

@ -19,13 +19,15 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ShowDetails;
/** /**
* Reactive {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint} * Reactive {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint}
@ -44,8 +46,14 @@ public class CloudFoundryReactiveHealthEndpointWebExtension {
} }
@ReadOperation @ReadOperation
public Mono<WebEndpointResponse<Health>> health() { public Mono<WebEndpointResponse<? extends HealthComponent>> health() {
return this.delegate.health(null, ShowDetails.ALWAYS); return this.delegate.health(SecurityContext.NONE, true);
}
@ReadOperation
public Mono<WebEndpointResponse<? extends HealthComponent>> health(
@Selector(match = Match.ALL_REMAINING) String... path) {
return this.delegate.health(SecurityContext.NONE, true, path);
} }
} }

@ -17,13 +17,15 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ShowDetails;
/** /**
* {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint} that always * {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint} that always
@ -42,8 +44,13 @@ public class CloudFoundryHealthEndpointWebExtension {
} }
@ReadOperation @ReadOperation
public WebEndpointResponse<Health> getHealth() { public WebEndpointResponse<HealthComponent> health() {
return this.delegate.getHealth(null, ShowDetails.ALWAYS); return this.delegate.health(SecurityContext.NONE, true);
}
@ReadOperation
public WebEndpointResponse<HealthComponent> health(@Selector(match = Match.ALL_REMAINING) String... path) {
return this.delegate.health(SecurityContext.NONE, true, path);
} }
} }

@ -0,0 +1,73 @@
/*
* Copyright 2012-2019 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.health;
import java.lang.reflect.Constructor;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* Base class for health contributor configurations that can combine source beans into a
* composite.
*
* @param <C> the contributor type
* @param <I> the health indicator type
* @param <B> the bean type
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
public abstract class AbstractCompositeHealthContributorConfiguration<C, I extends C, B> {
private final Class<?> indicatorType;
private final Class<?> beanType;
AbstractCompositeHealthContributorConfiguration() {
ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class,
getClass());
this.indicatorType = type.resolveGeneric(1);
this.beanType = type.resolveGeneric(2);
}
protected final C createContributor(Map<String, B> beans) {
Assert.notEmpty(beans, "Beans must not be empty");
if (beans.size() == 1) {
return createIndicator(beans.values().iterator().next());
}
return createComposite(beans);
}
protected abstract C createComposite(Map<String, B> beans);
@SuppressWarnings("unchecked")
protected I createIndicator(B bean) {
try {
Constructor<I> constructor = (Constructor<I>) this.indicatorType.getDeclaredConstructor(this.beanType);
return BeanUtils.instantiateClass(constructor, bean);
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to create health indicator " + this.indicatorType + " for bean type " + this.beanType, ex);
}
}
}

@ -0,0 +1,91 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Collection;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.util.CollectionUtils;
/**
* Auto-configured {@link HealthEndpointSettings} backed by
* {@link HealthEndpointProperties}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class AutoConfiguredHealthEndpointSettings implements HealthEndpointSettings {
private final StatusAggregator statusAggregator;
private final HttpCodeStatusMapper httpCodeStatusMapper;
private final ShowDetails showDetails;
private final Collection<String> roles;
/**
* Create a new {@link AutoConfiguredHealthEndpointSettings} instance.
* @param statusAggregator the status aggregator to use
* @param httpCodeStatusMapper the HTTP code status mapper to use
* @param showDetails the show details setting
* @param roles the roles to match
*/
AutoConfiguredHealthEndpointSettings(StatusAggregator statusAggregator, HttpCodeStatusMapper httpCodeStatusMapper,
ShowDetails showDetails, Collection<String> roles) {
this.statusAggregator = statusAggregator;
this.httpCodeStatusMapper = httpCodeStatusMapper;
this.showDetails = showDetails;
this.roles = roles;
}
@Override
public boolean includeDetails(SecurityContext securityContext) {
ShowDetails showDetails = this.showDetails;
switch (showDetails) {
case NEVER:
return false;
case ALWAYS:
return true;
case WHEN_AUTHORIZED:
return isAuthorized(securityContext);
}
throw new IllegalStateException("Unsupported ShowDetails value " + showDetails);
}
private boolean isAuthorized(SecurityContext securityContext) {
if (securityContext.getPrincipal() == null) {
return false;
}
return CollectionUtils.isEmpty(this.roles) || this.roles.stream().anyMatch(securityContext::isUserInRole);
}
@Override
public StatusAggregator getStatusAggregator() {
return this.statusAggregator;
}
@Override
public HttpCodeStatusMapper getHttpCodeStatusMapper() {
return this.httpCodeStatusMapper;
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthIndicator;
/**
* Base class for health contributor configurations that can combine source beans into a
* composite.
*
* @param <I> the health indicator type
* @param <B> the bean type
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
public abstract class CompositeHealthContributorConfiguration<I extends HealthIndicator, B>
extends AbstractCompositeHealthContributorConfiguration<HealthContributor, I, B> {
@Override
protected final HealthContributor createComposite(Map<String, B> beans) {
return CompositeHealthContributor.fromMap(beans, this::createIndicator);
}
}

@ -34,7 +34,9 @@ import org.springframework.core.ResolvableType;
* @param <S> the bean source type * @param <S> the bean source type
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.0.0 in favor of {@link CompositeHealthContributorConfiguration}
*/ */
@Deprecated
public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> { public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> {
@Autowired @Autowired

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
/**
* Base class for health contributor configurations that can combine source beans into a
* composite.
*
* @param <I> the health indicator type
* @param <B> the bean type
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
public abstract class CompositeReactiveHealthContributorConfiguration<I extends ReactiveHealthIndicator, B>
extends AbstractCompositeHealthContributorConfiguration<ReactiveHealthContributor, I, B> {
@Override
protected final ReactiveHealthContributor createComposite(Map<String, B> beans) {
return CompositeReactiveHealthContributor.fromMap(beans, this::createIndicator);
}
}

@ -33,7 +33,10 @@ import org.springframework.core.ResolvableType;
* @param <S> the bean source type * @param <S> the bean source type
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of
* {@link CompositeReactiveHealthContributorConfiguration}
*/ */
@Deprecated
public abstract class CompositeReactiveHealthIndicatorConfiguration<H extends ReactiveHealthIndicator, S> { public abstract class CompositeReactiveHealthIndicatorConfiguration<H extends ReactiveHealthIndicator, S> {
@Autowired @Autowired

@ -0,0 +1,59 @@
/*
* Copyright 2012-2019 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.health;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.actuate.health.StatusAggregator;
/**
* Adapter class to convert a legacy {@link HealthAggregator} to a
* {@link StatusAggregator}.
*
* @author Phillip Webb
*/
@SuppressWarnings("deprecation")
class HealthAggregatorStatusAggregatorAdapter implements StatusAggregator {
private HealthAggregator healthAggregator;
HealthAggregatorStatusAggregatorAdapter(HealthAggregator healthAggregator) {
this.healthAggregator = healthAggregator;
}
@Override
public Status getAggregateStatus(Set<Status> statuses) {
int index = 0;
Map<String, Health> healths = new LinkedHashMap<String, Health>();
for (Status status : statuses) {
index++;
healths.put("health" + index, asHealth(status));
}
Health aggregate = this.healthAggregator.aggregate(healths);
return aggregate.getStatus();
}
private Health asHealth(Status status) {
return Health.status(status).build();
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthContributor health
* contributors}. Technology specific auto-configurations should be ordered before this
* auto-configuration.
*
* @author Phillip Webb
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
public class HealthContributorAutoConfiguration {
@Bean
@ConditionalOnMissingBean({ HealthContributor.class, ReactiveHealthContributor.class })
public ApplicationHealthIndicator applicationHealthContributor() {
return new ApplicationHealthIndicator();
}
}

@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -32,9 +33,12 @@ import org.springframework.context.annotation.Import;
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ HealthEndpointProperties.class, HealthIndicatorProperties.class }) @EnableConfigurationProperties(HealthEndpointProperties.class)
@AutoConfigureAfter(HealthIndicatorAutoConfiguration.class) @AutoConfigureAfter(HealthContributorAutoConfiguration.class)
@Import({ HealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class }) @ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class)
@Import({ LegacyHealthEndpointAdaptersConfiguration.class, LegacyHealthEndpointCompatibiltyConfiguration.class,
HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class,
HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class })
public class HealthEndpointAutoConfiguration { public class HealthEndpointAutoConfiguration {
} }

@ -16,30 +16,66 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import java.util.Map;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* Configuration for {@link HealthEndpoint}. * Configuration for {@link HealthEndpoint} infrastructure beans.
* *
* @author Stephane Nicoll * @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(HealthIndicatorRegistry.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class)
class HealthEndpointConfiguration { class HealthEndpointConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
HealthEndpoint healthEndpoint(HealthAggregator healthAggregator, HealthIndicatorRegistry registry) { StatusAggregator healthStatusAggregator(HealthEndpointProperties properties) {
return new HealthEndpoint(new CompositeHealthIndicator(healthAggregator, registry)); return new SimpleStatusAggregator(properties.getStatus().getOrder());
}
@Bean
@ConditionalOnMissingBean
HttpCodeStatusMapper healthHttpCodeStatusMapper(HealthEndpointProperties properties) {
return new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping());
}
@Bean
@ConditionalOnMissingBean
HealthEndpointSettings healthEndpointSettings(HealthEndpointProperties properties,
ObjectProvider<StatusAggregator> statusAggregatorProvider,
ObjectProvider<HttpCodeStatusMapper> httpCodeStatusMapperProvider) {
StatusAggregator statusAggregator = statusAggregatorProvider
.getIfAvailable(() -> new SimpleStatusAggregator(properties.getStatus().getOrder()));
HttpCodeStatusMapper httpCodeStatusMapper = httpCodeStatusMapperProvider
.getIfAvailable(() -> new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping()));
return new AutoConfiguredHealthEndpointSettings(statusAggregator, httpCodeStatusMapper,
properties.getShowDetails(), properties.getRoles());
}
@Bean
@ConditionalOnMissingBean
HealthContributorRegistry healthContributorRegistry(Map<String, HealthContributor> healthContributors) {
return new DefaultHealthContributorRegistry(healthContributors);
}
@Bean
@ConditionalOnMissingBean
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointSettings settings) {
return new HealthEndpoint(registry, settings);
} }
} }

@ -16,11 +16,13 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.HashMap;
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.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ShowDetails;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
@ -32,6 +34,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("management.endpoint.health") @ConfigurationProperties("management.endpoint.health")
public class HealthEndpointProperties { public class HealthEndpointProperties {
private final Status status = new Status();
/** /**
* When to show full health details. * When to show full health details.
*/ */
@ -43,6 +47,10 @@ public class HealthEndpointProperties {
*/ */
private Set<String> roles = new HashSet<>(); private Set<String> roles = new HashSet<>();
public Status getStatus() {
return this.status;
}
public ShowDetails getShowDetails() { public ShowDetails getShowDetails() {
return this.showDetails; return this.showDetails;
} }
@ -59,4 +67,59 @@ public class HealthEndpointProperties {
this.roles = roles; this.roles = roles;
} }
/**
* Status properties for the group.
*/
public static class Status {
/**
* Comma-separated list of health statuses in order of severity.
*/
private List<String> order = null;
/**
* Mapping of health statuses to HTTP status codes. By default, registered health
* statuses map to sensible defaults (for example, UP maps to 200).
*/
private final Map<String, Integer> httpMapping = new HashMap<>();
public List<String> getOrder() {
return this.order;
}
public void setOrder(List<String> statusOrder) {
if (statusOrder != null && !statusOrder.isEmpty()) {
this.order = statusOrder;
}
}
public Map<String, Integer> getHttpMapping() {
return this.httpMapping;
}
}
/**
* Options for showing details in responses from the {@link HealthEndpoint} web
* extensions.
*/
public enum ShowDetails {
/**
* Never show details in the response.
*/
NEVER,
/**
* Show details in the response when accessed by an authorized user.
*/
WHEN_AUTHORIZED,
/**
* Always show details in the response.
*/
ALWAYS
}
} }

@ -0,0 +1,49 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for {@link HealthEndpoint} reactive web extensions.
*
* @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnBean(HealthEndpoint.class)
class HealthEndpointReactiveWebExtensionConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(HealthEndpoint.class)
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointSettings settings) {
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, settings);
}
}

@ -16,83 +16,34 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* Configuration for health endpoint web extensions. * Configuration for {@link HealthEndpoint} web extensions.
* *
* @author Stephane Nicoll * @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HealthIndicatorProperties.class) @ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) @ConditionalOnBean(HealthEndpoint.class)
class HealthEndpointWebExtensionConfiguration { class HealthEndpointWebExtensionConfiguration {
@Bean @Bean
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnMissingBean @ConditionalOnMissingBean
HealthStatusHttpMapper createHealthStatusHttpMapper(HealthIndicatorProperties healthIndicatorProperties) { HealthEndpointWebExtension healthEndpointWebExtension(HealthContributorRegistry healthContributorRegistry,
HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper(); HealthEndpointSettings settings) {
if (healthIndicatorProperties.getHttpMapping() != null) { return new HealthEndpointWebExtension(healthContributorRegistry, settings);
statusHttpMapper.addStatusMapping(healthIndicatorProperties.getHttpMapping());
}
return statusHttpMapper;
}
@Bean
@ConditionalOnMissingBean
HealthWebEndpointResponseMapper healthWebEndpointResponseMapper(HealthStatusHttpMapper statusHttpMapper,
HealthEndpointProperties properties) {
return new HealthWebEndpointResponseMapper(statusHttpMapper, properties.getShowDetails(),
properties.getRoles());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnSingleCandidate(ReactiveHealthIndicatorRegistry.class)
static class ReactiveWebHealthConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(HealthEndpoint.class)
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
ObjectProvider<HealthAggregator> healthAggregator, ReactiveHealthIndicatorRegistry registry,
HealthWebEndpointResponseMapper responseMapper) {
return new ReactiveHealthEndpointWebExtension(new CompositeReactiveHealthIndicator(
healthAggregator.getIfAvailable(OrderedHealthAggregator::new), registry), responseMapper);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
static class ServletWebHealthConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(HealthEndpoint.class)
HealthEndpointWebExtension healthEndpointWebExtension(HealthEndpoint healthEndpoint,
HealthWebEndpointResponseMapper responseMapper) {
return new HealthEndpointWebExtension(healthEndpoint, responseMapper);
}
} }
} }

@ -16,74 +16,25 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.Map; import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import reactor.core.publisher.Flux;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistryFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthContributor health
* contributors}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Vedran Pavic * @author Vedran Pavic
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of {@link HealthContributorAutoConfiguration}
*/ */
@Deprecated
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ HealthIndicatorProperties.class }) @AutoConfigureBefore(HealthContributorAutoConfiguration.class)
public class HealthIndicatorAutoConfiguration { public class HealthIndicatorAutoConfiguration {
@Bean
@ConditionalOnMissingBean({ HealthIndicator.class, ReactiveHealthIndicator.class })
public ApplicationHealthIndicator applicationHealthIndicator() {
return new ApplicationHealthIndicator();
}
@Bean
@ConditionalOnMissingBean(HealthAggregator.class)
public OrderedHealthAggregator healthAggregator(HealthIndicatorProperties properties) {
OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
if (properties.getOrder() != null) {
healthAggregator.setStatusOrder(properties.getOrder());
}
return healthAggregator;
}
@Bean
@ConditionalOnMissingBean(HealthIndicatorRegistry.class)
public HealthIndicatorRegistry healthIndicatorRegistry(ApplicationContext applicationContext) {
return HealthIndicatorRegistryBeans.get(applicationContext);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Flux.class)
static class ReactiveHealthIndicatorConfiguration {
@Bean
@ConditionalOnMissingBean
ReactiveHealthIndicatorRegistry reactiveHealthIndicatorRegistry(
Map<String, ReactiveHealthIndicator> reactiveHealthIndicators,
Map<String, HealthIndicator> healthIndicators) {
return new ReactiveHealthIndicatorRegistryFactory()
.createReactiveHealthIndicatorRegistry(reactiveHealthIndicators, healthIndicators);
}
}
} }

@ -16,44 +16,41 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
/** /**
* Configuration properties for some health properties. * Configuration properties for some health properties.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of {@link HealthEndpointProperties}
*/ */
@Deprecated
@ConfigurationProperties(prefix = "management.health.status") @ConfigurationProperties(prefix = "management.health.status")
public class HealthIndicatorProperties { public class HealthIndicatorProperties {
/** private final HealthEndpointProperties healthEndpointProperties;
* Comma-separated list of health statuses in order of severity.
*/
private List<String> order = null;
/** HealthIndicatorProperties(HealthEndpointProperties healthEndpointProperties) {
* Mapping of health statuses to HTTP status codes. By default, registered health this.healthEndpointProperties = healthEndpointProperties;
* statuses map to sensible defaults (for example, UP maps to 200). }
*/
private final Map<String, Integer> httpMapping = new HashMap<>();
@DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.order")
public List<String> getOrder() { public List<String> getOrder() {
return this.order; return this.healthEndpointProperties.getStatus().getOrder();
} }
public void setOrder(List<String> statusOrder) { public void setOrder(List<String> statusOrder) {
if (statusOrder != null && !statusOrder.isEmpty()) { this.healthEndpointProperties.getStatus().setOrder(statusOrder);
this.order = statusOrder;
}
} }
@DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.http-mapping")
public Map<String, Integer> getHttpMapping() { public Map<String, Integer> getHttpMapping() {
return this.httpMapping; return this.healthEndpointProperties.getStatus().getHttpMapping();
} }
} }

@ -1,65 +0,0 @@
/*
* Copyright 2012-2019 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.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;
/**
* Creates a {@link HealthIndicatorRegistry} from beans in the {@link ApplicationContext}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
final class HealthIndicatorRegistryBeans {
private HealthIndicatorRegistryBeans() {
}
static HealthIndicatorRegistry get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>(
applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext).forEach(indicators::putIfAbsent);
}
HealthIndicatorRegistryFactory factory = new HealthIndicatorRegistryFactory();
return factory.createHealthIndicatorRegistry(indicators);
}
private static class ReactiveHealthIndicators {
Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
return indicators;
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return () -> indicator.health().block();
}
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.Status;
/**
* Adapter class to convert a legacy {@link HealthStatusHttpMapper} to a
* {@link HttpCodeStatusMapper}.
*
* @author Phillip Webb
*/
@SuppressWarnings("deprecation")
class HealthStatusHttpMapperHttpCodeStatusMapperAdapter implements HttpCodeStatusMapper {
private final HealthStatusHttpMapper healthStatusHttpMapper;
HealthStatusHttpMapperHttpCodeStatusMapperAdapter(HealthStatusHttpMapper healthStatusHttpMapper) {
this.healthStatusHttpMapper = healthStatusHttpMapper;
}
@Override
public int getStatusCode(Status status) {
return this.healthStatusHttpMapper.mapStatus(status);
}
}

@ -0,0 +1,68 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;
/**
* Member predicate that matches based on {@code include} and {@code exclude} sets.
*
* @author Phillip Webb
*/
class IncludeExcludeGroupMemberPredicate implements Predicate<String> {
private final Set<String> include;
private final Set<String> exclude;
IncludeExcludeGroupMemberPredicate(Set<String> include, Set<String> exclude) {
this.include = clean(include);
this.exclude = clean(exclude);
}
@Override
public boolean test(String name) {
return isIncluded(name) && !isExcluded(name);
}
private boolean isIncluded(String name) {
return this.include.contains("*") || this.include.contains(clean(name));
}
private boolean isExcluded(String name) {
return this.exclude.contains("*") || this.exclude.contains(clean(name));
}
private Set<String> clean(Set<String> names) {
if (names == null) {
return Collections.emptySet();
}
Set<String> cleaned = new LinkedHashSet<>(names.size());
for (String name : names) {
cleaned.add(name);
}
return Collections.unmodifiableSet(cleaned);
}
private String clean(String name) {
return name.trim().toLowerCase();
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration to adapt legacy deprecated health endpoint classes and interfaces.
*
* @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
@SuppressWarnings("deprecation")
class LegacyHealthEndpointAdaptersConfiguration {
@Bean
@ConditionalOnBean(org.springframework.boot.actuate.health.HealthAggregator.class)
StatusAggregator healthAggregatorStatusAggregatorAdapter(
org.springframework.boot.actuate.health.HealthAggregator healthAggregator) {
return new HealthAggregatorStatusAggregatorAdapter(healthAggregator);
}
@Bean
@ConditionalOnBean(org.springframework.boot.actuate.health.HealthStatusHttpMapper.class)
HttpCodeStatusMapper healthStatusHttpMapperHttpCodeStatusMapperAdapter(
org.springframework.boot.actuate.health.HealthStatusHttpMapper healthStatusHttpMapper) {
return new HealthStatusHttpMapperHttpCodeStatusMapperAdapter(healthStatusHttpMapper);
}
}

@ -0,0 +1,61 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration to adapt legacy deprecated health endpoint classes and interfaces.
*
* @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
@SuppressWarnings("deprecation")
class LegacyHealthEndpointCompatibiltyConfiguration {
@Bean
HealthIndicatorProperties healthIndicatorProperties(HealthEndpointProperties healthEndpointProperties) {
return new HealthIndicatorProperties(healthEndpointProperties);
}
@Bean
@ConditionalOnMissingBean
HealthAggregator healthAggregator(HealthIndicatorProperties healthIndicatorProperties) {
OrderedHealthAggregator aggregator = new OrderedHealthAggregator();
if (healthIndicatorProperties.getOrder() != null) {
aggregator.setStatusOrder(healthIndicatorProperties.getOrder());
}
return aggregator;
}
@Bean
@ConditionalOnMissingBean
HealthStatusHttpMapper healthStatusHttpMapper(HealthIndicatorProperties healthIndicatorProperties) {
HealthStatusHttpMapper mapper = new HealthStatusHttpMapper();
if (healthIndicatorProperties.getHttpMapping() != null) {
mapper.setStatusMapping(healthIndicatorProperties.getHttpMapping());
}
return mapper;
}
}

@ -0,0 +1,57 @@
/*
* Copyright 2012-2019 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.health;
import java.util.LinkedHashMap;
import java.util.Map;
import reactor.core.publisher.Flux;
import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for reactive {@link HealthEndpoint} infrastructure beans.
*
* @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Flux.class)
@ConditionalOnBean(HealthEndpoint.class)
class ReactiveHealthEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry(
Map<String, HealthContributor> healthContributors,
Map<String, ReactiveHealthContributor> reactiveHealthContributors) {
Map<String, ReactiveHealthContributor> allContributors = new LinkedHashMap<>(reactiveHealthContributors);
healthContributors.forEach((name, contributor) -> allContributors.computeIfAbsent(name,
(key) -> ReactiveHealthContributor.adapt(contributor)));
return new DefaultReactiveHealthContributorRegistry(allContributors);
}
}

@ -21,6 +21,7 @@ org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfi
org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.influx.InfluxDbHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.influx.InfluxDbHealthIndicatorAutoConfiguration,\

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.amqp;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.amqp.RabbitHealthIndicator; import org.springframework.boot.actuate.amqp.RabbitHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -36,7 +38,8 @@ class RabbitHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class,
RabbitHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); RabbitHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.cassandra;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
@ -40,7 +42,8 @@ class CassandraHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CassandraConfiguration.class, .withConfiguration(AutoConfigurations.of(CassandraConfiguration.class,
CassandraHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); CassandraHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.cassandra;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraHealthIndicator;
import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator; import org.springframework.boot.actuate.cassandra.CassandraReactiveHealthIndicator;
@ -40,7 +42,8 @@ class CassandraReactiveHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class)) .withBean(ReactiveCassandraOperations.class, () -> mock(ReactiveCassandraOperations.class))
.withConfiguration(AutoConfigurations.of(CassandraReactiveHealthIndicatorAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(CassandraReactiveHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class)); HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -35,8 +35,9 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -110,7 +111,9 @@ class CloudFoundryWebEndpointDiscovererTests {
@Bean @Bean
HealthEndpoint healthEndpoint() { HealthEndpoint healthEndpoint() {
return new HealthEndpoint(mock(HealthIndicator.class)); HealthContributorRegistry registry = mock(HealthContributorRegistry.class);
HealthEndpointSettings settings = mock(HealthEndpointSettings.class);
return new HealthEndpoint(registry, settings);
} }
@Bean @Bean

@ -22,9 +22,13 @@ 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.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.health.CompositeHealth;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
@ -53,16 +57,28 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests {
ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class, ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class,
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
ReactiveCloudFoundryActuatorAutoConfiguration.class)); ReactiveCloudFoundryActuatorAutoConfiguration.class))
.withUserConfiguration(TestHealthIndicator.class);
@Test @Test
void healthDetailsAlwaysPresent() { void healthDetailsAlwaysPresent() {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {
CloudFoundryReactiveHealthEndpointWebExtension extension = context CloudFoundryReactiveHealthEndpointWebExtension extension = context
.getBean(CloudFoundryReactiveHealthEndpointWebExtension.class); .getBean(CloudFoundryReactiveHealthEndpointWebExtension.class);
assertThat(extension.health().block(Duration.ofSeconds(30)).getBody().getDetails()).isNotEmpty(); HealthComponent body = extension.health().block(Duration.ofSeconds(30)).getBody();
HealthComponent health = ((CompositeHealth) body).getDetails().entrySet().iterator().next().getValue();
assertThat(((Health) health).getDetails()).containsEntry("spring", "boot");
}); });
} }
private static class TestHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("spring", "boot").build();
}
}
} }

@ -32,8 +32,8 @@ import reactor.netty.http.HttpResources;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryInfoEndpointWebExtension; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryInfoEndpointWebExtension;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
@ -87,7 +87,7 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class,
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class));
@ -237,7 +237,7 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
.run((context) -> { .run((context) -> {
Collection<ExposableWebEndpoint> endpoints = getHandlerMapping(context).getEndpoints(); Collection<ExposableWebEndpoint> endpoints = getHandlerMapping(context).getEndpoints();
ExposableWebEndpoint endpoint = endpoints.iterator().next(); ExposableWebEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getOperations()).hasSize(3); assertThat(endpoint.getOperations()).hasSize(2);
WebOperation webOperation = findOperationWithRequestPath(endpoint, "health"); WebOperation webOperation = findOperationWithRequestPath(endpoint, "health");
Object invoker = ReflectionTestUtils.getField(webOperation, "invoker"); Object invoker = ReflectionTestUtils.getField(webOperation, "invoker");
assertThat(ReflectionTestUtils.getField(invoker, "target")) assertThat(ReflectionTestUtils.getField(invoker, "target"))

@ -23,8 +23,8 @@ 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.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointId;
@ -220,7 +220,7 @@ class CloudFoundryActuatorAutoConfigurationTests {
this.contextRunner this.contextRunner
.withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id",
"vcap.application.cf_api:https://my-cloud-controller.com") "vcap.application.cf_api:https://my-cloud-controller.com")
.withConfiguration(AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class)) HealthEndpointAutoConfiguration.class))
.run((context) -> { .run((context) -> {
Collection<ExposableWebEndpoint> endpoints = context Collection<ExposableWebEndpoint> endpoints = context
@ -228,7 +228,7 @@ class CloudFoundryActuatorAutoConfigurationTests {
CloudFoundryWebEndpointServletHandlerMapping.class) CloudFoundryWebEndpointServletHandlerMapping.class)
.getEndpoints(); .getEndpoints();
ExposableWebEndpoint endpoint = endpoints.iterator().next(); ExposableWebEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getOperations()).hasSize(3); assertThat(endpoint.getOperations()).hasSize(2);
WebOperation webOperation = findOperationWithRequestPath(endpoint, "health"); WebOperation webOperation = findOperationWithRequestPath(endpoint, "health");
Object invoker = ReflectionTestUtils.getField(webOperation, "invoker"); Object invoker = ReflectionTestUtils.getField(webOperation, "invoker");
assertThat(ReflectionTestUtils.getField(invoker, "target")) assertThat(ReflectionTestUtils.getField(invoker, "target"))

@ -20,10 +20,14 @@ 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.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.health.CompositeHealth;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
@ -50,16 +54,28 @@ class CloudFoundryHealthEndpointWebExtensionTests {
HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
RestTemplateAutoConfiguration.class, ManagementContextAutoConfiguration.class, RestTemplateAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, WebEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class)); HealthEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class))
.withUserConfiguration(TestHealthIndicator.class);
@Test @Test
void healthDetailsAlwaysPresent() { void healthDetailsAlwaysPresent() {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {
CloudFoundryHealthEndpointWebExtension extension = context CloudFoundryHealthEndpointWebExtension extension = context
.getBean(CloudFoundryHealthEndpointWebExtension.class); .getBean(CloudFoundryHealthEndpointWebExtension.class);
assertThat(extension.getHealth().getBody().getDetails()).isNotEmpty(); HealthComponent body = extension.health().getBody();
HealthComponent health = ((CompositeHealth) body).getDetails().entrySet().iterator().next().getValue();
assertThat(((Health) health).getDetails()).containsEntry("spring", "boot");
}); });
} }
private static class TestHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().withDetail("spring", "boot").build();
}
}
} }

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.couchbase;
import com.couchbase.client.java.Cluster; import com.couchbase.client.java.Cluster;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator; import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator;
import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator; import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator;
@ -37,8 +39,10 @@ import static org.mockito.Mockito.mock;
class CouchbaseHealthIndicatorAutoConfigurationTests { class CouchbaseHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(Cluster.class, () -> mock(Cluster.class)).withConfiguration(AutoConfigurations .withBean(Cluster.class, () -> mock(Cluster.class))
.of(CouchbaseHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(CouchbaseHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.couchbase;
import com.couchbase.client.java.Cluster; import com.couchbase.client.java.Cluster;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator; import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator;
import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator; import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator;
@ -36,8 +38,10 @@ import static org.mockito.Mockito.mock;
class CouchbaseReactiveHealthIndicatorAutoConfigurationTests { class CouchbaseReactiveHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(Cluster.class, () -> mock(Cluster.class)).withConfiguration(AutoConfigurations.of( .withBean(Cluster.class, () -> mock(Cluster.class))
CouchbaseReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(CouchbaseReactiveHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.elasticsearch;
import io.searchbox.client.JestClient; import io.searchbox.client.JestClient;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator; import org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator;
import org.springframework.boot.actuate.elasticsearch.ElasticsearchJestHealthIndicator; import org.springframework.boot.actuate.elasticsearch.ElasticsearchJestHealthIndicator;
@ -41,7 +43,8 @@ class ElasticsearchHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
.of(ElasticsearchAutoConfiguration.class, ElasticSearchClientHealthIndicatorAutoConfiguration.class, .of(ElasticsearchAutoConfiguration.class, ElasticSearchClientHealthIndicatorAutoConfiguration.class,
ElasticSearchJestHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); ElasticSearchJestHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -26,12 +26,19 @@ import javax.sql.DataSource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory; import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -63,14 +70,18 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
@Test @Test
void health() throws Exception { void health() throws Exception {
FieldDescriptor status = fieldWithPath("status").description("Overall status of the application.");
FieldDescriptor components = fieldWithPath("details").description("The components that make up the health.");
FieldDescriptor componentStatus = fieldWithPath("details.*.status")
.description("Status of a specific part of the application.");
FieldDescriptor componentDetails = subsectionWithPath("details.*.details")
.description("Details of the health of a specific part of the application. "
+ "Presence is controlled by `management.endpoint.health.show-details`.")
.optional();
FieldDescriptor nestedComponents = subsectionWithPath("details.*.details")
.description("Nested components that make up the health.").optional();
this.mockMvc.perform(get("/actuator/health")).andExpect(status().isOk()).andDo(document("health", this.mockMvc.perform(get("/actuator/health")).andExpect(status().isOk()).andDo(document("health",
responseFields(fieldWithPath("status").description("Overall status of the application."), responseFields(status, components, componentStatus, componentDetails, nestedComponents)));
fieldWithPath("details")
.description("Details of the health of the application. Presence is controlled by "
+ "`management.endpoint.health.show-details`)."),
fieldWithPath("details.*.status").description("Status of a specific part of the application."),
subsectionWithPath("details.*.details")
.description("Details of the health of a specific part of the application."))));
} }
@Test @Test
@ -91,9 +102,10 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
static class TestConfiguration { static class TestConfiguration {
@Bean @Bean
HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) { HealthEndpoint healthEndpoint(Map<String, HealthContributor> healthContributors) {
return new HealthEndpoint(new CompositeHealthIndicator(new OrderedHealthAggregator(), HealthContributorRegistry registry = new DefaultHealthContributorRegistry(healthContributors);
new HealthIndicatorRegistryFactory().createHealthIndicatorRegistry(healthIndicators))); HealthEndpointSettings settings = new TestHealthEndpointSettings();
return new HealthEndpoint(registry, settings);
} }
@Bean @Bean
@ -107,11 +119,34 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
} }
@Bean @Bean
CompositeHealthIndicator brokerHealthIndicator() { CompositeHealthContributor brokerHealthContributor() {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>(); Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.put("us1", () -> Health.up().withDetail("version", "1.0.2").build()); indicators.put("us1", () -> Health.up().withDetail("version", "1.0.2").build());
indicators.put("us2", () -> Health.up().withDetail("version", "1.0.4").build()); indicators.put("us2", () -> Health.up().withDetail("version", "1.0.4").build());
return new CompositeHealthIndicator(new OrderedHealthAggregator(), indicators); return CompositeHealthContributor.fromMap(indicators);
}
}
private static class TestHealthEndpointSettings implements HealthEndpointSettings {
private final StatusAggregator statusAggregator = new SimpleStatusAggregator();
private final HttpCodeStatusMapper httpCodeStatusMapper = new SimpleHttpCodeStatusMapper();
@Override
public boolean includeDetails(SecurityContext securityContext) {
return true;
}
@Override
public StatusAggregator getStatusAggregator() {
return this.statusAggregator;
}
@Override
public HttpCodeStatusMapper getHttpCodeStatusMapper() {
return this.httpCodeStatusMapper;
} }
} }

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.hazelcast;
import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.HazelcastInstance;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
@ -38,7 +40,8 @@ class HazelcastHealthIndicatorAutoConfigurationIntegrationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HazelcastHealthIndicatorAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(HazelcastHealthIndicatorAutoConfiguration.class,
HazelcastAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); HazelcastAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void hazelcastUp() { void hazelcastUp() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.hazelcast;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
@ -36,7 +38,8 @@ class HazelcastHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class,
HazelcastHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); HazelcastHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -0,0 +1,78 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import io.lettuce.core.dynamic.support.ResolvableType;
import org.junit.jupiter.api.Test;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link AbstractCompositeHealthContributorConfiguration}.
*
* @param <C> the contributor type
* @param <I> the health indicator type
* @author Phillip Webb
*/
abstract class AbstractCompositeHealthContributorConfigurationTests<C, I extends C> {
private final Class<?> indicatorType;
AbstractCompositeHealthContributorConfigurationTests() {
ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfigurationTests.class,
getClass());
this.indicatorType = type.resolveGeneric(1);
}
@Test
void createContributorWhenBeansIsEmptyThrowsException() {
Map<String, TestBean> beans = Collections.emptyMap();
assertThatIllegalArgumentException().isThrownBy(() -> newComposite().createContributor(beans))
.withMessage("Beans must not be empty");
}
@Test
void createContributorWhenBeansHasSingleElementCreatesIndicator() {
Map<String, TestBean> beans = Collections.singletonMap("test", new TestBean());
C contributor = newComposite().createContributor(beans);
assertThat(contributor).isInstanceOf(this.indicatorType);
}
@Test
void createContributorWhenBeansHasMultipleElementsCreatesComposite() {
Map<String, TestBean> beans = new LinkedHashMap<>();
beans.put("test1", new TestBean());
beans.put("test2", new TestBean());
C contributor = newComposite().createContributor(beans);
assertThat(contributor).isNotInstanceOf(this.indicatorType);
assertThat(ClassUtils.getShortName(contributor.getClass())).startsWith("Composite");
}
protected abstract AbstractCompositeHealthContributorConfiguration<C, I, TestBean> newComposite();
static class TestBean {
}
}

@ -0,0 +1,122 @@
/*
* Copyright 2012-2019 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.health;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Tests for {@link AutoConfiguredHealthEndpointSettings}.
*
* @author Phillip Webb
*/
class AutoConfiguredHealthEndpointSettingsTests {
@Mock
private StatusAggregator statusAggregator;
@Mock
private HttpCodeStatusMapper httpCodeStatusMapper;
@Mock
private SecurityContext securityContext;
@Mock
private Principal principal;
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
void includeDetailsWhenShowDetailsIsNeverReturnsFalse() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.NEVER, Collections.emptySet());
assertThat(settings.includeDetails(SecurityContext.NONE)).isFalse();
}
@Test
void includeDetailsWhenShowDetailsIsAlwaysReturnsTrue() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
assertThat(settings.includeDetails(SecurityContext.NONE)).isTrue();
}
@Test
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet());
given(this.securityContext.getPrincipal()).willReturn(null);
assertThat(settings.includeDetails(this.securityContext)).isFalse();
}
@Test
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet());
given(this.securityContext.getPrincipal()).willReturn(this.principal);
assertThat(settings.includeDetails(this.securityContext)).isTrue();
}
@Test
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Arrays.asList("admin", "root", "bossmode"));
given(this.securityContext.getPrincipal()).willReturn(this.principal);
given(this.securityContext.isUserInRole("root")).willReturn(true);
assertThat(settings.includeDetails(this.securityContext)).isTrue();
}
@Test
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Arrays.asList("admin", "rot", "bossmode"));
given(this.securityContext.getPrincipal()).willReturn(this.principal);
given(this.securityContext.isUserInRole("root")).willReturn(true);
assertThat(settings.includeDetails(this.securityContext)).isFalse();
}
@Test
void getStatusAggregatorReturnsStatusAggregator() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
assertThat(settings.getStatusAggregator()).isSameAs(this.statusAggregator);
}
@Test
void getHttpCodeStatusMapperReturnsHttpCodeStatusMapper() {
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
assertThat(settings.getHttpCodeStatusMapper()).isSameAs(this.httpCodeStatusMapper);
}
}

@ -0,0 +1,54 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfigurationTests.TestHealthIndicator;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.HealthContributor;
/**
* Tests for {@link CompositeHealthContributorConfiguration}.
*
* @author Phillip Webb
*/
class CompositeHealthContributorConfigurationTests
extends AbstractCompositeHealthContributorConfigurationTests<HealthContributor, TestHealthIndicator> {
@Override
protected AbstractCompositeHealthContributorConfiguration<HealthContributor, TestHealthIndicator, TestBean> newComposite() {
return new TestCompositeHealthContributorConfiguration();
}
static class TestCompositeHealthContributorConfiguration
extends CompositeHealthContributorConfiguration<TestHealthIndicator, TestBean> {
}
static class TestHealthIndicator extends AbstractHealthIndicator {
TestHealthIndicator(TestBean testBean) {
}
@Override
protected void doHealthCheck(Builder builder) throws Exception {
builder.up();
}
}
}

@ -0,0 +1,57 @@
/*
* Copyright 2012-2019 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.health;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfigurationTests.TestReactiveHealthIndicator;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
/**
* Tests for {@link CompositeReactiveHealthContributorConfiguration}.
*
* @author Phillip Webb
*/
class CompositeReactiveHealthContributorConfigurationTests extends
AbstractCompositeHealthContributorConfigurationTests<ReactiveHealthContributor, TestReactiveHealthIndicator> {
@Override
protected AbstractCompositeHealthContributorConfiguration<ReactiveHealthContributor, TestReactiveHealthIndicator, TestBean> newComposite() {
return new TestCompositeReactiveHealthContributorConfiguration();
}
static class TestCompositeReactiveHealthContributorConfiguration
extends CompositeReactiveHealthContributorConfiguration<TestReactiveHealthIndicator, TestBean> {
}
static class TestReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
TestReactiveHealthIndicator(TestBean testBean) {
}
@Override
protected Mono<Health> doHealthCheck(Builder builder) {
return Mono.just(builder.up().build());
}
}
}

@ -0,0 +1,53 @@
/*
* Copyright 2012-2019 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.health;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.AbstractHealthAggregator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.actuate.health.StatusAggregator;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HealthAggregatorStatusAggregatorAdapter}.
*
* @author Phillip Webb
*/
@SuppressWarnings("deprecation")
class HealthAggregatorStatusAggregatorAdapterTests {
@Test
void getAggregateStatusDelegateToHealthAggregator() {
StatusAggregator adapter = new HealthAggregatorStatusAggregatorAdapter(new TestHealthAggregator());
Status status = adapter.getAggregateStatus(Status.UP, Status.DOWN);
assertThat(status.getCode()).isEqualTo("called2");
}
private static class TestHealthAggregator extends AbstractHealthAggregator {
@Override
protected Status aggregateStatus(List<Status> candidates) {
return new Status("called" + candidates.size());
}
}
}

@ -16,17 +16,11 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -35,31 +29,31 @@ import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link HealthIndicatorAutoConfiguration}. * Tests for {@link HealthContributorAutoConfiguration}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
class HealthIndicatorAutoConfigurationTests { class HealthContributorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HealthIndicatorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class));
@Test @Test
void runWhenNoOtherIndicatorsShouldCreateDefaultApplicationHealthIndicator() { void runWhenNoOtherIndicatorsCreatesDefaultApplicationHealthIndicator() {
this.contextRunner.run((context) -> assertThat(context).getBean(HealthIndicator.class) this.contextRunner.run((context) -> assertThat(context).getBean(HealthIndicator.class)
.isInstanceOf(ApplicationHealthIndicator.class)); .isInstanceOf(ApplicationHealthIndicator.class));
} }
@Test @Test
void runWhenHasDefinedIndicatorShouldNotCreateDefaultApplicationHealthIndicator() { void runWhenHasDefinedIndicatorDoesNotCreateDefaultApplicationHealthIndicator() {
this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class) this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class)
.run((context) -> assertThat(context).getBean(HealthIndicator.class) .run((context) -> assertThat(context).getBean(HealthIndicator.class)
.isInstanceOf(CustomHealthIndicator.class)); .isInstanceOf(CustomHealthIndicator.class));
} }
@Test @Test
void runWhenHasDefaultsDisabledAndNoSingleIndicatorEnabledShouldCreateDefaultApplicationHealthIndicator() { void runWhenHasDefaultsDisabledAndNoSingleIndicatorEnabledCreatesDefaultApplicationHealthIndicator() {
this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class) this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class)
.withPropertyValues("management.health.defaults.enabled:false").run((context) -> assertThat(context) .withPropertyValues("management.health.defaults.enabled:false").run((context) -> assertThat(context)
.getBean(HealthIndicator.class).isInstanceOf(ApplicationHealthIndicator.class)); .getBean(HealthIndicator.class).isInstanceOf(ApplicationHealthIndicator.class));
@ -67,7 +61,7 @@ class HealthIndicatorAutoConfigurationTests {
} }
@Test @Test
void runWhenHasDefaultsDisabledAndSingleIndicatorEnabledShouldCreateEnabledIndicator() { void runWhenHasDefaultsDisabledAndSingleIndicatorEnabledDoesNotCreateEnabledIndicator() {
this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class) this.contextRunner.withUserConfiguration(CustomHealthIndicatorConfiguration.class)
.withPropertyValues("management.health.defaults.enabled:false", "management.health.custom.enabled:true") .withPropertyValues("management.health.defaults.enabled:false", "management.health.custom.enabled:true")
.run((context) -> assertThat(context).getBean(HealthIndicator.class) .run((context) -> assertThat(context).getBean(HealthIndicator.class)
@ -75,31 +69,6 @@ class HealthIndicatorAutoConfigurationTests {
} }
@Test
void runShouldCreateOrderedHealthAggregator() {
this.contextRunner.run((context) -> assertThat(context).getBean(HealthAggregator.class)
.isInstanceOf(OrderedHealthAggregator.class));
}
@Test
void runWhenHasCustomOrderPropertyShouldCreateOrderedHealthAggregator() {
this.contextRunner.withPropertyValues("management.health.status.order:UP,DOWN").run((context) -> {
OrderedHealthAggregator aggregator = context.getBean(OrderedHealthAggregator.class);
Map<String, Health> healths = new LinkedHashMap<>();
healths.put("foo", Health.up().build());
healths.put("bar", Health.down().build());
Health aggregate = aggregator.aggregate(healths);
assertThat(aggregate.getStatus()).isEqualTo(Status.UP);
});
}
@Test
void runWhenHasCustomHealthAggregatorShouldNotCreateOrderedHealthAggregator() {
this.contextRunner.withUserConfiguration(CustomHealthAggregatorConfiguration.class)
.run((context) -> assertThat(context).getBean(HealthAggregator.class)
.isNotInstanceOf(OrderedHealthAggregator.class));
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomHealthIndicatorConfiguration { static class CustomHealthIndicatorConfiguration {
@ -120,14 +89,4 @@ class HealthIndicatorAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class CustomHealthAggregatorConfiguration {
@Bean
HealthAggregator healthAggregator() {
return (healths) -> Health.down().build();
}
}
} }

@ -16,91 +16,387 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.AbstractHealthAggregator;
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link HealthEndpointAutoConfiguration}. * Tests for {@link HealthEndpointAutoConfiguration}.
* *
* @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
*/ */
@SuppressWarnings("deprecation")
class HealthEndpointAutoConfigurationTests { class HealthEndpointAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class)); .withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations
.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
private ReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner()
.withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations
.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test
void runWhenHealthEndpointIsDisabledDoesNotCreateBeans() {
this.contextRunner.withPropertyValues("management.endpoint.health.enabled=false").run((context) -> {
assertThat(context).doesNotHaveBean(StatusAggregator.class);
assertThat(context).doesNotHaveBean(HttpCodeStatusMapper.class);
assertThat(context).doesNotHaveBean(HealthEndpointSettings.class);
assertThat(context).doesNotHaveBean(HealthContributorRegistry.class);
assertThat(context).doesNotHaveBean(HealthEndpoint.class);
assertThat(context).doesNotHaveBean(ReactiveHealthContributorRegistry.class);
assertThat(context).doesNotHaveBean(HealthEndpointWebExtension.class);
assertThat(context).doesNotHaveBean(ReactiveHealthEndpointWebExtension.class);
});
}
@Test
void runWhenHasHealthAggregatorAdaptsToStatusAggregator() {
this.contextRunner.withUserConfiguration(HealthAggregatorConfiguration.class).run((context) -> {
StatusAggregator aggregator = context.getBean(StatusAggregator.class);
assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UNKNOWN);
});
}
@Test
void runWhenHasHealthStatusHttpMapperAdaptsToHttpCodeStatusMapper() {
this.contextRunner.withUserConfiguration(HealthStatusHttpMapperConfiguration.class).run((context) -> {
HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class);
assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123);
});
}
@Test
void runCreatesStatusAggregatorFromProperties() {
this.contextRunner.withPropertyValues("management.endpoint.health.status.order=up,down").run((context) -> {
StatusAggregator aggregator = context.getBean(StatusAggregator.class);
assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UP);
});
}
@Test
void runWhenUsingDeprecatedPropertyCreatesStatusAggregatorFromProperties() {
this.contextRunner.withPropertyValues("management.health.status.order=up,down").run((context) -> {
StatusAggregator aggregator = context.getBean(StatusAggregator.class);
assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UP);
});
}
@Test
void runWhenHasStatusAggregatorBeanIgnoresProperties() {
this.contextRunner.withUserConfiguration(StatusAggregatorConfiguration.class)
.withPropertyValues("management.health.status.order=up,down").run((context) -> {
StatusAggregator aggregator = context.getBean(StatusAggregator.class);
assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UNKNOWN);
});
}
@Test
void runCreatesHttpCodeStatusMapperFromProperties() {
this.contextRunner.withPropertyValues("management.endpoint.health.status.http-mapping.up=123")
.run((context) -> {
HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class);
assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123);
});
}
@Test
void runUsingDeprecatedPropertyCreatesHttpCodeStatusMapperFromProperties() {
this.contextRunner.withPropertyValues("management.health.status.http-mapping.up=123").run((context) -> {
HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class);
assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123);
});
}
@Test
void runWhenHasHttpCodeStatusMapperBeanIgnoresProperties() {
this.contextRunner.withUserConfiguration(HttpCodeStatusMapperConfiguration.class)
.withPropertyValues("management.health.status.http-mapping.up=123").run((context) -> {
HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class);
assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(456);
});
}
@Test
void runCreatesHealthEndpointSettings() {
this.contextRunner.run((context) -> {
HealthEndpointSettings settings = context.getBean(HealthEndpointSettings.class);
assertThat(settings).isInstanceOf(AutoConfiguredHealthEndpointSettings.class);
});
}
@Test
void runWhenHasHealthEndpointSettingsBeanDoesNotCreateAdditionalHealthEndpointSettings() {
this.contextRunner.withUserConfiguration(HealthEndpointSettingsConfiguration.class).run((context) -> {
HealthEndpointSettings settings = context.getBean(HealthEndpointSettings.class);
assertThat(Mockito.mockingDetails(settings).isMock()).isTrue();
});
}
@Test
void runCreatesHealthContributorRegistryContainingHealthBeans() {
this.contextRunner.run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional");
});
}
@Test
void runWhenHasHealthContributorRegistryBeanDoesNotCreateAdditionalRegistry() {
this.contextRunner.withUserConfiguration(HealthContributorRegistryConfiguration.class).run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
assertThat(names).isEmpty();
});
}
@Test
void runCreatesHealthEndpoint() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpoint endpoint = context.getBean(HealthEndpoint.class);
Health health = (Health) endpoint.healthForPath("simple");
assertThat(health.getDetails()).containsEntry("counter", 42);
});
}
@Test
void runWhenHasHealthEndpointBeanDoesNotCreateAdditionalHealthEndpoint() {
this.contextRunner.withUserConfiguration(HealthEndpointConfiguration.class).run((context) -> {
HealthEndpoint endpoint = context.getBean(HealthEndpoint.class);
assertThat(endpoint.health()).isNull();
});
}
@Test @Test
void healthEndpointShowDetailsDefault() { void runCreatesReactiveHealthContributorRegistryContainingAdaptedBeans() {
this.contextRunner.withBean(ReactiveHealthIndicator.class, this::reactiveHealthIndicator).run((context) -> { this.reactiveContextRunner.run((context) -> {
ReactiveHealthIndicator indicator = context.getBean("reactiveHealthIndicator", ReactiveHealthContributorRegistry registry = context.getBean(ReactiveHealthContributorRegistry.class);
ReactiveHealthIndicator.class); Object[] names = registry.stream().map(NamedContributor::getName).toArray();
verify(indicator, never()).health(); assertThat(names).containsExactlyInAnyOrder("simple", "additional", "reactive");
Health health = context.getBean(HealthEndpoint.class).health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).isNotEmpty();
verify(indicator, times(1)).health();
}); });
} }
@Test @Test
void healthEndpointAdaptReactiveHealthIndicator() { void runWhenHasReactiveHealthContributorRegistryBeanDoesNotCreateAdditionalReactiveHealthContributorRegistry() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always") this.reactiveContextRunner.withUserConfiguration(ReactiveHealthContributorRegistryConfiguration.class)
.withBean(ReactiveHealthIndicator.class, this::reactiveHealthIndicator).run((context) -> { .run((context) -> {
ReactiveHealthIndicator indicator = context.getBean("reactiveHealthIndicator", ReactiveHealthContributorRegistry registry = context
ReactiveHealthIndicator.class); .getBean(ReactiveHealthContributorRegistry.class);
verify(indicator, never()).health(); Object[] names = registry.stream().map(NamedContributor::getName).toArray();
Health health = context.getBean(HealthEndpoint.class).health(); assertThat(names).isEmpty();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnlyKeys("reactive");
verify(indicator, times(1)).health();
}); });
} }
@Test @Test
void healthEndpointMergeRegularAndReactive() { void runCreatesHealthEndpointWebExtension() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always") this.contextRunner.run((context) -> {
.withBean("simpleHealthIndicator", HealthIndicator.class, this::simpleHealthIndicator) HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class);
.withBean("reactiveHealthIndicator", ReactiveHealthIndicator.class, this::reactiveHealthIndicator) WebEndpointResponse<HealthComponent> response = webExtension.health(SecurityContext.NONE, true, "simple");
Health health = (Health) response.getBody();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(health.getDetails()).containsEntry("counter", 42);
});
}
@Test
void runWhenHasHealthEndpointWebExtensionBeanDoesNotCreateExtraHealthEndpointWebExtension() {
this.contextRunner.withUserConfiguration(HealthEndpointWebExtensionConfiguration.class).run((context) -> {
HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class);
WebEndpointResponse<HealthComponent> response = webExtension.health(SecurityContext.NONE, true, "simple");
assertThat(response).isNull();
});
}
@Test
void runCreatesReactiveHealthEndpointWebExtension() {
this.reactiveContextRunner.run((context) -> {
ReactiveHealthEndpointWebExtension webExtension = context.getBean(ReactiveHealthEndpointWebExtension.class);
Mono<WebEndpointResponse<? extends HealthComponent>> response = webExtension.health(SecurityContext.NONE,
true, "simple");
Health health = (Health) (response.block().getBody());
assertThat(health.getDetails()).containsEntry("counter", 42);
});
}
@Test
void runWhenHasReactiveHealthEndpointWebExtensionBeanDoesNotCreateExtraReactiveHealthEndpointWebExtension() {
this.reactiveContextRunner.withUserConfiguration(ReactiveHealthEndpointWebExtensionConfiguration.class)
.run((context) -> { .run((context) -> {
HealthIndicator indicator = context.getBean("simpleHealthIndicator", HealthIndicator.class); ReactiveHealthEndpointWebExtension webExtension = context
ReactiveHealthIndicator reactiveHealthIndicator = context.getBean("reactiveHealthIndicator", .getBean(ReactiveHealthEndpointWebExtension.class);
ReactiveHealthIndicator.class); Mono<WebEndpointResponse<? extends HealthComponent>> response = webExtension
verify(indicator, never()).health(); .health(SecurityContext.NONE, true, "simple");
verify(reactiveHealthIndicator, never()).health(); assertThat(response).isNull();
Health health = context.getBean(HealthEndpoint.class).health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnlyKeys("simple", "reactive");
verify(indicator, times(1)).health();
verify(reactiveHealthIndicator, times(1)).health();
}); });
} }
private HealthIndicator simpleHealthIndicator() { @Configuration(proxyBeanMethods = false)
HealthIndicator mock = mock(HealthIndicator.class); static class HealthIndicatorsConfiguration {
given(mock.health()).willReturn(Health.status(Status.UP).build());
return mock; @Bean
HealthIndicator simpleHealthIndicator() {
return () -> Health.up().withDetail("counter", 42).build();
}
@Bean
HealthIndicator additionalHealthIndicator() {
return () -> Health.up().build();
}
@Bean
ReactiveHealthIndicator reactiveHealthIndicator() {
return () -> Mono.just(Health.up().build());
}
}
@Configuration(proxyBeanMethods = false)
static class HealthAggregatorConfiguration {
@Bean
HealthAggregator healthAggregator() {
return new AbstractHealthAggregator() {
@Override
protected Status aggregateStatus(List<Status> candidates) {
return Status.UNKNOWN;
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class HealthStatusHttpMapperConfiguration {
@Bean
HealthStatusHttpMapper healthStatusHttpMapper() {
return new HealthStatusHttpMapper() {
@Override
public int mapStatus(Status status) {
return 123;
}
};
}
}
@Configuration(proxyBeanMethods = false)
static class StatusAggregatorConfiguration {
@Bean
StatusAggregator statusAggregator() {
return (statuses) -> Status.UNKNOWN;
}
}
@Configuration(proxyBeanMethods = false)
static class HttpCodeStatusMapperConfiguration {
@Bean
HttpCodeStatusMapper httpCodeStatusMapper() {
return (status) -> 456;
}
}
@Configuration(proxyBeanMethods = false)
static class HealthEndpointSettingsConfiguration {
@Bean
HealthEndpointSettings healthEndpointSettings() {
return mock(HealthEndpointSettings.class);
}
} }
private ReactiveHealthIndicator reactiveHealthIndicator() { @Configuration(proxyBeanMethods = false)
ReactiveHealthIndicator mock = mock(ReactiveHealthIndicator.class); static class HealthContributorRegistryConfiguration {
given(mock.health()).willReturn(Mono.just(Health.status(Status.UP).build()));
return mock; @Bean
HealthContributorRegistry healthContributorRegistry() {
return new DefaultHealthContributorRegistry();
}
}
@Configuration(proxyBeanMethods = false)
static class HealthEndpointConfiguration {
@Bean
HealthEndpoint healthEndpoint() {
return mock(HealthEndpoint.class);
}
}
@Configuration(proxyBeanMethods = false)
static class ReactiveHealthContributorRegistryConfiguration {
@Bean
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry() {
return new DefaultReactiveHealthContributorRegistry();
}
}
@Configuration(proxyBeanMethods = false)
static class HealthEndpointWebExtensionConfiguration {
@Bean
HealthEndpointWebExtension healthEndpointWebExtension() {
return mock(HealthEndpointWebExtension.class);
}
}
@Configuration(proxyBeanMethods = false)
static class ReactiveHealthEndpointWebExtensionConfiguration {
@Bean
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension() {
return mock(ReactiveHealthEndpointWebExtension.class);
}
} }
} }

@ -1,349 +0,0 @@
/*
* Copyright 2012-2019 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.health;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HealthEndpointAutoConfiguration} in a servlet environment.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
class HealthEndpointWebExtensionTests {
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations
.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test
void runShouldCreateExtensionBeans() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(HealthEndpointWebExtension.class));
}
@Test
void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans() {
this.contextRunner.withPropertyValues("management.endpoint.health.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(HealthEndpointWebExtension.class));
}
@Test
void runWithCustomHealthMappingShouldMapStatusCode() {
this.contextRunner.withPropertyValues("management.health.status.http-mapping.CUSTOM=500").run((context) -> {
Object extension = context.getBean(HealthEndpointWebExtension.class);
HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils
.getField(extension, "responseMapper");
Class<SecurityContext> securityContext = SecurityContext.class;
assertThat(responseMapper.map(Health.down().build(), mock(securityContext)).getStatus()).isEqualTo(503);
assertThat(responseMapper.map(Health.status("OUT_OF_SERVICE").build(), mock(securityContext)).getStatus())
.isEqualTo(503);
assertThat(responseMapper.map(Health.status("CUSTOM").build(), mock(securityContext)).getStatus())
.isEqualTo(500);
});
}
@Test
void unauthenticatedUsersAreNotShownDetailsByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertThat(extension.health(mock(SecurityContext.class)).getBody().getDetails()).isEmpty();
});
}
@Test
void authenticatedUsersAreNotShownDetailsByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertThat(extension.health(securityContext).getBody().getDetails()).isEmpty();
});
}
@Test
void authenticatedUsersWhenAuthorizedCanBeShownDetails() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized")
.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertThat(extension.health(securityContext).getBody().getDetails()).isNotEmpty();
});
}
@Test
void unauthenticatedUsersCanBeShownDetails() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertThat(extension.health(null).getBody().getDetails()).isNotEmpty();
});
}
@Test
void detailsCanBeHiddenFromAuthenticatedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertThat(extension.health(mock(SecurityContext.class)).getBody().getDetails()).isEmpty();
});
}
@Test
void detailsCanBeHiddenFromUnauthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(false);
assertThat(extension.health(securityContext).getBody().getDetails()).isEmpty();
});
}
@Test
void detailsCanBeShownToAuthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(true);
assertThat(extension.health(securityContext).getBody().getDetails()).isNotEmpty();
});
}
@Test
void unauthenticatedUsersAreNotShownComponentByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertDetailsNotFound(extension.healthForComponent(mock(SecurityContext.class), "simple"));
});
}
@Test
void authenticatedUsersAreNotShownComponentByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertDetailsNotFound(extension.healthForComponent(securityContext, "simple"));
});
}
@Test
void authenticatedUsersWhenAuthorizedCanBeShownComponent() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized")
.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertSimpleComponent(extension.healthForComponent(securityContext, "simple"));
});
}
@Test
void unauthenticatedUsersCanBeShownComponent() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertSimpleComponent(extension.healthForComponent(null, "simple"));
});
}
@Test
void componentCanBeHiddenFromAuthenticatedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertDetailsNotFound(extension.healthForComponent(mock(SecurityContext.class), "simple"));
});
}
@Test
void componentCanBeHiddenFromUnauthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(false);
assertDetailsNotFound(extension.healthForComponent(securityContext, "simple"));
});
}
@Test
void componentCanBeShownToAuthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(true);
assertSimpleComponent(extension.healthForComponent(securityContext, "simple"));
});
}
@Test
void componentThatDoesNotExistMapTo404() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertDetailsNotFound(extension.healthForComponent(null, "does-not-exist"));
});
}
@Test
void unauthenticatedUsersAreNotShownComponentInstanceByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertDetailsNotFound(
extension.healthForComponentInstance(mock(SecurityContext.class), "composite", "one"));
});
}
@Test
void authenticatedUsersAreNotShownComponentInstanceByDefault() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertDetailsNotFound(extension.healthForComponentInstance(securityContext, "composite", "one"));
});
}
@Test
void authenticatedUsersWhenAuthorizedCanBeShownComponentInstance() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized")
.run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertSimpleComponent(extension.healthForComponentInstance(securityContext, "composite", "one"));
});
}
@Test
void unauthenticatedUsersCanBeShownComponentInstance() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertSimpleComponent(extension.healthForComponentInstance(null, "composite", "one"));
});
}
@Test
void componentInstanceCanBeHiddenFromAuthenticatedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertDetailsNotFound(
extension.healthForComponentInstance(mock(SecurityContext.class), "composite", "one"));
});
}
@Test
void componentInstanceCanBeHiddenFromUnauthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(false);
assertDetailsNotFound(extension.healthForComponentInstance(securityContext, "composite", "one"));
});
}
@Test
void componentInstanceCanBeShownToAuthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(true);
assertSimpleComponent(extension.healthForComponentInstance(securityContext, "composite", "one"));
});
}
@Test
void componentInstanceThatDoesNotExistMapTo404() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
assertDetailsNotFound(extension.healthForComponentInstance(null, "composite", "does-not-exist"));
});
}
private void assertDetailsNotFound(WebEndpointResponse<?> response) {
assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
assertThat(response.getBody()).isNull();
}
private void assertSimpleComponent(WebEndpointResponse<Health> response) {
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getBody().getDetails()).containsOnly(entry("counter", 42));
}
@Test
void roleCanBeCustomized() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ADMIN").run((context) -> {
HealthEndpointWebExtension extension = context.getBean(HealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ADMIN")).willReturn(true);
assertThat(extension.health(securityContext).getBody().getDetails()).isNotEmpty();
});
}
@Configuration(proxyBeanMethods = false)
static class HealthIndicatorsConfiguration {
@Bean
HealthIndicator simpleHealthIndicator() {
return () -> Health.up().withDetail("counter", 42).build();
}
@Bean
HealthIndicator compositeHealthIndicator(HealthIndicator healthIndicator) {
Map<String, HealthIndicator> nestedIndicators = new HashMap<>();
nestedIndicators.put("one", healthIndicator);
nestedIndicators.put("two", () -> Health.up().build());
return new CompositeHealthIndicator(new OrderedHealthAggregator(), nestedIndicators);
}
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2012-2019 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.health;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.Status;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HealthStatusHttpMapperHttpCodeStatusMapperAdapter}.
*
* @author Phillip Webb
*/
@SuppressWarnings("deprecation")
class HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests {
@Test
void getStatusCodeDelegatesToHealthStatusHttpMapper() {
HttpCodeStatusMapper adapter = new HealthStatusHttpMapperHttpCodeStatusMapperAdapter(
new TestHealthStatusHttpMapper());
assertThat(adapter.getStatusCode(Status.UP)).isEqualTo(123);
}
static class TestHealthStatusHttpMapper extends HealthStatusHttpMapper {
@Override
public int mapStatus(Status status) {
return 123;
}
}
}

@ -0,0 +1,93 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link IncludeExcludeGroupMemberPredicate}.
*
* @author Phillip Webb
*/
class IncludeExcludeGroupMemberPredicateTests {
@Test
void testWhenEmptyIncludeAndExcludeRejectsAll() {
Predicate<String> predicate = new IncludeExcludeGroupMemberPredicate(null, null);
assertThat(predicate).rejects("a", "b", "c");
}
@Test
void testWhenStarIncludeAndEmptyExcludeAcceptsAll() {
Predicate<String> predicate = include("*").exclude();
assertThat(predicate).accepts("a", "b", "c");
}
@Test
void testWhenStarIncludeAndSpecificExcludeDoesNotAcceptExclude() {
Predicate<String> predicate = include("*").exclude("c");
assertThat(predicate).accepts("a", "b").rejects("c");
}
@Test
void testWhenSpecificIncludeAcceptsOnlyIncluded() {
Predicate<String> predicate = include("a", "b").exclude();
assertThat(predicate).accepts("a", "b").rejects("c");
}
@Test
void testWhenSpecifiedIncludeAndSpecifiedExcludeAcceptsAsExpected() {
Predicate<String> predicate = include("a", "b", "c").exclude("c");
assertThat(predicate).accepts("a", "b").rejects("c", "d");
}
@Test
void testWhenSpecifiedIncludeAndStarExcludeRejectsAll() {
Predicate<String> predicate = include("a", "b", "c").exclude("*");
assertThat(predicate).rejects("a", "b", "c", "d");
}
private Builder include(String... include) {
return new Builder(include);
}
private static class Builder {
private final String[] include;
Builder(String[] include) {
this.include = include;
}
Predicate<String> exclude(String... exclude) {
return new IncludeExcludeGroupMemberPredicate(asSet(this.include), asSet(exclude));
}
private Set<String> asSet(String[] names) {
return (names != null) ? new LinkedHashSet<>(Arrays.asList(names)) : null;
}
}
}

@ -1,221 +0,0 @@
/*
* Copyright 2012-2019 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.health;
import java.security.Principal;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HealthEndpointAutoConfiguration} in a reactive environment.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
class ReactiveHealthEndpointWebExtensionTests {
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withUserConfiguration(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class);
@Test
void runShouldCreateExtensionBeans() {
this.contextRunner
.run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class));
}
@Test
void runWhenHealthEndpointIsDisabledShouldNotCreateExtensionBeans() {
this.contextRunner.withPropertyValues("management.endpoint.health.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveHealthEndpointWebExtension.class));
}
@Test
void runWithCustomHealthMappingShouldMapStatusCode() {
this.contextRunner.withPropertyValues("management.health.status.http-mapping.CUSTOM=500").run((context) -> {
Object extension = context.getBean(ReactiveHealthEndpointWebExtension.class);
HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils
.getField(extension, "responseMapper");
Class<SecurityContext> securityContext = SecurityContext.class;
assertThat(responseMapper.map(Health.down().build(), mock(securityContext)).getStatus()).isEqualTo(503);
assertThat(responseMapper.map(Health.status("OUT_OF_SERVICE").build(), mock(securityContext)).getStatus())
.isEqualTo(503);
assertThat(responseMapper.map(Health.status("CUSTOM").build(), mock(securityContext)).getStatus())
.isEqualTo(500);
});
}
@Test
void regularAndReactiveHealthIndicatorsMatch() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always")
.withUserConfiguration(HealthIndicatorsConfiguration.class).run((context) -> {
HealthEndpoint endpoint = context.getBean(HealthEndpoint.class);
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
Health endpointHealth = endpoint.health();
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
Health extensionHealth = extension.health(securityContext).block(Duration.ofSeconds(30)).getBody();
assertThat(endpointHealth.getDetails()).containsOnlyKeys("application", "first", "second");
assertThat(extensionHealth.getDetails()).containsOnlyKeys("application", "first", "second");
});
}
@Test
void unauthenticatedUsersAreNotShownDetailsByDefault() {
this.contextRunner.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(
extension.health(mock(SecurityContext.class)).block(Duration.ofSeconds(30)).getBody().getDetails())
.isEmpty();
});
}
@Test
void authenticatedUsersAreNotShownDetailsByDefault() {
this.contextRunner.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails())
.isEmpty();
});
}
@Test
void authenticatedUsersWhenAuthorizedCanBeShownDetails() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized")
.run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails())
.isNotEmpty();
});
}
@Test
void unauthenticatedUsersCanBeShownDetails() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(extension.health(null).block(Duration.ofSeconds(30)).getBody().getDetails()).isNotEmpty();
});
}
@Test
void detailsCanBeHiddenFromAuthenticatedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=never").run((context) -> {
ReactiveHealthEndpointWebExtension extension = context.getBean(ReactiveHealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails())
.isEmpty();
});
}
@Test
void detailsCanBeHiddenFromUnauthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(false);
assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails())
.isEmpty();
});
}
@Test
void detailsCanBeShownToAuthorizedUsers() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ACTUATOR")).willReturn(true);
assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails())
.isNotEmpty();
});
}
@Test
void roleCanBeCustomized() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=when-authorized",
"management.endpoint.health.roles=ADMIN").run((context) -> {
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
SecurityContext securityContext = mock(SecurityContext.class);
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
given(securityContext.isUserInRole("ADMIN")).willReturn(true);
assertThat(extension.health(securityContext).block(Duration.ofSeconds(30)).getBody().getDetails())
.isNotEmpty();
});
}
@Test
void registryCanBeAltered() {
this.contextRunner.withUserConfiguration(HealthIndicatorsConfiguration.class)
.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
ReactiveHealthIndicatorRegistry registry = context.getBean(ReactiveHealthIndicatorRegistry.class);
ReactiveHealthEndpointWebExtension extension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
assertThat(extension.health(null).block(Duration.ofSeconds(30)).getBody().getDetails())
.containsOnlyKeys("application", "first", "second");
assertThat(registry.unregister("second")).isNotNull();
assertThat(extension.health(null).block(Duration.ofSeconds(30)).getBody().getDetails())
.containsKeys("application", "first");
});
}
@Configuration(proxyBeanMethods = false)
static class HealthIndicatorsConfiguration {
@Bean
HealthIndicator firstHealthIndicator() {
return () -> Health.up().build();
}
@Bean
ReactiveHealthIndicator secondHealthIndicator() {
return () -> Mono.just(Health.up().build());
}
}
}

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.influx;
import org.influxdb.InfluxDB; import org.influxdb.InfluxDB;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.influx.InfluxDbHealthIndicator; import org.springframework.boot.actuate.influx.InfluxDbHealthIndicator;
@ -36,8 +38,10 @@ import static org.mockito.Mockito.mock;
class InfluxDbHealthIndicatorAutoConfigurationTests { class InfluxDbHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(InfluxDB.class, () -> mock(InfluxDB.class)).withConfiguration(AutoConfigurations .withBean(InfluxDB.class, () -> mock(InfluxDB.class))
.of(InfluxDbHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(InfluxDbHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository; import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
@ -52,6 +54,7 @@ class JmxEndpointIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class,
JmxEndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class, JmxEndpointAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
HttpTraceAutoConfiguration.class)) HttpTraceAutoConfiguration.class))
.withUserConfiguration(HttpTraceRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class) .withUserConfiguration(HttpTraceRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true") .withPropertyValues("spring.jmx.enabled=true")

@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
@ -73,7 +75,8 @@ class WebMvcEndpointExposureIntegrationTests {
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) .withConfiguration(AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL))
.withUserConfiguration(CustomMvcEndpoint.class, CustomServletEndpoint.class, .withUserConfiguration(CustomMvcEndpoint.class, CustomServletEndpoint.class,
HttpTraceRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class) HttpTraceRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class)

@ -20,6 +20,8 @@ import javax.sql.DataSource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator;
@ -49,7 +51,8 @@ class DataSourceHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, DataSourceHealthIndicatorAutoConfiguration.class)) HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class, DataSourceHealthIndicatorAutoConfiguration.class))
.withPropertyValues("spring.datasource.initialization-mode=never"); .withPropertyValues("spring.datasource.initialization-mode=never");
@Test @Test

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.jms;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.jms.JmsHealthIndicator; import org.springframework.boot.actuate.jms.JmsHealthIndicator;
@ -37,7 +39,8 @@ class JmsHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class,
JmsHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); JmsHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.ldap;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.ldap.LdapHealthIndicator; import org.springframework.boot.actuate.ldap.LdapHealthIndicator;
@ -37,8 +39,10 @@ import static org.mockito.Mockito.mock;
class LdapHealthIndicatorAutoConfigurationTests { class LdapHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(LdapOperations.class, () -> mock(LdapOperations.class)).withConfiguration(AutoConfigurations .withBean(LdapOperations.class, () -> mock(LdapOperations.class))
.of(LdapHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(LdapHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.mail;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.mail.MailHealthIndicator; import org.springframework.boot.actuate.mail.MailHealthIndicator;
@ -36,7 +38,8 @@ class MailHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class,
MailHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) MailHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class))
.withPropertyValues("spring.mail.host:smtp.example.com"); .withPropertyValues("spring.mail.host:smtp.example.com");
@Test @Test

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.mongo;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.mongo.MongoHealthIndicator; import org.springframework.boot.actuate.mongo.MongoHealthIndicator;
@ -37,7 +39,8 @@ class MongoHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); MongoHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.mongo;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.mongo.MongoHealthIndicator; import org.springframework.boot.actuate.mongo.MongoHealthIndicator;
@ -41,7 +43,8 @@ class MongoReactiveHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class,
MongoReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); MongoReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -20,6 +20,8 @@ import org.junit.jupiter.api.Test;
import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory; import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
@ -42,8 +44,10 @@ import static org.mockito.Mockito.mock;
class Neo4jHealthIndicatorAutoConfigurationTests { class Neo4jHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(Neo4jConfiguration.class).withConfiguration(AutoConfigurations .withUserConfiguration(Neo4jConfiguration.class)
.of(Neo4jHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(Neo4jHealthIndicatorAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class, HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.redis;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.redis.RedisHealthIndicator; import org.springframework.boot.actuate.redis.RedisHealthIndicator;
@ -39,7 +41,8 @@ class RedisHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class,
RedisHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); RedisHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.redis;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.redis.RedisHealthIndicator; import org.springframework.boot.actuate.redis.RedisHealthIndicator;
@ -37,7 +39,8 @@ class RedisReactiveHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class,
RedisReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); RedisReactiveHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -28,8 +28,8 @@ import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
@ -66,12 +66,12 @@ import static org.mockito.Mockito.mock;
class ReactiveManagementWebSecurityAutoConfigurationTests { class ReactiveManagementWebSecurityAutoConfigurationTests {
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration( .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class,
ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class,
ReactiveManagementWebSecurityAutoConfiguration.class)); ReactiveManagementWebSecurityAutoConfiguration.class));
@Test @Test
void permitAllForHealth() { void permitAllForHealth() {

@ -23,8 +23,8 @@ 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.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class ManagementWebSecurityAutoConfigurationTests { class ManagementWebSecurityAutoConfigurationTests {
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration( private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner().withConfiguration(
AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class)); SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class));

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.solr;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.solr.SolrHealthIndicator; import org.springframework.boot.actuate.solr.SolrHealthIndicator;
@ -36,7 +38,8 @@ class SolrHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(SolrAutoConfiguration.class,
SolrHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); SolrHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.system;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
@ -36,7 +38,8 @@ import static org.assertj.core.api.Assertions.assertThat;
class DiskSpaceHealthIndicatorAutoConfigurationTests { class DiskSpaceHealthIndicatorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
.of(DiskSpaceHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); .of(DiskSpaceHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
@Test @Test
void runShouldCreateIndicator() { void runShouldCreateIndicator() {

@ -28,7 +28,9 @@ import java.util.stream.Collectors;
* @author Christian Dupuis * @author Christian Dupuis
* @author Vedran Pavic * @author Vedran Pavic
* @since 1.1.0 * @since 1.1.0
* @deprecated since 2.2.0 as {@link HealthAggregator} has been deprecated
*/ */
@Deprecated
public abstract class AbstractHealthAggregator implements HealthAggregator { public abstract class AbstractHealthAggregator implements HealthAggregator {
@Override @Override

@ -0,0 +1,58 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.TreeMap;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.util.Assert;
/**
* A {@link HealthComponent} that is composed of other {@link HealthComponent} instances.
* Used to provide a unified view of related components. For example, a database health
* indicator may be a composite containing the {@link Health} of each datasource
* connection.
*
* @author Phillip Webb
* @since 2.2.0
*/
public class CompositeHealth extends HealthComponent {
private Status status;
private Map<String, HealthComponent> details;
CompositeHealth(Status status, Map<String, HealthComponent> details) {
Assert.notNull(status, "Status must not be null");
this.status = status;
this.details = (details != null) ? new TreeMap<>(details) : details;
}
@Override
public Status getStatus() {
return this.status;
}
@JsonInclude(Include.NON_EMPTY)
public Map<String, HealthComponent> getDetails() {
return this.details;
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.function.Function;
/**
* A {@link HealthContributor} that is composed of other {@link HealthContributor}
* instances.
*
* @author Phillip Webb
* @since 2.2.0
* @see CompositeHealth
* @see CompositeReactiveHealthContributor
*/
public interface CompositeHealthContributor extends HealthContributor, NamedContributors<HealthContributor> {
/**
* Factory method that will create a {@link CompositeHealthContributor} from the
* specified map.
* @param map the source map
* @return a composite health contributor instance
*/
static CompositeHealthContributor fromMap(Map<String, ? extends HealthContributor> map) {
return fromMap(map, Function.identity());
}
/**
* Factory method that will create a {@link CompositeHealthContributor} from the
* specified map.
* @param <V> the value type
* @param map the source map
* @param valueAdapter function used to adapt the map value
* @return a composite health contributor instance
*/
static <V> CompositeHealthContributor fromMap(Map<String, V> map,
Function<V, ? extends HealthContributor> valueAdapter) {
return new CompositeHealthContributorMapAdapter<>(map, valueAdapter);
}
}

@ -0,0 +1,35 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.function.Function;
/**
* {@link CompositeHealthContributor} backed by a map with values adapted as necessary.
*
* @param <V> the value type
* @author Phillip Webb
*/
class CompositeHealthContributorMapAdapter<V> extends NamedContributorsMapAdapter<V, HealthContributor>
implements CompositeHealthContributor {
CompositeHealthContributorMapAdapter(Map<String, V> map, Function<V, ? extends HealthContributor> valueAdapter) {
super(map, valueAdapter);
}
}

@ -0,0 +1,67 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Iterator;
import org.springframework.util.Assert;
/**
* Adapts a {@link CompositeHealthContributor} to a
* {@link CompositeReactiveHealthContributor} so that it can be safely invoked in a
* reactive environment.
*
* @author Phillip Webb
* @see ReactiveHealthContributor#adapt(HealthContributor)
*/
class CompositeHealthContributorReactiveAdapter implements CompositeReactiveHealthContributor {
private final CompositeHealthContributor delegate;
CompositeHealthContributorReactiveAdapter(CompositeHealthContributor delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
@Override
public Iterator<NamedContributor<ReactiveHealthContributor>> iterator() {
Iterator<NamedContributor<HealthContributor>> iterator = this.delegate.iterator();
return new Iterator<NamedContributor<ReactiveHealthContributor>>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<ReactiveHealthContributor> next() {
NamedContributor<HealthContributor> namedContributor = iterator.next();
return NamedContributor.of(namedContributor.getName(),
ReactiveHealthContributor.adapt(namedContributor.getContributor()));
}
};
}
@Override
public ReactiveHealthContributor getContributor(String name) {
HealthContributor contributor = this.delegate.getContributor(name);
return (contributor != null) ? ReactiveHealthContributor.adapt(contributor) : null;
}
}

@ -26,7 +26,9 @@ import java.util.Map;
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis * @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
* @deprecated since 2.2.0 in favor of a {@link CompositeHealthContributor}
*/ */
@Deprecated
public class CompositeHealthIndicator implements HealthIndicator { public class CompositeHealthIndicator implements HealthIndicator {
private final HealthIndicatorRegistry registry; private final HealthIndicatorRegistry registry;

@ -0,0 +1,57 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.function.Function;
/**
* A {@link ReactiveHealthContributor} that is composed of other
* {@link ReactiveHealthContributor} instances.
*
* @author Phillip Webb
* @since 2.2.0
* @see CompositeHealth
* @see CompositeHealthContributor
*/
public interface CompositeReactiveHealthContributor
extends ReactiveHealthContributor, NamedContributors<ReactiveHealthContributor> {
/**
* Factory method that will create a {@link CompositeReactiveHealthContributor} from
* the specified map.
* @param map the source map
* @return a composite health contributor instance
*/
static CompositeReactiveHealthContributor fromMap(Map<String, ? extends ReactiveHealthContributor> map) {
return fromMap(map, Function.identity());
}
/**
* Factory method that will create a {@link CompositeReactiveHealthContributor} from
* the specified map.
* @param <V> the value type
* @param map the source map
* @param valueAdapter function used to adapt the map value
* @return a composite health contributor instance
*/
static <V> CompositeReactiveHealthContributor fromMap(Map<String, V> map,
Function<V, ? extends ReactiveHealthContributor> valueAdapter) {
return new CompositeReactiveHealthContributorMapAdapter<>(map, valueAdapter);
}
}

@ -0,0 +1,37 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.function.Function;
/**
* {@link CompositeReactiveHealthContributor} backed by a map with values adapted as
* necessary.
*
* @param <V> the value type
* @author Phillip Webb
*/
class CompositeReactiveHealthContributorMapAdapter<V> extends NamedContributorsMapAdapter<V, ReactiveHealthContributor>
implements CompositeReactiveHealthContributor {
CompositeReactiveHealthContributorMapAdapter(Map<String, V> map,
Function<V, ? extends ReactiveHealthContributor> valueAdapter) {
super(map, valueAdapter);
}
}

@ -30,7 +30,9 @@ import reactor.util.function.Tuple2;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of a {@link CompositeReactiveHealthContributor}
*/ */
@Deprecated
public class CompositeReactiveHealthIndicator implements ReactiveHealthIndicator { public class CompositeReactiveHealthIndicator implements ReactiveHealthIndicator {
private final ReactiveHealthIndicatorRegistry registry; private final ReactiveHealthIndicatorRegistry registry;

@ -0,0 +1,50 @@
/*
* Copyright 2012-2019 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.health;
/**
* A mutable registry of health endpoint contributors (either {@link HealthContributor} or
* {@link ReactiveHealthContributor}).
*
* @param <C> the contributor type
* @author Phillip Webb
* @author Andy Wilkinson
* @author Vedran Pavic
* @author Stephane Nicoll
* @since 2.2.0
* @see NamedContributors
*/
public interface ContributorRegistry<C> extends NamedContributors<C> {
/**
* Register a contributor with the given {@code name}.
* @param name the name of the contributor
* @param contributor the contributor to register
* @throws IllegalStateException if the contributor cannot be registered with the
* given {@code name}.
*/
void registerContributor(String name, C contributor);
/**
* Unregister a previously registered contributor.
* @param name the name of the contributor to unregister
* @return the unregistered indicator, or {@code null} if no indicator was found in
* the registry for the given {@code name}.
*/
C unregisterContributor(String name);
}

@ -0,0 +1,114 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import org.springframework.util.Assert;
/**
* Default {@link ContributorRegistry} implementation.
*
* @param <C> the health contributor type
* @author Phillip Webb
* @see DefaultHealthContributorRegistry
* @see DefaultReactiveHealthContributorRegistry
*/
class DefaultContributorRegistry<C> implements ContributorRegistry<C> {
private final Function<String, String> nameFactory;
private final Object monitor = new Object();
private volatile Map<String, C> contributors;
DefaultContributorRegistry() {
this(Collections.emptyMap());
}
DefaultContributorRegistry(Map<String, C> contributors) {
this(contributors, HealthContributorNameFactory.INSTANCE);
}
DefaultContributorRegistry(Map<String, C> contributors, Function<String, String> nameFactory) {
Assert.notNull(contributors, "Contributors must not be null");
Assert.notNull(nameFactory, "NameFactory must not be null");
this.nameFactory = nameFactory;
Map<String, C> namedContributors = new LinkedHashMap<>();
contributors.forEach((name, contributor) -> namedContributors.put(nameFactory.apply(name), contributor));
this.contributors = Collections.unmodifiableMap(namedContributors);
}
@Override
public void registerContributor(String name, C contributor) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(contributor, "Contributor must not be null");
String adaptedName = this.nameFactory.apply(name);
synchronized (this.monitor) {
Assert.state(!this.contributors.containsKey(adaptedName),
() -> "A contributor named \"" + adaptedName + "\" has already been registered");
Map<String, C> contributors = new LinkedHashMap<>(this.contributors);
contributors.put(adaptedName, contributor);
this.contributors = Collections.unmodifiableMap(contributors);
}
}
@Override
public C unregisterContributor(String name) {
Assert.notNull(name, "Name must not be null");
String adaptedName = this.nameFactory.apply(name);
synchronized (this.monitor) {
C unregistered = this.contributors.get(adaptedName);
if (unregistered != null) {
Map<String, C> contributors = new LinkedHashMap<>(this.contributors);
contributors.remove(adaptedName);
this.contributors = Collections.unmodifiableMap(contributors);
}
return unregistered;
}
}
@Override
public C getContributor(String name) {
return this.contributors.get(name);
}
@Override
public Iterator<NamedContributor<C>> iterator() {
Iterator<Map.Entry<String, C>> iterator = this.contributors.entrySet().iterator();
return new Iterator<NamedContributor<C>>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<C> next() {
Entry<String, C> entry = iterator.next();
return NamedContributor.of(entry.getKey(), entry.getValue());
}
};
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.function.Function;
/**
* Default {@link HealthContributorRegistry} implementation.
*
* @author Phillip Webb
* @since 2.2.0
*/
public class DefaultHealthContributorRegistry extends DefaultContributorRegistry<HealthContributor>
implements HealthContributorRegistry {
public DefaultHealthContributorRegistry() {
}
public DefaultHealthContributorRegistry(Map<String, HealthContributor> contributors) {
super(contributors);
}
public DefaultHealthContributorRegistry(Map<String, HealthContributor> contributors,
Function<String, String> nameFactory) {
super(contributors, nameFactory);
}
}

@ -28,7 +28,9 @@ import org.springframework.util.Assert;
* @author Vedran Pavic * @author Vedran Pavic
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.1.0 * @since 2.1.0
* @deprecated since 2.2.0 in favor of {@link DefaultContributorRegistry}
*/ */
@Deprecated
public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry {
private final Object monitor = new Object(); private final Object monitor = new Object();

@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Map;
import java.util.function.Function;
/**
* Default {@link ReactiveHealthContributorRegistry} implementation.
*
* @author Phillip Webb
* @since 2.0.0
*/
public class DefaultReactiveHealthContributorRegistry extends DefaultContributorRegistry<ReactiveHealthContributor>
implements ReactiveHealthContributorRegistry {
public DefaultReactiveHealthContributorRegistry() {
}
public DefaultReactiveHealthContributorRegistry(Map<String, ReactiveHealthContributor> contributors) {
super(contributors);
}
public DefaultReactiveHealthContributorRegistry(Map<String, ReactiveHealthContributor> contributors,
Function<String, String> nameFactory) {
super(contributors, nameFactory);
}
}

@ -28,7 +28,9 @@ import org.springframework.util.Assert;
* @author Vedran Pavic * @author Vedran Pavic
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.1.0 * @since 2.1.0
* @deprecated since 2.2.0 in favor of {@link DefaultContributorRegistry}
*/ */
@Deprecated
public class DefaultReactiveHealthIndicatorRegistry implements ReactiveHealthIndicatorRegistry { public class DefaultReactiveHealthIndicatorRegistry implements ReactiveHealthIndicatorRegistry {
private final Object monitor = new Object(); private final Object monitor = new Object();

@ -22,15 +22,13 @@ import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Carries information about the health of a component or subsystem. * Carries information about the health of a component or subsystem. Extends
* <p> * {@link HealthComponent} so that additional contextual details about the system can be
* {@link Health} contains a {@link Status} to express the state of a component or * returned along with the {@link Status}.
* subsystem and some additional details to carry some contextual information.
* <p> * <p>
* {@link Health} instances can be created by using {@link Builder}'s fluent API. Typical * {@link Health} instances can be created by using {@link Builder}'s fluent API. Typical
* usage in a {@link HealthIndicator} would be: * usage in a {@link HealthIndicator} would be:
@ -38,10 +36,10 @@ import org.springframework.util.Assert;
* <pre class="code"> * <pre class="code">
* try { * try {
* // do some test to determine state of component * // do some test to determine state of component
* return new Health.Builder().up().withDetail("version", "1.1.2").build(); * return Health.up().withDetail("version", "1.1.2").build();
* } * }
* catch (Exception ex) { * catch (Exception ex) {
* return new Health.Builder().down(ex).build(); * return Health.down(ex).build();
* } * }
* </pre> * </pre>
* *
@ -51,7 +49,7 @@ import org.springframework.util.Assert;
* @since 1.1.0 * @since 1.1.0
*/ */
@JsonInclude(Include.NON_EMPTY) @JsonInclude(Include.NON_EMPTY)
public final class Health { public final class Health extends HealthComponent {
private final Status status; private final Status status;
@ -71,7 +69,7 @@ public final class Health {
* Return the status of the health. * Return the status of the health.
* @return the status (never {@code null}) * @return the status (never {@code null})
*/ */
@JsonUnwrapped @Override
public Status getStatus() { public Status getStatus() {
return this.status; return this.status;
} }
@ -80,10 +78,24 @@ public final class Health {
* Return the details of the health. * Return the details of the health.
* @return the details (or an empty map) * @return the details (or an empty map)
*/ */
@JsonInclude(Include.NON_EMPTY)
public Map<String, Object> getDetails() { public Map<String, Object> getDetails() {
return this.details; return this.details;
} }
/**
* Return a new instance of this {@link Health} with all {@link #getDetails() details}
* removed.
* @return a new instance without details
* @since 2.2.0
*/
Health withoutDetails() {
if (this.details.isEmpty()) {
return this;
}
return status(getStatus()).build();
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == this) { if (obj == this) {

@ -32,8 +32,10 @@ import java.util.Map;
* *
* @author Christian Dupuis * @author Christian Dupuis
* @since 1.1.0 * @since 1.1.0
* @deprecated since 2.2.0 in favor of {@link StatusAggregator}
*/ */
@FunctionalInterface @FunctionalInterface
@Deprecated
public interface HealthAggregator { public interface HealthAggregator {
/** /**

@ -0,0 +1,41 @@
/*
* Copyright 2012-2019 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.health;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
/**
* An component that contributes data to results returned from the {@link HealthEndpoint}.
*
* @author Phillip Webb
* @since 2.2.0
* @see Health
* @see CompositeHealth
*/
public abstract class HealthComponent {
HealthComponent() {
}
/**
* Return the status of the component.
* @return the component status
*/
@JsonUnwrapped
public abstract Status getStatus();
}

@ -0,0 +1,31 @@
/*
* Copyright 2012-2019 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.health;
/**
* Tagging interface for classes that contribute to {@link HealthComponent health
* components} to the results returned from the {@link HealthEndpoint}. A contributor must
* be either a {@link HealthIndicator} or a {@link CompositeHealthContributor}.
*
* @author Phillip Webb
* @since 2.2.0
* @see HealthIndicator
* @see CompositeHealthContributor
*/
public interface HealthContributor {
}

@ -0,0 +1,48 @@
/*
* Copyright 2012-2019 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.health;
import java.util.Locale;
import java.util.function.Function;
/**
* Generate a sensible health indicator name based on its bean name.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
public class HealthContributorNameFactory implements Function<String, String> {
private static final String[] SUFFIXES = { "healthindicator", "healthcontributor" };
/**
* A shared singleton {@link HealthContributorNameFactory} instance.
*/
public static final HealthContributorNameFactory INSTANCE = new HealthContributorNameFactory();
@Override
public String apply(String name) {
for (String suffix : SUFFIXES) {
if (name != null && name.toLowerCase(Locale.ENGLISH).endsWith(suffix)) {
return name.substring(0, name.length() - suffix.length());
}
}
return name;
}
}

@ -0,0 +1,27 @@
/*
* Copyright 2012-2019 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.health;
/**
* {@link ContributorRegistry} for {@link HealthContributor HealthContributors}.
*
* @author Phillip Webb
* @since 2.2.0
*/
public interface HealthContributorRegistry extends ContributorRegistry<HealthContributor> {
}

@ -16,10 +16,13 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.util.Assert; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
/** /**
* {@link Endpoint @Endpoint} to expose application health information. * {@link Endpoint @Endpoint} to expose application health information.
@ -31,57 +34,49 @@ import org.springframework.util.Assert;
* @since 2.0.0 * @since 2.0.0
*/ */
@Endpoint(id = "health") @Endpoint(id = "health")
public class HealthEndpoint { public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, HealthComponent> {
private final HealthIndicator healthIndicator; private static final String[] EMPTY_PATH = {};
/** /**
* Create a new {@link HealthEndpoint} instance that will use the given * Create a new {@link HealthEndpoint} instance that will use the given {@code
* {@code healthIndicator} to generate its response. * healthIndicator} to generate its response.
* @param healthIndicator the health indicator * @param healthIndicator the health indicator
* @deprecated since 2.2.0 in favor of
* {@link #HealthEndpoint(HealthContributorRegistry, HealthEndpointSettings)}
*/ */
@Deprecated
public HealthEndpoint(HealthIndicator healthIndicator) { public HealthEndpoint(HealthIndicator healthIndicator) {
Assert.notNull(healthIndicator, "HealthIndicator must not be null");
this.healthIndicator = healthIndicator;
}
@ReadOperation
public Health health() {
return this.healthIndicator.health();
} }
/** /**
* Return the {@link Health} of a particular component or {@code null} if such * Create a new {@link HealthEndpoint} instance.
* component does not exist. * @param registry the health contributor registry
* @param component the name of a particular {@link HealthIndicator} * @param settings the health endpoint settings
* @return the {@link Health} for the component or {@code null}
*/ */
public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointSettings settings) {
super(registry, settings);
}
@ReadOperation @ReadOperation
public Health healthForComponent(@Selector String component) { public HealthComponent health() {
HealthIndicator indicator = getNestedHealthIndicator(this.healthIndicator, component); return healthForPath(EMPTY_PATH);
return (indicator != null) ? indicator.health() : null;
} }
/**
* Return the {@link Health} of a particular {@code instance} managed by the specified
* {@code component} or {@code null} if that particular component is not a
* {@link CompositeHealthIndicator} or if such instance does not exist.
* @param component the name of a particular {@link CompositeHealthIndicator}
* @param instance the name of an instance managed by that component
* @return the {@link Health} for the component instance of {@code null}
*/
@ReadOperation @ReadOperation
public Health healthForComponentInstance(@Selector String component, @Selector String instance) { public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) {
HealthIndicator indicator = getNestedHealthIndicator(this.healthIndicator, component); return getHealth(SecurityContext.NONE, true, path);
HealthIndicator nestedIndicator = getNestedHealthIndicator(indicator, instance); }
return (nestedIndicator != null) ? nestedIndicator.health() : null;
@Override
protected HealthComponent getHealth(HealthContributor contributor, boolean includeDetails) {
return ((HealthIndicator) contributor).getHealth(includeDetails);
} }
private HealthIndicator getNestedHealthIndicator(HealthIndicator healthIndicator, String name) { @Override
if (healthIndicator instanceof CompositeHealthIndicator) { protected HealthComponent aggregateContributions(Map<String, HealthComponent> contributions,
return ((CompositeHealthIndicator) healthIndicator).getRegistry().get(name); StatusAggregator statusAggregator, boolean includeDetails) {
} return getCompositeHealth(contributions, statusAggregator, includeDetails);
return null;
} }
} }

@ -0,0 +1,49 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.boot.actuate.endpoint.SecurityContext;
/**
* Setting for a {@link HealthEndpoint}.
*
* @author Phillip Webb
* @since 2.2.0
*/
public interface HealthEndpointSettings {
/**
* Returns if {@link Health#getDetails() health details} should be included in the
* response.
* @param securityContext the endpoint security context
* @return {@code true} to included details or {@code false} to hide them
*/
boolean includeDetails(SecurityContext securityContext);
/**
* Returns the status agreggator that should be used for the endpoint.
* @return the status aggregator
*/
StatusAggregator getStatusAggregator();
/**
* Returns the {@link HttpCodeStatusMapper} that should be used for the endpoint.
* @return the HTTP code status mapper
*/
HttpCodeStatusMapper getHttpCodeStatusMapper();
}

@ -0,0 +1,127 @@
/*
* Copyright 2012-2019 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.health;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.util.Assert;
/**
* Base class for health endpoints and health endpoint extensions.
*
* @param <C> the contributor type
* @param <T> the contributed health component type
* @author Phillip Webb
*/
abstract class HealthEndpointSupport<C, T> {
private final ContributorRegistry<C> registry;
private final HealthEndpointSettings settings;
/**
* Throw a new {@link IllegalStateException} to indicate a constructor has been
* deprecated.
* @deprecated since 2.2.0 in order to support deprecated subclass constructors
*/
@Deprecated
HealthEndpointSupport() {
throw new IllegalStateException("Unable to create " + getClass() + " using deprecated constructor");
}
/**
* Create a new {@link HealthEndpointSupport} instance.
* @param registry the health contributor registry
* @param settings the health settings
*/
HealthEndpointSupport(ContributorRegistry<C> registry, HealthEndpointSettings settings) {
Assert.notNull(registry, "Registry must not be null");
Assert.notNull(settings, "Settings must not be null");
this.registry = registry;
this.settings = settings;
}
/**
* Return the health endpoint settings.
* @return the settings
*/
protected final HealthEndpointSettings getSettings() {
return this.settings;
}
T getHealth(SecurityContext securityContext, boolean alwaysIncludeDetails, String... path) {
boolean includeDetails = alwaysIncludeDetails || this.settings.includeDetails(securityContext);
boolean isRoot = path.length == 0;
if (!includeDetails && !isRoot) {
return null;
}
Object contributor = getContributor(path);
return getContribution(contributor, includeDetails);
}
@SuppressWarnings("unchecked")
private Object getContributor(String[] path) {
Object contributor = this.registry;
int pathOffset = 0;
while (pathOffset < path.length) {
if (!(contributor instanceof NamedContributors)) {
return null;
}
contributor = ((NamedContributors<C>) contributor).getContributor(path[pathOffset]);
pathOffset++;
}
return contributor;
}
@SuppressWarnings("unchecked")
private T getContribution(Object contributor, boolean includeDetails) {
if (contributor instanceof NamedContributors) {
return getAggregateHealth((NamedContributors<C>) contributor, includeDetails);
}
return (contributor != null) ? getHealth((C) contributor, includeDetails) : null;
}
private T getAggregateHealth(NamedContributors<C> namedContributors, boolean includeDetails) {
Map<String, T> contributions = new LinkedHashMap<>();
for (NamedContributor<C> namedContributor : namedContributors) {
String name = namedContributor.getName();
T contribution = getContribution(namedContributor.getContributor(), includeDetails);
contributions.put(name, contribution);
}
if (contributions.isEmpty()) {
return null;
}
return aggregateContributions(contributions, this.settings.getStatusAggregator(), includeDetails);
}
protected abstract T getHealth(C contributor, boolean includeDetails);
protected abstract T aggregateContributions(Map<String, T> contributions, StatusAggregator statusAggregator,
boolean includeDetails);
protected final CompositeHealth getCompositeHealth(Map<String, HealthComponent> components,
StatusAggregator statusAggregator, boolean includeDetails) {
Status status = statusAggregator.getAggregateStatus(
components.values().stream().map(HealthComponent::getStatus).collect(Collectors.toSet()));
Map<String, HealthComponent> includedComponents = includeDetails ? components : null;
return new CompositeHealth(status, includedComponents);
}
}

@ -16,11 +16,12 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.function.Supplier; import java.util.Map;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
@ -37,37 +38,60 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten
* @since 2.0.0 * @since 2.0.0
*/ */
@EndpointWebExtension(endpoint = HealthEndpoint.class) @EndpointWebExtension(endpoint = HealthEndpoint.class)
public class HealthEndpointWebExtension { public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthContributor, HealthComponent> {
private final HealthEndpoint delegate; private static final String[] NO_PATH = {};
private final HealthWebEndpointResponseMapper responseMapper;
/**
* Create a new {@link HealthEndpointWebExtension} instance using a delegate endpoint.
* @param delegate the delegate endpoint
* @param responseMapper the response mapper
* @deprecated since 2.2.0 in favor of
* {@link #HealthEndpointWebExtension(HealthContributorRegistry, HealthEndpointSettings)}
*/
@Deprecated
public HealthEndpointWebExtension(HealthEndpoint delegate, HealthWebEndpointResponseMapper responseMapper) { public HealthEndpointWebExtension(HealthEndpoint delegate, HealthWebEndpointResponseMapper responseMapper) {
this.delegate = delegate;
this.responseMapper = responseMapper;
} }
@ReadOperation /**
public WebEndpointResponse<Health> health(SecurityContext securityContext) { * Create a new {@link HealthEndpointWebExtension} instance.
return this.responseMapper.map(this.delegate.health(), securityContext); * @param registry the health contributor registry
* @param settings the health endpoint settings
*/
public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointSettings settings) {
super(registry, settings);
} }
@ReadOperation @ReadOperation
public WebEndpointResponse<Health> healthForComponent(SecurityContext securityContext, @Selector String component) { public WebEndpointResponse<HealthComponent> health(SecurityContext securityContext) {
Supplier<Health> health = () -> this.delegate.healthForComponent(component); return health(securityContext, NO_PATH);
return this.responseMapper.mapDetails(health, securityContext);
} }
@ReadOperation @ReadOperation
public WebEndpointResponse<Health> healthForComponentInstance(SecurityContext securityContext, public WebEndpointResponse<HealthComponent> health(SecurityContext securityContext,
@Selector String component, @Selector String instance) { @Selector(match = Match.ALL_REMAINING) String... path) {
Supplier<Health> health = () -> this.delegate.healthForComponentInstance(component, instance); return health(securityContext, false, path);
return this.responseMapper.mapDetails(health, securityContext); }
public WebEndpointResponse<HealthComponent> health(SecurityContext securityContext, boolean alwaysIncludeDetails,
String... path) {
HealthComponent health = getHealth(securityContext, alwaysIncludeDetails, path);
if (health == null) {
return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
}
int statusCode = getSettings().getHttpCodeStatusMapper().getStatusCode(health.getStatus());
return new WebEndpointResponse<>(health, statusCode);
}
@Override
protected HealthComponent getHealth(HealthContributor contributor, boolean includeDetails) {
return ((HealthIndicator) contributor).getHealth(includeDetails);
} }
public WebEndpointResponse<Health> getHealth(SecurityContext securityContext, ShowDetails showDetails) { @Override
return this.responseMapper.map(this.delegate.health(), securityContext, showDetails); protected HealthComponent aggregateContributions(Map<String, HealthComponent> contributions,
StatusAggregator statusAggregator, boolean includeDetails) {
return getCompositeHealth(contributions, statusAggregator, includeDetails);
} }
} }

@ -17,18 +17,31 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
/** /**
* Strategy interface used to provide an indication of application health. * Strategy interface used to contribute {@link Health} to the results returned from the
* {@link HealthEndpoint}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
* @since 1.0.0 * @since 1.0.0
* @see ApplicationHealthIndicator * @see ApplicationHealthIndicator
*/ */
@FunctionalInterface @FunctionalInterface
public interface HealthIndicator { public interface HealthIndicator extends HealthContributor {
/** /**
* Return an indication of health. * Return an indication of health.
* @return the health for * @param includeDetails if details should be included or removed
* @return the health
* @since 2.2.0
*/
default Health getHealth(boolean includeDetails) {
Health health = health();
return includeDetails ? health : health.withoutDetails();
}
/**
* Return an indication of health.
* @return the health
*/ */
Health health(); Health health();

@ -16,24 +16,15 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Locale;
import java.util.function.Function;
/** /**
* Generate a sensible health indicator name based on its bean name. * Generate a sensible health indicator name based on its bean name.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of {@link HealthContributorNameFactory}
*/ */
public class HealthIndicatorNameFactory implements Function<String, String> { @Deprecated
public class HealthIndicatorNameFactory extends HealthContributorNameFactory {
@Override
public String apply(String name) {
int index = name.toLowerCase(Locale.ENGLISH).indexOf("healthindicator");
if (index > 0) {
return name.substring(0, index);
}
return name;
}
} }

@ -27,7 +27,11 @@ import org.springframework.util.Assert;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of
* {@link ReactiveHealthContributor#adapt(HealthContributor)}
* @see ReactiveHealthContributor#adapt(HealthContributor)
*/ */
@Deprecated
public class HealthIndicatorReactiveAdapter implements ReactiveHealthIndicator { public class HealthIndicatorReactiveAdapter implements ReactiveHealthIndicator {
private final HealthIndicator delegate; private final HealthIndicator delegate;

@ -19,7 +19,7 @@ package org.springframework.boot.actuate.health;
import java.util.Map; import java.util.Map;
/** /**
* A registry of {@link HealthIndicator HealthIndicators}. * A mutable registry of {@link HealthIndicator HealthIndicators}.
* <p> * <p>
* Implementations <strong>must</strong> be thread-safe. * Implementations <strong>must</strong> be thread-safe.
* *
@ -27,7 +27,10 @@ import java.util.Map;
* @author Vedran Pavic * @author Vedran Pavic
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.1.0 * @since 2.1.0
* @see HealthEndpoint
* @deprecated since 2.2.0 in favor of a {@link HealthContributorRegistry}
*/ */
@Deprecated
public interface HealthIndicatorRegistry { public interface HealthIndicatorRegistry {
/** /**
@ -35,8 +38,8 @@ public interface HealthIndicatorRegistry {
* {@code name}. * {@code name}.
* @param name the name of the indicator * @param name the name of the indicator
* @param healthIndicator the indicator * @param healthIndicator the indicator
* @throws IllegalStateException if an indicator with the given {@code name} is * @throws IllegalStateException if the indicator cannot be registered with the given
* already registered. * {@code name}.
*/ */
void register(String name, HealthIndicator healthIndicator); void register(String name, HealthIndicator healthIndicator);

@ -26,7 +26,9 @@ import org.springframework.util.Assert;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.1.0 * @since 2.1.0
* @deprecated since 2.2.0 in favor of {@link DefaultHealthIndicatorRegistry}
*/ */
@Deprecated
public class HealthIndicatorRegistryFactory { public class HealthIndicatorRegistryFactory {
private final Function<String, String> healthIndicatorNameFactory; private final Function<String, String> healthIndicatorNameFactory;

@ -28,7 +28,10 @@ import org.springframework.util.Assert;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of {@link HttpCodeStatusMapper} or
* {@link SimpleHttpCodeStatusMapper}
*/ */
@Deprecated
public class HealthStatusHttpMapper { public class HealthStatusHttpMapper {
private Map<String, Integer> statusMapping = new HashMap<>(); private Map<String, Integer> statusMapping = new HashMap<>();

@ -28,7 +28,10 @@ import org.springframework.util.CollectionUtils;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.2.0 in favor of {@link HealthEndpointWebExtension} or
* {@link ReactiveHealthEndpointWebExtension}
*/ */
@Deprecated
public class HealthWebEndpointResponseMapper { public class HealthWebEndpointResponseMapper {
private final HealthStatusHttpMapper statusHttpMapper; private final HealthStatusHttpMapper statusHttpMapper;

@ -0,0 +1,37 @@
/*
* Copyright 2012-2019 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.health;
/**
* Strategy used to map a {@link Status health status} to an HTTP status code.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
@FunctionalInterface
public interface HttpCodeStatusMapper {
/**
* Return the HTTP status code that corresponds to the given {@link Status health
* status}.
* @param status the health status to map
* @return the corresponding HTTP status code
*/
int getStatusCode(Status status);
}

@ -0,0 +1,62 @@
/*
* Copyright 2012-2019 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.health;
import org.springframework.util.Assert;
/**
* A single named health endpoint contributors (either {@link HealthContributor} or
* {@link ReactiveHealthContributor}).
*
* @param <C> the contributor type
* @author Phillip Webb
* @since 2.0.0
* @see NamedContributors
*/
public interface NamedContributor<C> {
/**
* Returns the name of the contributor.
* @return the contributor name
*/
String getName();
/**
* Returns the contributor instance.
* @return the contributor instance
*/
C getContributor();
static <C> NamedContributor<C> of(String name, C contributor) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(contributor, "Contributor must not be null");
return new NamedContributor<C>() {
@Override
public String getName() {
return name;
}
@Override
public C getContributor() {
return contributor;
}
};
}
}

@ -0,0 +1,48 @@
/*
* Copyright 2012-2019 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.health;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* A collection of named health endpoint contributors (either {@link HealthContributor} or
* {@link ReactiveHealthContributor}).
*
* @param <C> the contributor type
* @author Phillip Webb
* @since 2.0.0
* @see NamedContributor
*/
public interface NamedContributors<C> extends Iterable<NamedContributor<C>> {
/**
* Return the contributor with the given name.
* @param name the name of the contributor
* @return a contributor instance of {@code null}
*/
C getContributor(String name);
/**
* Return a stream of the {@link NamedContributor named contributors}.
* @return the stream of named contributors
*/
default Stream<NamedContributor<C>> stream() {
return StreamSupport.stream(spliterator(), false);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save