- Notifications
You must be signed in to change notification settings - Fork 41.6k
Update Neo4j support to require Neo4j Java Driver 6.0.0 #47381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
| @@ -21,9 +21,12 @@ | |
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.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 @@ | |
@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 @@ PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properti | |
@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 @@ Driver neo4jDriver(Neo4jProperties properties, Environment environment, Neo4jCon | |
} | ||
| ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's quite a bit of ceremony here. Do we really need to invoke "any" factory this way? Also this adds an import on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's the way the drivers team designed that. Maybe Gerrit has a better idea working around it, I don't have one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where can I read more about this? I am confused about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @injectives Can you please chime in here? In the code in question we want to validate the scheme of the URI provided by a user through configuration. We used to be able to do this via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, I wasn't aware of such need. Therefore, the official drivers don't really offer such option. However, one way of checking if the driver would actually support a given scheme in a specific deployment would be to try creating one: GraphDatabase.driver("scheme://localhost", AuthTokens.none()); If the scheme is not supported, it throws:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for following up! I am not keen to do that, this relies on an implementation detail and has a scope much broader than what we're trying to determine. @michael-simons given the feedback above, perhaps we should stop validating the scheme upfront? I don't want to support something that the driver doesn't really expect. Unless I am missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then we leave it to the driver. Please remove this bit from the autoconfig 🙏 It also makes it less complex, what should be the goal for the config and its maintenance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, one more option. Similar to the previous implementation in a sense that it is also using internal driver classes: var loader = new BoltConnectionProviderFactoryLoader(Logging.none(), URI.create("scheme://localhost")); loader.providerFactory().isPresent(); // true if supported Obviously, this is not a public API. | ||
.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 @@ private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool) { | |
builder.withMaxConnectionLifetime(pool.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS); | ||
builder.withConnectionAcquisitionTimeout(pool.getConnectionAcquisitionTimeout().toMillis(), | ||
TimeUnit.MILLISECONDS); | ||
if (pool.isMetricsEnabled()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure I like that change. If metrics vs. no metrics is no longer needed, then I think the Is there a reason to keep having a flag for this or was it just an attempt at migrating what we had before? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The latter. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK cool, Let me remove the flag then. | ||
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, | ||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't do this here. If we did then we could not configure a Neo4j driver without the observation registry on the classpath.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Michael already answered the most bits. I think the approach with your
ConfigBuilderCustomiser
makes most sense. Thanks for putting the effort in to take it over the line ;)