DATAGRAPH-1441 - Register necessary callbacks internally inside the mapping context.

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.
This commit is contained in:
Michael Simons
2020-11-24 16:53:17 +01:00
parent de9671a547
commit 0daecff5d5
47 changed files with 1249 additions and 339 deletions

View File

@@ -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 {
/**

View File

@@ -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 {
/**

View File

@@ -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;
/**

View File

@@ -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> neo4jClient,
final @Any Instance<Neo4jMappingContext> mappingContext,
final @Any Instance<DatabaseSelectionProvider> databaseNameProvider,
final Instance<BeforeBindCallback> services
@Any Instance<Neo4jClient> neo4jClient,
@Any Instance<Neo4jMappingContext> mappingContext,
@Any Instance<DatabaseSelectionProvider> 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> neo4JConversions) {
public Neo4jMappingContext neo4jMappingContext(Driver driver, @Any Instance<Neo4jConversions> neo4JConversions) {
Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext(resolve(neo4JConversions), driver.defaultTypeSystem());
return neo4jMappingContext;
}
@Produces @Singleton
public BeforeBindCallback<Object> idGeneratingBeforeBindCallback(
final @Any Instance<Neo4jMappingContext> mappingContext) {
return new IdGeneratingBeforeBindCallback(resolve(mappingContext));
}
@Produces @Singleton
public BeforeBindCallback<Object> optimisticLockingBeforeBindCallback(
final @Any Instance<Neo4jMappingContext> mappingContext) {
return new OptimisticLockingBeforeBindCallback(resolve(mappingContext));
return new Neo4jMappingContext(resolve(neo4JConversions), driver.defaultTypeSystem());
}
@Produces @Singleton

View File

@@ -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<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
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<Class<?>> scanForEntities(String basePackage) throws ClassNotFoundException {
if (!StringUtils.hasText(basePackage)) {
return Collections.emptySet();
}
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<Class<?>> 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<Class<?>> scan(Collection<String> 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<Class<?>> 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;
}
}

View File

@@ -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;
/**

View File

@@ -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> T maybeCallBeforeBind(T object) {
return entityCallbacks.callback(BeforeBindCallback.class, object);
}
}
}

View File

@@ -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<T> implements ExecutableQuery<T> {
@@ -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;
}
<T> Mono<T> maybeCallBeforeBind(T object) {
return entityCallbacks.callback(ReactiveBeforeBindCallback.class, object);
}
}
}

View File

@@ -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<Neo4jPersi
}
/**
* This API is primarly used from inside the CDI extension to configure the type system. This is necessary as
* This API is primarily used from inside the CDI extension to configure the type system. This is necessary as
* we don't get notified of the context via {@link #setApplicationContext(ApplicationContext applicationContext)}.
*
* @param neo4jConversions The conversions to be used
* @param typeSystem The current drivers typeystem
* @param typeSystem The current drivers typeystem. If this is null, we use the default one without accessing the driver.
*/
@API(status = API.Status.INTERNAL, since = "6.0")
public Neo4jMappingContext(Neo4jConversions neo4jConversions, TypeSystem typeSystem) {
public Neo4jMappingContext(Neo4jConversions neo4jConversions, @Nullable TypeSystem typeSystem) {
super.setSimpleTypeHolder(Neo4jSimpleTypes.HOLDER);
this.conversionService = new DefaultNeo4jConversionService(neo4jConversions);
DefaultNeo4jEntityConverter defaultNeo4jConverter = new DefaultNeo4jEntityConverter(INSTANTIATORS, conversionService, nodeDescriptionStore);
if (typeSystem != null) {
defaultNeo4jConverter.setTypeSystem(typeSystem);
}
DefaultNeo4jEntityConverter defaultNeo4jConverter = new DefaultNeo4jEntityConverter(INSTANTIATORS,
conversionService, nodeDescriptionStore);
defaultNeo4jConverter.setTypeSystem(typeSystem == null ? InternalTypeSystem.TYPE_SYSTEM : typeSystem);
this.entityConverter = defaultNeo4jConverter;
}
@@ -287,8 +286,6 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
super.setApplicationContext(applicationContext);
this.beanFactory = applicationContext.getAutowireCapableBeanFactory();
Driver driver = this.beanFactory.getBean(Driver.class);
((DefaultNeo4jEntityConverter) this.entityConverter).setTypeSystem(driver.defaultTypeSystem());
}
public CreateRelationshipStatementHolder createStatement(Neo4jPersistentEntity<?> neo4jPersistentEntity, NestedRelationshipContext relationshipContext,

View File

@@ -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<Object>, Ordered {
public static final int NEO4J_AUDITING_ORDER = 100;
private final ObjectFactory<IsNewAwareAuditingHandler> 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<IsNewAwareAuditingHandler> 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;
}
}

View File

@@ -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&lt;String, Object&gt;}).
*
* @author Michael J. Simons
* @param <T> 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<T> extends EntityCallback<T> {
/**
* 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);
}

View File

@@ -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> 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);
}
}

View File

@@ -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<Object>, Ordered {
final class IdGeneratingBeforeBindCallback implements BeforeBindCallback<Object>, Ordered {
private final IdPopulator idPopulator;
public IdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) {
IdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) {
this.idPopulator = new IdPopulator(neo4jMappingContext);
}

View File

@@ -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;

View File

@@ -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<Object>, 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;
}
}

View File

@@ -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<Object>, 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;
}
}

View File

@@ -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<Object>, Ordered {
public static final int NEO4J_REACTIVE_AUDITING_ORDER = 100;
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> 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<ReactiveIsNewAwareAuditingHandler> 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<Object> 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;
}
}

View File

@@ -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&lt;String, Object&gt;}).
*
* @param <T> 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<T> extends EntityCallback<T> {
/**
* 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<T> onBeforeBind(T entity);
}

View File

@@ -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 <T> Mono<T> maybeCallBeforeBind(T object) {
return entityCallbacks
.callback(org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback.class, object)
.flatMap(o -> entityCallbacks.callback(ReactiveBeforeBindCallback.class, o));
}
}

View File

@@ -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<Object>, Ordered {
final class ReactiveIdGeneratingBeforeBindCallback implements ReactiveBeforeBindCallback<Object>, Ordered {
private final IdPopulator idPopulator;
public ReactiveIdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) {
ReactiveIdGeneratingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) {
this.idPopulator = new IdPopulator(neo4jMappingContext);
}

View File

@@ -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<Object>, Ordered {
private final OptimisticLockingSupport optimisticLocking;
ReactiveOptimisticLockingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) {
this.optimisticLocking = new OptimisticLockingSupport(neo4jMappingContext);
}
@Override
public Publisher<Object> onBeforeBind(Object entity) {
return Mono.just(entity).map(optimisticLocking::getAndIncrementVersionPropertyIfNecessary);
}
@Override
public int getOrder() {
return ReactiveAuditingBeforeBindCallback.NEO4J_REACTIVE_AUDITING_ORDER + 11;
}
}

View File

@@ -0,0 +1,21 @@
/**
* <!-- tag::intent[] -->
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.
* <!-- end::intent[] -->
* @author Michael J. Simons
*/
@NonNullApi
package org.springframework.data.neo4j.core.mapping.callback;
import org.springframework.lang.NonNullApi;

View File

@@ -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.
*
* <!-- tag::intent[] -->
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.
* <!-- end::intent[] -->
* @author Michael J. Simons
*/
@NonNullApi

View File

@@ -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<Object>, Ordered {
public static final int NEO4J_AUDITING_ORDER = 100;
private final org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback delegate;
private final ObjectFactory<IsNewAwareAuditingHandler> 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<IsNewAwareAuditingHandler> 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();
}
}

