Commit 6134ff19 authored by Gerrit Meier's avatar Gerrit Meier Committed by Stephane Nicoll

Add auto-configuration for Neo4j driver

This commit adds the support for creating a managed instance of the
Neo4j Java driver. The low-level support for Neo4j is helpful in
situations where the high-level abstraction of Spring Data Neo4j is not
needed.

See gh-22301
parent a46572a8
...@@ -34,6 +34,7 @@ import org.springframework.boot.build.context.properties.DocumentOptions.Builder ...@@ -34,6 +34,7 @@ import org.springframework.boot.build.context.properties.DocumentOptions.Builder
* {@link Task} used to document auto-configuration classes. * {@link Task} used to document auto-configuration classes.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Michael J. Simons
*/ */
public class DocumentConfigurationProperties extends DefaultTask { public class DocumentConfigurationProperties extends DefaultTask {
...@@ -80,8 +81,8 @@ public class DocumentConfigurationProperties extends DefaultTask { ...@@ -80,8 +81,8 @@ public class DocumentConfigurationProperties extends DefaultTask {
.addSection("security").withKeyPrefixes("spring.security", "spring.ldap", "spring.session") .addSection("security").withKeyPrefixes("spring.security", "spring.ldap", "spring.session")
.addSection("data-migration").withKeyPrefixes("spring.flyway", "spring.liquibase").addSection("data") .addSection("data-migration").withKeyPrefixes("spring.flyway", "spring.liquibase").addSection("data")
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx", .withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx",
"spring.mongodb", "spring.redis", "spring.dao", "spring.data", "spring.datasource", "spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data",
"spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc") "spring.datasource", "spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc")
.addOverride("spring.datasource.dbcp2", .addOverride("spring.datasource.dbcp2",
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource") "Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource")
.addOverride("spring.datasource.tomcat", .addOverride("spring.datasource.tomcat",
......
...@@ -180,6 +180,7 @@ dependencies { ...@@ -180,6 +180,7 @@ dependencies {
testImplementation("org.testcontainers:couchbase") testImplementation("org.testcontainers:couchbase")
testImplementation("org.testcontainers:elasticsearch") testImplementation("org.testcontainers:elasticsearch")
testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:neo4j")
testImplementation("org.testcontainers:testcontainers") testImplementation("org.testcontainers:testcontainers")
testImplementation("org.yaml:snakeyaml") testImplementation("org.yaml:snakeyaml")
......
/*
* 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.autoconfigure.neo4j;
import java.io.File;
import java.net.URI;
import java.time.Duration;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
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.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* Automatic configuration of Neo4j's Java Driver.
*
* @author Michael J. Simons
* @since 2.4.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Driver.class)
@EnableConfigurationProperties(Neo4jProperties.class)
public class Neo4jAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Driver.class)
Driver neo4jDriver(Neo4jProperties properties) {
AuthToken authToken = asAuthToken(properties.getAuthentication());
Config config = asDriverConfig(properties);
return GraphDatabase.driver(properties.getUri(), authToken, config);
}
static AuthToken asAuthToken(Neo4jProperties.Authentication authentication) {
String username = authentication.getUsername();
String password = authentication.getPassword();
String kerberosTicket = authentication.getKerberosTicket();
String realm = authentication.getRealm();
boolean hasUsername = StringUtils.hasText(username);
boolean hasPassword = StringUtils.hasText(password);
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.");
}
if (hasUsername && hasPassword) {
return AuthTokens.basic(username, password, realm);
}
if (hasKerberosTicket) {
return AuthTokens.kerberos(kerberosTicket);
}
return AuthTokens.none();
}
static Config asDriverConfig(Neo4jProperties properties) {
Config.ConfigBuilder builder = Config.builder();
applyTo(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();
}
static boolean isSimpleScheme(String scheme) {
String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH);
try {
Scheme.validateScheme(lowerCaseScheme);
}
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()) {
builder.withLeakedSessionsLogging();
}
builder.withMaxConnectionPoolSize(poolSettings.getMaxConnectionPoolSize());
Duration idleTimeBeforeConnectionTest = poolSettings.getIdleTimeBeforeConnectionTest();
if (idleTimeBeforeConnectionTest != null) {
builder.withConnectionLivenessCheckTimeout(idleTimeBeforeConnectionTest.toMillis(), TimeUnit.MILLISECONDS);
}
builder.withMaxConnectionLifetime(poolSettings.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS);
builder.withConnectionAcquisitionTimeout(poolSettings.getConnectionAcquisitionTimeout().toMillis(),
TimeUnit.MILLISECONDS);
if (poolSettings.isMetricsEnabled()) {
builder.withDriverMetrics();
}
else {
builder.withoutDriverMetrics();
}
}
private static void applyTo(Config.ConfigBuilder builder, Neo4jProperties.DriverSettings driverSettings,
boolean withEncryptionAndTrustSettings) {
if (withEncryptionAndTrustSettings) {
applyEncryptionAndTrustSettings(builder, driverSettings);
}
builder.withConnectionTimeout(driverSettings.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS);
builder.withMaxTransactionRetryTime(driverSettings.getMaxTransactionRetryTime().toMillis(),
TimeUnit.MILLISECONDS);
Class<? extends ServerAddressResolver> serverAddressResolverClass = driverSettings
.getServerAddressResolverClass();
if (serverAddressResolverClass != null) {
builder.withResolver(BeanUtils.instantiateClass(serverAddressResolverClass));
}
}
private static void applyEncryptionAndTrustSettings(Config.ConfigBuilder builder,
Neo4jProperties.DriverSettings driverSettings) {
if (driverSettings.isEncrypted()) {
builder.withEncryption();
}
else {
builder.withoutEncryption();
}
builder.withTrustStrategy(toInternalRepresentation(driverSettings.getTrustSettings()));
}
static Config.TrustStrategy toInternalRepresentation(Neo4jProperties.TrustSettings trustSettings) {
String propertyName = "org.neo4j.driver.config.trust-settings";
Config.TrustStrategy internalRepresentation;
Neo4jProperties.TrustSettings.Strategy strategy = trustSettings.getStrategy();
switch (strategy) {
case TRUST_ALL_CERTIFICATES:
internalRepresentation = Config.TrustStrategy.trustAllCertificates();
break;
case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
internalRepresentation = Config.TrustStrategy.trustSystemCertificates();
break;
case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
File certFile = trustSettings.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;
default:
throw new InvalidConfigurationPropertyValueException(propertyName, strategy.name(), "Unknown strategy.");
}
if (trustSettings.isHostnameVerificationEnabled()) {
internalRepresentation.withHostnameVerification();
}
else {
internalRepresentation.withoutHostnameVerification();
}
return internalRepresentation;
}
}
/*
* 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.autoconfigure.neo4j;
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}.
*
* @author Michael J. Simons
* @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.
*/
private URI uri = URI.create("bolt://localhost:7687");
/**
* Authentication the driver is supposed to use. Maybe null.
*/
private Authentication authentication = new Authentication();
/**
* Configuration of the connection pool.
*/
private PoolSettings pool = new PoolSettings();
/**
* Detailed configuration of the driver.
*/
private DriverSettings config = new DriverSettings();
public URI getUri() {
return this.uri;
}
public void setUri(URI uri) {
this.uri = uri;
}
public Authentication getAuthentication() {
return this.authentication;
}
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
public PoolSettings 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 static class Authentication {
/**
* Login of the user connecting to the database.
*/
private String username;
/**
* Password of the user connecting to the database.
*/
private String password;
/**
* Realm to connect to.
*/
private String realm;
/**
* Kerberos ticket for connecting to the database. Mutual exclusive with a given
* username.
*/
private String kerberosTicket;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRealm() {
return this.realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public String getKerberosTicket() {
return this.kerberosTicket;
}
public void setKerberosTicket(String kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}
}
public static class PoolSettings {
/**
* Flag, if metrics are enabled.
*/
private boolean metricsEnabled = false;
/**
* Flag, if leaked sessions logging is enabled.
*/
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;
/**
* Pooled connections that have been idle in the pool for longer than this timeout
* will be tested before they are used again.
*/
private Duration idleTimeBeforeConnectionTest;
/**
* 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);
/**
* 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);
public boolean isLogLeakedSessions() {
return this.logLeakedSessions;
}
public void setLogLeakedSessions(boolean logLeakedSessions) {
this.logLeakedSessions = logLeakedSessions;
}
public int getMaxConnectionPoolSize() {
return this.maxConnectionPoolSize;
}
public void setMaxConnectionPoolSize(int maxConnectionPoolSize) {
this.maxConnectionPoolSize = maxConnectionPoolSize;
}
public Duration getIdleTimeBeforeConnectionTest() {
return this.idleTimeBeforeConnectionTest;
}
public void setIdleTimeBeforeConnectionTest(Duration idleTimeBeforeConnectionTest) {
this.idleTimeBeforeConnectionTest = idleTimeBeforeConnectionTest;
}
public Duration getMaxConnectionLifetime() {
return this.maxConnectionLifetime;
}
public void setMaxConnectionLifetime(Duration maxConnectionLifetime) {
this.maxConnectionLifetime = maxConnectionLifetime;
}
public Duration getConnectionAcquisitionTimeout() {
return this.connectionAcquisitionTimeout;
}
public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
this.connectionAcquisitionTimeout = connectionAcquisitionTimeout;
}
public boolean isMetricsEnabled() {
return this.metricsEnabled;
}
public void setMetricsEnabled(boolean metricsEnabled) {
this.metricsEnabled = metricsEnabled;
}
}
public static class DriverSettings {
/**
* Flag, if 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.
*/
private TrustSettings trustSettings = new TrustSettings();
/**
* Specify socket connection timeout.
*/
private Duration connectionTimeout = Duration.ofSeconds(30);
/**
* Specify the maximum time transactions are allowed to retry.
*/
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<? extends ServerAddressResolver> serverAddressResolverClass;
public boolean isEncrypted() {
return this.encrypted;
}
public void setEncrypted(boolean encrypted) {
this.encrypted = encrypted;
}
public TrustSettings getTrustSettings() {
return this.trustSettings;
}
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<? extends ServerAddressResolver> getServerAddressResolverClass() {
return this.serverAddressResolverClass;
}
public void setServerAddressResolverClass(Class<? extends ServerAddressResolver> 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 File getCertFile() {
return this.certFile;
}
public void setCertFile(File certFile) {
this.certFile = certFile;
}
public boolean isHostnameVerificationEnabled() {
return this.hostnameVerificationEnabled;
}
public void setHostnameVerificationEnabled(boolean hostnameVerificationEnabled) {
this.hostnameVerificationEnabled = hostnameVerificationEnabled;
}
}
}
/*
* 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.autoconfigure.neo4j;
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);
}
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 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();
}
}
}
/*
* 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.
*/
/**
* Auto-configuration for Neo4j.
*/
package org.springframework.boot.autoconfigure.neo4j;
...@@ -100,6 +100,7 @@ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfigura ...@@ -100,6 +100,7 @@ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfigura
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\ org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
......
/*
* 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.autoconfigure.neo4j;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Transaction;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Michael J. Simons
*/
@SpringBootTest
@Testcontainers(disabledWithoutDocker = true)
class Neo4jAutoConfigurationIntegrationTests {
@Container
private static Neo4jContainer<?> neo4jServer = new Neo4jContainer<>("neo4j:4.0");
@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("spring.neo4j.uri", neo4jServer::getBoltUrl);
registry.add("spring.neo4j.authentication.username", () -> "neo4j");
registry.add("spring.neo4j.authentication.password", neo4jServer::getAdminPassword);
}
private final Driver driver;
@Autowired
Neo4jAutoConfigurationIntegrationTests(Driver driver) {
this.driver = driver;
}
@Test
void ensureDriverIsOpen() {
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();
tx.commit();
}
}
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration(Neo4jAutoConfiguration.class)
static class TestConfiguration {
}
}
/*
* 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.autoconfigure.neo4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.neo4j.driver.Driver;
import org.neo4j.driver.exceptions.ClientException;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.when;
/**
* @author Michael J. Simons
*/
class Neo4jAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(Neo4jAutoConfiguration.class));
@Test
void shouldRequireAllNeededClasses() {
this.contextRunner.withPropertyValues("spring.neo4j.uri=bolt://localhost:4711")
.withClassLoader(new FilteredClassLoader(Driver.class))
.run((ctx) -> assertThat(ctx).doesNotHaveBean(Driver.class));
}
@Test
void shouldNotRequireUri() {
this.contextRunner.run((ctx) -> assertThat(ctx).hasSingleBean(Driver.class));
}
@Test
void shouldCreateDriver() {
this.contextRunner.withPropertyValues("spring.neo4j.uri=bolt://localhost:4711")
.run((ctx) -> assertThat(ctx).hasSingleBean(Driver.class));
}
/**
* 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) {
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 {
@Bean
Driver driver() {
Driver driver = mock(Driver.class);
when(driver.metrics()).thenThrow(ClientException.class);
return driver;
}
}
}
/*
* 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.autoconfigure.neo4j;
import java.util.Collections;
import java.util.Set;
import org.neo4j.driver.net.ServerAddress;
import org.neo4j.driver.net.ServerAddressResolver;
/**
* Resolver used only for configuration tests.
*
* @author Michael J. Simons
*/
class TestServerAddressResolver implements ServerAddressResolver {
@Override
public Set<ServerAddress> resolve(ServerAddress address) {
return Collections.emptySet();
}
}
...@@ -1170,6 +1170,13 @@ bom { ...@@ -1170,6 +1170,13 @@ bom {
] ]
} }
} }
library("Neo4j", "4.1.0") {
group("org.neo4j.driver") {
modules = [
"neo4j-java-driver"
]
}
}
library("Neo4j OGM", "3.2.12") { library("Neo4j OGM", "3.2.12") {
group("org.neo4j") { group("org.neo4j") {
modules = [ modules = [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment