From 8d2c090af871fd54292f106467b504e6552c8134 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 5 Sep 2025 15:05:27 +0100 Subject: [PATCH] Move MongoDB health support into spring-boot-mongodb Previously the MongoDB health indicates used Spring Data's templates (imperative and reactive) to access MongoDB. This prevented health information from being available in apps using Mongo's Java Driver directly. This commit updates the health indicates so that they no longer depend upon Spring Data MongoDB and instead using MongoDB's Java Driver directly. As they no longer depend on Spring Data MongoDB, the indicators have also moved from spring-boot-data-mongodb to spring-boot-mongodb. Closes gh-47051 --- module/spring-boot-data-mongodb/build.gradle | 9 --- .../health/MongoReactiveHealthIndicator.java | 54 -------------- ...ot.autoconfigure.AutoConfiguration.imports | 2 - module/spring-boot-mongodb/build.gradle | 2 + .../MongoHealthIndicatorIntegrationTests.java | 16 +++- ...activeHealthIndicatorIntegrationTests.java | 19 +++-- ...ngoHealthContributorAutoConfiguration.java | 19 +++-- ...iveHealthContributorAutoConfiguration.java | 18 ++--- .../autoconfigure/health/package-info.java | 2 +- .../mongodb/health/MongoHealthIndicator.java | 32 +++++--- .../health/MongoReactiveHealthIndicator.java | 74 +++++++++++++++++++ .../boot}/mongodb/health/package-info.java | 2 +- ...ot.autoconfigure.AutoConfiguration.imports | 2 + ...althContributorAutoConfigurationTests.java | 6 +- ...althContributorAutoConfigurationTests.java | 11 ++- .../health/MongoHealthIndicatorTests.java | 36 ++++++--- .../MongoReactiveHealthIndicatorTests.java | 34 +++++---- 17 files changed, 199 insertions(+), 139 deletions(-) delete mode 100644 module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicator.java rename module/{spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data => spring-boot-mongodb/src/dockerTest/java/org/springframework/boot}/mongodb/health/MongoHealthIndicatorIntegrationTests.java (83%) rename module/{spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data => spring-boot-mongodb/src/dockerTest/java/org/springframework/boot}/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java (83%) rename module/{spring-boot-data-mongodb/src/main/java/org/springframework/boot/data => spring-boot-mongodb/src/main/java/org/springframework/boot}/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java (77%) rename module/{spring-boot-data-mongodb/src/main/java/org/springframework/boot/data => spring-boot-mongodb/src/main/java/org/springframework/boot}/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java (76%) rename module/{spring-boot-data-mongodb/src/main/java/org/springframework/boot/data => spring-boot-mongodb/src/main/java/org/springframework/boot}/mongodb/autoconfigure/health/package-info.java (91%) rename module/{spring-boot-data-mongodb/src/main/java/org/springframework/boot/data => spring-boot-mongodb/src/main/java/org/springframework/boot}/mongodb/health/MongoHealthIndicator.java (57%) create mode 100644 module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicator.java rename module/{spring-boot-data-mongodb/src/main/java/org/springframework/boot/data => spring-boot-mongodb/src/main/java/org/springframework/boot}/mongodb/health/package-info.java (93%) rename module/{spring-boot-data-mongodb => spring-boot-mongodb}/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java (90%) rename module/{spring-boot-data-mongodb => spring-boot-mongodb}/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java (84%) rename module/{spring-boot-data-mongodb/src/test/java/org/springframework/boot/data => spring-boot-mongodb/src/test/java/org/springframework/boot}/mongodb/health/MongoHealthIndicatorTests.java (61%) rename module/{spring-boot-data-mongodb/src/test/java/org/springframework/boot/data => spring-boot-mongodb/src/test/java/org/springframework/boot}/mongodb/health/MongoReactiveHealthIndicatorTests.java (65%) diff --git a/module/spring-boot-data-mongodb/build.gradle b/module/spring-boot-data-mongodb/build.gradle index ec47e38cbcc..1eee13e63de 100644 --- a/module/spring-boot-data-mongodb/build.gradle +++ b/module/spring-boot-data-mongodb/build.gradle @@ -19,7 +19,6 @@ plugins { id "org.springframework.boot.auto-configuration" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.deployed" - id "org.springframework.boot.docker-test" id "org.springframework.boot.optional-dependencies" } @@ -35,18 +34,10 @@ dependencies { compileOnly("com.google.code.findbugs:jsr305") optional(project(":core:spring-boot-autoconfigure")) - optional(project(":module:spring-boot-health")) optional(project(":module:spring-boot-reactor")) optional("org.mongodb:mongodb-driver-reactivestreams") optional("org.mongodb:mongodb-driver-sync") - dockerTestImplementation(project(":core:spring-boot-test")) - dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) - dockerTestImplementation("ch.qos.logback:logback-classic") - dockerTestImplementation("org.junit.jupiter:junit-jupiter") - dockerTestImplementation("org.testcontainers:junit-jupiter") - dockerTestImplementation("org.testcontainers:mongodb") - testCompileOnly("com.fasterxml.jackson.core:jackson-annotations") testImplementation(project(":core:spring-boot-test")) diff --git a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicator.java b/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicator.java deleted file mode 100644 index 41867852846..00000000000 --- a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012-present 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.data.mongodb.health; - -import org.bson.Document; -import reactor.core.publisher.Mono; - -import org.springframework.boot.health.contributor.AbstractReactiveHealthIndicator; -import org.springframework.boot.health.contributor.Health; -import org.springframework.boot.health.contributor.ReactiveHealthIndicator; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.util.Assert; - -/** - * A {@link ReactiveHealthIndicator} for Mongo. - * - * @author Yulin Qin - * @since 4.0.0 - */ -public class MongoReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - - private final ReactiveMongoTemplate reactiveMongoTemplate; - - public MongoReactiveHealthIndicator(ReactiveMongoTemplate reactiveMongoTemplate) { - super("Mongo health check failed"); - Assert.notNull(reactiveMongoTemplate, "'reactiveMongoTemplate' must not be null"); - this.reactiveMongoTemplate = reactiveMongoTemplate; - } - - @Override - protected Mono doHealthCheck(Health.Builder builder) { - Mono buildInfo = this.reactiveMongoTemplate.executeCommand("{ hello: 1 }"); - return buildInfo.map((document) -> up(builder, document)); - } - - private Health up(Health.Builder builder, Document document) { - return builder.up().withDetail("maxWireVersion", document.getInteger("maxWireVersion")).build(); - } - -} diff --git a/module/spring-boot-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 21134753f00..041ff89777d 100644 --- a/module/spring-boot-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/module/spring-boot-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -2,5 +2,3 @@ org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveRepositoriesAutoConfiguration org.springframework.boot.data.mongodb.autoconfigure.MongoRepositoriesAutoConfiguration -org.springframework.boot.data.mongodb.autoconfigure.health.MongoHealthContributorAutoConfiguration -org.springframework.boot.data.mongodb.autoconfigure.health.MongoReactiveHealthContributorAutoConfiguration diff --git a/module/spring-boot-mongodb/build.gradle b/module/spring-boot-mongodb/build.gradle index c0b35f0de53..7c4133bb5b6 100644 --- a/module/spring-boot-mongodb/build.gradle +++ b/module/spring-boot-mongodb/build.gradle @@ -33,6 +33,7 @@ dependencies { optional(project(":core:spring-boot-autoconfigure")) optional(project(":core:spring-boot-docker-compose")) optional(project(":core:spring-boot-testcontainers")) + optional(project(":module:spring-boot-health")) optional(project(":module:spring-boot-micrometer-metrics")) optional(project(":module:spring-boot-reactor")) optional("io.netty:netty-transport") @@ -49,6 +50,7 @@ dependencies { testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-testcontainers"))) + testImplementation("io.projectreactor:reactor-test") testImplementation("org.springframework.data:spring-data-mongodb") testRuntimeOnly("ch.qos.logback:logback-classic") diff --git a/module/spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicatorIntegrationTests.java b/module/spring-boot-mongodb/src/dockerTest/java/org/springframework/boot/mongodb/health/MongoHealthIndicatorIntegrationTests.java similarity index 83% rename from module/spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicatorIntegrationTests.java rename to module/spring-boot-mongodb/src/dockerTest/java/org/springframework/boot/mongodb/health/MongoHealthIndicatorIntegrationTests.java index a016aa7a44c..1822f03da98 100644 --- a/module/spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicatorIntegrationTests.java +++ b/module/spring-boot-mongodb/src/dockerTest/java/org/springframework/boot/mongodb/health/MongoHealthIndicatorIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.health; +package org.springframework.boot.mongodb.health; import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; @@ -23,6 +23,7 @@ import com.mongodb.ServerApi; import com.mongodb.ServerApiVersion; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; @@ -31,7 +32,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Status; import org.springframework.boot.testsupport.container.TestImage; -import org.springframework.data.mongodb.core.MongoTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -49,13 +49,21 @@ class MongoHealthIndicatorIntegrationTests { @Test void standardApi() { Health health = mongoHealth(); - assertThat(health.getStatus()).isEqualTo(Status.UP); + assertHealth(health); } @Test void strictV1Api() { Health health = mongoHealth(ServerApi.builder().strict(true).version(ServerApiVersion.V1).build()); + assertHealth(health); + } + + private void assertHealth(Health health) { assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsKey("maxWireVersion"); + assertThat(health.getDetails()).hasEntrySatisfying("databases", + (databases) -> assertThat(databases).asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactlyInAnyOrder("local", "admin", "config")); } private Health mongoHealth() { @@ -70,7 +78,7 @@ class MongoHealthIndicatorIntegrationTests { } MongoClientSettings settings = settingsBuilder.build(); MongoClient mongoClient = MongoClients.create(settings); - MongoHealthIndicator healthIndicator = new MongoHealthIndicator(new MongoTemplate(mongoClient, "db")); + MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoClient); return healthIndicator.health(true); } diff --git a/module/spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java b/module/spring-boot-mongodb/src/dockerTest/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java similarity index 83% rename from module/spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java rename to module/spring-boot-mongodb/src/dockerTest/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java index 71e89f0a5b5..877333f31ba 100644 --- a/module/spring-boot-data-mongodb/src/dockerTest/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java +++ b/module/spring-boot-mongodb/src/dockerTest/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicatorIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.health; +package org.springframework.boot.mongodb.health; import java.time.Duration; @@ -25,6 +25,7 @@ import com.mongodb.ServerApi; import com.mongodb.ServerApiVersion; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; @@ -33,7 +34,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Status; import org.springframework.boot.testsupport.container.TestImage; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -51,13 +51,13 @@ class MongoReactiveHealthIndicatorIntegrationTests { @Test void standardApi() { Health health = mongoHealth(); - assertThat(health.getStatus()).isEqualTo(Status.UP); + assertHealth(health); } @Test void strictV1Api() { Health health = mongoHealth(ServerApi.builder().strict(true).version(ServerApiVersion.V1).build()); - assertThat(health.getStatus()).isEqualTo(Status.UP); + assertHealth(health); } private Health mongoHealth() { @@ -72,9 +72,16 @@ class MongoReactiveHealthIndicatorIntegrationTests { } MongoClientSettings settings = settingsBuilder.build(); MongoClient mongoClient = MongoClients.create(settings); - MongoReactiveHealthIndicator healthIndicator = new MongoReactiveHealthIndicator( - new ReactiveMongoTemplate(mongoClient, "db")); + MongoReactiveHealthIndicator healthIndicator = new MongoReactiveHealthIndicator(mongoClient); return healthIndicator.health(true).block(Duration.ofSeconds(30)); } + private void assertHealth(Health health) { + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsKey("maxWireVersion"); + assertThat(health.getDetails()).hasEntrySatisfying("databases", + (databases) -> assertThat(databases).asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactlyInAnyOrder("local", "admin", "config")); + } + } diff --git a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java similarity index 77% rename from module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java rename to module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java index 115d18fbaf9..7a740fee979 100644 --- a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java +++ b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/MongoHealthContributorAutoConfiguration.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.autoconfigure.health; +package org.springframework.boot.mongodb.autoconfigure.health; + +import com.mongodb.client.MongoClient; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -22,14 +24,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration; -import org.springframework.boot.data.mongodb.health.MongoHealthIndicator; import org.springframework.boot.health.autoconfigure.contributor.CompositeHealthContributorConfiguration; import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.health.contributor.HealthContributor; import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; +import org.springframework.boot.mongodb.health.MongoHealthIndicator; import org.springframework.context.annotation.Bean; -import org.springframework.data.mongodb.core.MongoTemplate; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link MongoHealthIndicator}. @@ -37,13 +37,12 @@ import org.springframework.data.mongodb.core.MongoTemplate; * @author Stephane Nicoll * @since 4.0.0 */ -@AutoConfiguration(after = { MongoReactiveHealthContributorAutoConfiguration.class, MongoDataAutoConfiguration.class, - MongoAutoConfiguration.class }) -@ConditionalOnClass({ MongoTemplate.class, MongoHealthIndicator.class, ConditionalOnEnabledHealthIndicator.class }) -@ConditionalOnBean(MongoTemplate.class) +@AutoConfiguration(after = { MongoReactiveHealthContributorAutoConfiguration.class, MongoAutoConfiguration.class }) +@ConditionalOnClass({ MongoClient.class, MongoHealthIndicator.class, ConditionalOnEnabledHealthIndicator.class }) +@ConditionalOnBean(MongoClient.class) @ConditionalOnEnabledHealthIndicator("mongodb") public final class MongoHealthContributorAutoConfiguration - extends CompositeHealthContributorConfiguration { + extends CompositeHealthContributorConfiguration { MongoHealthContributorAutoConfiguration() { super(MongoHealthIndicator::new); @@ -52,7 +51,7 @@ public final class MongoHealthContributorAutoConfiguration @Bean @ConditionalOnMissingBean(name = { "mongoHealthIndicator", "mongoHealthContributor" }) HealthContributor mongoHealthContributor(ConfigurableListableBeanFactory beanFactory) { - return createContributor(beanFactory, MongoTemplate.class); + return createContributor(beanFactory, MongoClient.class); } } diff --git a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java similarity index 76% rename from module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java rename to module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java index 8c0fe6b2d86..80e2da9a528 100644 --- a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java +++ b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfiguration.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.autoconfigure.health; +package org.springframework.boot.mongodb.autoconfigure.health; +import com.mongodb.reactivestreams.client.MongoClient; import reactor.core.publisher.Flux; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -24,13 +25,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration; -import org.springframework.boot.data.mongodb.health.MongoReactiveHealthIndicator; import org.springframework.boot.health.autoconfigure.contributor.CompositeReactiveHealthContributorConfiguration; import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.health.contributor.ReactiveHealthContributor; +import org.springframework.boot.mongodb.autoconfigure.MongoReactiveAutoConfiguration; +import org.springframework.boot.mongodb.health.MongoReactiveHealthIndicator; import org.springframework.context.annotation.Bean; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; /** * {@link EnableAutoConfiguration Auto-configuration} for @@ -39,13 +39,13 @@ import org.springframework.data.mongodb.core.ReactiveMongoTemplate; * @author Stephane Nicoll * @since 4.0.0 */ -@AutoConfiguration(after = MongoReactiveDataAutoConfiguration.class) -@ConditionalOnClass({ ReactiveMongoTemplate.class, Flux.class, MongoReactiveHealthIndicator.class, +@AutoConfiguration(after = MongoReactiveAutoConfiguration.class) +@ConditionalOnClass({ MongoClient.class, Flux.class, MongoReactiveHealthIndicator.class, ConditionalOnEnabledHealthIndicator.class }) -@ConditionalOnBean(ReactiveMongoTemplate.class) +@ConditionalOnBean(MongoClient.class) @ConditionalOnEnabledHealthIndicator("mongodb") public final class MongoReactiveHealthContributorAutoConfiguration - extends CompositeReactiveHealthContributorConfiguration { + extends CompositeReactiveHealthContributorConfiguration { MongoReactiveHealthContributorAutoConfiguration() { super(MongoReactiveHealthIndicator::new); @@ -54,7 +54,7 @@ public final class MongoReactiveHealthContributorAutoConfiguration @Bean @ConditionalOnMissingBean(name = { "mongoHealthIndicator", "mongoHealthContributor" }) ReactiveHealthContributor mongoHealthContributor(ConfigurableListableBeanFactory beanFactory) { - return createContributor(beanFactory, ReactiveMongoTemplate.class); + return createContributor(beanFactory, MongoClient.class); } } diff --git a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/package-info.java b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/package-info.java similarity index 91% rename from module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/package-info.java rename to module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/package-info.java index e22e168a0c1..8d8e7a24042 100644 --- a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/autoconfigure/health/package-info.java +++ b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/autoconfigure/health/package-info.java @@ -18,6 +18,6 @@ * Auto-configuration for Spring Data MongoDB health. */ @NullMarked -package org.springframework.boot.data.mongodb.autoconfigure.health; +package org.springframework.boot.mongodb.autoconfigure.health; import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicator.java b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoHealthIndicator.java similarity index 57% rename from module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicator.java rename to module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoHealthIndicator.java index 005df6362de..541a9a996f5 100644 --- a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicator.java +++ b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoHealthIndicator.java @@ -14,37 +14,51 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.health; +package org.springframework.boot.mongodb.health; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.mongodb.client.MongoClient; import org.bson.Document; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.HealthIndicator; -import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.util.Assert; /** * Simple implementation of a {@link HealthIndicator} returning status information for - * Mongo data stores. + * MongoDB. * * @author Christian Dupuis * @since 4.0.0 */ public class MongoHealthIndicator extends AbstractHealthIndicator { - private final MongoTemplate mongoTemplate; + private static final Document HELLO_COMMAND = Document.parse("{ hello: 1 }"); - public MongoHealthIndicator(MongoTemplate mongoTemplate) { + private final MongoClient mongoClient; + + public MongoHealthIndicator(MongoClient mongoClient) { super("MongoDB health check failed"); - Assert.notNull(mongoTemplate, "'mongoTemplate' must not be null"); - this.mongoTemplate = mongoTemplate; + Assert.notNull(mongoClient, "'mongoClient' must not be null"); + this.mongoClient = mongoClient; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { - Document result = this.mongoTemplate.executeCommand("{ hello: 1 }"); - builder.up().withDetail("maxWireVersion", result.getInteger("maxWireVersion")); + Map details = new LinkedHashMap<>(); + List databases = new ArrayList<>(); + details.put("databases", databases); + this.mongoClient.listDatabaseNames().forEach((database) -> { + Document result = this.mongoClient.getDatabase(database).runCommand(HELLO_COMMAND); + databases.add(database); + details.putIfAbsent("maxWireVersion", result.getInteger("maxWireVersion")); + }); + builder.up().withDetails(details); } } diff --git a/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicator.java b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicator.java new file mode 100644 index 00000000000..c73cdd50e6f --- /dev/null +++ b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicator.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-present 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.mongodb.health; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.mongodb.reactivestreams.client.MongoClient; +import org.bson.Document; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.boot.health.contributor.AbstractReactiveHealthIndicator; +import org.springframework.boot.health.contributor.Health; +import org.springframework.boot.health.contributor.ReactiveHealthIndicator; +import org.springframework.util.Assert; + +/** + * A {@link ReactiveHealthIndicator} for Mongo. + * + * @author Yulin Qin + * @since 4.0.0 + */ +public class MongoReactiveHealthIndicator extends AbstractReactiveHealthIndicator { + + private static final Document HELLO_COMMAND = Document.parse("{ hello: 1 }"); + + private final MongoClient mongoClient; + + public MongoReactiveHealthIndicator(MongoClient mongoClient) { + super("Mongo health check failed"); + Assert.notNull(mongoClient, "'mongoClient' must not be null"); + this.mongoClient = mongoClient; + } + + @Override + protected Mono doHealthCheck(Health.Builder builder) { + Mono> healthDetails = Flux.from(this.mongoClient.listDatabaseNames()) + .flatMap((database) -> Mono.from(this.mongoClient.getDatabase(database).runCommand(HELLO_COMMAND)) + .map((document) -> new HelloResponse(database, document))) + .collectList() + .map((responses) -> { + Map databaseDetails = new LinkedHashMap<>(); + List databases = new ArrayList<>(); + databaseDetails.put("databases", databases); + for (HelloResponse response : responses) { + databases.add(response.database()); + databaseDetails.putIfAbsent("maxWireVersion", response.document().getInteger("maxWireVersion")); + } + return databaseDetails; + }); + return healthDetails.map((details) -> builder.up().withDetails(details).build()); + } + + private record HelloResponse(String database, Document document) { + } + +} diff --git a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/package-info.java b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/package-info.java similarity index 93% rename from module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/package-info.java rename to module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/package-info.java index 4d93eff6db8..b96956377a9 100644 --- a/module/spring-boot-data-mongodb/src/main/java/org/springframework/boot/data/mongodb/health/package-info.java +++ b/module/spring-boot-mongodb/src/main/java/org/springframework/boot/mongodb/health/package-info.java @@ -18,6 +18,6 @@ * MongoDB health integration using Spring Data MongoDB. */ @NullMarked -package org.springframework.boot.data.mongodb.health; +package org.springframework.boot.mongodb.health; import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 4038ff363e1..f86289afe52 100644 --- a/module/spring-boot-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/module/spring-boot-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,5 @@ org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration org.springframework.boot.mongodb.autoconfigure.MongoReactiveAutoConfiguration +org.springframework.boot.mongodb.autoconfigure.health.MongoHealthContributorAutoConfiguration +org.springframework.boot.mongodb.autoconfigure.health.MongoReactiveHealthContributorAutoConfiguration org.springframework.boot.mongodb.autoconfigure.metrics.MongoMetricsAutoConfiguration diff --git a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java similarity index 90% rename from module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java rename to module/spring-boot-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java index 2f511e97bd2..dd6517ef40b 100644 --- a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java +++ b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoHealthContributorAutoConfigurationTests.java @@ -19,10 +19,10 @@ package org.springframework.boot.data.mongodb.autoconfigure.health; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration; -import org.springframework.boot.data.mongodb.health.MongoHealthIndicator; import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration; import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; +import org.springframework.boot.mongodb.autoconfigure.health.MongoHealthContributorAutoConfiguration; +import org.springframework.boot.mongodb.health.MongoHealthIndicator; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; class MongoHealthContributorAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)); @Test diff --git a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java similarity index 84% rename from module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java rename to module/spring-boot-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java index 432be660ae5..364fe01a61c 100644 --- a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java +++ b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/data/mongodb/autoconfigure/health/MongoReactiveHealthContributorAutoConfigurationTests.java @@ -19,13 +19,13 @@ package org.springframework.boot.data.mongodb.autoconfigure.health; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration; -import org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration; -import org.springframework.boot.data.mongodb.health.MongoHealthIndicator; -import org.springframework.boot.data.mongodb.health.MongoReactiveHealthIndicator; import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration; import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; import org.springframework.boot.mongodb.autoconfigure.MongoReactiveAutoConfiguration; +import org.springframework.boot.mongodb.autoconfigure.health.MongoHealthContributorAutoConfiguration; +import org.springframework.boot.mongodb.autoconfigure.health.MongoReactiveHealthContributorAutoConfiguration; +import org.springframework.boot.mongodb.health.MongoHealthIndicator; +import org.springframework.boot.mongodb.health.MongoReactiveHealthIndicator; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -38,8 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; class MongoReactiveHealthContributorAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, - MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoReactiveAutoConfiguration.class, MongoReactiveHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)); @Test diff --git a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicatorTests.java b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/mongodb/health/MongoHealthIndicatorTests.java similarity index 61% rename from module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicatorTests.java rename to module/spring-boot-mongodb/src/test/java/org/springframework/boot/mongodb/health/MongoHealthIndicatorTests.java index 45dd1be26a5..27d1f4f2a96 100644 --- a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/health/MongoHealthIndicatorTests.java +++ b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/mongodb/health/MongoHealthIndicatorTests.java @@ -14,51 +14,67 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.health; +package org.springframework.boot.mongodb.health; + +import java.util.List; +import java.util.function.Consumer; import com.mongodb.MongoException; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; import org.bson.Document; import org.junit.jupiter.api.Test; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Status; -import org.springframework.data.mongodb.core.MongoTemplate; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; /** * Tests for {@link MongoHealthIndicator}. * * @author Christian Dupuis + * @author Andy Wilkinson */ class MongoHealthIndicatorTests { @Test + @SuppressWarnings("unchecked") void mongoIsUp() { Document commandResult = mock(Document.class); given(commandResult.getInteger("maxWireVersion")).willReturn(10); - MongoTemplate mongoTemplate = mock(MongoTemplate.class); - given(mongoTemplate.executeCommand("{ hello: 1 }")).willReturn(commandResult); - MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); + MongoClient mongoClient = mock(MongoClient.class); + MongoIterable databaseNames = mock(MongoIterable.class); + willAnswer((invocation) -> { + ((Consumer) invocation.getArgument(0)).accept("db"); + return null; + }).given(databaseNames).forEach(any()); + given(mongoClient.listDatabaseNames()).willReturn(databaseNames); + MongoDatabase mongoDatabase = mock(MongoDatabase.class); + given(mongoClient.getDatabase("db")).willReturn(mongoDatabase); + given(mongoDatabase.runCommand(Document.parse("{ hello: 1 }"))).willReturn(commandResult); + MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoClient); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails()).containsEntry("maxWireVersion", 10); + assertThat(health.getDetails()).containsEntry("databases", List.of("db")); then(commandResult).should().getInteger("maxWireVersion"); - then(mongoTemplate).should().executeCommand("{ hello: 1 }"); } @Test void mongoIsDown() { - MongoTemplate mongoTemplate = mock(MongoTemplate.class); - given(mongoTemplate.executeCommand("{ hello: 1 }")).willThrow(new MongoException("Connection failed")); - MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate); + MongoClient mongoClient = mock(MongoClient.class); + given(mongoClient.listDatabaseNames()).willThrow(new MongoException("Connection failed")); + MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoClient); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat((String) health.getDetails().get("error")).contains("Connection failed"); - then(mongoTemplate).should().executeCommand("{ hello: 1 }"); } } diff --git a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicatorTests.java b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicatorTests.java similarity index 65% rename from module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicatorTests.java rename to module/spring-boot-mongodb/src/test/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicatorTests.java index 5c956f6ff4e..f6f115e40a4 100644 --- a/module/spring-boot-data-mongodb/src/test/java/org/springframework/boot/data/mongodb/health/MongoReactiveHealthIndicatorTests.java +++ b/module/spring-boot-mongodb/src/test/java/org/springframework/boot/mongodb/health/MongoReactiveHealthIndicatorTests.java @@ -14,11 +14,14 @@ * limitations under the License. */ -package org.springframework.boot.data.mongodb.health; +package org.springframework.boot.mongodb.health; import java.time.Duration; +import java.util.List; import com.mongodb.MongoException; +import com.mongodb.reactivestreams.client.MongoClient; +import com.mongodb.reactivestreams.client.MongoDatabase; import org.bson.Document; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -26,7 +29,6 @@ import reactor.test.StepVerifier; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Status; -import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -40,27 +42,29 @@ import static org.mockito.Mockito.mock; class MongoReactiveHealthIndicatorTests { @Test - void testMongoIsUp() { - Document buildInfo = mock(Document.class); - given(buildInfo.getInteger("maxWireVersion")).willReturn(10); - ReactiveMongoTemplate reactiveMongoTemplate = mock(ReactiveMongoTemplate.class); - given(reactiveMongoTemplate.executeCommand("{ hello: 1 }")).willReturn(Mono.just(buildInfo)); - MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator( - reactiveMongoTemplate); + void mongoIsUp() { + MongoClient mongoClient = mock(MongoClient.class); + given(mongoClient.listDatabaseNames()).willReturn(Mono.just("db")); + MongoDatabase mongoDatabase = mock(MongoDatabase.class); + given(mongoClient.getDatabase("db")).willReturn(mongoDatabase); + Document commandResult = mock(Document.class); + given(mongoDatabase.runCommand(Document.parse("{ hello: 1 }"))).willReturn(Mono.just(commandResult)); + given(commandResult.getInteger("maxWireVersion")).willReturn(10); + MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator(mongoClient); Mono health = mongoReactiveHealthIndicator.health(); StepVerifier.create(health).consumeNextWith((h) -> { assertThat(h.getStatus()).isEqualTo(Status.UP); - assertThat(h.getDetails()).containsOnlyKeys("maxWireVersion"); + assertThat(h.getDetails()).containsOnlyKeys("maxWireVersion", "databases"); assertThat(h.getDetails()).containsEntry("maxWireVersion", 10); + assertThat(h.getDetails()).containsEntry("databases", List.of("db")); }).expectComplete().verify(Duration.ofSeconds(30)); } @Test - void testMongoIsDown() { - ReactiveMongoTemplate reactiveMongoTemplate = mock(ReactiveMongoTemplate.class); - given(reactiveMongoTemplate.executeCommand("{ hello: 1 }")).willThrow(new MongoException("Connection failed")); - MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator( - reactiveMongoTemplate); + void mongoIsDown() { + MongoClient mongoClient = mock(MongoClient.class); + given(mongoClient.listDatabaseNames()).willThrow(new MongoException("Connection failed")); + MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator(mongoClient); Mono health = mongoReactiveHealthIndicator.health(); StepVerifier.create(health).consumeNextWith((h) -> { assertThat(h.getStatus()).isEqualTo(Status.DOWN);