View File

@@ -26,9 +26,11 @@ import org.springframework.data.mapping.callback.EntityCallback;
* @param <T> 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<T> extends EntityCallback<T> {
/**

View File

@@ -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<Object>, Ordered {
public static final int NEO4J_REACTIVE_AUDITING_ORDER = 100;
private final org.springframework.data.neo4j.core.mapping.callback.ReactiveAuditingBeforeBindCallback delegate;
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> 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<ReactiveIsNewAwareAuditingHandler> 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<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_REACTIVE_AUDITING_ORDER;
return delegate.getOrder();
}
}

View File

@@ -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<T> extends EntityCallback<T> {
/**

View File

@@ -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<Object>, Ordered {
private final Neo4jMappingContext neo4jMappingContext;
public ReactiveOptimisticLockingBeforeBindCallback(Neo4jMappingContext neo4jMappingContext) {
this.neo4jMappingContext = neo4jMappingContext;
}
@Override
public Publisher<Object> onBeforeBind(Object entity) {
return Mono.fromSupplier(() -> {
Neo4jPersistentEntity<?> neo4jPersistentEntity = (Neo4jPersistentEntity<?>) neo4jMappingContext
.getRequiredNodeDescription(entity.getClass());
if (neo4jPersistentEntity.hasVersionProperty()) {
PersistentPropertyAccessor<Object> 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;
}
}

View File

@@ -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.
* <p>
* 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
/**

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<Domain1Entity, Long> {
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<Domain2Entity, Long> {
}

View File

@@ -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;