Skip to content

Commit de8fb04

Browse files
wilkinsonamhalbritterphilwebb
committed
Add ConnectionDetail support to Neo4J auto-configuration
Update Neo4J auto-configuration so that `Neo4jConnectionDetails` beans may be optionally used to provide connection details. See gh-34657 Co-Authored-By: Mortitz Halbritter <[email protected]> Co-Authored-By: Phillip Webb <[email protected]>
1 parent 2ef33dc commit de8fb04

File tree

3 files changed

+152
-67
lines changed

3 files changed

+152
-67
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,70 +36,49 @@
3636
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3737
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3838
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
39+
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication;
3940
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Pool;
4041
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security;
4142
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4243
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
4344
import org.springframework.context.annotation.Bean;
4445
import org.springframework.core.env.Environment;
46+
import org.springframework.util.Assert;
4547
import org.springframework.util.StringUtils;
4648

4749
/**
4850
* {@link EnableAutoConfiguration Auto-configuration} for Neo4j.
4951
*
5052
* @author Michael J. Simons
5153
* @author Stephane Nicoll
54+
* @author Moritz Halbritter
55+
* @author Andy Wilkinson
56+
* @author Phillip Webb
5257
* @since 2.4.0
5358
*/
5459
@AutoConfiguration
5560
@ConditionalOnClass(Driver.class)
5661
@EnableConfigurationProperties(Neo4jProperties.class)
5762
public class Neo4jAutoConfiguration {
5863

59-
private static final URI DEFAULT_SERVER_URI = URI.create("bolt://localhost:7687");
60-
6164
@Bean
6265
@ConditionalOnMissingBean
6366
public Driver neo4jDriver(Neo4jProperties properties, Environment environment,
64-
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers) {
65-
AuthToken authToken = mapAuthToken(properties.getAuthentication(), environment);
66-
Config config = mapDriverConfig(properties, configBuilderCustomizers.orderedStream().toList());
67-
URI serverUri = determineServerUri(properties, environment);
68-
return GraphDatabase.driver(serverUri, authToken, config);
69-
}
70-
71-
URI determineServerUri(Neo4jProperties properties, Environment environment) {
72-
URI uri = properties.getUri();
73-
return (uri != null) ? uri : DEFAULT_SERVER_URI;
74-
}
75-
76-
AuthToken mapAuthToken(Neo4jProperties.Authentication authentication, Environment environment) {
77-
String username = authentication.getUsername();
78-
String password = authentication.getPassword();
79-
String kerberosTicket = authentication.getKerberosTicket();
80-
String realm = authentication.getRealm();
81-
82-
boolean hasUsername = StringUtils.hasText(username);
83-
boolean hasPassword = StringUtils.hasText(password);
84-
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
85-
86-
if (hasUsername && hasKerberosTicket) {
87-
throw new IllegalStateException(String
88-
.format("Cannot specify both username ('%s') and kerberos ticket ('%s')", username, kerberosTicket));
89-
}
90-
if (hasUsername && hasPassword) {
91-
return AuthTokens.basic(username, password, realm);
92-
}
93-
if (hasKerberosTicket) {
94-
return AuthTokens.kerberos(kerberosTicket);
95-
}
96-
return AuthTokens.none();
67+
ObjectProvider<ConfigBuilderCustomizer> configBuilderCustomizers,
68+
ObjectProvider<Neo4jConnectionDetails> connectionDetailsProvider) {
69+
Neo4jConnectionDetails connectionDetails = connectionDetailsProvider
70+
.getIfAvailable(() -> new PropertiesNeo4jConnectionDetails(properties));
71+
AuthToken authToken = connectionDetails.getAuthToken();
72+
Config config = mapDriverConfig(properties, connectionDetails,
73+
configBuilderCustomizers.orderedStream().toList());
74+
return GraphDatabase.driver(connectionDetails.getUri(), authToken, config);
9775
}
9876

99-
Config mapDriverConfig(Neo4jProperties properties, List<ConfigBuilderCustomizer> customizers) {
77+
Config mapDriverConfig(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails,
78+
List<ConfigBuilderCustomizer> customizers) {
10079
Config.ConfigBuilder builder = Config.builder();
10180
configurePoolSettings(builder, properties.getPool());
102-
URI uri = properties.getUri();
81+
URI uri = connectionDetails.getUri();
10382
String scheme = (uri != null) ? uri.getScheme() : "bolt";
10483
configureDriverSettings(builder, properties, isSimpleScheme(scheme));
10584
builder.withLogging(new Neo4jSpringJclLogging());
@@ -191,4 +170,43 @@ private TrustStrategy createTrustStrategy(Neo4jProperties.Security securityPrope
191170
}
192171
}
193172

173+
/**
174+
* Adapts {@link Neo4jProperties} to {@link Neo4jConnectionDetails}.
175+
*/
176+
static class PropertiesNeo4jConnectionDetails implements Neo4jConnectionDetails {
177+
178+
private final Neo4jProperties properties;
179+
180+
PropertiesNeo4jConnectionDetails(Neo4jProperties properties) {
181+
this.properties = properties;
182+
}
183+
184+
@Override
185+
public URI getUri() {
186+
URI uri = this.properties.getUri();
187+
return (uri != null) ? uri : Neo4jConnectionDetails.super.getUri();
188+
}
189+
190+
@Override
191+
public AuthToken getAuthToken() {
192+
Authentication authentication = this.properties.getAuthentication();
193+
String username = authentication.getUsername();
194+
String kerberosTicket = authentication.getKerberosTicket();
195+
boolean hasUsername = StringUtils.hasText(username);
196+
boolean hasKerberosTicket = StringUtils.hasText(kerberosTicket);
197+
Assert.state(!(hasUsername && hasKerberosTicket),
198+
() -> "Cannot specify both username ('%s') and kerberos ticket ('%s')".formatted(username,
199+
kerberosTicket));
200+
String password = authentication.getPassword();
201+
if (hasUsername && StringUtils.hasText(password)) {
202+
return AuthTokens.basic(username, password, authentication.getRealm());
203+
}
204+
if (hasKerberosTicket) {
205+
return AuthTokens.kerberos(kerberosTicket);
206+
}
207+
return AuthTokens.none();
208+
}
209+
210+
}
211+
194212
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2023 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.net.URI;
20+
21+
import org.neo4j.driver.AuthToken;
22+
import org.neo4j.driver.AuthTokens;
23+
24+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
25+
26+
/**
27+
* Details required to establish a connection to a Neo4j service.
28+
*
29+
* @author Moritz Halbritter
30+
* @author Andy Wilkinson
31+
* @author Phillip Webb
32+
* @since 3.1.0
33+
*/
34+
public interface Neo4jConnectionDetails extends ConnectionDetails {
35+
36+
/**
37+
* Returns the URI of the Neo4j server. Defaults to {@code bolt://localhost:7687"}.
38+
* @return the Neo4j server URI
39+
*/
40+
default URI getUri() {
41+
return URI.create("bolt://localhost:7687");
42+
}
43+
44+
/**
45+
* Returns the token to use for authentication. Defaults to {@link AuthTokens#none()}.
46+
* @return the auth token
47+
*/
48+
default AuthToken getAuthToken() {
49+
return AuthTokens.none();
50+
}
51+
52+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,19 @@
2626
import org.junit.jupiter.api.io.TempDir;
2727
import org.junit.jupiter.params.ParameterizedTest;
2828
import org.junit.jupiter.params.provider.ValueSource;
29-
import org.neo4j.driver.AuthToken;
3029
import org.neo4j.driver.AuthTokens;
3130
import org.neo4j.driver.Config;
3231
import org.neo4j.driver.Config.ConfigBuilder;
3332
import org.neo4j.driver.Driver;
3433

3534
import org.springframework.boot.autoconfigure.AutoConfigurations;
35+
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration.PropertiesNeo4jConnectionDetails;
3636
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Authentication;
3737
import org.springframework.boot.autoconfigure.neo4j.Neo4jProperties.Security.TrustStrategy;
3838
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
3939
import org.springframework.boot.test.context.FilteredClassLoader;
4040
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
41-
import org.springframework.core.env.Environment;
42-
import org.springframework.mock.env.MockEnvironment;
41+
import org.springframework.context.annotation.Bean;
4342

4443
import static org.assertj.core.api.Assertions.assertThat;
4544
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -50,6 +49,9 @@
5049
*
5150
* @author Michael J. Simons
5251
* @author Stephane Nicoll
52+
* @author Moritz Halbritter
53+
* @author Andy Wilkinson
54+
* @author Phillip Webb
5355
*/
5456
class Neo4jAutoConfigurationTests {
5557

@@ -103,6 +105,22 @@ void uriWithInvalidSchemesAreDetected(String invalidScheme) {
103105
.hasMessageContaining("'%s' is not a supported scheme.", invalidScheme));
104106
}
105107

108+
@Bean
109+
void usesCustomConnectionDetails() {
110+
this.contextRunner.withBean(Neo4jConnectionDetails.class, () -> new Neo4jConnectionDetails() {
111+
112+
@Override
113+
public URI getUri() {
114+
return URI.create("bolt+ssc://localhost:12345");
115+
}
116+
117+
}).run((context) -> {
118+
assertThat(context).hasSingleBean(Driver.class);
119+
Driver driver = context.getBean(Driver.class);
120+
assertThat(driver.isEncrypted()).isTrue();
121+
});
122+
}
123+
106124
@Test
107125
void connectionTimeout() {
108126
Neo4jProperties properties = new Neo4jProperties();
@@ -118,8 +136,8 @@ void maxTransactionRetryTime() {
118136
}
119137

120138
@Test
121-
void determineServerUriShouldDefaultToLocalhost() {
122-
assertThat(determineServerUri(new Neo4jProperties(), new MockEnvironment()))
139+
void uriShouldDefaultToLocalhost() {
140+
assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getUri())
123141
.isEqualTo(URI.create("bolt://localhost:7687"));
124142
}
125143

@@ -128,44 +146,52 @@ void determineServerUriWithCustomUriShouldOverrideDefault() {
128146
URI customUri = URI.create("bolt://localhost:4242");
129147
Neo4jProperties properties = new Neo4jProperties();
130148
properties.setUri(customUri);
131-
assertThat(determineServerUri(properties, new MockEnvironment())).isEqualTo(customUri);
149+
assertThat(new PropertiesNeo4jConnectionDetails(properties).getUri()).isEqualTo(customUri);
132150
}
133151

134152
@Test
135153
void authenticationShouldDefaultToNone() {
136-
assertThat(mapAuthToken(new Authentication())).isEqualTo(AuthTokens.none());
154+
assertThat(new PropertiesNeo4jConnectionDetails(new Neo4jProperties()).getAuthToken())
155+
.isEqualTo(AuthTokens.none());
137156
}
138157

139158
@Test
140159
void authenticationWithUsernameShouldEnableBasicAuth() {
141-
Authentication authentication = new Authentication();
142-
authentication.setUsername("Farin");
143-
authentication.setPassword("Urlaub");
144-
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub"));
160+
Neo4jProperties properties = new Neo4jProperties();
161+
properties.getAuthentication().setUsername("Farin");
162+
properties.getAuthentication().setPassword("Urlaub");
163+
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
164+
.isEqualTo(AuthTokens.basic("Farin", "Urlaub"));
145165
}
146166

147167
@Test
148168
void authenticationWithUsernameAndRealmShouldEnableBasicAuth() {
149-
Authentication authentication = new Authentication();
169+
Neo4jProperties properties = new Neo4jProperties();
170+
Authentication authentication = properties.getAuthentication();
150171
authentication.setUsername("Farin");
151172
authentication.setPassword("Urlaub");
152173
authentication.setRealm("Test Realm");
153-
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm"));
174+
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
175+
.isEqualTo(AuthTokens.basic("Farin", "Urlaub", "Test Realm"));
154176
}
155177

156178
@Test
157179
void authenticationWithKerberosTicketShouldEnableKerberos() {
158-
Authentication authentication = new Authentication();
180+
Neo4jProperties properties = new Neo4jProperties();
181+
Authentication authentication = properties.getAuthentication();
159182
authentication.setKerberosTicket("AABBCCDDEE");
160-
assertThat(mapAuthToken(authentication)).isEqualTo(AuthTokens.kerberos("AABBCCDDEE"));
183+
assertThat(new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
184+
.isEqualTo(AuthTokens.kerberos("AABBCCDDEE"));
161185
}
162186

163187
@Test
164188
void authenticationWithBothUsernameAndKerberosShouldNotBeAllowed() {
165-
Authentication authentication = new Authentication();
189+
Neo4jProperties properties = new Neo4jProperties();
190+
Authentication authentication = properties.getAuthentication();
166191
authentication.setUsername("Farin");
167192
authentication.setKerberosTicket("AABBCCDDEE");
168-
assertThatIllegalStateException().isThrownBy(() -> mapAuthToken(authentication))
193+
assertThatIllegalStateException()
194+
.isThrownBy(() -> new PropertiesNeo4jConnectionDetails(properties).getAuthToken())
169195
.withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')");
170196
}
171197

@@ -279,20 +305,9 @@ void driverConfigShouldBeConfiguredToUseUseSpringJclLogging() {
279305
assertThat(mapDriverConfig(new Neo4jProperties()).logging()).isInstanceOf(Neo4jSpringJclLogging.class);
280306
}
281307

282-
private URI determineServerUri(Neo4jProperties properties, Environment environment) {
283-
return new Neo4jAutoConfiguration().determineServerUri(properties, environment);
284-
}
285-
286-
private AuthToken mapAuthToken(Authentication authentication, Environment environment) {
287-
return new Neo4jAutoConfiguration().mapAuthToken(authentication, environment);
288-
}
289-
290-
private AuthToken mapAuthToken(Authentication authentication) {
291-
return mapAuthToken(authentication, new MockEnvironment());
292-
}
293-
294308
private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) {
295-
return new Neo4jAutoConfiguration().mapDriverConfig(properties, Arrays.asList(customizers));
309+
return new Neo4jAutoConfiguration().mapDriverConfig(properties,
310+
new PropertiesNeo4jConnectionDetails(properties), Arrays.asList(customizers));
296311
}
297312

298313
}

0 commit comments

Comments
 (0)