Skip to content

Commit b7847d9

Browse files
michael-simonsphilwebb
authored andcommitted
Auto-configure Neo4J BookmarkManager when possible
Add `Neo4jBookmarkManagementConfiguration` which provides an instance of `BookmarkManager` if necessary and Caffeine cache is on the classpath. Depending on the kind of application, the `BookmarkManager` will be request scoped or singleton, as recommended by Spring Data Neo4j. See gh-14568
1 parent 94b366b commit b7847d9

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-2018 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+
* http://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.data.neo4j;
18+
19+
import com.github.benmanes.caffeine.cache.Caffeine;
20+
21+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
27+
import org.springframework.cache.caffeine.CaffeineCacheManager;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.context.annotation.Scope;
31+
import org.springframework.context.annotation.ScopedProxyMode;
32+
import org.springframework.data.neo4j.bookmark.BeanFactoryBookmarkOperationAdvisor;
33+
import org.springframework.data.neo4j.bookmark.BookmarkInterceptor;
34+
import org.springframework.data.neo4j.bookmark.BookmarkManager;
35+
import org.springframework.data.neo4j.bookmark.CaffeineBookmarkManager;
36+
import org.springframework.web.context.WebApplicationContext;
37+
38+
/**
39+
* Provides a {@link BookmarkManager} for Neo4j's bookmark support based on Caffeine if
40+
* available. Depending on the applications kind (web or not) the bookmark manager will be
41+
* bound to the application or the request, as recommend by Spring Data Neo4j.
42+
*
43+
* @author Michael Simons
44+
* @since 2.1
45+
*/
46+
@Configuration
47+
@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class })
48+
@ConditionalOnMissingBean(BookmarkManager.class)
49+
@ConditionalOnBean({ BeanFactoryBookmarkOperationAdvisor.class,
50+
BookmarkInterceptor.class })
51+
class Neo4jBookmarkManagementConfiguration {
52+
53+
static final String BOOKMARK_MANAGER_BEAN_NAME = "bookmarkManager";
54+
55+
@Bean(BOOKMARK_MANAGER_BEAN_NAME)
56+
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
57+
@ConditionalOnWebApplication
58+
public BookmarkManager requestScopedBookmarkManager() {
59+
return new CaffeineBookmarkManager();
60+
}
61+
62+
@Bean(BOOKMARK_MANAGER_BEAN_NAME)
63+
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
64+
@ConditionalOnNotWebApplication
65+
public BookmarkManager singletonScopedBookmarkManager() {
66+
return new CaffeineBookmarkManager();
67+
}
68+
69+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.context.ApplicationContext;
3838
import org.springframework.context.annotation.Bean;
3939
import org.springframework.context.annotation.Configuration;
40+
import org.springframework.context.annotation.Import;
4041
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
4142
import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor;
4243
import org.springframework.transaction.PlatformTransactionManager;
@@ -52,13 +53,15 @@
5253
* @author Vince Bickers
5354
* @author Stephane Nicoll
5455
* @author Kazuki Shimizu
56+
* @author Michael Simons
5557
* @since 1.4.0
5658
*/
5759
@Configuration
5860
@ConditionalOnClass({ SessionFactory.class, Neo4jTransactionManager.class,
5961
PlatformTransactionManager.class })
6062
@ConditionalOnMissingBean(SessionFactory.class)
6163
@EnableConfigurationProperties(Neo4jProperties.class)
64+
@Import(Neo4jBookmarkManagementConfiguration.class)
6265
public class Neo4jDataAutoConfiguration {
6366

6467
@Bean

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.boot.autoconfigure.data.neo4j;
1818

19+
import java.util.function.Predicate;
20+
21+
import com.github.benmanes.caffeine.cache.Caffeine;
22+
import org.assertj.core.api.Condition;
1923
import org.junit.Test;
2024
import org.neo4j.ogm.session.Session;
2125
import org.neo4j.ogm.session.SessionFactory;
@@ -29,13 +33,19 @@
2933
import org.springframework.boot.autoconfigure.data.neo4j.country.Country;
3034
import org.springframework.boot.autoconfigure.domain.EntityScan;
3135
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
36+
import org.springframework.boot.test.context.FilteredClassLoader;
37+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3238
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
39+
import org.springframework.context.ConfigurableApplicationContext;
3340
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3441
import org.springframework.context.annotation.Bean;
3542
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.data.neo4j.annotation.EnableBookmarkManagement;
44+
import org.springframework.data.neo4j.bookmark.BookmarkManager;
3645
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
3746
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
3847
import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor;
48+
import org.springframework.web.context.WebApplicationContext;
3949

4050
import static org.assertj.core.api.Assertions.assertThat;
4151
import static org.mockito.ArgumentMatchers.any;
@@ -51,6 +61,7 @@
5161
* @author Vince Bickers
5262
* @author Andy Wilkinson
5363
* @author Kazuki Shimizu
64+
* @author Michael Simons
5465
*/
5566
public class Neo4jDataAutoConfigurationTests {
5667

@@ -69,6 +80,7 @@ public void defaultConfiguration() {
6980
assertThat(context).hasSingleBean(SessionFactory.class);
7081
assertThat(context).hasSingleBean(Neo4jTransactionManager.class);
7182
assertThat(context).hasSingleBean(OpenSessionInViewInterceptor.class);
83+
assertThat(context).doesNotHaveBean(BookmarkManager.class);
7284
});
7385
}
7486

@@ -146,6 +158,41 @@ public void eventListenersAreAutoRegistered() {
146158
});
147159
}
148160

