From 0daecff5d5cbde8d0ce4874333440a64082a44c2 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Tue, 24 Nov 2020 16:53:17 +0100 Subject: [PATCH] DATAGRAPH-1441 - Register necessary callbacks internally inside the mapping context. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IdGenerating callbacks must be registered for each Neo4j mapping context, otherwise they could check with the wrong context whether an entity is new or requires optimistic locking. In a case where the context is strict, this will lead to an exception. The callbacks are now registered in the templates belonging to the context. With this change, we aren’t dependend on the correct order of callback registrars anymore. It is next to impossible to find a way that deals with multiple context that works both in a plain Spring scenario only using our configuration, the default Spring Boot scenario or a completly custom one. There are too many ways a user can end up missing those required beans. In the light of that change it became appearant that the event / callback system doesn't belong inside the repository package but in the mapping one. As the id generating and versioning callbacks have not been public API in the first place, they have been moved directly to the package without a previous deprecation. They are not package private and only accessible through internal API. The auditing callbacks on the other hand might be instantiated directly by a user that doesn't want to use the dedicated annotation or the additional registrar. Therefore they have been deprecated and they delegate now to the replacement. The general before bind callback interfaces - both imperative and reactive - have been deprecated as well and replaced by a stable API in the new package. In 6.0.2 upwards, both will be used. They will be removed in 6.1.x. Last but not least, the registrars bringing in the id generating and versioning callbacks are now no-op operations and deprecated. SDN doesn't need them anymore. While working on the support for multiple context it became appearant that is sensible to extract the class scanning and provide it as a utility method rather than support class. --- .../neo4j/config/AbstractNeo4jConfig.java | 2 - .../config/AbstractReactiveNeo4jConfig.java | 2 - .../neo4j/config/Neo4jAuditingRegistrar.java | 2 +- .../config/Neo4jCdiConfigurationSupport.java | 36 +---- .../config/Neo4jConfigurationSupport.java | 37 +---- .../Neo4jDefaultCallbacksRegistrar.java | 20 +-- ...eo4jDefaultReactiveCallbacksRegistrar.java | 20 +-- .../data/neo4j/config/Neo4jEntityScanner.java | 136 ++++++++++++++++++ .../ReactiveNeo4jAuditingRegistrar.java | 2 +- .../data/neo4j/core/Neo4jTemplate.java | 25 +--- .../neo4j/core/ReactiveNeo4jTemplate.java | 25 +--- .../core/mapping/Neo4jMappingContext.java | 17 +-- .../callback/AuditingBeforeBindCallback.java | 71 +++++++++ .../mapping/callback/BeforeBindCallback.java | 45 ++++++ .../core/mapping/callback/EventSupport.java | 84 +++++++++++ .../IdGeneratingBeforeBindCallback.java | 10 +- .../mapping/callback}/IdPopulator.java | 2 +- .../OptimisticLockingBeforeBindCallback.java | 44 ++++++ .../callback/OptimisticLockingSupport.java} | 30 ++-- .../ReactiveAuditingBeforeBindCallback.java | 73 ++++++++++ .../callback/ReactiveBeforeBindCallback.java | 48 +++++++ .../callback/ReactiveEventSupport.java | 84 +++++++++++ ...eactiveIdGeneratingBeforeBindCallback.java | 10 +- ...veOptimisticLockingBeforeBindCallback.java | 48 +++++++ .../core/mapping/callback/package-info.java | 21 +++ .../data/neo4j/core/mapping/package-info.java | 8 +- .../event/AuditingBeforeBindCallback.java | 32 ++--- .../repository/event/BeforeBindCallback.java | 4 +- .../ReactiveAuditingBeforeBindCallback.java | 34 ++--- .../event/ReactiveBeforeBindCallback.java | 4 +- ...veOptimisticLockingBeforeBindCallback.java | 75 ---------- .../neo4j/repository/event/package-info.java | 17 --- .../AuditingBeforeBindCallbackTest.java | 2 +- .../mapping/callback}/IdPopulatorTest.java | 2 +- .../mapping/callback}/ImmutableSample.java | 2 +- ...eactiveAuditingBeforeBindCallbackTest.java | 2 +- .../mapping/callback}/Sample.java | 2 +- .../integration/imperative/CallbacksIT.java | 2 +- .../MultipleContextsIT.java | 120 ++++++++++++++++ .../multiple_ctx_imperative/SharedConfig.java | 35 +++++ .../domain1/Domain1Config.java | 92 ++++++++++++ .../domain1/Domain1Entity.java | 61 ++++++++ .../domain1/Domain1Repository.java | 25 ++++ .../domain2/Domain2Config.java | 91 ++++++++++++ .../domain2/Domain2Entity.java | 57 ++++++++ .../domain2/Domain2Repository.java | 25 ++++ .../reactive/ReactiveCallbacksIT.java | 2 +- 47 files changed, 1249 insertions(+), 339 deletions(-) create mode 100644 src/main/java/org/springframework/data/neo4j/config/Neo4jEntityScanner.java create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallback.java create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/BeforeBindCallback.java create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/EventSupport.java rename src/main/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/IdGeneratingBeforeBindCallback.java (78%) rename src/main/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/IdPopulator.java (98%) create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingBeforeBindCallback.java rename src/main/java/org/springframework/data/neo4j/{repository/event/OptimisticLockingBeforeBindCallback.java => core/mapping/callback/OptimisticLockingSupport.java} (68%) create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallback.java create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveBeforeBindCallback.java create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveEventSupport.java rename src/main/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/ReactiveIdGeneratingBeforeBindCallback.java (79%) create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveOptimisticLockingBeforeBindCallback.java create mode 100644 src/main/java/org/springframework/data/neo4j/core/mapping/callback/package-info.java delete mode 100644 src/main/java/org/springframework/data/neo4j/repository/event/ReactiveOptimisticLockingBeforeBindCallback.java delete mode 100644 src/main/java/org/springframework/data/neo4j/repository/event/package-info.java rename src/test/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/AuditingBeforeBindCallbackTest.java (98%) rename src/test/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/IdPopulatorTest.java (98%) rename src/test/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/ImmutableSample.java (94%) rename src/test/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/ReactiveAuditingBeforeBindCallbackTest.java (98%) rename src/test/java/org/springframework/data/neo4j/{repository/event => core/mapping/callback}/Sample.java (94%) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/MultipleContextsIT.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/SharedConfig.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Entity.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Repository.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Entity.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Repository.java diff --git a/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java b/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java index e25089a00..fa795c4ac 100644 --- a/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java +++ b/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java @@ -19,7 +19,6 @@ import org.apiguardian.api.API; import org.neo4j.driver.Driver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.neo4j.core.DatabaseSelectionProvider; import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.Neo4jOperations; @@ -39,7 +38,6 @@ import org.springframework.transaction.PlatformTransactionManager; */ @Configuration @API(status = API.Status.STABLE, since = "6.0") -@Import(Neo4jDefaultCallbacksRegistrar.class) public abstract class AbstractNeo4jConfig extends Neo4jConfigurationSupport { /** diff --git a/src/main/java/org/springframework/data/neo4j/config/AbstractReactiveNeo4jConfig.java b/src/main/java/org/springframework/data/neo4j/config/AbstractReactiveNeo4jConfig.java index a77072fcd..726c6e94a 100644 --- a/src/main/java/org/springframework/data/neo4j/config/AbstractReactiveNeo4jConfig.java +++ b/src/main/java/org/springframework/data/neo4j/config/AbstractReactiveNeo4jConfig.java @@ -19,7 +19,6 @@ import org.apiguardian.api.API; import org.neo4j.driver.Driver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider; import org.springframework.data.neo4j.core.ReactiveNeo4jClient; import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate; @@ -39,7 +38,6 @@ import org.springframework.transaction.ReactiveTransactionManager; */ @Configuration @API(status = API.Status.STABLE, since = "6.0") -@Import(Neo4jDefaultReactiveCallbacksRegistrar.class) public abstract class AbstractReactiveNeo4jConfig extends Neo4jConfigurationSupport { /** diff --git a/src/main/java/org/springframework/data/neo4j/config/Neo4jAuditingRegistrar.java b/src/main/java/org/springframework/data/neo4j/config/Neo4jAuditingRegistrar.java index 8b20b669e..0c66fc292 100644 --- a/src/main/java/org/springframework/data/neo4j/config/Neo4jAuditingRegistrar.java +++ b/src/main/java/org/springframework/data/neo4j/config/Neo4jAuditingRegistrar.java @@ -25,7 +25,7 @@ import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarS import org.springframework.data.auditing.config.AuditingConfiguration; import org.springframework.data.config.ParsingUtils; import org.springframework.data.mapping.context.PersistentEntities; -import org.springframework.data.neo4j.repository.event.AuditingBeforeBindCallback; +import org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback; import org.springframework.util.Assert; /** diff --git a/src/main/java/org/springframework/data/neo4j/config/Neo4jCdiConfigurationSupport.java b/src/main/java/org/springframework/data/neo4j/config/Neo4jCdiConfigurationSupport.java index 51fd1d1ef..8489e3271 100644 --- a/src/main/java/org/springframework/data/neo4j/config/Neo4jCdiConfigurationSupport.java +++ b/src/main/java/org/springframework/data/neo4j/config/Neo4jCdiConfigurationSupport.java @@ -23,8 +23,6 @@ import javax.inject.Singleton; import org.apiguardian.api.API; import org.neo4j.driver.Driver; -import org.springframework.data.mapping.callback.EntityCallback; -import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.neo4j.core.DatabaseSelectionProvider; import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.Neo4jOperations; @@ -32,9 +30,6 @@ import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.data.neo4j.core.convert.Neo4jConversions; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; -import org.springframework.data.neo4j.repository.event.BeforeBindCallback; -import org.springframework.data.neo4j.repository.event.IdGeneratingBeforeBindCallback; -import org.springframework.data.neo4j.repository.event.OptimisticLockingBeforeBindCallback; import org.springframework.transaction.PlatformTransactionManager; /** @@ -70,15 +65,11 @@ class Neo4jCdiConfigurationSupport { @Produces @Builtin @Singleton public Neo4jOperations neo4jOperations( - final @Any Instance neo4jClient, - final @Any Instance mappingContext, - final @Any Instance databaseNameProvider, - final Instance services + @Any Instance neo4jClient, + @Any Instance mappingContext, + @Any Instance databaseNameProvider ) { - - EntityCallbacks entityCallbacks = EntityCallbacks.create(services.stream().toArray(EntityCallback[]::new)); - return new Neo4jTemplate(resolve(neo4jClient), resolve(mappingContext), resolve(databaseNameProvider), - entityCallbacks); + return new Neo4jTemplate(resolve(neo4jClient), resolve(mappingContext), resolve(databaseNameProvider)); } @Produces @Singleton @@ -87,24 +78,9 @@ class Neo4jCdiConfigurationSupport { } @Produces @Singleton - public Neo4jMappingContext neo4jMappingContext(final Driver driver, final @Any Instance neo4JConversions) { + public Neo4jMappingContext neo4jMappingContext(Driver driver, @Any Instance neo4JConversions) { - Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext(resolve(neo4JConversions), driver.defaultTypeSystem()); - return neo4jMappingContext; - } - - @Produces @Singleton - public BeforeBindCallback idGeneratingBeforeBindCallback( - final @Any Instance mappingContext) { - - return new IdGeneratingBeforeBindCallback(resolve(mappingContext)); - } - - @Produces @Singleton - public BeforeBindCallback optimisticLockingBeforeBindCallback( - final @Any Instance mappingContext) { - - return new OptimisticLockingBeforeBindCallback(resolve(mappingContext)); + return new Neo4jMappingContext(resolve(neo4JConversions), driver.defaultTypeSystem()); } @Produces @Singleton diff --git a/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java b/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java index c2926c61c..013896180 100644 --- a/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java +++ b/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java @@ -17,21 +17,13 @@ package org.springframework.data.neo4j.config; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.Set; import org.apiguardian.api.API; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.annotation.Persistent; import org.springframework.data.neo4j.core.convert.Neo4jConversions; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.schema.Node; -import org.springframework.data.neo4j.core.schema.RelationshipProperties; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Internal support class for basic configuration. The support infrastructure here is basically all around finding out @@ -90,13 +82,7 @@ abstract class Neo4jConfigurationSupport { */ protected final Set> getInitialEntitySet() throws ClassNotFoundException { - Set> initialEntitySet = new HashSet>(); - - for (String basePackage : getMappingBasePackages()) { - initialEntitySet.addAll(scanForEntities(basePackage)); - } - - return initialEntitySet; + return Neo4jEntityScanner.get().scan(getMappingBasePackages()); } /** @@ -105,26 +91,11 @@ abstract class Neo4jConfigurationSupport { * @param basePackage must not be {@literal null}. * @return found entities in the package to scan. * @throws ClassNotFoundException if the given class cannot be loaded by the class loader. + * @deprecated since 6.0.2 Use {@link Neo4jEntityScanner} instead. */ + @Deprecated protected final Set> scanForEntities(String basePackage) throws ClassNotFoundException { - if (!StringUtils.hasText(basePackage)) { - return Collections.emptySet(); - } - - Set> initialEntitySet = new HashSet>(); - - ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( - false); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Node.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(RelationshipProperties.class)); - - ClassLoader classLoader = Neo4jConfigurationSupport.class.getClassLoader(); - for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { - initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), classLoader)); - } - - return initialEntitySet; + return Neo4jEntityScanner.get().scan(basePackage); } } diff --git a/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultCallbacksRegistrar.java b/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultCallbacksRegistrar.java index f10e1c13b..915ad348e 100644 --- a/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultCallbacksRegistrar.java +++ b/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultCallbacksRegistrar.java @@ -16,15 +16,11 @@ package org.springframework.data.neo4j.config; import org.apiguardian.api.API; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.neo4j.core.Neo4jOperations; -import org.springframework.data.neo4j.repository.event.IdGeneratingBeforeBindCallback; -import org.springframework.data.neo4j.repository.event.OptimisticLockingBeforeBindCallback; /** * This brings in the default callbacks required for the default implementation of {@link Neo4jOperations} to work. The @@ -34,23 +30,15 @@ import org.springframework.data.neo4j.repository.event.OptimisticLockingBeforeBi * @author Michael J. Simons * @soundtrack AC/DC - High Voltage * @since 6.0 + * @deprecated since 6.0.2, now an empty implementation, not needed anymore and our default callbacks will be added directly via our + * infrastructure. */ -@API(status = API.Status.STABLE, since = "6.0") +@API(status = API.Status.DEPRECATED, since = "6.0") +@Deprecated public final class Neo4jDefaultCallbacksRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator) { - // Id Generator - RootBeanDefinition beanDefinition = new RootBeanDefinition(IdGeneratingBeforeBindCallback.class); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry); - registry.registerBeanDefinition(beanName, beanDefinition); - - // Optimistic locking support - beanDefinition = new RootBeanDefinition(OptimisticLockingBeforeBindCallback.class); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - beanName = beanNameGenerator.generateBeanName(beanDefinition, registry); - registry.registerBeanDefinition(beanName, beanDefinition); } } diff --git a/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultReactiveCallbacksRegistrar.java b/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultReactiveCallbacksRegistrar.java index 7854fdfad..45a919ff5 100644 --- a/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultReactiveCallbacksRegistrar.java +++ b/src/main/java/org/springframework/data/neo4j/config/Neo4jDefaultReactiveCallbacksRegistrar.java @@ -16,15 +16,11 @@ package org.springframework.data.neo4j.config; import org.apiguardian.api.API; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.neo4j.core.Neo4jOperations; -import org.springframework.data.neo4j.repository.event.ReactiveIdGeneratingBeforeBindCallback; -import org.springframework.data.neo4j.repository.event.ReactiveOptimisticLockingBeforeBindCallback; /** * This brings in the default callbacks required for the default implementation of {@link Neo4jOperations} to work. The @@ -34,23 +30,15 @@ import org.springframework.data.neo4j.repository.event.ReactiveOptimisticLocking * @author Michael J. Simons * @soundtrack AC/DC - High Voltage * @since 6.0 + * @deprecated since 6.0.2, now an empty implementation, not needed anymore and our default callbacks will be added directly via our + * infrastructure. */ -@API(status = API.Status.STABLE, since = "6.0") +@API(status = API.Status.DEPRECATED, since = "6.0") +@Deprecated public final class Neo4jDefaultReactiveCallbacksRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator) { - // Id Generator - RootBeanDefinition beanDefinition = new RootBeanDefinition(ReactiveIdGeneratingBeforeBindCallback.class); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry); - registry.registerBeanDefinition(beanName, beanDefinition); - - // Optimistic locking support - beanDefinition = new RootBeanDefinition(ReactiveOptimisticLockingBeforeBindCallback.class); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - beanName = beanNameGenerator.generateBeanName(beanDefinition, registry); - registry.registerBeanDefinition(beanName, beanDefinition); } } diff --git a/src/main/java/org/springframework/data/neo4j/config/Neo4jEntityScanner.java b/src/main/java/org/springframework/data/neo4j/config/Neo4jEntityScanner.java new file mode 100644 index 000000000..34fca7f5f --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/config/Neo4jEntityScanner.java @@ -0,0 +1,136 @@ +/* + * Copyright 2011-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.data.neo4j.config; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apiguardian.api.API; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * A utility class providing a way to discover an initial entity set for a {@link org.springframework.data.neo4j.core.mapping.Neo4jMappingContext}. + * + * @author Michael J. Simons + * @soundtrack Kelis - Tasty + * @since 6.0.2 + */ +@API(status = API.Status.STABLE, since = "6.0.2") +public final class Neo4jEntityScanner { + + public static Neo4jEntityScanner get() { + + return new Neo4jEntityScanner(null); + } + + public static Neo4jEntityScanner get(@Nullable ResourceLoader resourceLoader) { + + return new Neo4jEntityScanner(resourceLoader); + } + + private @Nullable final ResourceLoader resourceLoader; + + /** + * Create a new {@link Neo4jEntityScanner} instance. + * + * @param resourceLoader an optional resource loader used for class scanning. + */ + private Neo4jEntityScanner(@Nullable ResourceLoader resourceLoader) { + + this.resourceLoader = resourceLoader; + } + + /** + * Scan for entities with the specified annotations. + * + * @param basePackages the list of base packages to scan. + * @return a set of entity classes + * @throws ClassNotFoundException if an entity class cannot be loaded + */ + @SafeVarargs + public final Set> scan(String... basePackages) throws ClassNotFoundException { + return scan(Arrays.stream(basePackages).collect(Collectors.toList())); + } + + /** + * Scan for entities with the specified annotations. + * + * @param packages the list of base packages to scan. + * @return a set of entity classes + * @throws ClassNotFoundException if an entity class cannot be loaded + * @see #scan(String...) + */ + public Set> scan(Collection packages) throws ClassNotFoundException { + + if (packages.isEmpty()) { + return Collections.emptySet(); + } + + ClassPathScanningCandidateComponentProvider scanner = + createClassPathScanningCandidateComponentProvider(this.resourceLoader); + + ClassLoader classLoader = + this.resourceLoader == null ? + Neo4jConfigurationSupport.class.getClassLoader() : + this.resourceLoader.getClassLoader(); + + Set> entitySet = new HashSet<>(); + for (String basePackage : packages) { + if (StringUtils.hasText(basePackage)) { + for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) { + entitySet.add(ClassUtils.forName(candidate.getBeanClassName(), classLoader)); + } + } + } + return entitySet; + } + + /** + * Create a {@link ClassPathScanningCandidateComponentProvider} to scan entities based + * on the specified {@link ApplicationContext}. + * + * @param resourceLoader an optional {@link ResourceLoader} to use + * @return a {@link ClassPathScanningCandidateComponentProvider} suitable to scan for Neo4j entities + */ + private static ClassPathScanningCandidateComponentProvider createClassPathScanningCandidateComponentProvider( + ResourceLoader resourceLoader) { + + ClassPathScanningCandidateComponentProvider delegate = new ClassPathScanningCandidateComponentProvider(false); + if (resourceLoader != null) { + delegate.setResourceLoader(resourceLoader); + } + + delegate.addIncludeFilter(new AnnotationTypeFilter(Node.class)); + delegate.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); + delegate.addIncludeFilter(new AnnotationTypeFilter(RelationshipProperties.class)); + + return delegate; + } +} diff --git a/src/main/java/org/springframework/data/neo4j/config/ReactiveNeo4jAuditingRegistrar.java b/src/main/java/org/springframework/data/neo4j/config/ReactiveNeo4jAuditingRegistrar.java index 840969cd8..e62c05642 100644 --- a/src/main/java/org/springframework/data/neo4j/config/ReactiveNeo4jAuditingRegistrar.java +++ b/src/main/java/org/springframework/data/neo4j/config/ReactiveNeo4jAuditingRegistrar.java @@ -25,7 +25,7 @@ import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarS import org.springframework.data.auditing.config.AuditingConfiguration; import org.springframework.data.config.ParsingUtils; import org.springframework.data.mapping.context.PersistentEntities; -import org.springframework.data.neo4j.repository.event.ReactiveAuditingBeforeBindCallback; +import org.springframework.data.neo4j.core.mapping.callback.ReactiveAuditingBeforeBindCallback; import org.springframework.util.Assert; /** diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index dce8b32fb..886249728 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -59,8 +59,8 @@ import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingS import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine.ProcessState; import org.springframework.data.neo4j.core.mapping.NodeDescription; import org.springframework.data.neo4j.core.mapping.RelationshipDescription; +import org.springframework.data.neo4j.core.mapping.callback.EventSupport; import org.springframework.data.neo4j.repository.NoResultException; -import org.springframework.data.neo4j.repository.event.BeforeBindCallback; import org.springframework.data.util.ClassTypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -87,7 +87,7 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware { private final CypherGenerator cypherGenerator; - private Neo4jEvents eventSupport; + private EventSupport eventSupport; private final DatabaseSelectionProvider databaseSelectionProvider; @@ -111,7 +111,7 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware { this.neo4jClient = neo4jClient; this.neo4jMappingContext = neo4jMappingContext; this.cypherGenerator = CypherGenerator.INSTANCE; - this.eventSupport = new Neo4jEvents(entityCallbacks); + this.eventSupport = EventSupport.useExistingCallbacks(neo4jMappingContext, entityCallbacks); this.databaseSelectionProvider = databaseSelectionProvider; } @@ -548,7 +548,7 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware { @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.eventSupport = new Neo4jEvents(EntityCallbacks.create(beanFactory)); + this.eventSupport = EventSupport.discoverCallbacks(neo4jMappingContext, beanFactory); } @Override @@ -596,21 +596,4 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware { return fetchSpec.one().orElseThrow(() -> new NoResultException(1, preparedQuery.getCypherQuery())); } } - - /** - * Utility class that orchestrates {@link EntityCallbacks}. All the methods provided here check for their availability - * and do nothing when an event cannot be published. - */ - final class Neo4jEvents { - - private final EntityCallbacks entityCallbacks; - - Neo4jEvents(EntityCallbacks entityCallbacks) { - this.entityCallbacks = entityCallbacks; - } - - public T maybeCallBeforeBind(T object) { - return entityCallbacks.callback(BeforeBindCallback.class, object); - } - } } diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java index 923005ceb..8065e96da 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -63,7 +63,7 @@ import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingS import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine.ProcessState; import org.springframework.data.neo4j.core.mapping.NodeDescription; import org.springframework.data.neo4j.core.mapping.RelationshipDescription; -import org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback; +import org.springframework.data.neo4j.core.mapping.callback.ReactiveEventSupport; import org.springframework.data.util.ClassTypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -90,7 +90,7 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea private final CypherGenerator cypherGenerator; - private ReactiveNeo4jEvents eventSupport; + private ReactiveEventSupport eventSupport; private final ReactiveDatabaseSelectionProvider databaseSelectionProvider; @@ -104,7 +104,7 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea this.neo4jClient = neo4jClient; this.neo4jMappingContext = neo4jMappingContext; this.cypherGenerator = CypherGenerator.INSTANCE; - this.eventSupport = new ReactiveNeo4jEvents(ReactiveEntityCallbacks.create()); + this.eventSupport = ReactiveEventSupport.useExistingCallbacks(neo4jMappingContext, ReactiveEntityCallbacks.create()); this.databaseSelectionProvider = databaseSelectionProvider; } @@ -577,7 +577,7 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.eventSupport = new ReactiveNeo4jEvents(ReactiveEntityCallbacks.create(beanFactory)); + this.eventSupport = ReactiveEventSupport.discoverCallbacks(neo4jMappingContext, beanFactory); } final class DefaultReactiveExecutableQuery implements ExecutableQuery { @@ -618,21 +618,4 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea } } } - - /** - * Utility class that orchestrates {@link ReactiveEntityCallbacks}. All the methods provided here check for their - * availability and do nothing when an event cannot be published. - */ - final class ReactiveNeo4jEvents { - - private final ReactiveEntityCallbacks entityCallbacks; - - ReactiveNeo4jEvents(ReactiveEntityCallbacks entityCallbacks) { - this.entityCallbacks = entityCallbacks; - } - - Mono maybeCallBeforeBind(T object) { - return entityCallbacks.callback(ReactiveBeforeBindCallback.class, object); - } - } } diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java b/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java index aaf39130b..854df78f9 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java @@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apiguardian.api.API; import org.neo4j.cypherdsl.core.Statement; -import org.neo4j.driver.Driver; +import org.neo4j.driver.internal.types.InternalTypeSystem; import org.neo4j.driver.types.TypeSystem; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -105,22 +105,21 @@ public final class Neo4jMappingContext extends AbstractMappingContext neo4jPersistentEntity, NestedRelationshipContext relationshipContext, diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallback.java new file mode 100644 index 000000000..79cb40201 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallback.java @@ -0,0 +1,71 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.core.Ordered; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.IsNewAwareAuditingHandler; +import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.util.Assert; + +/** + * {@link EntityCallback} to populate auditing related fields on an entity about to be bound to a record. + * + * @author Michael J. Simons + * @soundtrack Iron Maiden - Iron Maiden + * @since 6.0.2 + */ +@API(status = STABLE, since = "6.0.2") +public final class AuditingBeforeBindCallback implements BeforeBindCallback, Ordered { + + public static final int NEO4J_AUDITING_ORDER = 100; + + private final ObjectFactory auditingHandlerFactory; + + /** + * Creates a new {@link AuditingBeforeBindCallback} using the given {@link AuditingHandler} provided by the given + * {@link ObjectFactory}. + * + * @param auditingHandlerFactory must not be {@literal null}. + */ + public AuditingBeforeBindCallback(ObjectFactory auditingHandlerFactory) { + + Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); + this.auditingHandlerFactory = auditingHandlerFactory; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.neo4j.repository.event.BeforeBindCallback#onBeforeBind(java.lang.Object) + */ + @Override + public Object onBeforeBind(Object entity) { + return auditingHandlerFactory.getObject().markAudited(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.Ordered#getOrder() + */ + @Override + public int getOrder() { + return NEO4J_AUDITING_ORDER; + } +} diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/BeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/BeforeBindCallback.java new file mode 100644 index 000000000..983810ac3 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/BeforeBindCallback.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Entity callback triggered before an Entity is bound to a record (represented by a {@link java.util.Map + * java.util.Map<String, Object>}). + * + * @author Michael J. Simons + * @param The type of the entity. + * @since 6.0.2 + * @soundtrack Bon Jovi - Slippery When Wet + */ +@FunctionalInterface +@API(status = STABLE, since = "6.0.2") +public interface BeforeBindCallback extends EntityCallback { + + /** + * Entity callback method invoked before a domain object is saved. Can return either the same or a modified instance + * of the domain object. This method is called before converting the {@code entity} to a {@link java.util.Map}, so the + * outcome of this callback is used to create the record for the domain object. + * + * @param entity the domain object to save. + * @return the domain object to be persisted. + */ + T onBeforeBind(T entity); +} diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/EventSupport.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/EventSupport.java new file mode 100644 index 000000000..0e3883433 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/EventSupport.java @@ -0,0 +1,84 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.data.mapping.callback.EntityCallbacks; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; + +/** + * Utility class that orchestrates {@link EntityCallbacks}. Not to be used outside the framework. + * + * @author Michael J. Simons + * @since 6.0.2 + */ +@API(status = INTERNAL, since = "6.0.2") +public final class EventSupport { + + /** + * Creates event support containing the required default events plus all entity callbacks discoverable through + * the {@link BeanFactory}. + * + * @param context The mapping context that is used in some of the callbacks. + * @param beanFactory The bean factory used to discover additional callbacks. + * @return A new instance of the event support + */ + public static EventSupport discoverCallbacks(Neo4jMappingContext context, BeanFactory beanFactory) { + + EntityCallbacks entityCallbacks = EntityCallbacks.create(beanFactory); + addDefaultEntityCallbacks(context, entityCallbacks); + return new EventSupport(entityCallbacks); + } + + /** + * Creates event support containing the required default events plus all explicitly defined events. + * + * @param context The mapping context that is used in some of the callbacks. + * @param entityCallbacks predefined callbacks. + * @return A new instance of the event support + */ + public static EventSupport useExistingCallbacks(Neo4jMappingContext context, EntityCallbacks entityCallbacks) { + + addDefaultEntityCallbacks(context, entityCallbacks); + return new EventSupport(entityCallbacks); + } + + private static void addDefaultEntityCallbacks(Neo4jMappingContext context, EntityCallbacks entityCallbacks) { + + entityCallbacks.addEntityCallback(new IdGeneratingBeforeBindCallback(context)); + entityCallbacks.addEntityCallback(new OptimisticLockingBeforeBindCallback(context)); + } + + private final EntityCallbacks entityCallbacks; + + private EventSupport(EntityCallbacks entityCallbacks) { + this.entityCallbacks = entityCallbacks; + } + + @SuppressWarnings("deprecation") + public T maybeCallBeforeBind(T object) { + + if (object == null) { + return object; + } + T o = entityCallbacks + .callback(org.springframework.data.neo4j.repository.event.BeforeBindCallback.class, object); + return entityCallbacks.callback(BeforeBindCallback.class, o); + } +} diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/IdGeneratingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/IdGeneratingBeforeBindCallback.java similarity index 78% rename from src/main/java/org/springframework/data/neo4j/repository/event/IdGeneratingBeforeBindCallback.java rename to src/main/java/org/springframework/data/neo4j/core/mapping/callback/IdGeneratingBeforeBindCallback.java index 28cfb18d5..1a19620d8 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/IdGeneratingBeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/IdGeneratingBeforeBindCallback.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; -import org.apiguardian.api.API; import org.springframework.core.Ordered; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; @@ -24,14 +23,13 @@ import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; * * @author Michael J. Simons * @soundtrack Various - Kung Fury (Original Motion Picture Soundtrack) - * @since 6.0 + * @since 6.0.2 */ -@API(status = API.Status.INTERNAL, since = "6.0") -public final class IdGeneratingBeforeBindCallback implements BeforeBindCallback, Ordered { +final class IdGeneratingBeforeBindCallback implements BeforeBindCallback, Ordered { private final IdPopulator idPopulator; - public IdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { + IdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { this.idPopulator = new IdPopulator(neo4jMappingContext); } diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/IdPopulator.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulator.java similarity index 98% rename from src/main/java/org/springframework/data/neo4j/repository/event/IdPopulator.java rename to src/main/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulator.java index bd19a21c4..4ade92343 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/IdPopulator.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import java.util.Optional; diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingBeforeBindCallback.java new file mode 100644 index 000000000..8ea2be159 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingBeforeBindCallback.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import org.springframework.core.Ordered; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; + +/** + * Callback to increment the value of the version property for a given entity. + * + * @author Gerrit Meier + * @since 6.0.2 + */ +final class OptimisticLockingBeforeBindCallback implements BeforeBindCallback, Ordered { + + private final OptimisticLockingSupport optimisticLocking; + + OptimisticLockingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { + this.optimisticLocking = new OptimisticLockingSupport(neo4jMappingContext); + } + + @Override + public Object onBeforeBind(Object entity) { + return optimisticLocking.getAndIncrementVersionPropertyIfNecessary(entity); + } + + @Override + public int getOrder() { + return AuditingBeforeBindCallback.NEO4J_AUDITING_ORDER + 11; + } +} diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/OptimisticLockingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingSupport.java similarity index 68% rename from src/main/java/org/springframework/data/neo4j/repository/event/OptimisticLockingBeforeBindCallback.java rename to src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingSupport.java index 1a4cddabd..8c2ed9a2d 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/OptimisticLockingBeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/OptimisticLockingSupport.java @@ -13,33 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; -import org.apiguardian.api.API; -import org.springframework.core.Ordered; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty; /** - * Callback to increment the value of the version property for a given entity. + * Common logic for retrieving entity metadata from the context and incrementing the version property if necessary. * - * @author Gerrit Meier - * @since 6.0 + * @author Michael J. Simons + * @soundtrack Body Count - Violent Demise: The Last Days */ -@API(status = API.Status.INTERNAL, since = "6.0") -public final class OptimisticLockingBeforeBindCallback implements BeforeBindCallback, Ordered { +final class OptimisticLockingSupport { - private final Neo4jMappingContext neo4jMappingContext; + private final Neo4jMappingContext mappingContext; - public OptimisticLockingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { - this.neo4jMappingContext = neo4jMappingContext; + OptimisticLockingSupport(Neo4jMappingContext mappingContext) { + this.mappingContext = mappingContext; } - @Override - public Object onBeforeBind(Object entity) { - Neo4jPersistentEntity neo4jPersistentEntity = (Neo4jPersistentEntity) neo4jMappingContext + Object getAndIncrementVersionPropertyIfNecessary(Object entity) { + + Neo4jPersistentEntity neo4jPersistentEntity = (Neo4jPersistentEntity) mappingContext .getRequiredNodeDescription(entity.getClass()); if (neo4jPersistentEntity.hasVersionProperty()) { @@ -61,9 +58,4 @@ public final class OptimisticLockingBeforeBindCallback implements BeforeBindCall } return entity; } - - @Override - public int getOrder() { - return AuditingBeforeBindCallback.NEO4J_AUDITING_ORDER + 11; - } } diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallback.java new file mode 100644 index 000000000..1978ac1ed --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallback.java @@ -0,0 +1,73 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.reactivestreams.Publisher; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.core.Ordered; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler; +import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.util.Assert; + +/** + * Reactive {@link EntityCallback} to populate auditing related fields on an entity about to be bound to a record. + * + * @author Michael J. Simons + * @soundtrack Iron Maiden - The Number Of The Beast + * @since 6.0.2 + */ +@API(status = STABLE, since = "6.0.2") +public final class ReactiveAuditingBeforeBindCallback implements ReactiveBeforeBindCallback, Ordered { + + public static final int NEO4J_REACTIVE_AUDITING_ORDER = 100; + + private final ObjectFactory auditingHandlerFactory; + + /** + * Creates a new {@link ReactiveAuditingBeforeBindCallback} using the {@link AuditingHandler} provided by the given + * {@link ObjectFactory}. + * + * @param auditingHandlerFactory must not be {@literal null}. + */ + public ReactiveAuditingBeforeBindCallback(ObjectFactory auditingHandlerFactory) { + + Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); + this.auditingHandlerFactory = auditingHandlerFactory; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback#onBeforeBind(java.lang.Object) + */ + @Override + public Publisher onBeforeBind(Object entity) { + + return auditingHandlerFactory.getObject().markAudited(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.Ordered#getOrder() + */ + @Override + public int getOrder() { + return NEO4J_REACTIVE_AUDITING_ORDER; + } +} diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveBeforeBindCallback.java new file mode 100644 index 000000000..57b029ec0 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveBeforeBindCallback.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; +import org.reactivestreams.Publisher; +import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; + +/** + * Entity callback triggered before an Entity is bound to a record (represented by a {@link java.util.Map + * java.util.Map<String, Object>}). + * + * @param The type of the entity. + * @author Michael J. Simons + * @soundtrack Iron Maiden - Killers + * @see ReactiveEntityCallbacks + * @since 6.0.2 + */ +@FunctionalInterface +@API(status = STABLE, since = "6.0.2") +public interface ReactiveBeforeBindCallback extends EntityCallback { + + /** + * Entity callback method invoked before a domain object is saved. Can return either the same or a modified instance + * of the domain object. This method is called before converting the {@code entity} to a {@link java.util.Map}, so the + * outcome of this callback is used to create the record for the domain object. + * + * @param entity the domain object to save. + * @return the domain object to be persisted. + */ + Publisher onBeforeBind(T entity); +} diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveEventSupport.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveEventSupport.java new file mode 100644 index 000000000..ed6334309 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveEventSupport.java @@ -0,0 +1,84 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import reactor.core.publisher.Mono; + +import org.apiguardian.api.API; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.data.mapping.callback.EntityCallbacks; +import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; + +/** + * Utility class that orchestrates {@link EntityCallbacks}. Not to be used outside the framework. + * + * @author Michael J. Simons + * @since 6.0.2 + */ +@API(status = INTERNAL, since = "6.0.2") +public final class ReactiveEventSupport { + + /** + * Creates event support containing the required default events plus all entity callbacks discoverable through + * the {@link BeanFactory}. + * + * @param context The mapping context that is used in some of the callbacks. + * @param beanFactory The bean factory used to discover additional callbacks. + * @return A new instance of the event support + */ + public static ReactiveEventSupport discoverCallbacks(Neo4jMappingContext context, BeanFactory beanFactory) { + + ReactiveEntityCallbacks entityCallbacks = ReactiveEntityCallbacks.create(beanFactory); + addDefaultEntityCallbacks(context, entityCallbacks); + return new ReactiveEventSupport(entityCallbacks); + } + + /** + * Creates event support containing the required default events plus all explicitly defined events. + * + * @param context The mapping context that is used in some of the callbacks. + * @param entityCallbacks predefined callbacks. + * @return A new instance of the event support + */ + public static ReactiveEventSupport useExistingCallbacks(Neo4jMappingContext context, ReactiveEntityCallbacks entityCallbacks) { + + addDefaultEntityCallbacks(context, entityCallbacks); + return new ReactiveEventSupport(entityCallbacks); + } + + private static void addDefaultEntityCallbacks(Neo4jMappingContext context, + ReactiveEntityCallbacks entityCallbacks) { + + entityCallbacks.addEntityCallback(new ReactiveIdGeneratingBeforeBindCallback(context)); + entityCallbacks.addEntityCallback(new ReactiveOptimisticLockingBeforeBindCallback(context)); + } + + private final ReactiveEntityCallbacks entityCallbacks; + + private ReactiveEventSupport(ReactiveEntityCallbacks entityCallbacks) { + this.entityCallbacks = entityCallbacks; + } + + @SuppressWarnings("deprecation") + public Mono maybeCallBeforeBind(T object) { + return entityCallbacks + .callback(org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback.class, object) + .flatMap(o -> entityCallbacks.callback(ReactiveBeforeBindCallback.class, o)); + } +} diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveIdGeneratingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveIdGeneratingBeforeBindCallback.java similarity index 79% rename from src/main/java/org/springframework/data/neo4j/repository/event/ReactiveIdGeneratingBeforeBindCallback.java rename to src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveIdGeneratingBeforeBindCallback.java index 49985eff8..1720811ba 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveIdGeneratingBeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveIdGeneratingBeforeBindCallback.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import reactor.core.publisher.Mono; -import org.apiguardian.api.API; import org.reactivestreams.Publisher; import org.springframework.core.Ordered; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; @@ -27,14 +26,13 @@ import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; * * @author Michael J. Simons * @soundtrack Various - Kung Fury (Original Motion Picture Soundtrack) - * @since 6.0 + * @since 6.0.2 */ -@API(status = API.Status.INTERNAL, since = "6.0") -public final class ReactiveIdGeneratingBeforeBindCallback implements ReactiveBeforeBindCallback, Ordered { +final class ReactiveIdGeneratingBeforeBindCallback implements ReactiveBeforeBindCallback, Ordered { private final IdPopulator idPopulator; - public ReactiveIdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { + ReactiveIdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { this.idPopulator = new IdPopulator(neo4jMappingContext); } diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveOptimisticLockingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveOptimisticLockingBeforeBindCallback.java new file mode 100644 index 000000000..7baa9e34b --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveOptimisticLockingBeforeBindCallback.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011-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.data.neo4j.core.mapping.callback; + +import reactor.core.publisher.Mono; + +import org.reactivestreams.Publisher; +import org.springframework.core.Ordered; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; + +/** + * Callback to increment the value of the version property for a given entity. + * + * @author Gerrit Meier + * @since 6.0.2 + */ +final class ReactiveOptimisticLockingBeforeBindCallback implements ReactiveBeforeBindCallback, Ordered { + + private final OptimisticLockingSupport optimisticLocking; + + ReactiveOptimisticLockingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { + this.optimisticLocking = new OptimisticLockingSupport(neo4jMappingContext); + } + + @Override + public Publisher onBeforeBind(Object entity) { + + return Mono.just(entity).map(optimisticLocking::getAndIncrementVersionPropertyIfNecessary); + } + + @Override + public int getOrder() { + return ReactiveAuditingBeforeBindCallback.NEO4J_REACTIVE_AUDITING_ORDER + 11; + } +} diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/callback/package-info.java b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/package-info.java new file mode 100644 index 000000000..ee233fe29 --- /dev/null +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/callback/package-info.java @@ -0,0 +1,21 @@ +/** + * + This package contains the callback API. There are both imperative and reactive callbacks available that get invoked + just before an entity is bound to a statement. These can be implemented by client code. For further convinience, + both imperative and reactive auditing callbacks are available. The config package contains a registrar and an + annotation to enable those without having to provide the beans manually. + + The event system comes in two flavours: Events that are based on Spring's application event system and callbacks that + are based on Spring Data's callback system. Application events can be configured to run asynchronously, which make + them a bad fit in transactional workloads. + + As a rule of thumb, use Entity callbacks for modifying entities before persisting and application events otherwise. + The best option however to react in a transactional way to changes of an entity is to implement + {@link org.springframework.data.domain.DomainEvents} on an aggregate root. + * + * @author Michael J. Simons + */ +@NonNullApi +package org.springframework.data.neo4j.core.mapping.callback; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/package-info.java b/src/main/java/org/springframework/data/neo4j/core/mapping/package-info.java index a477bbdf6..ac8083eda 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/package-info.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/package-info.java @@ -1,7 +1,9 @@ /** - * The main mapping framework. This package orchestrates the reading and writing of entities and all tasks related to it. - * The package has to be considered as internal api as a whole and we don't give any guarantees of API stability. - * + * + The main mapping framework. This package orchestrates the reading and writing of entities and all tasks related to it. + The only public API of this package is the subpackage {@literal callback}, containing the event support. + The core package itself has to be considered an internal api and we don't give any guarantees of API stability. + * * @author Michael J. Simons */ @NonNullApi diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallback.java index e8ab37ffe..23d3fa5c0 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallback.java @@ -18,10 +18,8 @@ package org.springframework.data.neo4j.repository.event; import org.apiguardian.api.API; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.Ordered; -import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.mapping.callback.EntityCallback; -import org.springframework.util.Assert; /** * {@link EntityCallback} to populate auditing related fields on an entity about to be bound to a record. @@ -29,41 +27,27 @@ import org.springframework.util.Assert; * @author Michael J. Simons * @soundtrack Iron Maiden - Iron Maiden * @since 6.0 + * @deprecated since 6.0.2, please use {@link org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback}. */ -@API(status = API.Status.INTERNAL, since = "6.0") +@API(status = API.Status.DEPRECATED, since = "6.0") +@Deprecated public final class AuditingBeforeBindCallback implements BeforeBindCallback, Ordered { - public static final int NEO4J_AUDITING_ORDER = 100; + private final org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback delegate; - private final ObjectFactory auditingHandlerFactory; - - /** - * Creates a new {@link AuditingBeforeBindCallback} using the given {@link AuditingHandler} provided by the given - * {@link ObjectFactory}. - * - * @param auditingHandlerFactory must not be {@literal null}. - */ public AuditingBeforeBindCallback(ObjectFactory auditingHandlerFactory) { - Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); - this.auditingHandlerFactory = auditingHandlerFactory; + this.delegate = new org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback( + auditingHandlerFactory); } - /* - * (non-Javadoc) - * @see org.springframework.data.neo4j.repository.event.BeforeBindCallback#onBeforeBind(java.lang.Object) - */ @Override public Object onBeforeBind(Object entity) { - return auditingHandlerFactory.getObject().markAudited(entity); + return delegate.onBeforeBind(entity); } - /* - * (non-Javadoc) - * @see org.springframework.core.Ordered#getOrder() - */ @Override public int getOrder() { - return NEO4J_AUDITING_ORDER; + return delegate.getOrder(); } } diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/BeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/repository/event/BeforeBindCallback.java index ca44193cc..c673a2597 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/BeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/repository/event/BeforeBindCallback.java @@ -26,9 +26,11 @@ import org.springframework.data.mapping.callback.EntityCallback; * @param The type of the entity. * @since 6.0 * @soundtrack Bon Jovi - Slippery When Wet + * @deprecated since 6.0.2, please use {@link org.springframework.data.neo4j.core.mapping.callback.BeforeBindCallback}. */ @FunctionalInterface -@API(status = API.Status.STABLE, since = "6.0") +@API(status = API.Status.DEPRECATED, since = "6.0") +@Deprecated public interface BeforeBindCallback extends EntityCallback { /** diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallback.java index 2ed3f9c01..015b32b43 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallback.java @@ -19,53 +19,35 @@ import org.apiguardian.api.API; import org.reactivestreams.Publisher; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.Ordered; -import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler; import org.springframework.data.mapping.callback.EntityCallback; -import org.springframework.util.Assert; /** * Reactive {@link EntityCallback} to populate auditing related fields on an entity about to be bound to a record. * * @author Michael J. Simons * @soundtrack Iron Maiden - The Number Of The Beast - * @since 6.0 + * @deprecated since 6.0.2, please use {@link org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback}. */ -@API(status = API.Status.INTERNAL, since = "6.0") +@API(status = API.Status.DEPRECATED, since = "6.0") +@Deprecated public final class ReactiveAuditingBeforeBindCallback implements ReactiveBeforeBindCallback, Ordered { - public static final int NEO4J_REACTIVE_AUDITING_ORDER = 100; + private final org.springframework.data.neo4j.core.mapping.callback.ReactiveAuditingBeforeBindCallback delegate; - private final ObjectFactory auditingHandlerFactory; - - /** - * Creates a new {@link ReactiveAuditingBeforeBindCallback} using the {@link AuditingHandler} provided by the given - * {@link ObjectFactory}. - * - * @param auditingHandlerFactory must not be {@literal null}. - */ public ReactiveAuditingBeforeBindCallback(ObjectFactory auditingHandlerFactory) { - Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); - this.auditingHandlerFactory = auditingHandlerFactory; + this.delegate = new org.springframework.data.neo4j.core.mapping.callback.ReactiveAuditingBeforeBindCallback( + auditingHandlerFactory); } - /* - * (non-Javadoc) - * @see org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback#onBeforeBind(java.lang.Object) - */ @Override public Publisher onBeforeBind(Object entity) { - - return auditingHandlerFactory.getObject().markAudited(entity); + return delegate.onBeforeBind(entity); } - /* - * (non-Javadoc) - * @see org.springframework.core.Ordered#getOrder() - */ @Override public int getOrder() { - return NEO4J_REACTIVE_AUDITING_ORDER; + return delegate.getOrder(); } } diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveBeforeBindCallback.java index 3a79132c1..ff0f4bb7a 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveBeforeBindCallback.java +++ b/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveBeforeBindCallback.java @@ -29,9 +29,11 @@ import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; * @soundtrack Iron Maiden - Killers * @see ReactiveEntityCallbacks * @since 6.0 + * @deprecated since 6.0.2, please use {@link org.springframework.data.neo4j.core.mapping.callback.ReactiveAuditingBeforeBindCallback}. */ @FunctionalInterface -@API(status = API.Status.STABLE, since = "6.0") +@API(status = API.Status.DEPRECATED, since = "6.0") +@Deprecated public interface ReactiveBeforeBindCallback extends EntityCallback { /** diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveOptimisticLockingBeforeBindCallback.java b/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveOptimisticLockingBeforeBindCallback.java deleted file mode 100644 index 96bccf1cd..000000000 --- a/src/main/java/org/springframework/data/neo4j/repository/event/ReactiveOptimisticLockingBeforeBindCallback.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2011-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.data.neo4j.repository.event; - -import reactor.core.publisher.Mono; - -import org.apiguardian.api.API; -import org.reactivestreams.Publisher; -import org.springframework.core.Ordered; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; -import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity; -import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty; - -/** - * Callback to increment the value of the version property for a given entity. - * - * @author Gerrit Meier - * @since 6.0 - */ -@API(status = API.Status.INTERNAL, since = "6.0") -public final class ReactiveOptimisticLockingBeforeBindCallback implements ReactiveBeforeBindCallback, Ordered { - - private final Neo4jMappingContext neo4jMappingContext; - - public ReactiveOptimisticLockingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) { - this.neo4jMappingContext = neo4jMappingContext; - } - - @Override - public Publisher onBeforeBind(Object entity) { - - return Mono.fromSupplier(() -> { - Neo4jPersistentEntity neo4jPersistentEntity = (Neo4jPersistentEntity) neo4jMappingContext - .getRequiredNodeDescription(entity.getClass()); - - if (neo4jPersistentEntity.hasVersionProperty()) { - PersistentPropertyAccessor propertyAccessor = neo4jPersistentEntity.getPropertyAccessor(entity); - Neo4jPersistentProperty versionProperty = neo4jPersistentEntity.getRequiredVersionProperty(); - - if (!Long.class.isAssignableFrom(versionProperty.getType())) { - return entity; - } - - Long versionPropertyValue = (Long) propertyAccessor.getProperty(versionProperty); - - long newVersionValue = 0; - if (versionPropertyValue != null) { - newVersionValue = versionPropertyValue + 1; - } - - propertyAccessor.setProperty(versionProperty, newVersionValue); - } - return entity; - }); - } - - @Override - public int getOrder() { - return ReactiveAuditingBeforeBindCallback.NEO4J_REACTIVE_AUDITING_ORDER + 11; - } -} diff --git a/src/main/java/org/springframework/data/neo4j/repository/event/package-info.java b/src/main/java/org/springframework/data/neo4j/repository/event/package-info.java deleted file mode 100644 index 9c202d5e3..000000000 --- a/src/main/java/org/springframework/data/neo4j/repository/event/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Contains the infrastructure for the event system. The event system comes in two flavours: Events that are based on - * Spring's application event system and callbacks that are based on Spring Data's callback system. Application events - * can be configured to run asynchronously, which make them a bad fit in transactional workloads. - *

