From b7847d98a780c14ba9b1eec62b04acff9bf3c97d Mon Sep 17 00:00:00 2001 From: "Michael J. Simons" Date: Fri, 21 Sep 2018 13:36:44 +0200 Subject: [PATCH 1/2] 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 --- .../Neo4jBookmarkManagementConfiguration.java | 69 +++++++++++++++++++ .../neo4j/Neo4jDataAutoConfiguration.java | 3 + .../Neo4jDataAutoConfigurationTests.java | 53 ++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java new file mode 100644 index 0000000000..24bd5b253f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.data.neo4j; + +import com.github.benmanes.caffeine.cache.Caffeine; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.data.neo4j.bookmark.BeanFactoryBookmarkOperationAdvisor; +import org.springframework.data.neo4j.bookmark.BookmarkInterceptor; +import org.springframework.data.neo4j.bookmark.BookmarkManager; +import org.springframework.data.neo4j.bookmark.CaffeineBookmarkManager; +import org.springframework.web.context.WebApplicationContext; + +/** + * Provides a {@link BookmarkManager} for Neo4j's bookmark support based on Caffeine if + * available. Depending on the applications kind (web or not) the bookmark manager will be + * bound to the application or the request, as recommend by Spring Data Neo4j. + * + * @author Michael Simons + * @since 2.1 + */ +@Configuration +@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) +@ConditionalOnMissingBean(BookmarkManager.class) +@ConditionalOnBean({ BeanFactoryBookmarkOperationAdvisor.class, + BookmarkInterceptor.class }) +class Neo4jBookmarkManagementConfiguration { + + static final String BOOKMARK_MANAGER_BEAN_NAME = "bookmarkManager"; + + @Bean(BOOKMARK_MANAGER_BEAN_NAME) + @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) + @ConditionalOnWebApplication + public BookmarkManager requestScopedBookmarkManager() { + return new CaffeineBookmarkManager(); + } + + @Bean(BOOKMARK_MANAGER_BEAN_NAME) + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + @ConditionalOnNotWebApplication + public BookmarkManager singletonScopedBookmarkManager() { + return new CaffeineBookmarkManager(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java index 15b763ab6d..a8d25afdb9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java @@ -37,6 +37,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.data.neo4j.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor; import org.springframework.transaction.PlatformTransactionManager; @@ -52,6 +53,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * @author Vince Bickers * @author Stephane Nicoll * @author Kazuki Shimizu + * @author Michael Simons * @since 1.4.0 */ @Configuration @@ -59,6 +61,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; PlatformTransactionManager.class }) @ConditionalOnMissingBean(SessionFactory.class) @EnableConfigurationProperties(Neo4jProperties.class) +@Import(Neo4jBookmarkManagementConfiguration.class) public class Neo4jDataAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java index 16f94b4eac..0c55dcaa3b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java @@ -16,6 +16,10 @@ package org.springframework.boot.autoconfigure.data.neo4j; +import java.util.function.Predicate; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.assertj.core.api.Condition; import org.junit.Test; import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.SessionFactory; @@ -29,13 +33,19 @@ import org.springframework.boot.autoconfigure.data.neo4j.city.City; import org.springframework.boot.autoconfigure.data.neo4j.country.Country; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.annotation.EnableBookmarkManagement; +import org.springframework.data.neo4j.bookmark.BookmarkManager; import org.springframework.data.neo4j.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor; +import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -51,6 +61,7 @@ import static org.mockito.Mockito.verify; * @author Vince Bickers * @author Andy Wilkinson * @author Kazuki Shimizu + * @author Michael Simons */ public class Neo4jDataAutoConfigurationTests { @@ -69,6 +80,7 @@ public class Neo4jDataAutoConfigurationTests { assertThat(context).hasSingleBean(SessionFactory.class); assertThat(context).hasSingleBean(Neo4jTransactionManager.class); assertThat(context).hasSingleBean(OpenSessionInViewInterceptor.class); + assertThat(context).doesNotHaveBean(BookmarkManager.class); }); } @@ -146,6 +158,41 @@ public class Neo4jDataAutoConfigurationTests { }); } + @Test + public void providesARequestScopedBookmarkManangerIfNecessaryAndPossible() { + Predicate hasRequestScopedBookmarkManager = ( + context) -> context.getBeanFactory() // + .getBeanDefinition("scopedTarget." + + Neo4jBookmarkManagementConfiguration.BOOKMARK_MANAGER_BEAN_NAME) // + .getScope() // + .equals(WebApplicationContext.SCOPE_REQUEST); + + this.contextRunner + .withUserConfiguration(BookmarkManagementEnabledConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(new Condition<>(hasRequestScopedBookmarkManager, + "hasRequestScopedBookmarkManager"))); + } + + @Test + public void providesASingletonScopedBookmarkManangerIfNecessaryAndPossible() { + new ApplicationContextRunner() + .withUserConfiguration(TestConfiguration.class, + BookmarkManagementEnabledConfiguration.class) + .withConfiguration(AutoConfigurations.of(Neo4jDataAutoConfiguration.class, + TransactionAutoConfiguration.class)) + .run((context) -> assertThat(context) + .hasSingleBean(BookmarkManager.class)); + } + + @Test + public void doesNotProvideABookmarkManagerIfNotPossible() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Caffeine.class)) + .withUserConfiguration(BookmarkManagementEnabledConfiguration.class) + .run((context) -> assertThat(context) + .doesNotHaveBean(BookmarkManager.class)); + } + private static void assertDomainTypesDiscovered(Neo4jMappingContext mappingContext, Class... types) { for (Class type : types) { @@ -180,6 +227,12 @@ public class Neo4jDataAutoConfigurationTests { } + @Configuration + @EnableBookmarkManagement + static class BookmarkManagementEnabledConfiguration { + + } + @Configuration static class EventListenerConfiguration { From 2add65fe158cc6f430b45808a848dd482d3b9c4e Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 1 Oct 2018 10:07:56 -0700 Subject: [PATCH 2/2] Polish Neo4J BookmarkManager auto-configuration Closes gh-14568 --- .../Neo4jBookmarkManagementConfiguration.java | 8 ++--- .../neo4j/Neo4jDataAutoConfiguration.java | 1 - .../Neo4jDataAutoConfigurationTests.java | 30 ++++++++----------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java index 24bd5b253f..e3efca1507 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jBookmarkManagementConfiguration.java @@ -18,7 +18,6 @@ package org.springframework.boot.autoconfigure.data.neo4j; import com.github.benmanes.caffeine.cache.Caffeine; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -41,7 +40,7 @@ import org.springframework.web.context.WebApplicationContext; * bound to the application or the request, as recommend by Spring Data Neo4j. * * @author Michael Simons - * @since 2.1 + * @since 2.1.0 */ @Configuration @ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) @@ -50,17 +49,16 @@ import org.springframework.web.context.WebApplicationContext; BookmarkInterceptor.class }) class Neo4jBookmarkManagementConfiguration { - static final String BOOKMARK_MANAGER_BEAN_NAME = "bookmarkManager"; + private static final String BOOKMARK_MANAGER_BEAN_NAME = "bookmarkManager"; @Bean(BOOKMARK_MANAGER_BEAN_NAME) - @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) @ConditionalOnWebApplication + @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES) public BookmarkManager requestScopedBookmarkManager() { return new CaffeineBookmarkManager(); } @Bean(BOOKMARK_MANAGER_BEAN_NAME) - @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) @ConditionalOnNotWebApplication public BookmarkManager singletonScopedBookmarkManager() { return new CaffeineBookmarkManager(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java index a8d25afdb9..620dbfb0d3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java @@ -53,7 +53,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * @author Vince Bickers * @author Stephane Nicoll * @author Kazuki Shimizu - * @author Michael Simons * @since 1.4.0 */ @Configuration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java index 0c55dcaa3b..dd4ff2526a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -16,10 +16,7 @@ package org.springframework.boot.autoconfigure.data.neo4j; -import java.util.function.Predicate; - import com.github.benmanes.caffeine.cache.Caffeine; -import org.assertj.core.api.Condition; import org.junit.Test; import org.neo4j.ogm.session.Session; import org.neo4j.ogm.session.SessionFactory; @@ -27,6 +24,7 @@ import org.neo4j.ogm.session.event.Event; import org.neo4j.ogm.session.event.EventListener; import org.neo4j.ogm.session.event.PersistenceEvent; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.data.neo4j.city.City; @@ -36,7 +34,6 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfigu import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -160,18 +157,14 @@ public class Neo4jDataAutoConfigurationTests { @Test public void providesARequestScopedBookmarkManangerIfNecessaryAndPossible() { - Predicate hasRequestScopedBookmarkManager = ( - context) -> context.getBeanFactory() // - .getBeanDefinition("scopedTarget." - + Neo4jBookmarkManagementConfiguration.BOOKMARK_MANAGER_BEAN_NAME) // - .getScope() // - .equals(WebApplicationContext.SCOPE_REQUEST); - this.contextRunner .withUserConfiguration(BookmarkManagementEnabledConfiguration.class) - .run((context) -> assertThat(context) - .satisfies(new Condition<>(hasRequestScopedBookmarkManager, - "hasRequestScopedBookmarkManager"))); + .run((context) -> { + BeanDefinition bookmarkManagerBean = context.getBeanFactory() + .getBeanDefinition("scopedTarget.bookmarkManager"); + assertThat(bookmarkManagerBean.getScope()) + .isEqualTo(WebApplicationContext.SCOPE_REQUEST); + }); } @Test @@ -181,8 +174,11 @@ public class Neo4jDataAutoConfigurationTests { BookmarkManagementEnabledConfiguration.class) .withConfiguration(AutoConfigurations.of(Neo4jDataAutoConfiguration.class, TransactionAutoConfiguration.class)) - .run((context) -> assertThat(context) - .hasSingleBean(BookmarkManager.class)); + .run((context) -> { + assertThat(context).hasSingleBean(BookmarkManager.class); + assertThat(context.getBeanDefinitionNames()) + .doesNotContain("scopedTarget.bookmarkManager"); + }); } @Test