diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java index 44b0119a2f..ac6e3b7b83 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java @@ -34,7 +34,6 @@ import org.springframework.boot.build.context.properties.DocumentOptions.Builder * {@link Task} used to document auto-configuration classes. * * @author Andy Wilkinson - * @author Michael J. Simons */ public class DocumentConfigurationProperties extends DefaultTask { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/TestServerAddressResolver.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/ConfigBuilderCustomizer.java similarity index 57% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/TestServerAddressResolver.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/ConfigBuilderCustomizer.java index 6c2f5dfa9d..10885b17f2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/TestServerAddressResolver.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/ConfigBuilderCustomizer.java @@ -16,22 +16,23 @@ package org.springframework.boot.autoconfigure.neo4j; -import java.util.Collections; -import java.util.Set; - -import org.neo4j.driver.net.ServerAddress; -import org.neo4j.driver.net.ServerAddressResolver; +import org.neo4j.driver.Config; +import org.neo4j.driver.Config.ConfigBuilder; /** - * Resolver used only for configuration tests. + * Callback interface that can be implemented by beans wishing to customize the + * {@link Config} via a {@link ConfigBuilder} whilst retaining default auto-configuration. * - * @author Michael J. Simons + * @author Stephane Nicoll + * @since 2.4.0 */ -class TestServerAddressResolver implements ServerAddressResolver { +@FunctionalInterface +public interface ConfigBuilderCustomizer { - @Override - public Set resolve(ServerAddress address) { - return Collections.emptySet(); - } + /** + * Customize the {@link ConfigBuilder}. + * @param configBuilder the {@link ConfigBuilder} to customize + */ + void customize(ConfigBuilder configBuilder); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java index ef9b6c9dd6..ffd358a846 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java @@ -19,20 +19,25 @@ package org.springframework.boot.autoconfigure.neo4j; import java.io.File; import java.net.URI; import java.time.Duration; +import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; +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.net.ServerAddressResolver; -import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Pool; +import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.context.annotation.Bean; @@ -40,9 +45,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; /** - * Automatic configuration of Neo4j's Java Driver. + * {@link EnableAutoConfiguration Auto-configuration} for Neo4j. * * @author Michael J. Simons + * @author Stephane Nicoll * @since 2.4.0 */ @Configuration(proxyBeanMethods = false) @@ -51,14 +57,16 @@ import org.springframework.util.StringUtils; public class Neo4jAutoConfiguration { @Bean - @ConditionalOnMissingBean(Driver.class) - Driver neo4jDriver(Neo4jProperties properties) { - AuthToken authToken = asAuthToken(properties.getAuthentication()); - Config config = asDriverConfig(properties); + @ConditionalOnMissingBean + public Driver neo4jDriver(Neo4jProperties properties, + ObjectProvider configBuilderCustomizers) { + AuthToken authToken = mapAuthToken(properties.getAuthentication()); + Config config = mapDriverConfig(properties, + configBuilderCustomizers.orderedStream().collect(Collectors.toList())); return GraphDatabase.driver(properties.getUri(), authToken, config); } - static AuthToken asAuthToken(Neo4jProperties.Authentication authentication) { + AuthToken mapAuthToken(Neo4jProperties.Authentication authentication) { String username = authentication.getUsername(); String password = authentication.getPassword(); String kerberosTicket = authentication.getKerberosTicket(); @@ -69,33 +77,30 @@ public class Neo4jAutoConfiguration { boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket); if (hasUsername && hasKerberosTicket) { - throw new InvalidConfigurationPropertyValueException("org.neo4j.driver.authentication", - "username=" + username + ",kerberos-ticket=" + kerberosTicket, - "Cannot specify both username and kerberos ticket."); + throw new IllegalStateException(String.format( + "Cannot specify both username ('%s') and kerberos ticket ('%s')", username, kerberosTicket)); } - if (hasUsername && hasPassword) { return AuthTokens.basic(username, password, realm); } - if (hasKerberosTicket) { return AuthTokens.kerberos(kerberosTicket); } - return AuthTokens.none(); } - static Config asDriverConfig(Neo4jProperties properties) { + Config mapDriverConfig(Neo4jProperties properties, List customizers) { Config.ConfigBuilder builder = Config.builder(); - applyTo(builder, properties.getPool()); + configurePoolSettings(builder, properties.getPool()); URI uri = properties.getUri(); String scheme = (uri != null) ? uri.getScheme() : "bolt"; - applyTo(builder, properties.getConfig(), isSimpleScheme(scheme)); - - return builder.withLogging(new Neo4jSpringJclLogging()).build(); + configureDriverSettings(builder, properties, isSimpleScheme(scheme)); + builder.withLogging(new Neo4jSpringJclLogging()); + customizers.forEach((customizer) -> customizer.customize(builder)); + return builder.build(); } - static boolean isSimpleScheme(String scheme) { + private boolean isSimpleScheme(String scheme) { String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH); try { Scheme.validateScheme(lowerCaseScheme); @@ -103,24 +108,22 @@ public class Neo4jAutoConfiguration { catch (IllegalArgumentException ex) { throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme)); } - return lowerCaseScheme.equals("bolt") || lowerCaseScheme.equals("neo4j"); } - private static void applyTo(Config.ConfigBuilder builder, Neo4jProperties.PoolSettings poolSettings) { - if (poolSettings.isLogLeakedSessions()) { + private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool) { + if (pool.isLogLeakedSessions()) { builder.withLeakedSessionsLogging(); } - builder.withMaxConnectionPoolSize(poolSettings.getMaxConnectionPoolSize()); - Duration idleTimeBeforeConnectionTest = poolSettings.getIdleTimeBeforeConnectionTest(); + builder.withMaxConnectionPoolSize(pool.getMaxConnectionPoolSize()); + Duration idleTimeBeforeConnectionTest = pool.getIdleTimeBeforeConnectionTest(); if (idleTimeBeforeConnectionTest != null) { builder.withConnectionLivenessCheckTimeout(idleTimeBeforeConnectionTest.toMillis(), TimeUnit.MILLISECONDS); } - builder.withMaxConnectionLifetime(poolSettings.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS); - builder.withConnectionAcquisitionTimeout(poolSettings.getConnectionAcquisitionTimeout().toMillis(), + builder.withMaxConnectionLifetime(pool.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS); + builder.withConnectionAcquisitionTimeout(pool.getConnectionAcquisitionTimeout().toMillis(), TimeUnit.MILLISECONDS); - - if (poolSettings.isMetricsEnabled()) { + if (pool.isMetricsEnabled()) { builder.withDriverMetrics(); } else { @@ -128,66 +131,56 @@ public class Neo4jAutoConfiguration { } } - private static void applyTo(Config.ConfigBuilder builder, Neo4jProperties.DriverSettings driverSettings, + private void configureDriverSettings(Config.ConfigBuilder builder, Neo4jProperties properties, boolean withEncryptionAndTrustSettings) { if (withEncryptionAndTrustSettings) { - applyEncryptionAndTrustSettings(builder, driverSettings); - } - - builder.withConnectionTimeout(driverSettings.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS); - builder.withMaxTransactionRetryTime(driverSettings.getMaxTransactionRetryTime().toMillis(), - TimeUnit.MILLISECONDS); - - Class serverAddressResolverClass = driverSettings - .getServerAddressResolverClass(); - if (serverAddressResolverClass != null) { - builder.withResolver(BeanUtils.instantiateClass(serverAddressResolverClass)); + applyEncryptionAndTrustSettings(builder, properties.getSecurity()); } + builder.withConnectionTimeout(properties.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS); + builder.withMaxTransactionRetryTime(properties.getMaxTransactionRetryTime().toMillis(), TimeUnit.MILLISECONDS); } - private static void applyEncryptionAndTrustSettings(Config.ConfigBuilder builder, - Neo4jProperties.DriverSettings driverSettings) { - if (driverSettings.isEncrypted()) { + private void applyEncryptionAndTrustSettings(Config.ConfigBuilder builder, + Neo4jProperties.Security securityProperties) { + if (securityProperties.isEncrypted()) { builder.withEncryption(); } else { builder.withoutEncryption(); } - builder.withTrustStrategy(toInternalRepresentation(driverSettings.getTrustSettings())); + builder.withTrustStrategy(mapTrustStrategy(securityProperties)); } - static Config.TrustStrategy toInternalRepresentation(Neo4jProperties.TrustSettings trustSettings) { - String propertyName = "org.neo4j.driver.config.trust-settings"; + private Config.TrustStrategy mapTrustStrategy(Neo4jProperties.Security securityProperties) { + String propertyName = "spring.neo4j.security.trust-strategy"; + Security.TrustStrategy strategy = securityProperties.getTrustStrategy(); + TrustStrategy trustStrategy = createTrustStrategy(securityProperties, propertyName, strategy); + if (securityProperties.isHostnameVerificationEnabled()) { + trustStrategy.withHostnameVerification(); + } + else { + trustStrategy.withoutHostnameVerification(); + } + return trustStrategy; + } - Config.TrustStrategy internalRepresentation; - Neo4jProperties.TrustSettings.Strategy strategy = trustSettings.getStrategy(); + private TrustStrategy createTrustStrategy(Neo4jProperties.Security securityProperties, String propertyName, + Security.TrustStrategy strategy) { switch (strategy) { case TRUST_ALL_CERTIFICATES: - internalRepresentation = Config.TrustStrategy.trustAllCertificates(); - break; + return TrustStrategy.trustAllCertificates(); case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: - internalRepresentation = Config.TrustStrategy.trustSystemCertificates(); - break; + return TrustStrategy.trustSystemCertificates(); case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: - File certFile = trustSettings.getCertFile(); + File certFile = securityProperties.getCertFile(); if (certFile == null || !certFile.isFile()) { throw new InvalidConfigurationPropertyValueException(propertyName, strategy.name(), "Configured trust strategy requires a certificate file."); } - internalRepresentation = Config.TrustStrategy.trustCustomCertificateSignedBy(certFile); - break; + return TrustStrategy.trustCustomCertificateSignedBy(certFile); default: throw new InvalidConfigurationPropertyValueException(propertyName, strategy.name(), "Unknown strategy."); } - - if (trustSettings.isHostnameVerificationEnabled()) { - internalRepresentation.withHostnameVerification(); - } - else { - internalRepresentation.withoutHostnameVerification(); - } - - return internalRepresentation; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java index 294fdca2f2..cb7b2638bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java @@ -20,38 +20,38 @@ import java.io.File; import java.net.URI; import java.time.Duration; -import org.neo4j.driver.net.ServerAddressResolver; - import org.springframework.boot.context.properties.ConfigurationProperties; /** - * Used to configure an instance of the {@link org.neo4j.driver.Driver Neo4j-Java-Driver}. + * Configuration properties for Neo4j. * * @author Michael J. Simons + * @author Stephane Nicoll * @since 2.4.0 */ @ConfigurationProperties(prefix = "spring.neo4j") public class Neo4jProperties { /** - * Uri this driver should connect to. The driver supports bolt or neo4j as schemes. + * URI used by the driver. */ private URI uri = URI.create("bolt://localhost:7687"); /** - * Authentication the driver is supposed to use. Maybe null. + * Timeout for borrowing connections from the pool. */ - private Authentication authentication = new Authentication(); + private Duration connectionTimeout = Duration.ofSeconds(30); /** - * Configuration of the connection pool. + * Maximum time transactions are allowed to retry. */ - private PoolSettings pool = new PoolSettings(); + private Duration maxTransactionRetryTime = Duration.ofSeconds(30); - /** - * Detailed configuration of the driver. - */ - private DriverSettings config = new DriverSettings(); + private final Authentication authentication = new Authentication(); + + private final Pool pool = new Pool(); + + private final Security security = new Security(); public URI getUri() { return this.uri; @@ -61,39 +61,43 @@ public class Neo4jProperties { this.uri = uri; } + public Duration getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public Duration getMaxTransactionRetryTime() { + return this.maxTransactionRetryTime; + } + + public void setMaxTransactionRetryTime(Duration maxTransactionRetryTime) { + this.maxTransactionRetryTime = maxTransactionRetryTime; + } + public Authentication getAuthentication() { return this.authentication; } - public void setAuthentication(Authentication authentication) { - this.authentication = authentication; - } - - public PoolSettings getPool() { + public Pool getPool() { return this.pool; } - public void setPool(PoolSettings pool) { - this.pool = pool; - } - - public DriverSettings getConfig() { - return this.config; - } - - public void setConfig(DriverSettings config) { - this.config = config; + public Security getSecurity() { + return this.security; } public static class Authentication { /** - * Login of the user connecting to the database. + * Login user of the server. */ private String username; /** - * Password of the user connecting to the database. + * Login password of the server. */ private String password; @@ -142,26 +146,26 @@ public class Neo4jProperties { } - public static class PoolSettings { + public static class Pool { /** - * Flag, if metrics are enabled. + * Whether to enable metrics. */ private boolean metricsEnabled = false; /** - * Flag, if leaked sessions logging is enabled. + * Whether to log leaked sessions. */ private boolean logLeakedSessions = false; /** * Maximum amount of connections in the connection pool towards a single database. */ - private int maxConnectionPoolSize = org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_MAX_CONNECTION_POOL_SIZE; + private int maxConnectionPoolSize = 100; /** - * Pooled connections that have been idle in the pool for longer than this timeout - * will be tested before they are used again. + * Pooled connections that have been idle in the pool for longer than this + * threshold will be tested before they are used again. */ private Duration idleTimeBeforeConnectionTest; @@ -169,15 +173,13 @@ public class Neo4jProperties { * Pooled connections older than this threshold will be closed and removed from * the pool. */ - private Duration maxConnectionLifetime = Duration - .ofMillis(org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME); + private Duration maxConnectionLifetime = Duration.ofHours(1); /** * Acquisition of new connections will be attempted for at most configured * timeout. */ - private Duration connectionAcquisitionTimeout = Duration - .ofMillis(org.neo4j.driver.internal.async.pool.PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT); + private Duration connectionAcquisitionTimeout = Duration.ofSeconds(60); public boolean isLogLeakedSessions() { return this.logLeakedSessions; @@ -229,35 +231,27 @@ public class Neo4jProperties { } - public static class DriverSettings { + public static class Security { /** - * Flag, if the driver should use encrypted traffic. + * Whether the driver should use encrypted traffic. */ private boolean encrypted = false; /** - * Specify how to determine the authenticity of an encryption certificate provided - * by the Neo4j instance we are connecting to. + * Trust strategy to use. */ - private TrustSettings trustSettings = new TrustSettings(); + private TrustStrategy trustStrategy = TrustStrategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES; /** - * Specify socket connection timeout. + * Path to the file that holds the trusted certificates. */ - private Duration connectionTimeout = Duration.ofSeconds(30); + private File certFile; /** - * Specify the maximum time transactions are allowed to retry. + * Whether hostname verification is required. */ - private Duration maxTransactionRetryTime = Duration - .ofMillis(org.neo4j.driver.internal.retry.RetrySettings.DEFAULT.maxRetryTimeMs()); - - /** - * Specify a custom server address resolver used by the routing driver to resolve - * the initial address used to create the driver. - */ - private Class serverAddressResolverClass; + private boolean hostnameVerificationEnabled = true; public boolean isEncrypted() { return this.encrypted; @@ -267,73 +261,12 @@ public class Neo4jProperties { this.encrypted = encrypted; } - public TrustSettings getTrustSettings() { - return this.trustSettings; + public TrustStrategy getTrustStrategy() { + return this.trustStrategy; } - public void setTrustSettings(TrustSettings trustSettings) { - this.trustSettings = trustSettings; - } - - public Duration getConnectionTimeout() { - return this.connectionTimeout; - } - - public void setConnectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public Duration getMaxTransactionRetryTime() { - return this.maxTransactionRetryTime; - } - - public void setMaxTransactionRetryTime(Duration maxTransactionRetryTime) { - this.maxTransactionRetryTime = maxTransactionRetryTime; - } - - public Class getServerAddressResolverClass() { - return this.serverAddressResolverClass; - } - - public void setServerAddressResolverClass(Class serverAddressResolverClass) { - this.serverAddressResolverClass = serverAddressResolverClass; - } - - } - - public static class TrustSettings { - - public enum Strategy { - - TRUST_ALL_CERTIFICATES, - - TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, - - TRUST_SYSTEM_CA_SIGNED_CERTIFICATES - - } - - /** - * Configures the strategy to use use. - */ - private Strategy strategy = Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES; - - /** - * File of the certificate to use. - */ - private File certFile; - - /** - * Flag, if hostname verification is used. - */ - private boolean hostnameVerificationEnabled = false; - - public Strategy getStrategy() { - return this.strategy; - } - - public void setStrategy(Strategy strategy) { - this.strategy = strategy; + public void setTrustStrategy(TrustStrategy trustStrategy) { + this.trustStrategy = trustStrategy; } public File getCertFile() { @@ -352,6 +285,25 @@ public class Neo4jProperties { this.hostnameVerificationEnabled = hostnameVerificationEnabled; } + public enum TrustStrategy { + + /** + * Trust all certificates. + */ + TRUST_ALL_CERTIFICATES, + + /** + * Trust certificates that are signed by a trusted certificate. + */ + TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, + + /** + * Trust certificates that can be verified through the local system store. + */ + TRUST_SYSTEM_CA_SIGNED_CERTIFICATES + + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jSpringJclLogging.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jSpringJclLogging.java index 8e6adf0c83..19fa363f52 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jSpringJclLogging.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jSpringJclLogging.java @@ -37,7 +37,6 @@ class Neo4jSpringJclLogging implements Logging { @Override public Logger getLog(String name) { - String requestedLog = name; if (!requestedLog.startsWith(AUTOMATIC_PREFIX)) { requestedLog = AUTOMATIC_PREFIX + name; @@ -46,7 +45,7 @@ class Neo4jSpringJclLogging implements Logging { return new SpringJclLogger(springJclLog); } - static final class SpringJclLogger implements Logger { + private static final class SpringJclLogger implements Logger { private final Log delegate; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java index 91112fda4e..f9f07e5cdd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationIntegrationTests.java @@ -35,14 +35,17 @@ import org.springframework.test.context.DynamicPropertySource; import static org.assertj.core.api.Assertions.assertThat; /** + * Integration tests for {@link Neo4jAutoConfiguration}. + * * @author Michael J. Simons + * @author Stephane Nicoll */ @SpringBootTest @Testcontainers(disabledWithoutDocker = true) class Neo4jAutoConfigurationIntegrationTests { @Container - private static Neo4jContainer neo4jServer = new Neo4jContainer<>("neo4j:4.0"); + private static final Neo4jContainer neo4jServer = new Neo4jContainer<>("neo4j:4.0"); @DynamicPropertySource static void neo4jProperties(DynamicPropertyRegistry registry) { @@ -51,16 +54,11 @@ class Neo4jAutoConfigurationIntegrationTests { registry.add("spring.neo4j.authentication.password", neo4jServer::getAdminPassword); } - private final Driver driver; - @Autowired - Neo4jAutoConfigurationIntegrationTests(Driver driver) { - this.driver = driver; - } + private Driver driver; @Test - void ensureDriverIsOpen() { - + void driverCanHandleRequest() { try (Session session = this.driver.session(); Transaction tx = session.beginTransaction()) { Result statementResult = tx.run("MATCH (n:Thing) RETURN n LIMIT 1"); assertThat(statementResult.hasNext()).isFalse(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java index d614a098e1..a10140a6ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java @@ -16,24 +16,37 @@ package org.springframework.boot.autoconfigure.neo4j; +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.Arrays; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Config.ConfigBuilder; import org.neo4j.driver.Driver; -import org.neo4j.driver.exceptions.ClientException; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication; +import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security.TrustStrategy; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; 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 static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.when; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** + * Tests for {@link Neo4jAutoConfiguration}. + * * @author Michael J. Simons + * @author Stephane Nicoll */ class Neo4jAutoConfigurationTests { @@ -41,54 +54,220 @@ class Neo4jAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(Neo4jAutoConfiguration.class)); @Test - void shouldRequireAllNeededClasses() { - + void driverNotConfiguredWithoutDriverApi() { this.contextRunner.withPropertyValues("spring.neo4j.uri=bolt://localhost:4711") .withClassLoader(new FilteredClassLoader(Driver.class)) .run((ctx) -> assertThat(ctx).doesNotHaveBean(Driver.class)); } @Test - void shouldNotRequireUri() { - + void driverShouldNotRequireUri() { this.contextRunner.run((ctx) -> assertThat(ctx).hasSingleBean(Driver.class)); } @Test - void shouldCreateDriver() { - + void driverShouldInvokeConfigBuilderCustomizers() { this.contextRunner.withPropertyValues("spring.neo4j.uri=bolt://localhost:4711") - .run((ctx) -> assertThat(ctx).hasSingleBean(Driver.class)); + .withBean(ConfigBuilderCustomizer.class, () -> ConfigBuilder::withEncryption) + .run((ctx) -> assertThat(ctx.getBean(Driver.class).isEncrypted()).isTrue()); } - /** - * These tests assert correct configuration behaviour for cases in which one of the - * "advanced" schemes is used to configure the driver. If any of the schemes is used, - * than a contradicting explicit configuration will throw an error. - * @param scheme The scheme to test. - */ @ParameterizedTest - @ValueSource(strings = { "bolt+s", "bolt+ssc", "neo4j+s", "neo4j+ssc" }) - void schemesShouldBeApplied(String scheme) { - + @ValueSource(strings = { "bolt", "neo4j" }) + void uriWithSimpleSchemeAreDetected(String scheme) { this.contextRunner.withPropertyValues("spring.neo4j.uri=" + scheme + "://localhost:4711").run((ctx) -> { assertThat(ctx).hasSingleBean(Driver.class); + assertThat(ctx.getBean(Driver.class).isEncrypted()).isFalse(); + }); + } + @ParameterizedTest + @ValueSource(strings = { "bolt+s", "bolt+ssc", "neo4j+s", "neo4j+ssc" }) + void uriWithAdvancedSchemesAreDetected(String scheme) { + this.contextRunner.withPropertyValues("spring.neo4j.uri=" + scheme + "://localhost:4711").run((ctx) -> { + assertThat(ctx).hasSingleBean(Driver.class); Driver driver = ctx.getBean(Driver.class); assertThat(driver.isEncrypted()).isTrue(); }); } - @Configuration(proxyBeanMethods = false) - static class WithDriver { + @ParameterizedTest + @ValueSource(strings = { "bolt+routing", "bolt+x", "neo4j+wth" }) + void uriWithInvalidSchemesAreDetected(String invalidScheme) { + this.contextRunner.withPropertyValues("spring.neo4j.uri=" + invalidScheme + "://localhost:4711") + .run((ctx) -> assertThat(ctx).hasFailed().getFailure() + .hasMessageContaining("'%s' is not a supported scheme.", invalidScheme)); + } - @Bean - Driver driver() { - Driver driver = mock(Driver.class); - when(driver.metrics()).thenThrow(ClientException.class); - return driver; - } + @Test + void connectionTimeout() { + Neo4jProperties properties = new Neo4jProperties(); + properties.setConnectionTimeout(Duration.ofMillis(500)); + assertThat(mapDriverConfig(properties).connectionTimeoutMillis()).isEqualTo(500); + } + @Test + void maxTransactionRetryTime() { + Neo4jProperties properties = new Neo4jProperties(); + properties.setMaxTransactionRetryTime(Duration.ofSeconds(2)); + assertThat(mapDriverConfig(properties)).extracting("retrySettings") + .hasFieldOrPropertyWithValue("maxRetryTimeMs", 2000L); + } + + @Test + void authenticationShouldDefaultToNone() { + assertThat(mapAuthToken(new Authentication())).isEqualTo(AuthTokens.none()); + } + + @Test + void authenticationWithUsernameShouldEnableBasicAuth() { + Authentication authentication = new Authentication(); + authentication.setUsername("Farin"); + authentication.setPassword("Urlaub"); + assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub")); + } + + @Test + void authenticationWithUsernameAndRealmShouldEnableBasicAuth() { + Authentication authentication = new Authentication(); + authentication.setUsername("Farin"); + authentication.setPassword("Urlaub"); + authentication.setRealm("Test Realm"); + assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm")); + } + + @Test + void authenticationWithKerberosTicketShouldEnableKerberos() { + Authentication authentication = new Authentication(); + authentication.setKerberosTicket("AABBCCDDEE"); + assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.kerberos("AABBCCDDEE")); + } + + @Test + void authenticationWithBothUsernameAndKerberosShouldNotBeAllowed() { + Authentication authentication = new Authentication(); + authentication.setUsername("Farin"); + authentication.setKerberosTicket("AABBCCDDEE"); + assertThatIllegalStateException().isThrownBy(() -> mapAuthToken(authentication)) + .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(); + properties.getPool().setLogLeakedSessions(true); + assertThat(mapDriverConfig(properties).logLeakedSessions()).isTrue(); + } + + @Test + void poolWithMaxConnectionPoolSize() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getPool().setMaxConnectionPoolSize(4711); + assertThat(mapDriverConfig(properties).maxConnectionPoolSize()).isEqualTo(4711); + } + + @Test + void poolWithIdleTimeBeforeConnectionTest() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getPool().setIdleTimeBeforeConnectionTest(Duration.ofSeconds(23)); + assertThat(mapDriverConfig(properties).idleTimeBeforeConnectionTest()).isEqualTo(23000); + } + + @Test + void poolWithMaxConnectionLifetime() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getPool().setMaxConnectionLifetime(Duration.ofSeconds(30)); + assertThat(mapDriverConfig(properties).maxConnectionLifetimeMillis()).isEqualTo(30000); + } + + @Test + void poolWithConnectionAcquisitionTimeout() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getPool().setConnectionAcquisitionTimeout(Duration.ofSeconds(5)); + assertThat(mapDriverConfig(properties).connectionAcquisitionTimeoutMillis()).isEqualTo(5000); + } + + @Test + void securityWithEncrypted() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setEncrypted(true); + assertThat(mapDriverConfig(properties).encrypted()).isTrue(); + } + + @Test + void securityWithTrustSignedCertificates() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setTrustStrategy(TrustStrategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); + assertThat(mapDriverConfig(properties).trustStrategy().strategy()) + .isEqualTo(Config.TrustStrategy.Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); + } + + @Test + void securityWithTrustAllCertificates() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setTrustStrategy(TrustStrategy.TRUST_ALL_CERTIFICATES); + assertThat(mapDriverConfig(properties).trustStrategy().strategy()) + .isEqualTo(Config.TrustStrategy.Strategy.TRUST_ALL_CERTIFICATES); + } + + @Test + void securityWitHostnameVerificationEnabled() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setTrustStrategy(TrustStrategy.TRUST_ALL_CERTIFICATES); + properties.getSecurity().setHostnameVerificationEnabled(true); + assertThat(mapDriverConfig(properties).trustStrategy().isHostnameVerificationEnabled()).isTrue(); + } + + @Test + void securityWithCustomCertificates(@TempDir File directory) throws IOException { + File certFile = new File(directory, "neo4j-driver.cert"); + assertThat(certFile.createNewFile()); + + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setTrustStrategy(TrustStrategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES); + properties.getSecurity().setCertFile(certFile); + Config.TrustStrategy trustStrategy = mapDriverConfig(properties).trustStrategy(); + assertThat(trustStrategy.strategy()) + .isEqualTo(Config.TrustStrategy.Strategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES); + assertThat(trustStrategy.certFile()).isEqualTo(certFile); + } + + @Test + void securityWithCustomCertificatesShouldFailWithoutCertificate() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setTrustStrategy(TrustStrategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES); + assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class) + .isThrownBy(() -> mapDriverConfig(properties)).withMessage( + "Property spring.neo4j.security.trust-strategy with value 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' is invalid: Configured trust strategy requires a certificate file."); + } + + @Test + void securityWithTrustSystemCertificates() { + Neo4jProperties properties = new Neo4jProperties(); + properties.getSecurity().setTrustStrategy(TrustStrategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); + assertThat(mapDriverConfig(properties).trustStrategy().strategy()) + .isEqualTo(Config.TrustStrategy.Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); + } + + @Test + void driverConfigShouldBeConfiguredToUseUseSpringJclLogging() { + assertThat(mapDriverConfig(new Neo4jProperties()).logging()).isNotNull() + .isInstanceOf(Neo4jSpringJclLogging.class); + } + + private AuthToken mapAuthToken(Authentication authentication) { + return new Neo4jAutoConfiguration().mapAuthToken(authentication); + } + + private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) { + return new Neo4jAutoConfiguration().mapDriverConfig(properties, Arrays.asList(customizers)); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java index a090f47044..79e14d5bce 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java @@ -16,35 +16,62 @@ package org.springframework.boot.autoconfigure.neo4j; -import java.io.File; -import java.io.IOException; import java.net.URI; import java.time.Duration; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; import org.neo4j.driver.internal.retry.RetrySettings; -import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication; -import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.DriverSettings; -import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.PoolSettings; -import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.TrustSettings; -import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.TrustSettings.Strategy; -import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; +import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Pool; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** + * Tests for {@link Neo4jProperties}. + * * @author Michael J. Simons + * @author Stephane Nicoll */ class Neo4jPropertiesTests { + @Test + 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()); + assertDuration(pool.getMaxConnectionLifetime(), defaultConfig.maxConnectionLifetimeMillis()); + assertDuration(pool.getConnectionAcquisitionTimeout(), defaultConfig.connectionAcquisitionTimeoutMillis()); + } + + @Test + void securitySettingsHaveConsistentDefaults() { + Config defaultConfig = Config.defaultConfig(); + Neo4jProperties properties = new Neo4jProperties(); + assertThat(properties.getSecurity().isEncrypted()).isEqualTo(defaultConfig.encrypted()); + assertThat(properties.getSecurity().getTrustStrategy().name()) + .isEqualTo(defaultConfig.trustStrategy().strategy().name()); + assertThat(properties.getSecurity().isHostnameVerificationEnabled()) + .isEqualTo(defaultConfig.trustStrategy().isHostnameVerificationEnabled()); + } + + @Test + void driverSettingsHaveConsistentDefaults() { + Config defaultConfig = Config.defaultConfig(); + Neo4jProperties properties = new Neo4jProperties(); + assertDuration(properties.getConnectionTimeout(), defaultConfig.connectionTimeoutMillis()); + assertDuration(properties.getMaxTransactionRetryTime(), RetrySettings.DEFAULT.maxRetryTimeMs()); + } + + @Test + void shouldAssumeDefaultValuesForUrl() { + Neo4jProperties driverProperties = new Neo4jProperties(); + assertThat(driverProperties.getUri()).isEqualTo(URI.create("bolt://localhost:7687")); + } + private static void assertDuration(Duration duration, long expectedValueInMillis) { if (expectedValueInMillis == org.neo4j.driver.internal.async.pool.PoolSettings.NOT_CONFIGURED) { assertThat(duration).isNull(); @@ -54,266 +81,4 @@ class Neo4jPropertiesTests { } } - @Test - void shouldAllowEmptyListOfURIs() { - Neo4jProperties driverProperties = new Neo4jProperties(); - assertThat(driverProperties.getAuthentication()).isNotNull(); - } - - @Test - void noAuthenticationShouldWork() { - Authentication authentication = new Authentication(); - assertThat(Neo4jAutoConfiguration.asAuthToken(authentication)).isEqualTo(AuthTokens.none()); - } - - @Test - void basicAuthShouldWork() { - Authentication authentication = new Authentication(); - authentication.setUsername("Farin"); - authentication.setPassword("Urlaub"); - - assertThat(Neo4jAutoConfiguration.asAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub")); - } - - @Test - void basicAuthWithRealmShouldWork() { - Authentication authentication = new Authentication(); - authentication.setUsername("Farin"); - authentication.setPassword("Urlaub"); - authentication.setRealm("Die Ärzte"); - - assertThat(Neo4jAutoConfiguration.asAuthToken(authentication)) - .isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Die Ärzte")); - } - - @Test - void kerberosAuthShouldWork() { - Authentication authentication = new Authentication(); - authentication.setKerberosTicket("AABBCCDDEE"); - - assertThat(Neo4jAutoConfiguration.asAuthToken(authentication)).isEqualTo(AuthTokens.kerberos("AABBCCDDEE")); - } - - @Test - void ambiguousShouldNotBeAllowed() { - Authentication authentication = new Authentication(); - authentication.setUsername("Farin"); - authentication.setKerberosTicket("AABBCCDDEE"); - - assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class) - .isThrownBy(() -> Neo4jAutoConfiguration.asAuthToken(authentication)).withMessage( - "Property org.neo4j.driver.authentication with value 'username=Farin,kerberos-ticket=AABBCCDDEE' is invalid: Cannot specify both username and kerberos ticket."); - } - - @Test - void poolSettingsShouldDefaultToDriversValues() { - Config defaultConfig = Config.defaultConfig(); - - Neo4jProperties driverProperties = new Neo4jProperties(); - - PoolSettings poolSettings = driverProperties.getPool(); - assertThat(poolSettings.isLogLeakedSessions()).isEqualTo(defaultConfig.logLeakedSessions()); - assertThat(poolSettings.getMaxConnectionPoolSize()).isEqualTo(defaultConfig.maxConnectionPoolSize()); - assertDuration(poolSettings.getIdleTimeBeforeConnectionTest(), defaultConfig.idleTimeBeforeConnectionTest()); - assertDuration(poolSettings.getMaxConnectionLifetime(), defaultConfig.maxConnectionLifetimeMillis()); - assertDuration(poolSettings.getConnectionAcquisitionTimeout(), - defaultConfig.connectionAcquisitionTimeoutMillis()); - assertThat(poolSettings.isMetricsEnabled()).isFalse(); - } - - @Test - void logLeakedSessionsSettingsShouldWork() { - Neo4jProperties driverProperties; - - driverProperties = new Neo4jProperties(); - driverProperties.getPool().setLogLeakedSessions(true); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).logLeakedSessions()).isTrue(); - - driverProperties = new Neo4jProperties(); - driverProperties.getPool().setLogLeakedSessions(false); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).logLeakedSessions()).isFalse(); - } - - @Test - void maxConnectionPoolSizeSettingsShouldWork() { - Neo4jProperties driverProperties = new Neo4jProperties(); - driverProperties.getPool().setMaxConnectionPoolSize(4711); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).maxConnectionPoolSize()).isEqualTo(4711); - } - - @Test - void idleTimeBeforeConnectionTestSettingsShouldWork() { - Neo4jProperties driverProperties; - - driverProperties = new Neo4jProperties(); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).idleTimeBeforeConnectionTest()) - .isEqualTo(-1); - - driverProperties = new Neo4jProperties(); - driverProperties.getPool().setIdleTimeBeforeConnectionTest(Duration.ofSeconds(23)); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).idleTimeBeforeConnectionTest()) - .isEqualTo(23_000); - } - - @Test - void connectionAcquisitionTimeoutSettingsShouldWork() { - Neo4jProperties driverProperties = new Neo4jProperties(); - driverProperties.getPool().setConnectionAcquisitionTimeout(Duration.ofSeconds(23)); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).connectionAcquisitionTimeoutMillis()) - .isEqualTo(23_000); - } - - @Test - void enableMetricsShouldWork() { - Neo4jProperties driverProperties = new Neo4jProperties(); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).isMetricsEnabled()).isFalse(); - - driverProperties.getPool().setMetricsEnabled(true); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).isMetricsEnabled()).isTrue(); - } - - @Test - void driverSettingsShouldDefaultToDriversValues() { - Config defaultConfig = Config.defaultConfig(); - - Neo4jProperties driverProperties = new Neo4jProperties(); - - DriverSettings driverSettings = driverProperties.getConfig(); - assertThat(driverSettings.isEncrypted()).isEqualTo(defaultConfig.encrypted()); - assertThat(driverSettings.getTrustSettings().getStrategy().name()) - .isEqualTo(defaultConfig.trustStrategy().strategy().name()); - assertDuration(driverSettings.getConnectionTimeout(), defaultConfig.connectionTimeoutMillis()); - assertDuration(driverSettings.getMaxTransactionRetryTime(), RetrySettings.DEFAULT.maxRetryTimeMs()); - assertThat(driverSettings.getServerAddressResolverClass()).isNull(); - } - - @Test - void encryptedSettingsShouldWork() { - Neo4jProperties driverProperties; - - driverProperties = new Neo4jProperties(); - driverProperties.getConfig().setEncrypted(true); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).encrypted()).isTrue(); - - driverProperties = new Neo4jProperties(); - driverProperties.getConfig().setEncrypted(false); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).encrypted()).isFalse(); - } - - @Test - void trustSettingsShouldWork() { - Neo4jProperties driverProperties = new Neo4jProperties(); - TrustSettings trustSettings = new TrustSettings(); - trustSettings.setStrategy(Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); - driverProperties.getConfig().setTrustSettings(trustSettings); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).trustStrategy().strategy()) - .isEqualTo(Config.TrustStrategy.Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); - } - - @Test - void connectionTimeoutSettingsShouldWork() { - Neo4jProperties driverProperties = new Neo4jProperties(); - driverProperties.getConfig().setConnectionTimeout(Duration.ofSeconds(23)); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).connectionTimeoutMillis()).isEqualTo(23_000); - } - - @Test - @Disabled("The internal driver has no means of retrieving that value back again") - void maxTransactionRetryTimeSettingsShouldWork() { - DriverSettings driverSettings = new DriverSettings(); - driverSettings.setMaxTransactionRetryTime(Duration.ofSeconds(23)); - } - - @Test - void serverAddressResolverClassSettingsShouldWork() { - Neo4jProperties driverProperties = new Neo4jProperties(); - driverProperties.getConfig().setServerAddressResolverClass(TestServerAddressResolver.class); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).resolver()).isNotNull() - .isInstanceOf(TestServerAddressResolver.class); - } - - @Test - void shouldUseSpringJclLogging() { - Neo4jProperties driverProperties = new Neo4jProperties(); - assertThat(Neo4jAutoConfiguration.asDriverConfig(driverProperties).logging()).isNotNull() - .isInstanceOf(Neo4jSpringJclLogging.class); - } - - @Test - void trustAllCertificatesShouldWork() { - TrustSettings settings = new TrustSettings(); - settings.setStrategy(Strategy.TRUST_ALL_CERTIFICATES); - - assertThat(Neo4jAutoConfiguration.toInternalRepresentation(settings).strategy()) - .isEqualTo(Config.TrustStrategy.Strategy.TRUST_ALL_CERTIFICATES); - } - - @Test - void shouldEnableHostnameVerification() { - TrustSettings settings = new TrustSettings(); - settings.setStrategy(Strategy.TRUST_ALL_CERTIFICATES); - settings.setHostnameVerificationEnabled(true); - - assertThat(Neo4jAutoConfiguration.toInternalRepresentation(settings).isHostnameVerificationEnabled()).isTrue(); - } - - @Test - void trustSystemCertificatesShouldWork() { - - TrustSettings settings = new TrustSettings(); - settings.setStrategy(Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); - - assertThat(Neo4jAutoConfiguration.toInternalRepresentation(settings).strategy()) - .isEqualTo(Config.TrustStrategy.Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); - } - - @Test - void trustCustomCertificatesShouldWork() throws IOException { - File certFile = File.createTempFile("neo4j-driver", ".cert"); - - TrustSettings settings = new TrustSettings(); - settings.setStrategy(Strategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES); - settings.setCertFile(certFile); - - Config.TrustStrategy trustStrategy = Neo4jAutoConfiguration.toInternalRepresentation(settings); - assertThat(trustStrategy.strategy()) - .isEqualTo(Config.TrustStrategy.Strategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES); - assertThat(trustStrategy.certFile()).isEqualTo(certFile); - } - - @Test - void trustCustomCertificatesShouldFailWithoutCertificate() { - TrustSettings settings = new TrustSettings(); - settings.setStrategy(Strategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES); - - assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class) - .isThrownBy(() -> Neo4jAutoConfiguration.toInternalRepresentation(settings)).withMessage( - "Property org.neo4j.driver.config.trust-settings with value 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' is invalid: Configured trust strategy requires a certificate file."); - } - - @Test - void shouldAssumeDefaultValuesForUrl() { - Neo4jProperties driverProperties = new Neo4jProperties(); - assertThat(driverProperties.getUri()).isEqualTo(URI.create("bolt://localhost:7687")); - } - - @ParameterizedTest - @ValueSource(strings = { "bolt", "Bolt", "neo4j", "Neo4J" }) - void shouldDetectSimpleSchemes(String aSimpleScheme) { - assertThat(Neo4jAutoConfiguration.isSimpleScheme(aSimpleScheme)).isTrue(); - } - - @ParameterizedTest - @ValueSource(strings = { "bolt+s", "Bolt+ssc", "neo4j+s", "Neo4J+ssc" }) - void shouldDetectAdvancedSchemes(String anAdvancedScheme) { - assertThat(Neo4jAutoConfiguration.isSimpleScheme(anAdvancedScheme)).isFalse(); - } - - @ParameterizedTest - @ValueSource(strings = { "bolt+routing", "bolt+x", "neo4j+wth" }) - void shouldFailEarlyOnInvalidSchemes(String invalidScheme) { - assertThatIllegalArgumentException().isThrownBy(() -> Neo4jAutoConfiguration.isSimpleScheme(invalidScheme)) - .withMessage("'%s' is not a supported scheme.", invalidScheme); - } - } diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b83d9a9f75..b933efa0d1 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1170,7 +1170,7 @@ bom { ] } } - library("Neo4j", "4.1.0") { + library("Neo4j", "4.1.1") { group("org.neo4j.driver") { modules = [ "neo4j-java-driver" diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 927a408d0b..0d34edd13a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -4495,20 +4495,20 @@ Spring Boot offers several conveniences for working with Neo4j, including the `s [[boot-features-connecting-to-neo4j]] ==== Connecting to a Neo4j Database -To access a Neo4j server, you can inject an auto-configured `org.neo4j.ogm.session.Session`. +To access a Neo4j server, you can inject an auto-configured `org.neo4j.driver.Driver`. By default, the instance tries to connect to a Neo4j server at `localhost:7687` using the Bolt protocol. -The following example shows how to inject a Neo4j `Session`: +The following example shows how to inject a Neo4j `Driver` that gives you access, amongst other things, to a `Session`: [source,java,indent=0] ---- @Component public class MyBean { - private final Session session; + private final Driver driver; @Autowired - public MyBean(Session session) { - this.session = session; + public MyBean(Driver driver) { + this.driver = driver; } // ... @@ -4516,16 +4516,19 @@ The following example shows how to inject a Neo4j `Session`: } ---- -You can configure the uri and credentials to use by setting the `spring.data.neo4j.*` properties, as shown in the following example: +You can configure various aspects of the driver using `spring.neo4j.*` properties. +The following example shows how to configure the uri and credentials to use: [source,properties,indent=0,configprops] ---- - spring.data.neo4j.uri=bolt://my-server:7687 - spring.data.neo4j.username=neo4j - spring.data.neo4j.password=secret + spring.neo4j.uri=bolt://my-server:7687 + spring.neo4j.authentication.username=neo4j + spring.neo4j.authentication.password=secret ---- -You can take full control over the session creation by adding either an `org.neo4j.ogm.config.Configuration` bean or an `org.neo4j.ogm.session.SessionFactory` bean. +The auto-configured `Driver` is created using `ConfigBuilder`. +To fine-tune its configuration, declare one or more `ConfigBuilderCustomizer` beans. +Each will be called in order with the `ConfigBuilder` that is used to build the `Driver`.