Skip to content

Commit e6f1819

Browse files
committed
Merge pull request #22301 from meistermeier
* pr/22301: Polish "Add auto-configuration for Neo4j driver" Add auto-configuration for Neo4j driver Closes gh-22301
2 parents a46572a + 8c418ad commit e6f1819

File tree

13 files changed

+1111
-12
lines changed

13 files changed

+1111
-12
lines changed

buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ void documentConfigurationProperties() throws IOException {
8080
.addSection("security").withKeyPrefixes("spring.security", "spring.ldap", "spring.session")
8181
.addSection("data-migration").withKeyPrefixes("spring.flyway", "spring.liquibase").addSection("data")
8282
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx",
83-
"spring.mongodb", "spring.redis", "spring.dao", "spring.data", "spring.datasource",
84-
"spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc")
83+
"spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data",
84+
"spring.datasource", "spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc")
8585
.addOverride("spring.datasource.dbcp2",
8686
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource")
8787
.addOverride("spring.datasource.tomcat",

spring-boot-project/spring-boot-autoconfigure/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ dependencies {
180180
testImplementation("org.testcontainers:couchbase")
181181
testImplementation("org.testcontainers:elasticsearch")
182182
testImplementation("org.testcontainers:junit-jupiter")
183+
testImplementation("org.testcontainers:neo4j")
183184
testImplementation("org.testcontainers:testcontainers")
184185
testImplementation("org.yaml:snakeyaml")
185186

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.neo4j;
18+
19+
import org.neo4j.driver.Config;
20+
import org.neo4j.driver.Config.ConfigBuilder;
21+
22+
/**
23+
* Callback interface that can be implemented by beans wishing to customize the
24+
* {@link Config} via a {@link ConfigBuilder} whilst retaining default auto-configuration.
25+
*
26+
* @author Stephane Nicoll
27+
* @since 2.4.0
28+
*/
29+
@FunctionalInterface
30+
public interface ConfigBuilderCustomizer {
31+
32+
/**
33+
* Customize the {@link ConfigBuilder}.
34+
* @param configBuilder the {@link ConfigBuilder} to customize
35+
*/
36+
void customize(ConfigBuilder configBuilder);
37+
38+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.neo4j;
18+
19+
import java.io.File;
20+
import java.net.URI;
21+
import java.time.Duration;
22+
import java.util.List;
23+
import java.util.Locale;
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.stream.Collectors;
26+
27+
import org.neo4j.driver.AuthToken;
28+
import org.neo4j.driver.AuthTokens;
29+
import org.neo4j.driver.Config;
30+
import org.neo4j.driver.Config.TrustStrategy;
31+
import org.neo4j.driver.Driver;
32+
import org.neo4j.driver.GraphDatabase;
33+
import org.neo4j.driver.internal.Scheme;
34+
35+
import org.springframework.beans.factory.ObjectProvider;
36+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
37+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
38+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
39+
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Pool;
40+
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security;
41+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
42+
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
43+
import org.springframework.context.annotation.Bean;
44+
import org.springframework.context.annotation.Configuration;
45+
import org.springframework.util.StringUtils;
46+
47+
/**
48+
* {@link EnableAutoConfiguration Auto-configuration} for Neo4j.
49+
*
50+
* @author Michael J. Simons
51+
* @author Stephane Nicoll
52+
* @since 2.4.0
53+
*/
54+
@Configuration(proxyBeanMethods = false)
55+
@ConditionalOnClass(Driver.class)
56+
@EnableConfigurationProperties(Neo4jProperties.class)
57+
public class Neo4jAutoConfiguration {
58+
59+
@Bean
60+
@ConditionalOnMissingBean
61+
public Driver neo4jDriver(Neo4jProperties properties,
62+
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers) {
63+
AuthToken authToken = mapAuthToken(properties.getAuthentication());
64+
Config config = mapDriverConfig(properties,
65+
configBuilderCustomizers.orderedStream().collect(Collectors.toList()));
66+
return GraphDatabase.driver(properties.getUri(), authToken, config);
67+
}
68+
69+
AuthToken mapAuthToken(Neo4jProperties.Authentication authentication) {
70+
String username = authentication.getUsername();
71+
String password = authentication.getPassword();
72+
String kerberosTicket = authentication.getKerberosTicket();
73+
String realm = authentication.getRealm();
74+
75+
boolean hasUsername = StringUtils.hasText(username);
76+
boolean hasPassword = StringUtils.hasText(password);
77+
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
78+
79+
if (hasUsername && hasKerberosTicket) {
80+
throw new IllegalStateException(String.format(
81+
"Cannot specify both username ('%s') and kerberos ticket ('%s')", username, kerberosTicket));
82+
}
83+
if (hasUsername && hasPassword) {
84+
return AuthTokens.basic(username, password, realm);
85+
}
86+
if (hasKerberosTicket) {
87+
return AuthTokens.kerberos(kerberosTicket);
88+
}
89+
return AuthTokens.none();
90+
}
91+
92+
Config mapDriverConfig(Neo4jProperties properties, List<ConfigBuilderCustomizer> customizers) {
93+
Config.ConfigBuilder builder = Config.builder();
94+
configurePoolSettings(builder, properties.getPool());
95+
URI uri = properties.getUri();
96+
String scheme = (uri != null) ? uri.getScheme() : "bolt";
97+
configureDriverSettings(builder, properties, isSimpleScheme(scheme));
98+
builder.withLogging(new Neo4jSpringJclLogging());
99+
customizers.forEach((customizer) -> customizer.customize(builder));
100+
return builder.build();
101+
}
102+
103+
private boolean isSimpleScheme(String scheme) {
104+
String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH);
105+
try {
106+
Scheme.validateScheme(lowerCaseScheme);
107+
}
108+
catch (IllegalArgumentException ex) {
109+
throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme));
110+
}
111+
return lowerCaseScheme.equals("bolt") || lowerCaseScheme.equals("neo4j");
112+
}
113+
114+
private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool) {
115+
if (pool.isLogLeakedSessions()) {
116+
builder.withLeakedSessionsLogging();
117+
}
118+
builder.withMaxConnectionPoolSize(pool.getMaxConnectionPoolSize());
119+
Duration idleTimeBeforeConnectionTest = pool.getIdleTimeBeforeConnectionTest();
120+
if (idleTimeBeforeConnectionTest != null) {
121+
builder.withConnectionLivenessCheckTimeout(idleTimeBeforeConnectionTest.toMillis(), TimeUnit.MILLISECONDS);
122+
}
123+
builder.withMaxConnectionLifetime(pool.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS);
124+
builder.withConnectionAcquisitionTimeout(pool.getConnectionAcquisitionTimeout().toMillis(),
125+
TimeUnit.MILLISECONDS);
126+
if (pool.isMetricsEnabled()) {
127+
builder.withDriverMetrics();
128+
}
129+
else {
130+
builder.withoutDriverMetrics();
131+
}
132+
}
133+
134+
private void configureDriverSettings(Config.ConfigBuilder builder, Neo4jProperties properties,
135+
boolean withEncryptionAndTrustSettings) {
136+
if (withEncryptionAndTrustSettings) {
137+
applyEncryptionAndTrustSettings(builder, properties.getSecurity());
138+
}
139+
builder.withConnectionTimeout(properties.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS);
140+
builder.withMaxTransactionRetryTime(properties.getMaxTransactionRetryTime().toMillis(), TimeUnit.MILLISECONDS);
141+
}
142+
143+
private void applyEncryptionAndTrustSettings(Config.ConfigBuilder builder,
144+
Neo4jProperties.Security securityProperties) {
145+
if (securityProperties.isEncrypted()) {
146+
builder.withEncryption();
147+
}
148+
else {
149+
builder.withoutEncryption();
150+
}
151+
builder.withTrustStrategy(mapTrustStrategy(securityProperties));
152+
}
153+
154+
private Config.TrustStrategy mapTrustStrategy(Neo4jProperties.Security securityProperties) {
155+
String propertyName = "spring.neo4j.security.trust-strategy";
156+
Security.TrustStrategy strategy = securityProperties.getTrustStrategy();
157+
TrustStrategy trustStrategy = createTrustStrategy(securityProperties, propertyName, strategy);
158+
if (securityProperties.isHostnameVerificationEnabled()) {
159+
trustStrategy.withHostnameVerification();
160+
}
161+
else {
162+
trustStrategy.withoutHostnameVerification();
163+
}
164+
return trustStrategy;
165+
}
166+
167+
private TrustStrategy createTrustStrategy(Neo4jProperties.Security securityProperties, String propertyName,
168+
Security.TrustStrategy strategy) {
169+
switch (strategy) {
170+
case TRUST_ALL_CERTIFICATES:
171+
return TrustStrategy.trustAllCertificates();
172+
case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
173+
return TrustStrategy.trustSystemCertificates();
174+
case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
175+
File certFile = securityProperties.getCertFile();
176+
if (certFile == null || !certFile.isFile()) {
177+
throw new InvalidConfigurationPropertyValueException(propertyName, strategy.name(),
178+
"Configured trust strategy requires a certificate file.");
179+
}
180+
return TrustStrategy.trustCustomCertificateSignedBy(certFile);
181+
default:
182+
throw new InvalidConfigurationPropertyValueException(propertyName, strategy.name(), "Unknown strategy.");
183+
}
184+
}
185+
186+
}

0 commit comments

Comments
 (0)