161+
@Test
162+
public void providesARequestScopedBookmarkManangerIfNecessaryAndPossible() {
163+
Predicate<ConfigurableApplicationContext> hasRequestScopedBookmarkManager = (
164+
context) -> context.getBeanFactory() //
165+
.getBeanDefinition("scopedTarget."
166+
+ Neo4jBookmarkManagementConfiguration.BOOKMARK_MANAGER_BEAN_NAME) //
167+
.getScope() //
168+
.equals(WebApplicationContext.SCOPE_REQUEST);
169+
170+
this.contextRunner
171+
.withUserConfiguration(BookmarkManagementEnabledConfiguration.class)
172+
.run((context) -> assertThat(context)
173+
.satisfies(new Condition<>(hasRequestScopedBookmarkManager,
174+
"hasRequestScopedBookmarkManager")));
175+
}
176+
177+
@Test
178+
public void providesASingletonScopedBookmarkManangerIfNecessaryAndPossible() {
179+
new ApplicationContextRunner()
180+
.withUserConfiguration(TestConfiguration.class,
181+
BookmarkManagementEnabledConfiguration.class)
182+
.withConfiguration(AutoConfigurations.of(Neo4jDataAutoConfiguration.class,
183+
TransactionAutoConfiguration.class))
184+
.run((context) -> assertThat(context)
185+
.hasSingleBean(BookmarkManager.class));
186+
}
187+
188+
@Test
189+
public void doesNotProvideABookmarkManagerIfNotPossible() {
190+
this.contextRunner.withClassLoader(new FilteredClassLoader(Caffeine.class))
191+
.withUserConfiguration(BookmarkManagementEnabledConfiguration.class)
192+
.run((context) -> assertThat(context)
193+
.doesNotHaveBean(BookmarkManager.class));
194+
}
195+
149196
private static void assertDomainTypesDiscovered(Neo4jMappingContext mappingContext,
150197
Class<?>... types) {
151198
for (Class<?> type : types) {
@@ -180,6 +227,12 @@ public org.neo4j.ogm.config.Configuration myConfiguration() {
180227

181228
}
182229

230+
@Configuration
231+
@EnableBookmarkManagement
232+
static class BookmarkManagementEnabledConfiguration {
233+
234+
}
235+
183236
@Configuration
184237
static class EventListenerConfiguration {
185238

0 commit comments

Comments
 (0)