- * As a rule of thumb, use Entity callbacks for modifying entities before persisting and application events otherwise. - * The best option however to react in a transactional way to changes of an entity is to implement - * {@link org.springframework.data.domain.DomainEvents} on an aggregate root. - * - * @author Michael J. Simons - * @since 6.0 - * @soundtrack Bon Jovi - Slippery When Wet - */ -@NonNullApi -package org.springframework.data.neo4j.repository.event; - -import org.springframework.lang.NonNullApi; diff --git a/src/test/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallbackTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallbackTest.java similarity index 98% rename from src/test/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallbackTest.java rename to src/test/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallbackTest.java index c54c3f531..2dddab439 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/event/AuditingBeforeBindCallbackTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/AuditingBeforeBindCallbackTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; diff --git a/src/test/java/org/springframework/data/neo4j/repository/event/IdPopulatorTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java similarity index 98% rename from src/test/java/org/springframework/data/neo4j/repository/event/IdPopulatorTest.java rename to src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java index 817e9c2a8..7db6659e9 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/event/IdPopulatorTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; diff --git a/src/test/java/org/springframework/data/neo4j/repository/event/ImmutableSample.java b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/ImmutableSample.java similarity index 94% rename from src/test/java/org/springframework/data/neo4j/repository/event/ImmutableSample.java rename to src/test/java/org/springframework/data/neo4j/core/mapping/callback/ImmutableSample.java index a60b0ccef..0aec7359b 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/event/ImmutableSample.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/ImmutableSample.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; diff --git a/src/test/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallbackTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallbackTest.java similarity index 98% rename from src/test/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallbackTest.java rename to src/test/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallbackTest.java index d53952823..51c5157df 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/event/ReactiveAuditingBeforeBindCallbackTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/ReactiveAuditingBeforeBindCallbackTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; diff --git a/src/test/java/org/springframework/data/neo4j/repository/event/Sample.java b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/Sample.java similarity index 94% rename from src/test/java/org/springframework/data/neo4j/repository/event/Sample.java rename to src/test/java/org/springframework/data/neo4j/core/mapping/callback/Sample.java index fe13104e8..f48d8da70 100644 --- a/src/test/java/org/springframework/data/neo4j/repository/event/Sample.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/Sample.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.repository.event; +package org.springframework.data.neo4j.core.mapping.callback; import java.util.Date; diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java index ddd77629b..1384716d3 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/CallbacksIT.java @@ -26,11 +26,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.config.AbstractNeo4jConfig; +import org.springframework.data.neo4j.core.mapping.callback.BeforeBindCallback; import org.springframework.data.neo4j.integration.imperative.repositories.ThingRepository; import org.springframework.data.neo4j.integration.shared.common.CallbacksITBase; import org.springframework.data.neo4j.integration.shared.common.ThingWithAssignedId; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; -import org.springframework.data.neo4j.repository.event.BeforeBindCallback; import org.springframework.transaction.annotation.EnableTransactionManagement; /** diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/MultipleContextsIT.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/MultipleContextsIT.java new file mode 100644 index 000000000..ac285faca --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/MultipleContextsIT.java @@ -0,0 +1,120 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.neo4j.integration.multiple_ctx_imperative.domain1.Domain1Config; +import org.springframework.data.neo4j.integration.multiple_ctx_imperative.domain1.Domain1Entity; +import org.springframework.data.neo4j.integration.multiple_ctx_imperative.domain1.Domain1Repository; +import org.springframework.data.neo4j.integration.multiple_ctx_imperative.domain2.Domain2Config; +import org.springframework.data.neo4j.integration.multiple_ctx_imperative.domain2.Domain2Entity; +import org.springframework.data.neo4j.integration.multiple_ctx_imperative.domain2.Domain2Repository; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Tests whether multiple context are truly separated. + * + * @author Michael J. Simons + */ +@SpringJUnitConfig(classes = { SharedConfig.class, Domain1Config.class, Domain2Config.class }) +@Testcontainers(disabledWithoutDocker = true) +public class MultipleContextsIT { + + @Container + private static Neo4jContainer container1 = new Neo4jContainer<>("neo4j:4.0") + .withAdminPassword("secret1"); + + @Container + private static Neo4jContainer container2 = new Neo4jContainer<>("neo4j:4.0") + .withAdminPassword("secret2"); + + @DynamicPropertySource + static void neo4jSettings(DynamicPropertyRegistry registry) { + + registry.add("database1.url", container1::getBoltUrl); + registry.add("database1.password", () -> "secret1"); + + registry.add("database2.url", container2::getBoltUrl); + registry.add("database2.password", () -> "secret2"); + } + + @Test // DATAGRAPH-1441 + void repositoriesShouldTargetTheCorrectDatabase( + @Autowired Domain1Repository repo1, + @Autowired Domain2Repository repo2 + ) { + + Domain1Entity newEntity1 = repo1.save(new Domain1Entity("For domain 1")); + newEntity1.setAnAttribute(newEntity1.getAnAttribute() + " updated"); + newEntity1 = repo1.save(newEntity1); + long id1 = newEntity1.getId(); + + Domain2Entity newEntity2 = new Domain2Entity("For domain 2"); + newEntity2.setAnAttribute(newEntity2.getAnAttribute() + " updated"); + newEntity2 = repo2.save(newEntity2); + long id2 = repo2.save(newEntity2).getId(); + + try (Driver driver = newDriver(container1.getBoltUrl(), container1.getAdminPassword()); + Session session = driver.session()) { + verifyExistenceAndVersion(id1, session); + } + + try (Driver driver = newDriver(container2.getBoltUrl(), container2.getAdminPassword()); + Session session = driver.session()) { + verifyExistenceAndVersion(id2, session); + } + } + + /** + * Create drivers independend from the setup under test. + * + * @param boltUrl Where to connect to + * @param password Which password + * @return Minimal driver instance. + */ + private static Driver newDriver(String boltUrl, String password) { + + Config driverConfig = Config.builder() + .withMaxConnectionPoolSize(1) + .withLogging(Logging.none()) + .withEventLoopThreads(1) + .build(); + return GraphDatabase.driver(boltUrl, AuthTokens.basic("neo4j", password), driverConfig); + } + + private static void verifyExistenceAndVersion(long id1, Session session) { + Long version = session + .readTransaction(tx -> tx.run("MATCH (n) WHERE id(n) = $id RETURN n.version", Collections + .singletonMap("id", id1)).single().get(0).asLong()); + assertThat(version).isOne(); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/SharedConfig.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/SharedConfig.java new file mode 100644 index 000000000..e2a21c032 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/SharedConfig.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.core.convert.Neo4jConversions; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * @author Michael J. Simons + * @soundtrack Kelis - Tasty + */ +@EnableTransactionManagement +@Configuration(proxyBeanMethods = false) +public class SharedConfig { + + @Bean + public Neo4jConversions neo4jConversions() { + return new Neo4jConversions(); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java new file mode 100644 index 000000000..32ff80081 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java @@ -0,0 +1,92 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative.domain1; + +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.Environment; +import org.springframework.data.neo4j.config.Neo4jEntityScanner; +import org.springframework.data.neo4j.core.DatabaseSelection; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.data.neo4j.core.convert.Neo4jConversions; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Michael J. Simons + * @soundtrack Kelis - Tasty + */ +@Configuration(proxyBeanMethods = false) +@EnableNeo4jRepositories( + basePackageClasses = Domain1Config.class, + neo4jMappingContextRef = "domain1Context", + neo4jTemplateRef = "domain1Template", + transactionManagerRef = "domain1Manager" +) +public class Domain1Config { + + @Primary @Bean + public Driver domain1Driver(Environment env) { + + return GraphDatabase.driver(env.getRequiredProperty("database1.url"), + AuthTokens.basic("neo4j", env.getRequiredProperty("database1.password"))); + } + + @Primary @Bean + public Neo4jClient domain1Client(@Qualifier("domain1Driver") Driver driver) { + return Neo4jClient.create(driver); + } + + @Primary @Bean + public Neo4jOperations domain1Template( + @Qualifier("domain1Client") Neo4jClient domain1Client, + @Qualifier("domain1Context") Neo4jMappingContext domain1Context, + @Qualifier("domain1Selection") DatabaseSelectionProvider domain1Selection + ) { + return new Neo4jTemplate(domain1Client, domain1Context, domain1Selection); + } + + @Primary @Bean + public PlatformTransactionManager domain1Manager( + @Qualifier("domain1Driver") Driver driver, + @Qualifier("domain1Selection") DatabaseSelectionProvider domain1Selection + ) { + return new Neo4jTransactionManager(driver, domain1Selection); + } + + @Primary @Bean + public DatabaseSelectionProvider domain1Selection() { + return () -> DatabaseSelection.undecided(); + } + + @Primary @Bean + public Neo4jMappingContext domain1Context(Neo4jConversions neo4jConversions) throws ClassNotFoundException { + Neo4jMappingContext context = new Neo4jMappingContext(neo4jConversions); + context.setInitialEntitySet(Neo4jEntityScanner.get().scan(this.getClass().getPackage().getName())); + context.setStrict(true); + return context; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Entity.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Entity.java new file mode 100644 index 000000000..97cbee141 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Entity.java @@ -0,0 +1,61 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative.domain1; + +import org.springframework.data.annotation.Version; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * @author Michael J. Simons + * @soundtrack Kelis - Tasty + */ +@Node +public class Domain1Entity { + + @Id @GeneratedValue + private Long id; + + @Version + private Long version; + + private String anAttribute; + + public Domain1Entity(String anAttribute) { + this.anAttribute = anAttribute; + } + + public Long getId() { + return id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public String getAnAttribute() { + return anAttribute; + } + + public void setAnAttribute(String anAttribute) { + this.anAttribute = anAttribute; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Repository.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Repository.java new file mode 100644 index 000000000..878b90d70 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Repository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative.domain1; + +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + * @author Michael J. Simons + * @soundtrack Various - T2 Trainspotting + */ +public interface Domain1Repository extends Neo4jRepository { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java new file mode 100644 index 000000000..ebcc46751 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java @@ -0,0 +1,91 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative.domain2; + +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.data.neo4j.config.Neo4jEntityScanner; +import org.springframework.data.neo4j.core.DatabaseSelection; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.data.neo4j.core.convert.Neo4jConversions; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Michael J. Simons + * @soundtrack Kelis - Tasty + */ +@Configuration(proxyBeanMethods = false) +@EnableNeo4jRepositories( + basePackageClasses = Domain2Config.class, + neo4jMappingContextRef = "domain2Context", + neo4jTemplateRef = "domain2Template", + transactionManagerRef = "domain2Manager" +) +public class Domain2Config { + + @Bean + public Driver domain2Driver(Environment env) { + + return GraphDatabase.driver(env.getRequiredProperty("database2.url"), + AuthTokens.basic("neo4j", env.getRequiredProperty("database2.password"))); + } + + @Bean + public Neo4jClient domain2Client(@Qualifier("domain2Driver") Driver driver) { + return Neo4jClient.create(driver); + } + + @Bean + public Neo4jOperations domain2Template( + @Qualifier("domain2Client") Neo4jClient domain2Client, + @Qualifier("domain2Context") Neo4jMappingContext domain2Context, + @Qualifier("domain2Selection") DatabaseSelectionProvider domain2Selection + ) { + return new Neo4jTemplate(domain2Client, domain2Context, domain2Selection); + } + + @Bean + public PlatformTransactionManager domain2Manager( + @Qualifier("domain2Driver") Driver driver, + @Qualifier("domain2Selection") DatabaseSelectionProvider domain2Selection + ) { + return new Neo4jTransactionManager(driver, domain2Selection); + } + + @Bean + public DatabaseSelectionProvider domain2Selection() { + return () -> DatabaseSelection.undecided(); + } + + @Bean + public Neo4jMappingContext domain2Context(Neo4jConversions neo4jConversions) throws ClassNotFoundException { + Neo4jMappingContext context = new Neo4jMappingContext(neo4jConversions); + context.setInitialEntitySet(Neo4jEntityScanner.get().scan(this.getClass().getPackage().getName())); + context.setStrict(true); + return context; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Entity.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Entity.java new file mode 100644 index 000000000..f5debb95d --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Entity.java @@ -0,0 +1,57 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative.domain2; + +import org.springframework.data.annotation.Version; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * @author Michael J. Simons + * @soundtrack Kelis - Tasty + */ +@Node +public class Domain2Entity { + + @Id @GeneratedValue + private Long id; + + @Version + private Long version; + + private String anAttribute; + + public Domain2Entity(String anAttribute) { + this.anAttribute = anAttribute; + } + + public Long getId() { + return id; + } + + public Long getVersion() { + return version; + } + + public String getAnAttribute() { + return anAttribute; + } + + public void setAnAttribute(String anAttribute) { + this.anAttribute = anAttribute; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Repository.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Repository.java new file mode 100644 index 000000000..ddfebb67a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Repository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2011-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.data.neo4j.integration.multiple_ctx_imperative.domain2; + +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + * @author Michael J. Simons + * @soundtrack Various - T2 Trainspotting + */ +public interface Domain2Repository extends Neo4jRepository { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveCallbacksIT.java b/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveCallbacksIT.java index c2593ff6b..81217d367 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveCallbacksIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveCallbacksIT.java @@ -30,11 +30,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig; +import org.springframework.data.neo4j.core.mapping.callback.ReactiveBeforeBindCallback; import org.springframework.data.neo4j.integration.reactive.repositories.ReactiveThingRepository; import org.springframework.data.neo4j.integration.shared.common.CallbacksITBase; import org.springframework.data.neo4j.integration.shared.common.ThingWithAssignedId; import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories; -import org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback; import org.springframework.data.neo4j.test.Neo4jExtension; import org.springframework.transaction.ReactiveTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement;