Add support for Neo4j Java Driver 6.0.0

See gh-47381

Signed-off-by: Gerrit Meier <meistermeier@gmail.com>
This commit is contained in:
Gerrit Meier 2025-10-02 13:45:11 +02:00 committed by Stéphane Nicoll
parent 43b06ca9b9
commit 2542430e5b
6 changed files with 49 additions and 142 deletions

View File

@ -36,6 +36,7 @@ dependencies {
optional(project(":core:spring-boot-testcontainers"))
optional(project(":module:spring-boot-health"))
optional("org.testcontainers:neo4j")
optional("org.neo4j.driver:neo4j-java-driver-observation-micrometer")
dockerTestImplementation(project(":core:spring-boot-test"))
dockerTestImplementation(project(":test-support:spring-boot-docker-test-support"))

View File

@ -21,9 +21,12 @@ import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Locale;
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
import io.micrometer.observation.ObservationRegistry;
import org.jspecify.annotations.Nullable;
import org.neo4j.bolt.connection.BoltConnectionProviderFactory;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokenManager;
import org.neo4j.driver.AuthTokens;
@ -32,6 +35,7 @@ import org.neo4j.driver.Config.TrustStrategy;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.internal.Scheme;
import org.neo4j.driver.observation.micrometer.MicrometerObservationProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -63,6 +67,20 @@ import org.springframework.util.StringUtils;
@EnableConfigurationProperties(Neo4jProperties.class)
public final class Neo4jAutoConfiguration {
private static final boolean HAS_DRIVER_METRICS;
static {
boolean metricsObservationProviderFound = true;
try {
Class.forName("org.neo4j.driver.observation.micrometer.MicrometerObservationProvider", false,
Neo4jAutoConfiguration.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
metricsObservationProviderFound = false;
}
HAS_DRIVER_METRICS = metricsObservationProviderFound;
}
@Bean
@ConditionalOnMissingBean(Neo4jConnectionDetails.class)
PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties,
@ -73,10 +91,11 @@ public final class Neo4jAutoConfiguration {
@Bean
@ConditionalOnMissingBean
Driver neo4jDriver(Neo4jProperties properties, Environment environment, Neo4jConnectionDetails connectionDetails,
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers) {
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers,
ObjectProvider<ObservationRegistry> observationRegistryProvider) {
Config config = mapDriverConfig(properties, connectionDetails,
configBuilderCustomizers.orderedStream().toList());
configBuilderCustomizers.orderedStream().toList(), observationRegistryProvider);
AuthTokenManager authTokenManager = connectionDetails.getAuthTokenManager();
if (authTokenManager != null) {
return GraphDatabase.driver(connectionDetails.getUri(), authTokenManager, config);
@ -86,29 +105,29 @@ public final class Neo4jAutoConfiguration {
}
Config mapDriverConfig(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails,
List<ConfigBuilderCustomizer> customizers) {
List<ConfigBuilderCustomizer> customizers,
ObjectProvider<ObservationRegistry> observationRegistryProvider) {
Config.ConfigBuilder builder = Config.builder();
configurePoolSettings(builder, properties.getPool());
configurePoolSettings(builder, properties.getPool(), observationRegistryProvider);
URI uri = connectionDetails.getUri();
String scheme = (uri != null) ? uri.getScheme() : "bolt";
configureDriverSettings(builder, properties, isSimpleScheme(scheme));
builder.withLogging(new Neo4jSpringJclLogging());
customizers.forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
private boolean isSimpleScheme(String scheme) {
String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH);
try {
Scheme.validateScheme(lowerCaseScheme);
}
catch (IllegalArgumentException ex) {
if (!ServiceLoader.load(BoltConnectionProviderFactory.class)
.stream()
.anyMatch((p) -> p.get().supports(lowerCaseScheme))) {
throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme));
}
return lowerCaseScheme.equals("bolt") || lowerCaseScheme.equals("neo4j");
return !Scheme.isSecurityScheme(lowerCaseScheme);
}
private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool) {
private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool,
ObjectProvider<ObservationRegistry> observationRegistryProvider) {
if (pool.isLogLeakedSessions()) {
builder.withLeakedSessionsLogging();
}
@ -120,12 +139,11 @@ public final class Neo4jAutoConfiguration {
builder.withMaxConnectionLifetime(pool.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS);
builder.withConnectionAcquisitionTimeout(pool.getConnectionAcquisitionTimeout().toMillis(),
TimeUnit.MILLISECONDS);
if (pool.isMetricsEnabled()) {
builder.withDriverMetrics();
}
else {
builder.withoutDriverMetrics();
observationRegistryProvider.ifAvailable((orp) -> {
if (pool.isMetricsEnabled() && HAS_DRIVER_METRICS) {
builder.withObservationProvider(MicrometerObservationProvider.builder(orp).build());
}
});
}
private void configureDriverSettings(Config.ConfigBuilder builder, Neo4jProperties properties,

View File

@ -1,109 +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.neo4j.autoconfigure;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
/**
* Shim to use Spring JCL implementation, delegating all the hard work of deciding the
* underlying system to Spring and Spring Boot.
*
* @author Michael J. Simons
*/
class Neo4jSpringJclLogging implements Logging {
/**
* This prefix gets added to the log names the driver requests to add some namespace
* around it in a bigger application scenario.
*/
private static final String AUTOMATIC_PREFIX = "org.neo4j.driver.";
@Override
public Logger getLog(String name) {
String requestedLog = name;
if (!requestedLog.startsWith(AUTOMATIC_PREFIX)) {
requestedLog = AUTOMATIC_PREFIX + name;
}
Log springJclLog = LogFactory.getLog(requestedLog);
return new SpringJclLogger(springJclLog);
}
private static final class SpringJclLogger implements Logger {
private final Log delegate;
SpringJclLogger(Log delegate) {
this.delegate = delegate;
}
@Override
public void error(String message, Throwable cause) {
this.delegate.error(message, cause);
}
@Override
public void info(String format, Object... params) {
this.delegate.info(String.format(format, params));
}
@Override
public void warn(String format, Object... params) {
this.delegate.warn(String.format(format, params));
}
@Override
public void warn(String message, Throwable cause) {
this.delegate.warn(message, cause);
}
@Override
public void debug(String format, Object... params) {
if (isDebugEnabled()) {
this.delegate.debug(String.format(format, params));
}
}
@Override
public void debug(String message, Throwable throwable) {
if (isDebugEnabled()) {
this.delegate.debug(message, throwable);
}
}
@Override
public void trace(String format, Object... params) {
if (isTraceEnabled()) {
this.delegate.trace(String.format(format, params));
}
}
@Override
public boolean isTraceEnabled() {
return this.delegate.isTraceEnabled();
}
@Override
public boolean isDebugEnabled() {
return this.delegate.isDebugEnabled();
}
}
}

View File

@ -21,7 +21,9 @@ import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.Arrays;
import java.util.stream.Stream;
import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
@ -32,6 +34,7 @@ import org.neo4j.driver.Config;
import org.neo4j.driver.Config.ConfigBuilder;
import org.neo4j.driver.Driver;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration.PropertiesNeo4jConnectionDetails;
@ -217,13 +220,6 @@ class Neo4jAutoConfigurationTests {
.withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')");
}
@Test
void poolWithMetricsEnabled() {
Neo4jProperties properties = new Neo4jProperties();
properties.getPool().setMetricsEnabled(true);
assertThat(mapDriverConfig(properties).isMetricsEnabled()).isTrue();
}
@Test
void poolWithLogLeakedSessions() {
Neo4jProperties properties = new Neo4jProperties();
@ -322,14 +318,15 @@ class Neo4jAutoConfigurationTests {
.isEqualTo(Config.TrustStrategy.Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES);
}
@Test
void driverConfigShouldBeConfiguredToUseUseSpringJclLogging() {
assertThat(mapDriverConfig(new Neo4jProperties()).logging()).isInstanceOf(Neo4jSpringJclLogging.class);
}
private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) {
return new Neo4jAutoConfiguration().mapDriverConfig(properties,
new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers));
new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers),
new ObjectProvider<>() {
@Override
public Stream<ObservationRegistry> stream() {
return Stream.empty();
}
});
}
}

View File

@ -38,7 +38,6 @@ class Neo4jPropertiesTests {
void poolSettingsHaveConsistentDefaults() {
Config defaultConfig = Config.defaultConfig();
Pool pool = new Neo4jProperties().getPool();
assertThat(pool.isMetricsEnabled()).isEqualTo(defaultConfig.isMetricsEnabled());
assertThat(pool.isLogLeakedSessions()).isEqualTo(defaultConfig.logLeakedSessions());
assertThat(pool.getMaxConnectionPoolSize()).isEqualTo(defaultConfig.maxConnectionPoolSize());
assertDuration(pool.getIdleTimeBeforeConnectionTest(), defaultConfig.idleTimeBeforeConnectionTest());

View File

@ -1621,7 +1621,7 @@ bom {
]
}
}
library("Neo4j Java Driver", "5.28.9") {
library("Neo4j Java Driver", "6.0.0") {
alignWith {
version {
from "org.springframework.data:spring-data-neo4j"
@ -1630,7 +1630,8 @@ bom {
}
group("org.neo4j.driver") {
modules = [
"neo4j-java-driver"
"neo4j-java-driver",
"neo4j-java-driver-observation-micrometer"
]
}
links {