diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMetricsAutoConfiguration.java
new file mode 100644
index 0000000000..17f5f2c838
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMetricsAutoConfiguration.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.autoconfigure.neo4j;
+
+import io.micrometer.core.instrument.MeterRegistry;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.neo4j.driver.Driver;
+import org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+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.neo4j.Neo4jDriverAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
+ * {@link Driver drivers}.
+ *
+ * The reason we are doing this dance with the manual binding is the fact that this
+ * autoconfiguration should work with more than one instance of the driver. If a user has
+ * multiple instances configured, than each instance should be bound via the binder to
+ * registry. Without that requirement, we could just add a {@link Bean @Bean} of type
+ * {@link Neo4jDriverMetrics} to the context and be done.
+ *
+ * @author Michael J. Simons
+ * @since 2.4.0
+ */
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureAfter({ MetricsAutoConfiguration.class, Neo4jDriverAutoConfiguration.class,
+ SimpleMetricsExportAutoConfiguration.class })
+@ConditionalOnClass({ Driver.class, MeterRegistry.class })
+@ConditionalOnBean({ Driver.class, MeterRegistry.class })
+public class Neo4jDriverMetricsAutoConfiguration {
+
+ private static final Log logger = LogFactory.getLog(Neo4jDriverMetricsAutoConfiguration.class);
+
+ @Autowired
+ public void bindDataSourcesToRegistry(Map drivers, MeterRegistry registry) {
+
+ drivers.forEach((name, driver) -> {
+ if (!Neo4jDriverMetrics.metricsAreEnabled(driver)) {
+ return;
+ }
+ driver.verifyConnectivityAsync()
+ .thenRunAsync(() -> new Neo4jDriverMetrics(name, driver, Collections.emptyList()).bindTo(registry))
+ .exceptionally(e -> {
+ logger.warn("Could not verify connection for " + driver + " and thus not bind to metrics: "
+ + e.getMessage());
+ return null;
+ });
+ });
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfiguration.java
index 9f24c18323..48a8ee9382 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,42 +16,77 @@
package org.springframework.boot.actuate.autoconfigure.neo4j;
-import java.util.Map;
-
-import org.neo4j.ogm.session.SessionFactory;
-
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
+import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
+import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
+import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthContributor;
+import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator;
+import org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
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.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration;
+import org.springframework.boot.autoconfigure.neo4j.Neo4jDriverAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import reactor.core.publisher.Flux;
+
+import java.util.Map;
+
+import org.neo4j.driver.Driver;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link Neo4jHealthIndicator}.
+ * The auto-configuration here is responsible for both imperative and reactive health
+ * checks. The reactive health check has precedence over the imperative one.
*
* @author Eric Spiegelberg
* @author Stephane Nicoll
+ * @author Michael J. Simons
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
-@ConditionalOnClass(SessionFactory.class)
-@ConditionalOnBean(SessionFactory.class)
+@ConditionalOnClass({ Driver.class, Health.class })
+@ConditionalOnBean(Driver.class)
@ConditionalOnEnabledHealthIndicator("neo4j")
-@AutoConfigureAfter(Neo4jDataAutoConfiguration.class)
-public class Neo4jHealthContributorAutoConfiguration
- extends CompositeHealthContributorConfiguration {
+@AutoConfigureBefore(HealthContributorAutoConfiguration.class)
+@AutoConfigureAfter({ Neo4jDriverAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
+public class Neo4jHealthContributorAutoConfiguration {
+
+ @Configuration(proxyBeanMethods = false)
+ @Order(-20)
+ static class Neo4jHealthIndicatorConfiguration
+ extends CompositeHealthContributorConfiguration {
+
+ @Bean
+ // If Neo4jReactiveHealthIndicatorConfiguration kicked in, don't add the
+ // imperative version as well
+ @ConditionalOnMissingBean(name = "neo4jHealthContributor")
+ public HealthContributor neo4jHealthContributor(Map drivers) {
+ return createContributor(drivers);
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass({ Flux.class })
+ @Order(-30)
+ static class Neo4jReactiveHealthIndicatorConfiguration
+ extends CompositeReactiveHealthContributorConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(name = "neo4jHealthContributor")
+ public ReactiveHealthContributor neo4jHealthContributor(Map drivers) {
+ return createComposite(drivers);
+ }
- @Bean
- @ConditionalOnMissingBean(name = { "neo4jHealthIndicator", "neo4jHealthContributor" })
- public HealthContributor neo4jHealthContributor(Map sessionFactories) {
- return createContributor(sessionFactories);
}
}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/package-info.java
index 6e11d71c1f..7191701934 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/package-info.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMetricsAutoConfigurationTest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMetricsAutoConfigurationTest.java
new file mode 100644
index 0000000000..02eff7faeb
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMetricsAutoConfigurationTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.autoconfigure.neo4j;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
+import org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.boot.test.context.runner.ContextConsumer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import org.neo4j.driver.Driver;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithMetrics;
+import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
+
+/**
+ * @author Michael J. Simons
+ */
+class Neo4jDriverMetricsAutoConfigurationTest {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
+ AutoConfigurations.of(MetricsAutoConfiguration.class, Neo4jDriverMetricsAutoConfiguration.class));
+
+ private final ContextConsumer assertNoInteractionsWithRegistry = ctx -> {
+
+ if (ctx.getBeansOfType(MeterRegistry.class).isEmpty()) {
+ return;
+ }
+
+ MeterRegistry mockedRegistry = ctx.getBean(MeterRegistry.class);
+ verify(mockedRegistry).config();
+ verifyNoMoreInteractions(mockedRegistry);
+ };
+
+ @Nested
+ class NoMatches {
+
+ @Test
+ void shouldRequireAllNeededClasses() {
+ contextRunner.withUserConfiguration(WithMeterRegistry.class)
+ .withClassLoader(new FilteredClassLoader(Driver.class)).run(assertNoInteractionsWithRegistry);
+
+ contextRunner.withUserConfiguration(WithDriverWithMetrics.class)
+ .withClassLoader(new FilteredClassLoader(MeterRegistry.class))
+ .run(assertNoInteractionsWithRegistry);
+ }
+
+ @Test
+ void shouldRequireAllNeededBeans() {
+ contextRunner.withUserConfiguration(WithDriverWithMetrics.class).run(assertNoInteractionsWithRegistry);
+
+ contextRunner.withUserConfiguration(WithMeterRegistry.class).run(assertNoInteractionsWithRegistry);
+ }
+
+ @Test
+ void shouldRequireDriverWithMetrics() {
+ contextRunner.withUserConfiguration(WithDriverWithoutMetrics.class, WithMeterRegistry.class)
+ .run(assertNoInteractionsWithRegistry);
+
+ }
+
+ }
+
+ @Nested
+ class Matches {
+
+ @Test
+ void shouldRequireDriverWithMetrics() {
+ contextRunner.withUserConfiguration(WithDriverWithMetrics.class, WithMeterRegistry.class).run(ctx -> {
+
+ // Wait a bit to let the completable future of the test that mocks
+ // connectiviy complete.
+ Thread.sleep(500L);
+
+ MeterRegistry meterRegistry = ctx.getBean(MeterRegistry.class);
+ assertThat(meterRegistry.getMeters()).extracting(m -> m.getId().getName())
+ .filteredOn(s -> s.startsWith(Neo4jDriverMetrics.PREFIX)).isNotEmpty();
+ });
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class WithDriverWithMetrics {
+
+ @Bean
+ Driver driver() {
+ return mockDriverWithMetrics();
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class WithDriverWithoutMetrics {
+
+ @Bean
+ Driver driver() {
+ return mockDriverWithoutMetrics();
+ }
+
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class WithMeterRegistry {
+
+ @Bean
+ MeterRegistry meterRegistry() {
+
+ MeterRegistry meterRegistry = spy(SimpleMeterRegistry.class);
+ return meterRegistry;
+ }
+
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMocks.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMocks.java
new file mode 100644
index 0000000000..e8331e2e0b
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jDriverMocks.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.autoconfigure.neo4j;
+
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+
+import org.neo4j.driver.ConnectionPoolMetrics;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Metrics;
+import org.neo4j.driver.exceptions.ClientException;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Some predefined mocks, only to be used internally for tests.
+ *
+ * @author Michael J. Simons
+ */
+final class Neo4jDriverMocks {
+
+ public static Driver mockDriverWithMetrics() {
+ ConnectionPoolMetrics p1 = mock(ConnectionPoolMetrics.class);
+ when(p1.id()).thenReturn("p1");
+
+ Metrics metrics = mock(Metrics.class);
+ when(metrics.connectionPoolMetrics()).thenReturn(Collections.singletonList(p1));
+
+ Driver driver = mock(Driver.class);
+ when(driver.metrics()).thenReturn(metrics);
+
+ when(driver.verifyConnectivityAsync()).thenReturn(CompletableFuture.completedFuture(null));
+
+ return driver;
+ }
+
+ public static Driver mockDriverWithoutMetrics() {
+
+ Driver driver = mock(Driver.class);
+ when(driver.metrics()).thenThrow(ClientException.class);
+ return driver;
+ }
+
+ private Neo4jDriverMocks() {
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfigurationTests.java
index b768bc2faf..b2f6976bc3 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/neo4j/Neo4jHealthContributorAutoConfigurationTests.java
@@ -1,11 +1,14 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright (c) 2019-2020 "Neo4j,"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * This file is part of Neo4j.
*
* 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
+ * 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,
@@ -13,79 +16,147 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.springframework.boot.actuate.autoconfigure.neo4j;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import org.neo4j.ogm.session.Session;
-import org.neo4j.ogm.session.SessionFactory;
-
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
+import org.springframework.boot.actuate.health.AbstractHealthIndicator;
+import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.PingHealthIndicator;
import org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator;
+import org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import reactor.core.publisher.Flux;
+
+import org.neo4j.driver.Driver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.mock;
+import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
/**
* Tests for {@link Neo4jHealthContributorAutoConfiguration}.
*
* @author Phillip Webb
* @author Stephane Nicoll
+ * @author Michael J. Simons
*/
class Neo4jHealthContributorAutoConfigurationTests {
- private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
- .withUserConfiguration(Neo4jConfiguration.class).withConfiguration(AutoConfigurations
- .of(Neo4jHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class));
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
+ Neo4jHealthContributorAutoConfiguration.class));
+
+ @Nested
+ class NoMatches {
+
+ @Test
+ void shouldRespectManagementDecisions() {
+ contextRunner.withUserConfiguration(WithDriver.class)
+ .withPropertyValues("management.health.neo4j.enabled=false")
+ .run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class)
+ .hasSingleBean(PingHealthIndicator.class));
+ }
+
+ @Test
+ void shouldRequireHealthClass() {
+ contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Health.class))
+ .run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
+ }
+
+ @Test
+ void shouldRequireDriverClass() {
+ contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Driver.class))
+ .run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
+ }
+
+ @Test
+ void shouldRequireDriverBean() {
+ contextRunner.run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
+ }
+
+ @Test
+ void shouldRequireHealthIndicatorClasses() {
+ contextRunner.withUserConfiguration(WithDriver.class)
+ .withClassLoader(new FilteredClassLoader(Health.class, Flux.class))
+ .run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
+
+ contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Flux.class))
+ .run(ctx -> assertThat(ctx).hasSingleBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
+ }
+
+ @Test
+ void defaultIndicatorCanBeReplaced() {
+ contextRunner.withUserConfiguration(WithDriver.class, WithCustomIndicator.class).run((context) -> {
+ assertThat(context).hasBean("neo4jHealthIndicator");
+ Health health = context.getBean("neo4jHealthIndicator", HealthIndicator.class).health();
+ assertThat(health.getDetails()).containsOnly(entry("test", true));
+ });
+ }
+
+ }
+
+ @Nested
+ class Matches {
+
+ @Test
+ void shouldCreateHealthIndicator() {
+ contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Flux.class))
+ .run(ctx -> assertThat(ctx).hasSingleBean(Neo4jHealthIndicator.class)
+ .doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
+ }
- @Test
- void runShouldCreateIndicator() {
- this.contextRunner.run((context) -> assertThat(context).hasSingleBean(Neo4jHealthIndicator.class));
}
@Test
- void runWhenDisabledShouldNotCreateIndicator() {
- this.contextRunner.withPropertyValues("management.health.neo4j.enabled:false")
- .run((context) -> assertThat(context).doesNotHaveBean(Neo4jHealthIndicator.class));
- }
+ void reactiveHealthCheckShouldHavePrecedence() {
+ contextRunner.withUserConfiguration(WithDriver.class).run(ctx -> {
+ assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class).hasBean("neo4jHealthContributor");
- @Test
- void defaultIndicatorCanBeReplaced() {
- this.contextRunner.withUserConfiguration(CustomIndicatorConfiguration.class).run((context) -> {
- assertThat(context).hasSingleBean(Neo4jHealthIndicator.class);
- Health health = context.getBean(Neo4jHealthIndicator.class).health();
- assertThat(health.getDetails()).containsOnly(entry("test", true));
- });
+ assertThat(ctx).getBean("neo4jHealthContributor").isInstanceOf(CompositeReactiveHealthContributor.class)
+ .satisfies(o -> {
+ CompositeReactiveHealthContributor chc = (CompositeReactiveHealthContributor) o;
+ assertThat(chc.getContributor("driver")).isInstanceOf(Neo4jReactiveHealthIndicator.class);
+ });
+ }
+
+ );
}
@Configuration(proxyBeanMethods = false)
- static class Neo4jConfiguration {
+ static class WithDriver {
@Bean
- SessionFactory sessionFactory() {
- return mock(SessionFactory.class);
+ Driver driver() {
+ return mockDriverWithoutMetrics();
}
}
@Configuration(proxyBeanMethods = false)
- static class CustomIndicatorConfiguration {
+ static class WithCustomIndicator {
@Bean
- Neo4jHealthIndicator neo4jHealthIndicator(SessionFactory sessionFactory) {
- return new Neo4jHealthIndicator(sessionFactory) {
+ HealthIndicator neo4jHealthIndicator() {
+ return new AbstractHealthIndicator() {
- @Override
- protected void extractResult(Session session, Health.Builder builder) {
+ protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.up().withDetail("test", true);
}
-
};
}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMetrics.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMetrics.java
new file mode 100644
index 0000000000..65ebb6395f
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMetrics.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.neo4j;
+
+import java.util.function.Consumer;
+
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.binder.MeterBinder;
+import org.neo4j.driver.ConnectionPoolMetrics;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Metrics;
+import org.neo4j.driver.exceptions.ClientException;
+
+import org.springframework.util.Assert;
+
+/**
+ * This is a {@link MeterBinder} that binds all available Neo4j driver metrics to
+ * Micrometer.
+ *
+ * @author Michael J. Simons
+ * @since 2.4.0
+ */
+public final class Neo4jDriverMetrics implements MeterBinder {
+
+ /**
+ * Prefixed used for driver metrics.
+ */
+ public static final String PREFIX = "neo4j.driver.connections";
+
+ private static final String BASE_UNIT_CONNECTIONS = "connections";
+
+ private final Driver driver;
+
+ private final Iterable tags;
+
+ public Neo4jDriverMetrics(String name, Driver driver, Iterable tags) {
+
+ Assert.notNull(name, "Bean name must not be null");
+ Assert.notNull(driver, "Driver must not be null");
+ Assert.notNull(tags, "Tags must not be null (but may be empty)");
+ this.driver = driver;
+ this.tags = Tags.concat(tags, "name", name);
+ }
+
+ @Override
+ public void bindTo(MeterRegistry meterRegistry) {
+
+ Metrics metrics = this.driver.metrics();
+ metrics.connectionPoolMetrics().forEach(this.getPoolMetricsBinder(meterRegistry));
+ }
+
+ Consumer getPoolMetricsBinder(MeterRegistry meterRegistry) {
+ return (poolMetrics) -> {
+ Iterable poolTags = Tags.concat(this.tags, "poolId", poolMetrics.id());
+
+ FunctionCounter.builder(PREFIX + ".acquired", poolMetrics, ConnectionPoolMetrics::acquired).tags(poolTags)
+ .baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections that have been acquired.")
+ .register(meterRegistry);
+
+ FunctionCounter.builder(PREFIX + ".closed", poolMetrics, ConnectionPoolMetrics::closed).tags(poolTags)
+ .baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections have been closed.")
+ .register(meterRegistry);
+
+ FunctionCounter.builder(PREFIX + ".created", poolMetrics, ConnectionPoolMetrics::created).tags(poolTags)
+ .baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections have ever been created.")
+ .register(meterRegistry);
+
+ FunctionCounter.builder(PREFIX + ".failedToCreate", poolMetrics, ConnectionPoolMetrics::failedToCreate)
+ .tags(poolTags).baseUnit(BASE_UNIT_CONNECTIONS)
+ .description("The amount of connections have been failed to create.").register(meterRegistry);
+
+ Gauge.builder(PREFIX + ".idle", poolMetrics, ConnectionPoolMetrics::idle).tags(poolTags)
+ .baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections that are currently idle.")
+ .register(meterRegistry);
+
+ Gauge.builder(PREFIX + ".inUse", poolMetrics, ConnectionPoolMetrics::inUse).tags(poolTags)
+ .baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections that are currently in-use.")
+ .register(meterRegistry);
+
+ FunctionCounter
+ .builder(PREFIX + ".timedOutToAcquire", poolMetrics, ConnectionPoolMetrics::timedOutToAcquire)
+ .tags(poolTags).baseUnit(BASE_UNIT_CONNECTIONS)
+ .description(
+ "The amount of failures to acquire a connection from a pool within maximum connection acquisition timeout.")
+ .register(meterRegistry);
+ };
+ }
+
+ /**
+ * Utility method to check whether driver metrics are enabled without throwing an
+ * exception.
+ * @param driver the bean to check whether it has metrics enabled or not
+ * @return true, if the given bean exposes metrics
+ */
+ public static boolean metricsAreEnabled(Driver driver) {
+
+ try {
+ driver.metrics();
+ return true;
+ }
+ catch (ClientException ex) {
+ return false;
+ }
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java
index 3f972070b5..1239594154 100644
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java
@@ -16,61 +16,117 @@
package org.springframework.boot.actuate.neo4j;
-import java.util.Collections;
-
-import org.neo4j.ogm.model.Result;
-import org.neo4j.ogm.session.Session;
-import org.neo4j.ogm.session.SessionFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.neo4j.driver.AccessMode;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Result;
+import org.neo4j.driver.Session;
+import org.neo4j.driver.SessionConfig;
+import org.neo4j.driver.exceptions.SessionExpiredException;
+import org.neo4j.driver.summary.DatabaseInfo;
+import org.neo4j.driver.summary.ResultSummary;
+import org.neo4j.driver.summary.ServerInfo;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
-import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.util.StringUtils;
/**
* {@link HealthIndicator} that tests the status of a Neo4j by executing a Cypher
- * statement.
+ * statement and extracting server and database information.
*
* @author Eric Spiegelberg
* @author Stephane Nicoll
+ * @author Michael J. Simons
* @since 2.0.0
*/
public class Neo4jHealthIndicator extends AbstractHealthIndicator {
+ private static final Log logger = LogFactory.getLog(Neo4jHealthIndicator.class);
+
/**
* The Cypher statement used to verify Neo4j is up.
*/
- static final String CYPHER = "CALL dbms.components() YIELD versions, edition"
- + " UNWIND versions as version return version, edition";
-
- private final SessionFactory sessionFactory;
+ static final String CYPHER = "CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition";
/**
- * Create a new {@link Neo4jHealthIndicator} using the specified
- * {@link SessionFactory}.
- * @param sessionFactory the SessionFactory
+ * Message indicating that the health check failed.
*/
- public Neo4jHealthIndicator(SessionFactory sessionFactory) {
- super("Neo4J health check failed");
- this.sessionFactory = sessionFactory;
+ static final String MESSAGE_HEALTH_CHECK_FAILED = "Neo4j health check failed";
+
+ /**
+ * Message logged before retrying a health check.
+ */
+ static final String MESSAGE_SESSION_EXPIRED = "Neo4j session has expired, retrying one single time to retrieve server health.";
+
+ /**
+ * The default session config to use while connecting.
+ */
+ static final SessionConfig DEFAULT_SESSION_CONFIG = SessionConfig.builder().withDefaultAccessMode(AccessMode.WRITE)
+ .build();
+
+ /**
+ * The driver for this health indicator instance.
+ */
+ private final Driver driver;
+
+ public Neo4jHealthIndicator(Driver driver) {
+ super(MESSAGE_HEALTH_CHECK_FAILED);
+ this.driver = driver;
}
@Override
- protected void doHealthCheck(Health.Builder builder) throws Exception {
- Session session = this.sessionFactory.openSession();
- extractResult(session, builder);
+ protected void doHealthCheck(Health.Builder builder) {
+
+ try {
+ ResultSummaryWithEdition resultSummaryWithEdition;
+ // Retry one time when the session has been expired
+ try {
+ resultSummaryWithEdition = runHealthCheckQuery();
+ }
+ catch (SessionExpiredException sessionExpiredException) {
+ logger.warn(MESSAGE_SESSION_EXPIRED);
+ resultSummaryWithEdition = runHealthCheckQuery();
+ }
+ buildStatusUp(resultSummaryWithEdition, builder);
+ }
+ catch (Exception ex) {
+ builder.down().withException(ex);
+ }
}
/**
- * Provide health details using the specified {@link Session} and {@link Builder
- * Builder}.
- * @param session the session to use to execute a cypher statement
- * @param builder the builder to add details to
- * @throws Exception if getting health details failed
+ * Applies the given {@link ResultSummary} to the {@link Health.Builder builder}
+ * without actually calling {@code build}.
+ * @param resultSummaryWithEdition the result summary returned by the server
+ * @param builder the health builder to be modified
+ * @return the modified health builder
*/
- protected void extractResult(Session session, Health.Builder builder) throws Exception {
- Result result = session.query(CYPHER, Collections.emptyMap());
- builder.up().withDetails(result.queryResults().iterator().next());
+ static Health.Builder buildStatusUp(ResultSummaryWithEdition resultSummaryWithEdition, Health.Builder builder) {
+ ServerInfo serverInfo = resultSummaryWithEdition.resultSummary.server();
+ DatabaseInfo databaseInfo = resultSummaryWithEdition.resultSummary.database();
+
+ builder.up().withDetail("server", serverInfo.version() + "@" + serverInfo.address()).withDetail("edition",
+ resultSummaryWithEdition.edition);
+
+ if (StringUtils.hasText(databaseInfo.name())) {
+ builder.withDetail("database", databaseInfo.name());
+ }
+
+ return builder;
+ }
+
+ ResultSummaryWithEdition runHealthCheckQuery() {
+ // We use WRITE here to make sure UP is returned for a server that supports
+ // all possible workloads
+ try (Session session = this.driver.session(DEFAULT_SESSION_CONFIG)) {
+ Result result = session.run(CYPHER);
+ String edition = result.single().get("edition").asString();
+ ResultSummary resultSummary = result.consume();
+ return new ResultSummaryWithEdition(resultSummary, edition);
+ }
}
}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicator.java
new file mode 100644
index 0000000000..535fcc4043
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jReactiveHealthIndicator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.neo4j;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.exceptions.SessionExpiredException;
+import org.neo4j.driver.reactive.RxResult;
+import org.neo4j.driver.reactive.RxSession;
+import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
+
+import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
+
+/**
+ * {@link ReactiveHealthIndicator} that tests the status of a Neo4j by executing a Cypher
+ * statement and extracting server and database information.
+ *
+ * @author Michael J. Simons
+ * @since 2.4.0
+ */
+public final class Neo4jReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
+
+ private static final Log logger = LogFactory.getLog(Neo4jReactiveHealthIndicator.class);
+
+ /**
+ * The driver for this health indicator instance.
+ */
+ private final Driver driver;
+
+ public Neo4jReactiveHealthIndicator(Driver driver) {
+ this.driver = driver;
+ }
+
+ @Override
+ protected Mono doHealthCheck(Health.Builder builder) {
+ return runHealthCheckQuery()
+ .doOnError(SessionExpiredException.class,
+ (e) -> logger.warn(Neo4jHealthIndicator.MESSAGE_SESSION_EXPIRED))
+ .retryWhen(Retry.max(1).filter(SessionExpiredException.class::isInstance))
+ .map((r) -> Neo4jHealthIndicator.buildStatusUp(r, builder).build());
+ }
+
+ Mono runHealthCheckQuery() {
+ // We use WRITE here to make sure UP is returned for a server that supports
+ // all possible workloads
+ return Mono.using(() -> this.driver.rxSession(Neo4jHealthIndicator.DEFAULT_SESSION_CONFIG), (session) -> {
+ RxResult result = session.run(Neo4jHealthIndicator.CYPHER);
+ return Mono.from(result.records()).map((record) -> record.get("edition").asString())
+ .zipWhen((edition) -> Mono.from(result.consume()), (e, r) -> new ResultSummaryWithEdition(r, e));
+ }, RxSession::close);
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/ResultSummaryWithEdition.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/ResultSummaryWithEdition.java
new file mode 100644
index 0000000000..9220a5d525
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/ResultSummaryWithEdition.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.neo4j;
+
+import org.neo4j.driver.summary.ResultSummary;
+
+/**
+ * A holder for a {@link ResultSummary result summary} and database edition.
+ *
+ * @author Michael J. Simons
+ */
+final class ResultSummaryWithEdition {
+
+ final ResultSummary resultSummary;
+
+ final String edition;
+
+ ResultSummaryWithEdition(ResultSummary resultSummary, String edition) {
+ this.resultSummary = resultSummary;
+ this.edition = edition;
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/package-info.java
index 340170eff5..7b89b60097 100644
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/package-info.java
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMetricsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMetricsTests.java
new file mode 100644
index 0000000000..7b85151d65
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMetricsTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.neo4j;
+
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import org.neo4j.driver.Driver;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics.PREFIX;
+import static org.springframework.boot.actuate.neo4j.Neo4jDriverMocks.mockDriverWithMetrics;
+import static org.springframework.boot.actuate.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
+
+/**
+ * @author Michael J. Simons
+ */
+class Neo4jDriverMetricsTests {
+
+ @Test
+ void shouldDetectEnabledMetrics() {
+
+ Driver driver = mockDriverWithMetrics();
+ assertThat(Neo4jDriverMetrics.metricsAreEnabled(driver)).isTrue();
+ }
+
+ @Test
+ void shouldDetectDisabledMetrics() {
+
+ Driver driver = mockDriverWithoutMetrics();
+ assertThat(Neo4jDriverMetrics.metricsAreEnabled(driver)).isFalse();
+ }
+
+ @Test
+ void shouldRegisterCorrectMeters() {
+
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ Neo4jDriverMetrics metrics = new Neo4jDriverMetrics("driver", mockDriverWithMetrics(), Collections.emptyList());
+ metrics.bindTo(registry);
+
+ assertThat(registry.get(PREFIX + ".acquired").functionCounter()).isNotNull();
+ assertThat(registry.get(PREFIX + ".closed").functionCounter()).isNotNull();
+ assertThat(registry.get(PREFIX + ".created").functionCounter()).isNotNull();
+ assertThat(registry.get(PREFIX + ".failedToCreate").functionCounter()).isNotNull();
+ assertThat(registry.get(PREFIX + ".idle").gauge()).isNotNull();
+ assertThat(registry.get(PREFIX + ".inUse").gauge()).isNotNull();
+ assertThat(registry.get(PREFIX + ".timedOutToAcquire").functionCounter()).isNotNull();
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMocks.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMocks.java
new file mode 100644
index 0000000000..f6e44d5fdc
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jDriverMocks.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.neo4j;
+
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+
+import org.neo4j.driver.ConnectionPoolMetrics;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Metrics;
+import org.neo4j.driver.exceptions.ClientException;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Some predefined mocks, only to be used internally for tests.
+ *
+ * @author Michael J. Simons
+ */
+final class Neo4jDriverMocks {
+
+ public static Driver mockDriverWithMetrics() {
+ ConnectionPoolMetrics p1 = mock(ConnectionPoolMetrics.class);
+ when(p1.id()).thenReturn("p1");
+
+ Metrics metrics = mock(Metrics.class);
+ when(metrics.connectionPoolMetrics()).thenReturn(Collections.singletonList(p1));
+
+ Driver driver = mock(Driver.class);
+ when(driver.metrics()).thenReturn(metrics);
+
+ when(driver.verifyConnectivityAsync()).thenReturn(CompletableFuture.completedFuture(null));
+
+ return driver;
+ }
+
+ public static Driver mockDriverWithoutMetrics() {
+
+ Driver driver = mock(Driver.class);
+ when(driver.metrics()).thenThrow(ClientException.class);
+ return driver;
+ }
+
+ private Neo4jDriverMocks() {
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTestBase.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTestBase.java
new file mode 100644
index 0000000000..036ce85254
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTestBase.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019-2020 "Neo4j,"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * 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.neo4j;
+
+import org.mockito.Mock;
+
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Record;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.summary.DatabaseInfo;
+import org.neo4j.driver.summary.ResultSummary;
+import org.neo4j.driver.summary.ServerInfo;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * Contains some shared mocks for the health indicator tests.
+ *
+ * @author Michael J. Simons
+ */
+abstract class Neo4jHealthIndicatorTestBase {
+
+ @Mock
+ protected Driver driver;
+
+ @Mock
+ protected ResultSummary resultSummary;
+
+ @Mock
+ protected ServerInfo serverInfo;
+
+ @Mock
+ protected DatabaseInfo databaseInfo;
+
+ @Mock
+ protected Record record;
+
+ protected void prepareSharedMocks() {
+
+ when(serverInfo.version()).thenReturn("4711");
+ when(serverInfo.address()).thenReturn("Zu Hause");
+ when(databaseInfo.name()).thenReturn("n/a");
+ when(resultSummary.server()).thenReturn(serverInfo);
+ when(resultSummary.database()).thenReturn(databaseInfo);
+ when(record.get("edition")).thenReturn(Values.value("ultimate collectors edition"));
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTests.java
index 6eec0f5933..c81bd28923 100644
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTests.java
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicatorTests.java
@@ -16,25 +16,29 @@
package org.springframework.boot.actuate.neo4j;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.neo4j.ogm.exception.CypherException;
-import org.neo4j.ogm.model.Result;
-import org.neo4j.ogm.session.Session;
-import org.neo4j.ogm.session.SessionFactory;
-
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.neo4j.driver.Result;
+import org.neo4j.driver.Session;
+import org.neo4j.driver.SessionConfig;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.exceptions.ServiceUnavailableException;
+import org.neo4j.driver.exceptions.SessionExpiredException;
+
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
/**
* Tests for {@link Neo4jHealthIndicator}.
@@ -43,48 +47,126 @@ import static org.mockito.Mockito.mock;
* @author Stephane Nicoll
* @author Michael Simons
*/
-class Neo4jHealthIndicatorTests {
+@ExtendWith(MockitoExtension.class)
+class Neo4jHealthIndicatorTests extends Neo4jHealthIndicatorTestBase {
+ @Mock
private Session session;
- private Neo4jHealthIndicator neo4jHealthIndicator;
-
- @BeforeEach
- void before() {
- this.session = mock(Session.class);
- SessionFactory sessionFactory = mock(SessionFactory.class);
- given(sessionFactory.openSession()).willReturn(this.session);
- this.neo4jHealthIndicator = new Neo4jHealthIndicator(sessionFactory);
- }
+ @Mock
+ private Result statementResult;
@Test
- void neo4jUp() {
- Result result = mock(Result.class);
- given(this.session.query(Neo4jHealthIndicator.CYPHER, Collections.emptyMap())).willReturn(result);
- Map expectedCypherDetails = new HashMap<>();
- String edition = "community";
- String version = "4.0.0";
- expectedCypherDetails.put("edition", edition);
- expectedCypherDetails.put("version", version);
- List