refactor: Migrate to JSpecify annotations for nullability constraints. (#3016)
Replaces all deprecated Spring annotations with JSpecify annotations, and includes a full refactor so that the parent build profile `-Pnullaway` compiles and works proper. Closes #2987 Signed-off-by: Michael Simons <michael@simons.ac>
This commit is contained in:
104
pom.xml
104
pom.xml
@@ -84,7 +84,11 @@
|
||||
<java-module-name>spring.data.neo4j</java-module-name>
|
||||
<java.version>17</java.version>
|
||||
<jaxb.version>2.3.1</jaxb.version>
|
||||
<jna.version>5.8.0</jna.version>
|
||||
<jspecify.version>1.0.0</jspecify.version>
|
||||
<jsr305.version>3.0.2</jsr305.version>
|
||||
<junit-cc-testcontainer>2021.0.1</junit-cc-testcontainer>
|
||||
<junit-pioneer.version>2.2.0</junit-pioneer.version>
|
||||
<maven-install-plugin.version>3.1.4</maven-install-plugin.version>
|
||||
<maven-site-plugin.version>3.7.1</maven-site-plugin.version>
|
||||
<maven.compiler.release>${java.version}</maven.compiler.release>
|
||||
@@ -92,6 +96,7 @@
|
||||
<neo4j-migrations.version>2.17.3</neo4j-migrations.version>
|
||||
<neo4j.version>4.4.41</neo4j.version>
|
||||
<objenesis.version>3.0.1</objenesis.version>
|
||||
<org.osgi.core.version>6.0.0</org.osgi.core.version>
|
||||
<project.build.docs>${project.build.directory}/docs</project.build.docs>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<r2dbc.releasetrain>Arabba-SR11</r2dbc.releasetrain>
|
||||
@@ -102,22 +107,14 @@
|
||||
<skipUnitTests>${skipTests}</skipUnitTests>
|
||||
|
||||
<springdata.commons>4.0.0-SNAPSHOT</springdata.commons>
|
||||
<junit-pioneer.version>2.2.0</junit-pioneer.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>neo4j-cypher-dsl-bom</artifactId>
|
||||
<version>${cypher-dsl.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>${jsr305.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tngtech.archunit</groupId>
|
||||
@@ -131,9 +128,9 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit-pioneer</groupId>
|
||||
<artifactId>junit-pioneer</artifactId>
|
||||
<version>${junit-pioneer.version}</version>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound</artifactId>
|
||||
<version>${blockhound.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
@@ -161,18 +158,35 @@
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apiguardian</groupId>
|
||||
<artifactId>apiguardian-api</artifactId>
|
||||
<version>${apiguardian.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<version>${jspecify.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit-pioneer</groupId>
|
||||
<artifactId>junit-pioneer</artifactId>
|
||||
<version>${junit-pioneer.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>neo4j</artifactId>
|
||||
<version>${neo4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>neo4j-cypher-dsl-bom</artifactId>
|
||||
<version>${cypher-dsl.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j.driver</groupId>
|
||||
<artifactId>neo4j-java-driver</artifactId>
|
||||
@@ -191,7 +205,7 @@
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.core</artifactId>
|
||||
<version>6.0.0</version>
|
||||
<version>${org.osgi.core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -210,11 +224,6 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound</artifactId>
|
||||
<version>${blockhound.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -279,6 +288,11 @@
|
||||
<scope>test</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
@@ -341,6 +355,11 @@
|
||||
<artifactId>kotlinx-coroutines-reactor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit-pioneer</groupId>
|
||||
<artifactId>junit-pioneer</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>neo4j-cypher-dsl</artifactId>
|
||||
@@ -415,28 +434,18 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit-pioneer</groupId>
|
||||
<artifactId>junit-pioneer</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
@@ -643,31 +652,6 @@
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>java-compile</id>
|
||||
<configuration combine.self="append">
|
||||
<compilerArgs>
|
||||
<arg>-Xlint:all,-options,-path</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<!-- You would think that applying above configuration to java-test-compile would have the same result.
|
||||
Alas, it doesn't. The build just fails, -e -X doesn't show anything sane.
|
||||
So, showDeprecation is just warnings, if set to errors, than it fails hard (which is nice for
|
||||
deprecation, but it also fails during missing When.MAYBE whateever Jakarta annotation
|
||||
and there's just no motiviation to deal with Spring Data Commons build setup in this project -->
|
||||
<id>java-test-compile</id>
|
||||
<configuration combine.self="append">
|
||||
<showDeprecation>true</showDeprecation>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
@@ -689,8 +673,8 @@
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/antora/resources/antora-resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<directory>src/main/antora/resources/antora-resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.aot;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.aot.ManagedTypesBeanRegistrationAotProcessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Gerrit Meier
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public class Neo4jManagedTypesBeanRegistrationAotProcessor extends ManagedTypesBeanRegistrationAotProcessor {
|
||||
public final class Neo4jManagedTypesBeanRegistrationAotProcessor extends ManagedTypesBeanRegistrationAotProcessor {
|
||||
|
||||
public Neo4jManagedTypesBeanRegistrationAotProcessor() {
|
||||
setModuleIdentifier("neo4j");
|
||||
@@ -36,7 +36,7 @@ public class Neo4jManagedTypesBeanRegistrationAotProcessor extends ManagedTypesB
|
||||
return isNeo4jManagedTypes(beanType) || super.isMatch(beanType, beanName);
|
||||
}
|
||||
|
||||
protected boolean isNeo4jManagedTypes(@Nullable Class<?> beanType) {
|
||||
boolean isNeo4jManagedTypes(@Nullable Class<?> beanType) {
|
||||
return beanType != null && ClassUtils.isAssignable(Neo4jManagedTypes.class, beanType);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.aot;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
@@ -34,7 +35,6 @@ import org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository;
|
||||
import org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository;
|
||||
import org.springframework.data.querydsl.QuerydslUtils;
|
||||
import org.springframework.data.util.ReactiveWrappers;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.aot;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.neo4j.config;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -30,7 +31,6 @@ import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
|
||||
import org.springframework.data.neo4j.repository.config.Neo4jRepositoryConfigurationExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
@@ -65,11 +65,11 @@ public abstract class AbstractNeo4jConfig extends Neo4jConfigurationSupport {
|
||||
* @return A imperative Neo4j client.
|
||||
*/
|
||||
@Bean(Neo4jRepositoryConfigurationExtension.DEFAULT_NEO4J_CLIENT_BEAN_NAME)
|
||||
public Neo4jClient neo4jClient(Driver driver, DatabaseSelectionProvider databaseSelectionProvider) {
|
||||
public Neo4jClient neo4jClient(Driver driver, @Nullable DatabaseSelectionProvider databaseSelectionProvider) {
|
||||
|
||||
return Neo4jClient.with(driver)
|
||||
.withDatabaseSelectionProvider(databaseSelectionProvider)
|
||||
.withUserSelectionProvider(getUserSelectionProvider())
|
||||
.withUserSelectionProvider(this.userSelectionProviders.getIfUnique())
|
||||
.withNeo4jBookmarkManager(getBootBookmarkManager())
|
||||
.build();
|
||||
}
|
||||
@@ -78,11 +78,6 @@ public abstract class AbstractNeo4jConfig extends Neo4jConfigurationSupport {
|
||||
return this.bookmarkManagerProviders.getIfAvailable(Neo4jBookmarkManager::create);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private UserSelectionProvider getUserSelectionProvider() {
|
||||
return this.userSelectionProviders == null ? null : this.userSelectionProviders.getIfUnique();
|
||||
}
|
||||
|
||||
@Bean(Neo4jRepositoryConfigurationExtension.DEFAULT_NEO4J_TEMPLATE_BEAN_NAME)
|
||||
public Neo4jOperations neo4jTemplate(final Neo4jClient neo4jClient, final Neo4jMappingContext mappingContext) {
|
||||
|
||||
@@ -97,12 +92,12 @@ public abstract class AbstractNeo4jConfig extends Neo4jConfigurationSupport {
|
||||
* @return A platform transaction manager
|
||||
*/
|
||||
@Bean(Neo4jRepositoryConfigurationExtension.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)
|
||||
public PlatformTransactionManager transactionManager(Driver driver, DatabaseSelectionProvider databaseSelectionProvider) {
|
||||
public PlatformTransactionManager transactionManager(Driver driver, @Nullable DatabaseSelectionProvider databaseSelectionProvider) {
|
||||
|
||||
return Neo4jTransactionManager
|
||||
.with(driver)
|
||||
.withDatabaseSelectionProvider(databaseSelectionProvider)
|
||||
.withUserSelectionProvider(getUserSelectionProvider())
|
||||
.withUserSelectionProvider(this.userSelectionProviders.getIfUnique())
|
||||
.withBookmarkManager(getBootBookmarkManager())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.neo4j.config;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -29,7 +30,6 @@ import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
|
||||
import org.springframework.data.neo4j.repository.config.ReactiveNeo4jRepositoryConfigurationExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
|
||||
|
||||
@@ -90,18 +90,4 @@ abstract class Neo4jConfigurationSupport {
|
||||
|
||||
return Neo4jEntityScanner.get().scan(getMappingBasePackages());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the given base package for entities, i.e. Neo4j specific types annotated with {@link Node}.
|
||||
*
|
||||
* @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 {
|
||||
|
||||
return Neo4jEntityScanner.get().scan(basePackage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
@@ -31,7 +32,6 @@ 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;
|
||||
|
||||
@@ -55,7 +55,8 @@ public final class Neo4jEntityScanner {
|
||||
return new Neo4jEntityScanner(resourceLoader);
|
||||
}
|
||||
|
||||
private @Nullable final ResourceLoader resourceLoader;
|
||||
@Nullable
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Create a new {@link Neo4jEntityScanner} instance.
|
||||
@@ -121,8 +122,7 @@ public final class Neo4jEntityScanner {
|
||||
* @param resourceLoader an optional {@link ResourceLoader} to use
|
||||
* @return a {@link ClassPathScanningCandidateComponentProvider} suitable to scan for Neo4j entities
|
||||
*/
|
||||
private static ClassPathScanningCandidateComponentProvider createClassPathScanningCandidateComponentProvider(
|
||||
@Nullable ResourceLoader resourceLoader) {
|
||||
private static ClassPathScanningCandidateComponentProvider createClassPathScanningCandidateComponentProvider(@Nullable ResourceLoader resourceLoader) {
|
||||
|
||||
ClassPathScanningCandidateComponentProvider delegate = new ClassPathScanningCandidateComponentProvider(false);
|
||||
if (resourceLoader != null) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
The package provides some additional annotations that enable auditing.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.config;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.data.neo4j.core;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A value holder indicating a database selection based on an optional name. {@literal null} indicates to let the server
|
||||
@@ -33,7 +33,8 @@ public final class DatabaseSelection {
|
||||
|
||||
private static final DatabaseSelection DEFAULT_DATABASE_NAME = new DatabaseSelection(null);
|
||||
|
||||
@Nullable private final String value;
|
||||
@Nullable
|
||||
private final String value;
|
||||
|
||||
public static DatabaseSelection undecided() {
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Query;
|
||||
@@ -49,7 +50,6 @@ import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -64,8 +64,10 @@ import org.springframework.util.StringUtils;
|
||||
final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
|
||||
private final Driver driver;
|
||||
private @Nullable final DatabaseSelectionProvider databaseSelectionProvider;
|
||||
private @Nullable final UserSelectionProvider userSelectionProvider;
|
||||
@Nullable
|
||||
private final DatabaseSelectionProvider databaseSelectionProvider;
|
||||
@Nullable
|
||||
private final UserSelectionProvider userSelectionProvider;
|
||||
private final ConversionService conversionService;
|
||||
private final Neo4jPersistenceExceptionTranslator persistenceExceptionTranslator = new Neo4jPersistenceExceptionTranslator();
|
||||
|
||||
@@ -276,7 +278,7 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> OngoingBindSpec<T, RunnableSpec> bind(T value) {
|
||||
public <T> OngoingBindSpec<T, RunnableSpec> bind(@Nullable T value) {
|
||||
return new DefaultOngoingBindSpec<>(value);
|
||||
}
|
||||
|
||||
@@ -314,7 +316,8 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
|
||||
class DefaultOngoingBindSpec<T> implements OngoingBindSpec<T, RunnableSpec> {
|
||||
|
||||
@Nullable private final T value;
|
||||
@Nullable
|
||||
private final T value;
|
||||
|
||||
DefaultOngoingBindSpec(@Nullable T value) {
|
||||
this.value = value;
|
||||
@@ -360,7 +363,7 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> OngoingBindSpec<T, RunnableSpec> bind(T value) {
|
||||
public <T> OngoingBindSpec<T, RunnableSpec> bind(@Nullable T value) {
|
||||
return DefaultRunnableSpec.this.bind(value);
|
||||
}
|
||||
|
||||
@@ -395,7 +398,7 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> OngoingBindSpec<T, RunnableSpec> bind(T value) {
|
||||
public <T> OngoingBindSpec<T, RunnableSpec> bind(@Nullable T value) {
|
||||
return DefaultRunnableSpec.this.bind(value);
|
||||
}
|
||||
|
||||
@@ -410,7 +413,6 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
|
||||
private final DatabaseSelection databaseSelection;
|
||||
|
||||
@Nullable
|
||||
private final UserSelection impersonatedUser;
|
||||
|
||||
private final RunnableStatement runnableStatement;
|
||||
@@ -418,7 +420,7 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
private BiFunction<TypeSystem, Record, T> mappingFunction;
|
||||
|
||||
DefaultRecordFetchSpec(DatabaseSelection databaseSelection,
|
||||
@Nullable UserSelection impersonatedUser,
|
||||
UserSelection impersonatedUser,
|
||||
RunnableStatement runnableStatement,
|
||||
BiFunction<TypeSystem, Record, T> mappingFunction) {
|
||||
|
||||
@@ -501,7 +503,6 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
|
||||
private DatabaseSelection databaseSelection;
|
||||
|
||||
@Nullable
|
||||
private UserSelection impersonatedUser;
|
||||
|
||||
private final Function<QueryRunner, Optional<T>> callback;
|
||||
@@ -513,7 +514,7 @@ final class DefaultNeo4jClient implements Neo4jClient, ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunnableDelegation<T> in(@Nullable String targetDatabase) {
|
||||
public RunnableDelegation<T> in(String targetDatabase) {
|
||||
|
||||
this.databaseSelection = resolveTargetDatabaseName(targetDatabase);
|
||||
return this;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Query;
|
||||
@@ -38,7 +39,6 @@ import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils;
|
||||
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -66,8 +66,10 @@ import java.util.function.Supplier;
|
||||
final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, ApplicationContextAware {
|
||||
|
||||
private final Driver driver;
|
||||
private @Nullable final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
|
||||
private @Nullable final ReactiveUserSelectionProvider userSelectionProvider;
|
||||
@Nullable
|
||||
private final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
|
||||
@Nullable
|
||||
private final ReactiveUserSelectionProvider userSelectionProvider;
|
||||
private final ConversionService conversionService;
|
||||
private final Neo4jPersistenceExceptionTranslator persistenceExceptionTranslator = new Neo4jPersistenceExceptionTranslator();
|
||||
|
||||
@@ -241,7 +243,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, Applicati
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Neo4jClient.OngoingBindSpec<T, RunnableSpec> bind(T value) {
|
||||
public <T> Neo4jClient.OngoingBindSpec<T, RunnableSpec> bind(@Nullable T value) {
|
||||
return new DefaultOngoingBindSpec<>(value);
|
||||
}
|
||||
|
||||
@@ -267,12 +269,13 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, Applicati
|
||||
@Override
|
||||
public Mono<ResultSummary> run() {
|
||||
|
||||
return new DefaultRecordFetchSpec<>(databaseSelection, userSelection, cypherSupplier, this.parameters, null).run();
|
||||
return new DefaultRecordFetchSpec<>(databaseSelection, userSelection, cypherSupplier, this.parameters, (t, r) -> null).run();
|
||||
}
|
||||
|
||||
class DefaultOngoingBindSpec<T> implements Neo4jClient.OngoingBindSpec<T, RunnableSpec> {
|
||||
|
||||
@Nullable private final T value;
|
||||
@Nullable
|
||||
private final T value;
|
||||
|
||||
DefaultOngoingBindSpec(@Nullable T value) {
|
||||
this.value = value;
|
||||
@@ -318,7 +321,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, Applicati
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Neo4jClient.OngoingBindSpec<T, RunnableSpec> bind(T value) {
|
||||
public <T> Neo4jClient.OngoingBindSpec<T, RunnableSpec> bind(@Nullable T value) {
|
||||
return DefaultRunnableSpec.this.bind(value);
|
||||
}
|
||||
|
||||
@@ -353,7 +356,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, Applicati
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Neo4jClient.OngoingBindSpec<T, RunnableSpec> bind(T value) {
|
||||
public <T> Neo4jClient.OngoingBindSpec<T, RunnableSpec> bind(@Nullable T value) {
|
||||
return DefaultRunnableSpec.this.bind(value);
|
||||
}
|
||||
|
||||
@@ -376,8 +379,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, Applicati
|
||||
|
||||
private BiFunction<TypeSystem, Record, T> mappingFunction;
|
||||
|
||||
DefaultRecordFetchSpec(Mono<DatabaseSelection> databaseSelection, Mono<UserSelection> userSelection, Supplier<String> cypherSupplier, NamedParameters parameters,
|
||||
@Nullable BiFunction<TypeSystem, Record, T> mappingFunction) {
|
||||
DefaultRecordFetchSpec(Mono<DatabaseSelection> databaseSelection, Mono<UserSelection> userSelection, Supplier<String> cypherSupplier, NamedParameters parameters, BiFunction<TypeSystem, Record, T> mappingFunction) {
|
||||
|
||||
this.databaseSelection = databaseSelection;
|
||||
this.userSelection = userSelection;
|
||||
@@ -477,7 +479,7 @@ final class DefaultReactiveNeo4jClient implements ReactiveNeo4jClient, Applicati
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunnableDelegation<T> in(@Nullable @SuppressWarnings("HiddenField") String targetDatabase) {
|
||||
public RunnableDelegation<T> in(@SuppressWarnings("HiddenField") String targetDatabase) {
|
||||
|
||||
this.databaseSelection = resolveTargetDatabaseName(targetDatabase);
|
||||
return this;
|
||||
|
||||
@@ -21,12 +21,12 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.Node;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingMatchAndUpdate;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.NodeDescription;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Decorator for an ongoing update statement that removes obsolete dynamic labels and adds new ones.
|
||||
@@ -42,9 +42,9 @@ final class DynamicLabels implements UnaryOperator<OngoingMatchAndUpdate> {
|
||||
private final List<String> oldLabels;
|
||||
private final List<String> newLabels;
|
||||
|
||||
DynamicLabels(@Nullable NodeDescription<?> nodeDescription, Collection<String> oldLabels, Collection<String> newLabels) {
|
||||
DynamicLabels(@Nullable NodeDescription<?> nodeDescription, Collection<String> oldLabels, @Nullable Collection<String> newLabels) {
|
||||
this.oldLabels = new ArrayList<>(oldLabels);
|
||||
this.newLabels = new ArrayList<>(newLabels);
|
||||
this.newLabels = (newLabels != null) ? new ArrayList<>(newLabels) : List.of();
|
||||
this.rootNode = Cypher.anyNode(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link FluentFindOperation} allows creation and execution of Neo4j find operations in a fluent API style.
|
||||
@@ -105,7 +105,7 @@ public interface FluentFindOperation {
|
||||
* @return new instance of {@link TerminatingFind}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingFind<T> matching(String query, @Nullable Map<String, Object> parameter);
|
||||
TerminatingFind<T> matching(String query, Map<String, Object> parameter);
|
||||
|
||||
/**
|
||||
* Creates an executable query based on fragments and parameters. Hardly useful outside framework-code
|
||||
@@ -136,7 +136,7 @@ public interface FluentFindOperation {
|
||||
* @return new instance of {@link TerminatingFind}.
|
||||
* @throws IllegalArgumentException if statement is {@literal null}.
|
||||
*/
|
||||
TerminatingFind<T> matching(Statement statement, @Nullable Map<String, Object> parameter);
|
||||
TerminatingFind<T> matching(Statement statement, Map<String, Object> parameter);
|
||||
|
||||
/**
|
||||
* Set the filter {@link Statement statement} to be used.
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -51,12 +52,15 @@ final class FluentOperationSupport implements FluentFindOperation, FluentSaveOpe
|
||||
private final Neo4jTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
@Nullable
|
||||
private final String query;
|
||||
@Nullable
|
||||
private final Map<String, Object> parameters;
|
||||
@Nullable
|
||||
private final QueryFragmentsAndParameters queryFragmentsAndParameters;
|
||||
|
||||
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
|
||||
Map<String, Object> parameters) {
|
||||
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, @Nullable String query,
|
||||
@Nullable Map<String, Object> parameters) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
@@ -65,7 +69,7 @@ final class FluentOperationSupport implements FluentFindOperation, FluentSaveOpe
|
||||
this.queryFragmentsAndParameters = null;
|
||||
}
|
||||
|
||||
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
|
||||
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
@@ -107,6 +111,7 @@ final class FluentOperationSupport implements FluentFindOperation, FluentSaveOpe
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T oneValue() {
|
||||
|
||||
List<T> result = doFind(TemplateSupport.FetchType.ONE);
|
||||
@@ -149,7 +154,7 @@ final class FluentOperationSupport implements FluentFindOperation, FluentSaveOpe
|
||||
|
||||
List<T> result = doSave(Collections.singleton(instance));
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
throw new IllegalStateException("Instance was not saved");
|
||||
}
|
||||
return result.get(0);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.MapValueWrapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
@@ -141,7 +141,7 @@ final class NamedParameters {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String formatValue(@Nullable Object value) {
|
||||
private static String formatValue(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
} else if (value instanceof String) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -25,6 +26,7 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.QueryRunner;
|
||||
import org.neo4j.driver.Record;
|
||||
@@ -33,7 +35,6 @@ import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Definition of a modern Neo4j client.
|
||||
@@ -127,7 +128,7 @@ public interface Neo4jClient {
|
||||
* @return The builder
|
||||
* @since 6.3.3
|
||||
*/
|
||||
public Builder withNeo4jConversions(Neo4jConversions neo4jConversions) {
|
||||
public Builder withNeo4jConversions(@Nullable Neo4jConversions neo4jConversions) {
|
||||
this.neo4jConversions = neo4jConversions;
|
||||
return this;
|
||||
}
|
||||
@@ -141,7 +142,7 @@ public interface Neo4jClient {
|
||||
* @return The builder
|
||||
* @since 7.1.2
|
||||
*/
|
||||
public Builder withNeo4jBookmarkManager(Neo4jBookmarkManager bookmarkManager) {
|
||||
public Builder withNeo4jBookmarkManager(@Nullable Neo4jBookmarkManager bookmarkManager) {
|
||||
this.bookmarkManager = bookmarkManager;
|
||||
return this;
|
||||
}
|
||||
@@ -266,7 +267,7 @@ public interface Neo4jClient {
|
||||
* @param targetDatabase selected database to use. A {@literal null} value indicates the default database.
|
||||
* @return A runnable query specification that is now bound to a given database.
|
||||
*/
|
||||
RunnableSpecBoundToDatabase in(@Nullable String targetDatabase);
|
||||
RunnableSpecBoundToDatabase in(String targetDatabase);
|
||||
|
||||
/**
|
||||
* Pins the previously defined query to an impersonated user. A value of {@literal null} chooses the user owning
|
||||
@@ -275,7 +276,7 @@ public interface Neo4jClient {
|
||||
* @param asUser The name of the user to impersonate. A {@literal null} value indicates the connected user.
|
||||
* @return A runnable query specification that is now bound to a given database.
|
||||
*/
|
||||
RunnableSpecBoundToUser asUser(@Nullable String asUser);
|
||||
RunnableSpecBoundToUser asUser(String asUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,7 +409,7 @@ public interface Neo4jClient {
|
||||
* @param targetDatabase selected database to use. A {@literal null} value indicates the default database.
|
||||
* @return An ongoing delegation
|
||||
*/
|
||||
RunnableDelegation<T> in(@Nullable String targetDatabase);
|
||||
RunnableDelegation<T> in(String targetDatabase);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,6 +452,9 @@ public interface Neo4jClient {
|
||||
@API(status = API.Status.STABLE, since = "6.1.5")
|
||||
class IllegalDatabaseNameException extends IllegalArgumentException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 3496326026855204643L;
|
||||
|
||||
private final String illegalDatabaseName;
|
||||
|
||||
private IllegalDatabaseNameException(String illegalDatabaseName) {
|
||||
|
||||
@@ -21,13 +21,13 @@ import java.util.Optional;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.NoResultException;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Specifies operations one can perform on a database, based on an <em>Domain Type</em>.
|
||||
@@ -207,6 +207,7 @@ public interface Neo4jOperations {
|
||||
* @return the saved instance.
|
||||
* @since 6.3
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T saveAs(T instance, BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@@ -220,6 +221,7 @@ public interface Neo4jOperations {
|
||||
* @return the saved, projected instance.
|
||||
* @since 6.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T, R> R saveAs(T instance, Class<R> resultType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.function.BiFunction;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.exceptions.AuthenticationException;
|
||||
import org.neo4j.driver.exceptions.ClientException;
|
||||
import org.neo4j.driver.exceptions.DatabaseException;
|
||||
@@ -61,6 +62,7 @@ public final class Neo4jPersistenceExceptionTranslator implements PersistenceExc
|
||||
private static final Map<String, Optional<BiFunction<String, Throwable, DataAccessException>>> ERROR_CODE_MAPPINGS;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||
|
||||
if (ex instanceof DataAccessException) {
|
||||
|
||||
@@ -40,6 +40,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.FunctionInvocation;
|
||||
@@ -49,7 +51,6 @@ import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.neo4j.cypherdsl.core.renderer.Configuration;
|
||||
import org.neo4j.cypherdsl.core.renderer.Renderer;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.exceptions.NoSuchRecordException;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.types.Entity;
|
||||
@@ -96,11 +97,11 @@ import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParamete
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -135,18 +136,22 @@ public final class Neo4jTemplate implements
|
||||
|
||||
private final CypherGenerator cypherGenerator;
|
||||
|
||||
@Nullable
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
private EventSupport eventSupport;
|
||||
|
||||
@Nullable
|
||||
private ProjectionFactory projectionFactory;
|
||||
|
||||
private Renderer renderer;
|
||||
|
||||
private Function<Named, FunctionInvocation> elementIdOrIdFunction;
|
||||
|
||||
@Nullable
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
@Nullable
|
||||
private TransactionTemplate transactionTemplateReadOnly;
|
||||
|
||||
public Neo4jTemplate(Neo4jClient neo4jClient) {
|
||||
@@ -185,6 +190,18 @@ public final class Neo4jTemplate implements
|
||||
return Objects.requireNonNull(this.projectionFactory, "Projection support for the Neo4j template is only available when the template is a proper and fully initialized Spring bean.");
|
||||
}
|
||||
|
||||
private <T> T execute(TransactionCallback<T> action) throws TransactionException {
|
||||
return Objects.requireNonNull(Objects.requireNonNull(this.transactionTemplate).execute(action));
|
||||
}
|
||||
|
||||
private <T> T executeReadOnly(TransactionCallback<T> action) throws TransactionException {
|
||||
return Objects.requireNonNull(Objects.requireNonNull(this.transactionTemplateReadOnly).execute(action));
|
||||
}
|
||||
|
||||
private void executeWithoutResult(Consumer<TransactionStatus> action) throws TransactionException {
|
||||
Objects.requireNonNull(this.transactionTemplate).executeWithoutResult(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(Class<?> domainType) {
|
||||
|
||||
@@ -213,7 +230,7 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public long count(String cypherQuery, Map<String, Object> parameters) {
|
||||
return transactionTemplateReadOnly.execute(tx -> {
|
||||
return executeReadOnly(tx -> {
|
||||
PreparedQuery<Long> preparedQuery = PreparedQuery.queryFor(Long.class).withCypherQuery(cypherQuery)
|
||||
.withParameters(parameters).build();
|
||||
return toExecutableQuery(preparedQuery, true).getRequiredSingleResult();
|
||||
@@ -227,8 +244,7 @@ public final class Neo4jTemplate implements
|
||||
}
|
||||
|
||||
private <T> List<T> doFindAll(Class<T> domainType, @Nullable Class<?> resultType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> {
|
||||
return executeReadOnly(tx -> {
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
return createExecutableQuery(
|
||||
domainType, resultType, QueryFragmentsAndParameters.forFindAll(entityMetaData), true)
|
||||
@@ -238,61 +254,54 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public <T> List<T> findAll(Statement statement, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> createExecutableQuery(domainType, statement, true).getResults());
|
||||
return executeReadOnly(tx -> createExecutableQuery(domainType, statement, true).getResults());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> findAll(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> createExecutableQuery(domainType, null, statement, parameters, true).getResults());
|
||||
return executeReadOnly(tx -> createExecutableQuery(domainType, null, statement, parameters, true).getResults());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> findOne(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> createExecutableQuery(domainType, null, statement, parameters, true).getSingleResult());
|
||||
return executeReadOnly(tx -> createExecutableQuery(domainType, null, statement, parameters, true).getSingleResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> findAll(String cypherQuery, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> createExecutableQuery(domainType, cypherQuery, true).getResults());
|
||||
return executeReadOnly(tx -> createExecutableQuery(domainType, cypherQuery, true).getResults());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> findAll(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> createExecutableQuery(domainType, null, cypherQuery, parameters, true).getResults());
|
||||
return executeReadOnly(tx -> createExecutableQuery(domainType, null, cypherQuery, parameters, true).getResults());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> findOne(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> createExecutableQuery(domainType, null, cypherQuery, parameters, true).getSingleResult());
|
||||
return executeReadOnly(tx -> createExecutableQuery(domainType, null, cypherQuery, parameters, true).getSingleResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFind<T> find(Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> new FluentOperationSupport(this).find(domainType));
|
||||
return new FluentOperationSupport(this).find(domainType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T, R> List<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
|
||||
|
||||
return transactionTemplateReadOnly.execute(tx -> {
|
||||
List<T> intermediaResults = Collections.emptyList();
|
||||
return executeReadOnly(tx -> {
|
||||
List<T> intermediaResults;
|
||||
if (cypherQuery == null && queryFragmentsAndParameters == null && fetchType == TemplateSupport.FetchType.ALL) {
|
||||
intermediaResults = doFindAll(domainType, resultType);
|
||||
} else {
|
||||
ExecutableQuery<T> executableQuery;
|
||||
if (queryFragmentsAndParameters == null) {
|
||||
if (queryFragmentsAndParameters == null && cypherQuery != null) {
|
||||
executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
|
||||
parameters == null ? Collections.emptyMap() : parameters,
|
||||
true);
|
||||
} else {
|
||||
executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters, true);
|
||||
executableQuery = createExecutableQuery(domainType, resultType, Objects.requireNonNull(queryFragmentsAndParameters), true);
|
||||
}
|
||||
intermediaResults = switch (fetchType) {
|
||||
case ALL -> executableQuery.getResults();
|
||||
@@ -327,7 +336,7 @@ public final class Neo4jTemplate implements
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
QueryFragmentsAndParameters fragmentsAndParameters = QueryFragmentsAndParameters
|
||||
.forExistsById(entityMetaData, convertIdValues(entityMetaData.getRequiredIdProperty(), id));
|
||||
.forExistsById(entityMetaData, TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id));
|
||||
|
||||
Statement statement = fragmentsAndParameters.getQueryFragments().toStatement();
|
||||
Map<String, Object> parameters = fragmentsAndParameters.getParameters();
|
||||
@@ -337,13 +346,12 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> findById(Object id, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> {
|
||||
return executeReadOnly(tx -> {
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
return createExecutableQuery(domainType, null,
|
||||
QueryFragmentsAndParameters.forFindById(entityMetaData,
|
||||
convertIdValues(entityMetaData.getRequiredIdProperty(), id)),
|
||||
TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id)),
|
||||
true)
|
||||
.getSingleResult();
|
||||
});
|
||||
@@ -351,62 +359,44 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public <T> List<T> findAllById(Iterable<?> ids, Class<T> domainType) {
|
||||
return transactionTemplateReadOnly
|
||||
.execute(tx -> {
|
||||
return executeReadOnly(tx -> {
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
return createExecutableQuery(domainType, null,
|
||||
QueryFragmentsAndParameters.forFindByAllId(
|
||||
entityMetaData, convertIdValues(entityMetaData.getRequiredIdProperty(), ids)),
|
||||
entityMetaData, TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), ids)),
|
||||
true)
|
||||
.getResults();
|
||||
});
|
||||
}
|
||||
|
||||
private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, @Nullable Object idValues) {
|
||||
|
||||
if (idProperty != null && ((Neo4jPersistentEntity<?>) idProperty.getOwner()).isUsingInternalIds()) {
|
||||
return idValues;
|
||||
}
|
||||
|
||||
if (idValues != null) {
|
||||
return neo4jMappingContext.getConversionService().writeValue(idValues, TypeInformation.of(idValues.getClass()), idProperty == null ? null : idProperty.getOptionalConverter());
|
||||
} else if (idProperty != null) {
|
||||
return neo4jMappingContext.getConversionService().writeValue(idValues, idProperty.getTypeInformation(), idProperty.getOptionalConverter());
|
||||
} else {
|
||||
// Not much we can convert here
|
||||
return Values.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T save(T instance) {
|
||||
|
||||
return transactionTemplate
|
||||
.execute(tx -> saveImpl(instance, Collections.emptySet(), null));
|
||||
return execute(tx -> saveImpl(instance, Collections.emptySet(), null));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T saveAs(T instance, BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
|
||||
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
return transactionTemplate
|
||||
.execute(tx -> saveImpl(instance, TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, instance.getClass(), includeProperty), null));
|
||||
return execute(tx -> saveImpl(instance, TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, instance.getClass(), includeProperty), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T, R> R saveAs(T instance, Class<R> resultType) {
|
||||
|
||||
return transactionTemplate.execute(tx -> {
|
||||
Assert.notNull(resultType, "ResultType must not be null");
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null");
|
||||
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
return execute(tx -> {
|
||||
|
||||
if (resultType.equals(instance.getClass())) {
|
||||
return resultType.cast(save(instance));
|
||||
@@ -427,14 +417,14 @@ public final class Neo4jTemplate implements
|
||||
}
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(savedInstance.getClass());
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getIdProperty();
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(savedInstance);
|
||||
return localProjectionFactory.createProjection(resultType,
|
||||
this.findById(propertyAccessor.getProperty(idProperty), savedInstance.getClass()).get());
|
||||
this.findById(Objects.requireNonNull(propertyAccessor.getProperty(idProperty)), savedInstance.getClass()).orElseThrow());
|
||||
});
|
||||
}
|
||||
|
||||
private <T> T saveImpl(T instance, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
|
||||
private <T> T saveImpl(T instance, Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
|
||||
|
||||
if (stateMachine != null && stateMachine.hasProcessedValue(instance)) {
|
||||
return instance;
|
||||
@@ -471,8 +461,10 @@ public final class Neo4jTemplate implements
|
||||
if (!entityMetaData.isUsingDeprecatedInternalId() && TemplateSupport.rendererRendersElementId(renderer)) {
|
||||
return IdentitySupport.getElementId(node);
|
||||
}
|
||||
return node.id();
|
||||
}).get();
|
||||
@SuppressWarnings("deprecation")
|
||||
var id = node.id();
|
||||
return id;
|
||||
}).orElseThrow();
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
|
||||
TemplateSupport.setGeneratedIdIfNecessary(entityMetaData, propertyAccessor, elementId, newOrUpdatedNode);
|
||||
@@ -498,7 +490,7 @@ public final class Neo4jTemplate implements
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
Neo4jClient.RunnableSpec runnableQuery = neo4jClient
|
||||
.query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData)))
|
||||
.bind(convertIdValues(idProperty, propertyAccessor.getProperty(idProperty)))
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, idProperty, propertyAccessor.getProperty(idProperty)))
|
||||
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels())
|
||||
.to(Constants.NAME_OF_STATIC_LABELS_PARAM);
|
||||
|
||||
@@ -516,8 +508,7 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public <T> List<T> saveAll(Iterable<T> instances) {
|
||||
return transactionTemplate
|
||||
.execute(tx -> saveAllImpl(instances, Collections.emptySet(), null));
|
||||
return execute(tx -> saveAllImpl(instances, Collections.emptySet(), null));
|
||||
}
|
||||
|
||||
private boolean requiresSingleStatements(boolean heterogeneousCollection, Neo4jPersistentEntity<?> entityMetaData) {
|
||||
@@ -544,7 +535,7 @@ public final class Neo4jTemplate implements
|
||||
Class<?> domainClass = types.iterator().next();
|
||||
|
||||
Collection<PropertyFilter.ProjectedPath> pps = includeProperty == null ?
|
||||
includedProperties :
|
||||
Objects.requireNonNullElseGet(includedProperties, List::of) :
|
||||
TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, domainClass,
|
||||
includeProperty);
|
||||
|
||||
@@ -593,8 +584,8 @@ public final class Neo4jTemplate implements
|
||||
return entitiesToBeSaved.stream().map(t -> {
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(t.modifiedInstance);
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
Object id = convertIdValues(idProperty, propertyAccessor.getProperty(idProperty));
|
||||
String internalId = idToInternalIdMapping.get(id);
|
||||
Object id = TemplateSupport.convertIdValues(this.neo4jMappingContext, idProperty, propertyAccessor.getProperty(idProperty));
|
||||
String internalId = Objects.requireNonNull(idToInternalIdMapping.get(id));
|
||||
stateMachine.registerInitialObject(t.originalInstance, internalId);
|
||||
return this.<T>processRelations(entityMetaData, propertyAccessor, t.wasNew, stateMachine, TemplateSupport.computeIncludePropertyPredicate(pps, entityMetaData));
|
||||
}).collect(Collectors.toList());
|
||||
@@ -603,17 +594,15 @@ public final class Neo4jTemplate implements
|
||||
@Override
|
||||
public <T> List<T> saveAllAs(Iterable<T> instances, BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
|
||||
|
||||
return transactionTemplate
|
||||
.execute(tx -> saveAllImpl(instances, null, includeProperty));
|
||||
return execute(tx -> saveAllImpl(instances, null, includeProperty));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> List<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
|
||||
|
||||
return transactionTemplate
|
||||
.execute(tx -> {
|
||||
Assert.notNull(resultType, "ResultType must not be null");
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null");
|
||||
return execute(tx -> {
|
||||
|
||||
Class<?> commonElementType = TemplateSupport.findCommonElementType(instances);
|
||||
|
||||
@@ -645,7 +634,7 @@ public final class Neo4jTemplate implements
|
||||
}
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(commonElementType);
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getIdProperty();
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
|
||||
List<Object> ids = savedInstances.stream().map(savedInstance -> {
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(savedInstance);
|
||||
@@ -661,8 +650,7 @@ public final class Neo4jTemplate implements
|
||||
@Override
|
||||
public <T> void deleteById(Object id, Class<T> domainType) {
|
||||
|
||||
transactionTemplate
|
||||
.executeWithoutResult(tx -> {
|
||||
executeWithoutResult(tx -> {
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
String nameOfParameter = "id";
|
||||
@@ -672,7 +660,7 @@ public final class Neo4jTemplate implements
|
||||
|
||||
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
|
||||
ResultSummary summary = this.neo4jClient.query(renderer.render(statement))
|
||||
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), id))
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id))
|
||||
.to(nameOfParameter).run();
|
||||
|
||||
log.debug(() -> String.format("Deleted %d nodes and %d relationships.", summary.counters().nodesDeleted(),
|
||||
@@ -682,38 +670,37 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public <T> void deleteByIdWithVersion(Object id, Class<T> domainType, Neo4jPersistentProperty versionProperty,
|
||||
Object versionValue) {
|
||||
@Nullable Object versionValue) {
|
||||
|
||||
transactionTemplate
|
||||
.executeWithoutResult(tx -> {
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
executeWithoutResult(tx -> {
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
String nameOfParameter = "id";
|
||||
Condition condition = entityMetaData.getIdExpression().isEqualTo(parameter(nameOfParameter))
|
||||
.and(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName())
|
||||
.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM))
|
||||
.or(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName()).isNull()));
|
||||
String nameOfParameter = "id";
|
||||
Condition condition = entityMetaData.getIdExpression().isEqualTo(parameter(nameOfParameter))
|
||||
.and(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName())
|
||||
.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM))
|
||||
.or(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName()).isNull()));
|
||||
|
||||
Statement statement = cypherGenerator.prepareMatchOf(entityMetaData, condition)
|
||||
.returning(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData)).build();
|
||||
Statement statement = cypherGenerator.prepareMatchOf(entityMetaData, condition)
|
||||
.returning(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData)).build();
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put(nameOfParameter, convertIdValues(entityMetaData.getRequiredIdProperty(), id));
|
||||
parameters.put(Constants.NAME_OF_VERSION_PARAM, versionValue);
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put(nameOfParameter, TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id));
|
||||
parameters.put(Constants.NAME_OF_VERSION_PARAM, versionValue);
|
||||
|
||||
createExecutableQuery(domainType, null, statement, parameters, false).getSingleResult().orElseThrow(
|
||||
() -> new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE)
|
||||
);
|
||||
var lockedEntity = createExecutableQuery(domainType, null, statement, parameters, false).getSingleResult();
|
||||
if (lockedEntity.isEmpty()) {
|
||||
throw new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
deleteById(id, domainType);
|
||||
});
|
||||
deleteById(id, domainType);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAllById(Iterable<?> ids, Class<T> domainType) {
|
||||
|
||||
transactionTemplate
|
||||
.executeWithoutResult(tx -> {
|
||||
executeWithoutResult(tx -> {
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
String nameOfParameter = "ids";
|
||||
@@ -723,7 +710,7 @@ public final class Neo4jTemplate implements
|
||||
|
||||
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
|
||||
ResultSummary summary = this.neo4jClient.query(renderer.render(statement))
|
||||
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), ids))
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), ids))
|
||||
.to(nameOfParameter).run();
|
||||
|
||||
log.debug(() -> String.format("Deleted %d nodes and %d relationships.", summary.counters().nodesDeleted(),
|
||||
@@ -734,8 +721,7 @@ public final class Neo4jTemplate implements
|
||||
@Override
|
||||
public void deleteAll(Class<?> domainType) {
|
||||
|
||||
transactionTemplate
|
||||
.executeWithoutResult(tx -> {
|
||||
executeWithoutResult(tx -> {
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
log.debug(() -> String.format("Deleting all nodes with primary label %s", entityMetaData.getPrimaryLabel()));
|
||||
@@ -762,8 +748,9 @@ public final class Neo4jTemplate implements
|
||||
}
|
||||
|
||||
private <T> ExecutableQuery<T> createExecutableQuery(
|
||||
Class<T> domainType, @Nullable Class<?> resultType,
|
||||
@Nullable String cypherStatement,
|
||||
Class<T> domainType,
|
||||
@Nullable Class<?> resultType,
|
||||
String cypherStatement,
|
||||
Map<String, Object> parameters,
|
||||
boolean readOnly) {
|
||||
|
||||
@@ -802,6 +789,7 @@ public final class Neo4jTemplate implements
|
||||
stateMachine, includeProperty, startingPropertyPath);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private <T> T processNestedRelations(
|
||||
Neo4jPersistentEntity<?> sourceEntity,
|
||||
PersistentPropertyAccessor<?> propertyAccessor,
|
||||
@@ -837,7 +825,7 @@ public final class Neo4jTemplate implements
|
||||
idProperty = null;
|
||||
} else {
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity();
|
||||
idProperty = relationshipPropertiesEntity.getIdProperty();
|
||||
idProperty = (relationshipPropertiesEntity == null) ? null : relationshipPropertiesEntity.getIdProperty();
|
||||
}
|
||||
|
||||
// break recursive procession and deletion of previously created relationships
|
||||
@@ -859,7 +847,11 @@ public final class Neo4jTemplate implements
|
||||
continue;
|
||||
}
|
||||
|
||||
Object id = relationshipContext.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty);
|
||||
PersistentPropertyAccessor<?> relationshipPropertiesPropertyAccessor = relationshipContext.getRelationshipPropertiesPropertyAccessor(relatedValueToStore);
|
||||
if (relationshipPropertiesPropertyAccessor == null) {
|
||||
continue;
|
||||
}
|
||||
Object id = relationshipPropertiesPropertyAccessor.getProperty(idProperty);
|
||||
if (id != null) {
|
||||
knownRelationshipsIds.add(id);
|
||||
}
|
||||
@@ -869,7 +861,7 @@ public final class Neo4jTemplate implements
|
||||
Statement relationshipRemoveQuery = cypherGenerator.prepareDeleteOf(sourceEntity, relationshipDescription, canUseElementId);
|
||||
|
||||
neo4jClient.query(renderer.render(relationshipRemoveQuery))
|
||||
.bind(convertIdValues(sourceEntity.getIdProperty(), fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getIdProperty(), fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(knownRelationshipsIds) //
|
||||
.to(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM) //
|
||||
@@ -935,13 +927,17 @@ public final class Neo4jTemplate implements
|
||||
stateMachine.markRelationshipAsProcessed(possibleInternalLongId == null ? relatedInternalId : possibleInternalLongId,
|
||||
relationshipDescription.getRelationshipObverse());
|
||||
|
||||
Object idValue = idProperty != null
|
||||
? relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty)
|
||||
: null;
|
||||
Object idValue;
|
||||
PersistentPropertyAccessor<?> relationshipPropertiesPropertyAccessor = relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore);
|
||||
if (idProperty == null || relationshipPropertiesPropertyAccessor == null) {
|
||||
idValue = null;
|
||||
} else {
|
||||
idValue = relationshipPropertiesPropertyAccessor.getProperty(idProperty);
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
|
||||
properties.put(Constants.FROM_ID_PARAMETER_NAME, TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getRequiredIdProperty(), fromId));
|
||||
properties.put(Constants.TO_ID_PARAMETER_NAME, relatedInternalId);
|
||||
properties.put(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM, idValue);
|
||||
boolean isNewRelationship = idValue == null;
|
||||
@@ -955,7 +951,7 @@ public final class Neo4jTemplate implements
|
||||
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
|
||||
|
||||
Optional<Object> relationshipInternalId = neo4jClient.query(renderer.render(statementHolder.getStatement()))
|
||||
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getRequiredIdProperty(), fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(relatedInternalId) //
|
||||
.to(Constants.TO_ID_PARAMETER_NAME) //
|
||||
@@ -975,7 +971,7 @@ public final class Neo4jTemplate implements
|
||||
List<Object> row = Collections.singletonList(properties);
|
||||
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
|
||||
neo4jClient.query(renderer.render(statementHolder.getStatement()))
|
||||
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getRequiredIdProperty(), fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(relatedInternalId) //
|
||||
.to(Constants.TO_ID_PARAMETER_NAME) //
|
||||
@@ -984,7 +980,7 @@ public final class Neo4jTemplate implements
|
||||
.bindAll(statementHolder.getProperties())
|
||||
.run();
|
||||
}
|
||||
} else if (relationshipDescription.hasRelationshipProperties()) {
|
||||
} else if (relationshipDescription.hasRelationshipProperties() && fromId != null) {
|
||||
// check if bidi mapped already
|
||||
var hlp = ((MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValueToStore);
|
||||
var hasProcessedRelationshipEntity = stateMachine.hasProcessedRelationshipEntity(propertyAccessor.getBean(), hlp.getRelatedEntity(), relationshipContext.getRelationship());
|
||||
@@ -1034,7 +1030,7 @@ public final class Neo4jTemplate implements
|
||||
.bindAll(statementHolder.getProperties())
|
||||
.run();
|
||||
}
|
||||
if (!newRelationshipPropertiesToStore.isEmpty()) {
|
||||
if (!(newRelationshipPropertiesToStore.isEmpty() || idProperty == null)) {
|
||||
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(true,
|
||||
sourceEntity, relationshipDescription, newRelationshipPropertiesToStore, newRelationshipPropertiesRows, canUseElementId);
|
||||
List<Object> all = new ArrayList<>(neo4jClient.query(renderer.render(statementHolder.getStatement()))
|
||||
@@ -1061,10 +1057,10 @@ public final class Neo4jTemplate implements
|
||||
return finalSubgraphRoot;
|
||||
}
|
||||
|
||||
private Optional<Object> getRelationshipId(Statement statement, Neo4jPersistentProperty idProperty, Object fromId, Object toId) {
|
||||
private Optional<Object> getRelationshipId(Statement statement, @Nullable Neo4jPersistentProperty idProperty, Object fromId, Object toId) {
|
||||
|
||||
return neo4jClient.query(renderer.render(statement))
|
||||
.bind(convertIdValues(idProperty, fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, idProperty, fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(toId) //
|
||||
.to(Constants.TO_ID_PARAMETER_NAME) //
|
||||
@@ -1076,10 +1072,10 @@ public final class Neo4jTemplate implements
|
||||
|
||||
// The pendant to {@link #saveRelatedNode(Object, NodeDescription, PropertyFilter, PropertyFilter.RelaxedPropertyPath)}
|
||||
// We can't do without a query, as we need to refresh the internal id
|
||||
private Entity loadRelatedNode(NodeDescription<?> targetNodeDescription, Object relatedInternalId) {
|
||||
private Entity loadRelatedNode(NodeDescription<?> targetNodeDescription, @Nullable Object relatedInternalId) {
|
||||
|
||||
var targetPersistentEntity = (Neo4jPersistentEntity<?>) targetNodeDescription;
|
||||
var queryFragmentsAndParameters = QueryFragmentsAndParameters.forFindById(targetPersistentEntity, convertIdValues(targetPersistentEntity.getRequiredIdProperty(), relatedInternalId));
|
||||
var queryFragmentsAndParameters = QueryFragmentsAndParameters.forFindById(targetPersistentEntity, TemplateSupport.convertIdValues(this.neo4jMappingContext, targetPersistentEntity.getRequiredIdProperty(), relatedInternalId));
|
||||
var nodeName = Constants.NAME_OF_TYPED_ROOT_NODE.apply(targetNodeDescription).getValue();
|
||||
|
||||
return neo4jClient
|
||||
@@ -1097,9 +1093,12 @@ public final class Neo4jTemplate implements
|
||||
Neo4jPersistentProperty idProperty,
|
||||
Object relationshipInternalId
|
||||
) {
|
||||
relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
|
||||
.setProperty(idProperty, relationshipInternalId);
|
||||
PersistentPropertyAccessor<?> relationshipPropertiesPropertyAccessor = relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore);
|
||||
if (relationshipPropertiesPropertyAccessor != null) {
|
||||
relationshipPropertiesPropertyAccessor
|
||||
.setProperty(idProperty, relationshipInternalId);
|
||||
}
|
||||
}
|
||||
|
||||
private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescription, PropertyFilter includeProperty, PropertyFilter.RelaxedPropertyPath currentPropertyPath) {
|
||||
@@ -1113,10 +1112,10 @@ public final class Neo4jTemplate implements
|
||||
binderFunction = binderFunction.andThen(tree -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
|
||||
String idPropertyName = targetPersistentEntity.getIdProperty().getPropertyName();
|
||||
String idPropertyName = targetPersistentEntity.getRequiredIdProperty().getPropertyName();
|
||||
IdDescription idDescription = targetPersistentEntity.getIdDescription();
|
||||
boolean assignedId = idDescription.isAssignedId() || idDescription.isExternallyGeneratedId();
|
||||
if (!includeProperty.isNotFiltering()) {
|
||||
boolean assignedId = idDescription != null && (idDescription.isAssignedId() || idDescription.isExternallyGeneratedId());
|
||||
if (properties != null && !includeProperty.isNotFiltering()) {
|
||||
properties.entrySet()
|
||||
.removeIf(e -> {
|
||||
// we cannot skip the id property if it is an assigned id
|
||||
@@ -1143,6 +1142,7 @@ public final class Neo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
||||
//noinspection ConstantValue
|
||||
this.beanClassLoader = beanClassLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : beanClassLoader;
|
||||
}
|
||||
|
||||
@@ -1152,7 +1152,7 @@ public final class Neo4jTemplate implements
|
||||
this.eventSupport = EventSupport.discoverCallbacks(neo4jMappingContext, beanFactory);
|
||||
|
||||
SpelAwareProxyProjectionFactory spelAwareProxyProjectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
spelAwareProxyProjectionFactory.setBeanClassLoader(beanClassLoader);
|
||||
spelAwareProxyProjectionFactory.setBeanClassLoader(Objects.requireNonNull(this.beanClassLoader));
|
||||
spelAwareProxyProjectionFactory.setBeanFactory(beanFactory);
|
||||
this.projectionFactory = spelAwareProxyProjectionFactory;
|
||||
|
||||
@@ -1166,6 +1166,7 @@ public final class Neo4jTemplate implements
|
||||
if (this.transactionTemplate != null && this.transactionTemplateReadOnly != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformTransactionManager transactionManager = null;
|
||||
var it = beanFactory.getBeanProvider(PlatformTransactionManager.class).stream().iterator();
|
||||
while (it.hasNext()) {
|
||||
@@ -1203,7 +1204,8 @@ public final class Neo4jTemplate implements
|
||||
|
||||
|
||||
private <T> ExecutableQuery<T> createExecutableQuery(
|
||||
Class<T> domainType, @Nullable Class<?> resultType,
|
||||
Class<T> domainType,
|
||||
@Nullable Class<?> resultType,
|
||||
QueryFragmentsAndParameters queryFragmentsAndParameters,
|
||||
boolean readOnlyTransaction) {
|
||||
|
||||
@@ -1232,15 +1234,13 @@ public final class Neo4jTemplate implements
|
||||
}
|
||||
|
||||
<T, R> List<R> doSave(Iterable<R> instances, Class<T> domainType) {
|
||||
return transactionTemplate
|
||||
.execute(tx -> {
|
||||
// empty check
|
||||
if (!instances.iterator().hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (!instances.iterator().hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Class<?> resultType = TemplateSupport.findCommonElementType(instances);
|
||||
Class<?> resultType = Objects.requireNonNull(TemplateSupport.findCommonElementType(instances), () -> "Could not find a common type element to store and then project multiple instances of type %s".formatted(domainType));
|
||||
|
||||
return execute(tx -> {
|
||||
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(domainType, resultType,
|
||||
getProjectionFactory(), neo4jMappingContext);
|
||||
|
||||
@@ -1249,7 +1249,9 @@ public final class Neo4jTemplate implements
|
||||
EntityFromDtoInstantiatingConverter<T> converter = new EntityFromDtoInstantiatingConverter<>(domainType, neo4jMappingContext);
|
||||
for (R instance : instances) {
|
||||
T domainObject = converter.convert(instance);
|
||||
|
||||
if (domainObject == null) {
|
||||
continue;
|
||||
}
|
||||
T savedEntity = saveImpl(domainObject, pps, stateMachine);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -1271,11 +1273,12 @@ public final class Neo4jTemplate implements
|
||||
|
||||
DefaultExecutableQuery(PreparedQuery<T> preparedQuery, boolean readOnly) {
|
||||
this.preparedQuery = preparedQuery;
|
||||
this.txTemplate = readOnly ? transactionTemplateReadOnly : transactionTemplate;
|
||||
// At this time, both must be initialized
|
||||
this.txTemplate = Objects.requireNonNull(readOnly ? transactionTemplateReadOnly : transactionTemplate);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({"unchecked", "NullAway"})
|
||||
public List<T> getResults() {
|
||||
return txTemplate
|
||||
.execute(tx -> {
|
||||
@@ -1287,7 +1290,7 @@ public final class Neo4jTemplate implements
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({"unchecked", "NullAway"})
|
||||
public Optional<T> getSingleResult() {
|
||||
return txTemplate.execute(tx -> {
|
||||
try {
|
||||
@@ -1304,7 +1307,7 @@ public final class Neo4jTemplate implements
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({"unchecked", "NullAway"})
|
||||
public T getRequiredSingleResult() {
|
||||
return txTemplate.execute(tx -> {
|
||||
Optional<T> one = createFetchSpec().flatMap(Neo4jClient.RecordFetchSpec::one);
|
||||
@@ -1326,11 +1329,12 @@ public final class Neo4jTemplate implements
|
||||
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(queryFragments::includeField);
|
||||
if (cypherQuery == null || containsPossibleCircles) {
|
||||
Statement statement;
|
||||
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
|
||||
// The null check for the metadata is superfluous, but the easiest way to make NullAway happy
|
||||
if (entityMetaData != null && containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
|
||||
NodesAndRelationshipsByIdStatementProvider nodesAndRelationshipsById =
|
||||
createNodesAndRelationshipsByIdStatementProvider(entityMetaData, queryFragments, queryFragmentsAndParameters.getParameters());
|
||||
|
||||
if (nodesAndRelationshipsById.hasRootNodeIds()) {
|
||||
if (!nodesAndRelationshipsById.hasRootNodeIds()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
statement = nodesAndRelationshipsById.toStatement(entityMetaData);
|
||||
@@ -1341,7 +1345,7 @@ public final class Neo4jTemplate implements
|
||||
finalParameters = TemplateSupport.mergeParameters(statement, finalParameters);
|
||||
}
|
||||
|
||||
Neo4jClient.MappingSpec<T> newMappingSpec = neo4jClient.query(cypherQuery)
|
||||
Neo4jClient.MappingSpec<T> newMappingSpec = neo4jClient.query(Objects.requireNonNull(cypherQuery, "Could not compute a query"))
|
||||
.bindAll(finalParameters).fetchAs(preparedQuery.getResultType());
|
||||
return preparedQuery.getOptionalMappingFunction()
|
||||
.map(newMappingSpec::mappedBy).or(() -> Optional.of(newMappingSpec));
|
||||
@@ -1364,7 +1368,7 @@ public final class Neo4jTemplate implements
|
||||
.fetchAs(Value.class).mappedBy((t, r) -> r.get(Constants.NAME_OF_SYNTHESIZED_ROOT_NODE))
|
||||
.one()
|
||||
.map(value -> value.asList(TemplateSupport::convertIdOrElementIdToString))
|
||||
.get());
|
||||
.orElseThrow());
|
||||
|
||||
if (rootNodeIds.isEmpty()) {
|
||||
// fast return if no matching root node(s) are found
|
||||
@@ -1397,11 +1401,15 @@ public final class Neo4jTemplate implements
|
||||
Neo4jPersistentEntity<?> target = (Neo4jPersistentEntity<?>) sourceRelationshipDescription.getTarget();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
String fieldName = ((Association<Neo4jPersistentProperty>) sourceRelationshipDescription).getInverse().getFieldName();
|
||||
PropertyPathWalkStep nextPathStep = currentPathStep.with((sourceRelationshipDescription.hasRelationshipProperties() ?
|
||||
fieldName + "." + ((Neo4jPersistentEntity<?>) sourceRelationshipDescription.getRelationshipPropertiesEntity())
|
||||
.getPersistentProperty(TargetNode.class).getFieldName() : fieldName));
|
||||
|
||||
String fieldName = ((Association<@NonNull Neo4jPersistentProperty>) sourceRelationshipDescription).getInverse().getFieldName();
|
||||
PropertyPathWalkStep nextPathStep;
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) sourceRelationshipDescription.getRelationshipPropertiesEntity();
|
||||
if (sourceRelationshipDescription.hasRelationshipProperties() && relationshipPropertiesEntity != null) {
|
||||
var targetNodeProperty = Objects.requireNonNull(relationshipPropertiesEntity.getPersistentProperty(TargetNode.class), () -> "Could not get target node property on %s".formatted(relationshipPropertiesEntity.getType()));
|
||||
nextPathStep = currentPathStep.with(fieldName + "." + targetNodeProperty.getFieldName());
|
||||
} else {
|
||||
nextPathStep = currentPathStep.with(fieldName);
|
||||
}
|
||||
|
||||
Collection<RelationshipDescription> relationships = target
|
||||
.getRelationshipsInHierarchy(
|
||||
@@ -1430,7 +1438,6 @@ public final class Neo4jTemplate implements
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Consumer<Map<String, Object>> iterateAndMapNextLevel(Map<String, Set<String>> relationshipsToRelatedNodes,
|
||||
RelationshipDescription relationshipDescription,
|
||||
PropertyPathWalkStep currentPathStep) {
|
||||
@@ -1439,9 +1446,11 @@ public final class Neo4jTemplate implements
|
||||
|
||||
Map<String, Set<String>> relatedNodesVisited = new HashMap<>(relationshipsToRelatedNodes);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> newRelationshipIds = ((List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATIONS)).stream().map(TemplateSupport::convertIdOrElementIdToString).toList();
|
||||
var sr = (List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATIONS);
|
||||
List<String> newRelationshipIds = (sr != null) ? sr.stream().map(TemplateSupport::convertIdOrElementIdToString).toList() : List.of();
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> relatedIds = new HashSet<>(((List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES)).stream().map(TemplateSupport::convertIdOrElementIdToString).toList());
|
||||
var srn = (List<Object>) record.get(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES);
|
||||
Set<String> relatedIds = (srn != null) ? new HashSet<>(srn.stream().map(TemplateSupport::convertIdOrElementIdToString).toList()) : Set.of();
|
||||
|
||||
// use this list to get down the road
|
||||
// 1. remove already visited ones;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
@@ -27,7 +28,6 @@ import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.MappingSupport;
|
||||
import org.springframework.data.neo4j.core.mapping.NoRootNodeMappingException;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -35,6 +35,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -66,7 +67,9 @@ public final class PreparedQuery<T> {
|
||||
|
||||
private final Class<T> resultType;
|
||||
private final QueryFragmentsAndParameters queryFragmentsAndParameters;
|
||||
private final @Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
|
||||
@Nullable
|
||||
private final Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private volatile Optional<BiFunction<TypeSystem, Record, T>> lastMappingFunction = Optional.empty();
|
||||
|
||||
private PreparedQuery(OptionalBuildSteps<T> optionalBuildSteps) {
|
||||
@@ -127,7 +130,8 @@ public final class PreparedQuery<T> {
|
||||
|
||||
final Class<CT> resultType;
|
||||
final QueryFragmentsAndParameters queryFragmentsAndParameters;
|
||||
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
|
||||
@Nullable
|
||||
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
|
||||
|
||||
OptionalBuildSteps(Class<CT> resultType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
|
||||
this.resultType = resultType;
|
||||
@@ -140,8 +144,8 @@ public final class PreparedQuery<T> {
|
||||
* @param newParameters The new parameters for the prepared query.
|
||||
* @return This builder.
|
||||
*/
|
||||
public OptionalBuildSteps<CT> withParameters(Map<String, Object> newParameters) {
|
||||
this.queryFragmentsAndParameters.setParameters(newParameters);
|
||||
public OptionalBuildSteps<CT> withParameters(@Nullable Map<String, Object> newParameters) {
|
||||
this.queryFragmentsAndParameters.setParameters(Objects.requireNonNullElseGet(newParameters, Map::of));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -229,6 +233,9 @@ public final class PreparedQuery<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
// Suppressing the warnings for accessing `pathValues`: `partitioningBy`
|
||||
// will always provide entries for `true` and `false`
|
||||
@SuppressWarnings("NullAway")
|
||||
public Object apply(TypeSystem t, Record r) {
|
||||
|
||||
if (r.size() == 1) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.neo4j.core.mapping.GraphPropertyDescription;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
@@ -27,12 +28,13 @@ import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This class is responsible for creating a List of {@link PropertyPath} entries that contains all reachable
|
||||
@@ -85,7 +87,7 @@ public final class PropertyFilterSupport {
|
||||
// try to figure out the right property by name
|
||||
for (GraphPropertyDescription graphProperty : domainEntity.getGraphProperties()) {
|
||||
if (graphProperty.getPropertyName().equals(inputProperty.getName())) {
|
||||
typeInformation = domainEntity.getPersistentProperty(graphProperty.getFieldName()).getTypeInformation();
|
||||
typeInformation = Optional.ofNullable(domainEntity.getPersistentProperty(graphProperty.getFieldName())).map(PersistentProperty::getTypeInformation).orElse(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -93,13 +95,15 @@ public final class PropertyFilterSupport {
|
||||
if (typeInformation == null) {
|
||||
for (RelationshipDescription relationshipDescription : domainEntity.getRelationships()) {
|
||||
if (relationshipDescription.getFieldName().equals(inputProperty.getName())) {
|
||||
typeInformation = domainEntity.getPersistentProperty(relationshipDescription.getFieldName()).getTypeInformation();
|
||||
typeInformation = Optional.ofNullable(domainEntity.getPersistentProperty(relationshipDescription.getFieldName())).map(PersistentProperty::getTypeInformation).orElse(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addPropertiesFrom(domainType, returnType, projectionFactory, propertyPaths, new ProjectionPathProcessor(inputProperty.getName(), typeInformation), neo4jMappingContext);
|
||||
if (typeInformation != null) {
|
||||
addPropertiesFrom(domainType, returnType, projectionFactory, propertyPaths, new ProjectionPathProcessor(inputProperty.getName(), typeInformation), neo4jMappingContext);
|
||||
}
|
||||
}
|
||||
return propertyPaths;
|
||||
}
|
||||
@@ -127,16 +131,18 @@ public final class PropertyFilterSupport {
|
||||
TypeInformation<?> mapValueType = projectionPathProcessor.typeInformation.getRequiredMapValueType();
|
||||
if (mapValueType.isCollectionLike()) {
|
||||
currentTypeInformation = projectionPathProcessor.typeInformation.getRequiredMapValueType().getComponentType();
|
||||
propertyType = projectionPathProcessor.typeInformation.getRequiredMapValueType().getComponentType().getType();
|
||||
propertyType = Objects.requireNonNull(currentTypeInformation, "Cannot retrieve collection type").getType();
|
||||
} else {
|
||||
currentTypeInformation = projectionPathProcessor.typeInformation.getRequiredMapValueType();
|
||||
propertyType = projectionPathProcessor.typeInformation.getRequiredMapValueType().getType();
|
||||
propertyType = currentTypeInformation.getType();
|
||||
}
|
||||
} else if (projectionPathProcessor.typeInformation.isCollectionLike()) {
|
||||
currentTypeInformation = projectionPathProcessor.typeInformation.getComponentType();
|
||||
propertyType = projectionPathProcessor.typeInformation.getComponentType().getType();
|
||||
propertyType = Objects.requireNonNull(currentTypeInformation, "Cannot retrieve collection type").getType();
|
||||
}
|
||||
|
||||
Objects.requireNonNull(currentTypeInformation, "Property type is required");
|
||||
|
||||
// 1. Simple types can be added directly
|
||||
// 2. Something that looks like an entity needs to get processed as such
|
||||
// 3. Embedded projection
|
||||
@@ -150,17 +156,18 @@ public final class PropertyFilterSupport {
|
||||
if (nestedProjectionInformation.isClosed()) {
|
||||
filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, false));
|
||||
for (PropertyDescriptor nestedInputProperty : nestedProjectionInformation.getInputProperties()) {
|
||||
TypeInformation<?> typeInformation = currentTypeInformation.getProperty(nestedInputProperty.getName());
|
||||
TypeInformation<?> typeInformation = currentTypeInformation.getRequiredProperty(nestedInputProperty.getName());
|
||||
ProjectionPathProcessor nextProjectionPathProcessor = projectionPathProcessor.next(nestedInputProperty, typeInformation);
|
||||
|
||||
TypeInformation<?> actualType = Objects.requireNonNull(nextProjectionPathProcessor.typeInformation.getActualType());
|
||||
if (projectionPathProcessor.isChildLevel() &&
|
||||
(domainType.equals(nextProjectionPathProcessor.typeInformation.getType())
|
||||
|| returnedType.equals(nextProjectionPathProcessor.typeInformation.getActualType().getType())
|
||||
|| returnedType.equals(actualType.getType())
|
||||
|| returnedType.equals(nextProjectionPathProcessor.typeInformation.getType()))) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (projectionPathProcessor.typeInformation.getActualType().getType().equals(nextProjectionPathProcessor.typeInformation.getActualType().getType())
|
||||
if (projectionPathProcessor.typeInformation.getActualType() != null && projectionPathProcessor.typeInformation.getActualType().getType().equals(actualType.getType())
|
||||
|| (!projectionPathProcessor.typeInformation.isCollectionLike() && !projectionPathProcessor.typeInformation.isMap() && projectionPathProcessor.typeInformation.getType().equals(nextProjectionPathProcessor.typeInformation.getType()))) {
|
||||
filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, true));
|
||||
} else {
|
||||
@@ -182,13 +189,13 @@ public final class PropertyFilterSupport {
|
||||
final String path;
|
||||
final String name;
|
||||
|
||||
private ProjectionPathProcessor(String name, String path, @Nullable TypeInformation<?> typeInformation) {
|
||||
private ProjectionPathProcessor(String name, String path, TypeInformation<?> typeInformation) {
|
||||
this.typeInformation = typeInformation;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private ProjectionPathProcessor(String name, @Nullable TypeInformation<?> typeInformation) {
|
||||
private ProjectionPathProcessor(String name, TypeInformation<?> typeInformation) {
|
||||
this(name, name, typeInformation);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.Map;
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ReactiveFluentFindOperation} allows creation and execution of Neo4j find operations in a fluent API style.
|
||||
@@ -95,7 +94,7 @@ public interface ReactiveFluentFindOperation {
|
||||
* @return new instance of {@link TerminatingFind}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingFind<T> matching(String query, @Nullable Map<String, Object> parameter);
|
||||
TerminatingFind<T> matching(String query, Map<String, Object> parameter);
|
||||
|
||||
/**
|
||||
* Creates an executable query based on fragments and parameters. Hardly useful outside framework-code
|
||||
@@ -126,7 +125,7 @@ public interface ReactiveFluentFindOperation {
|
||||
* @return new instance of {@link TerminatingFind}.
|
||||
* @throws IllegalArgumentException if statement is {@literal null}.
|
||||
*/
|
||||
TerminatingFind<T> matching(Statement statement, @Nullable Map<String, Object> parameter);
|
||||
TerminatingFind<T> matching(Statement statement, Map<String, Object> parameter);
|
||||
|
||||
/**
|
||||
* Set the filter {@link Statement statement} to be used.
|
||||
|
||||
@@ -21,6 +21,7 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -54,12 +55,15 @@ final class ReactiveFluentOperationSupport implements ReactiveFluentFindOperatio
|
||||
private final ReactiveNeo4jTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
@Nullable
|
||||
private final String query;
|
||||
@Nullable
|
||||
private final Map<String, Object> parameters;
|
||||
@Nullable
|
||||
private final QueryFragmentsAndParameters queryFragmentsAndParameters;
|
||||
|
||||
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
|
||||
Map<String, Object> parameters) {
|
||||
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, @Nullable String query,
|
||||
@Nullable Map<String, Object> parameters) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
@@ -68,7 +72,7 @@ final class ReactiveFluentOperationSupport implements ReactiveFluentFindOperatio
|
||||
this.queryFragmentsAndParameters = null;
|
||||
}
|
||||
|
||||
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
|
||||
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -34,7 +35,6 @@ import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.data.neo4j.core.Neo4jClient.BindSpec;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Reactive Neo4j client. The main difference to the {@link Neo4jClient imperative Neo4j client} is the fact that all
|
||||
@@ -123,7 +123,7 @@ public interface ReactiveNeo4jClient {
|
||||
* @return The builder
|
||||
* @since 6.3.3
|
||||
*/
|
||||
public Builder withNeo4jConversions(Neo4jConversions neo4jConversions) {
|
||||
public Builder withNeo4jConversions(@Nullable Neo4jConversions neo4jConversions) {
|
||||
this.neo4jConversions = neo4jConversions;
|
||||
return this;
|
||||
}
|
||||
@@ -137,7 +137,7 @@ public interface ReactiveNeo4jClient {
|
||||
* @return The builder
|
||||
* @since 7.1.2
|
||||
*/
|
||||
public Builder withNeo4jBookmarkManager(Neo4jBookmarkManager bookmarkManager) {
|
||||
public Builder withNeo4jBookmarkManager(@Nullable Neo4jBookmarkManager bookmarkManager) {
|
||||
this.bookmarkManager = bookmarkManager;
|
||||
return this;
|
||||
}
|
||||
@@ -306,7 +306,7 @@ public interface ReactiveNeo4jClient {
|
||||
* @param targetDatabase selected database to use. A {@literal null} value indicates the default database.
|
||||
* @return A runnable query specification that is now bound to a given database.
|
||||
*/
|
||||
RunnableSpecBoundToDatabase in(@Nullable String targetDatabase);
|
||||
RunnableSpecBoundToDatabase in(String targetDatabase);
|
||||
|
||||
/**
|
||||
* Pins the previously defined query to an impersonated user. A value of {@literal null} chooses the user owning
|
||||
@@ -315,7 +315,7 @@ public interface ReactiveNeo4jClient {
|
||||
* @param asUser The name of the user to impersonate. A {@literal null} value indicates the connected user.
|
||||
* @return A runnable query specification that is now bound to a given database.
|
||||
*/
|
||||
RunnableSpecBoundToUser asUser(@Nullable String asUser);
|
||||
RunnableSpecBoundToUser asUser(String asUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,7 +361,7 @@ public interface ReactiveNeo4jClient {
|
||||
* @param targetDatabase selected database to use. A {@literal null} value indicates the default database.
|
||||
* @return An ongoing delegation
|
||||
*/
|
||||
RunnableDelegation<T> in(@Nullable String targetDatabase);
|
||||
RunnableDelegation<T> in(String targetDatabase);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
|
||||
@@ -27,7 +28,6 @@ import java.util.function.BiPredicate;
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Specifies reactive operations one can perform on a database, based on an <em>Domain Type</em>.
|
||||
@@ -272,8 +272,7 @@ public interface ReactiveNeo4jOperations {
|
||||
*/
|
||||
<T> Mono<Void> deleteById(Object id, Class<T> domainType);
|
||||
|
||||
<T> Mono<Void> deleteByIdWithVersion(Object id, Class<T> domainType, Neo4jPersistentProperty versionProperty,
|
||||
@Nullable Object versionValue);
|
||||
<T> Mono<Void> deleteByIdWithVersion(Object id, Class<T> domainType, Neo4jPersistentProperty versionProperty, @Nullable Object versionValue);
|
||||
|
||||
/**
|
||||
* Deletes all entities with one of the given ids, including all entities related to that entity.
|
||||
|
||||
@@ -15,8 +15,33 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
|
||||
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
|
||||
import static org.neo4j.cypherdsl.core.Cypher.parameter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.FunctionInvocation;
|
||||
@@ -26,7 +51,6 @@ import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.neo4j.cypherdsl.core.renderer.Configuration;
|
||||
import org.neo4j.cypherdsl.core.renderer.Renderer;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.Entity;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
@@ -72,42 +96,17 @@ import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParamete
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
|
||||
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
|
||||
import static org.neo4j.cypherdsl.core.Cypher.parameter;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
* @author Gerrit Meier
|
||||
@@ -138,14 +137,18 @@ public final class ReactiveNeo4jTemplate implements
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private TransactionalOperator transactionalOperatorReadOnly;
|
||||
|
||||
@Nullable
|
||||
private TransactionalOperator transactionalOperator;
|
||||
|
||||
@Nullable
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
private ReactiveEventSupport eventSupport;
|
||||
|
||||
@Nullable
|
||||
private ProjectionFactory projectionFactory;
|
||||
|
||||
private Renderer renderer;
|
||||
@@ -202,13 +205,29 @@ public final class ReactiveNeo4jTemplate implements
|
||||
public Mono<Long> count(String cypherQuery, Map<String, Object> parameters) {
|
||||
PreparedQuery<Long> preparedQuery = PreparedQuery.queryFor(Long.class).withCypherQuery(cypherQuery)
|
||||
.withParameters(parameters).build();
|
||||
return transactionalOperatorReadOnly.transactional(this.toExecutableQuery(preparedQuery).flatMap(ExecutableQuery::getSingleResult));
|
||||
return executeReadOnly(this.toExecutableQuery(preparedQuery).flatMap(ExecutableQuery::getSingleResult));
|
||||
}
|
||||
|
||||
private <T> Mono<T> executeReadOnly(Mono<T> action) {
|
||||
return Objects.requireNonNull(this.transactionalOperatorReadOnly).transactional(action);
|
||||
}
|
||||
|
||||
private <T> Flux<T> executeReadOnly(Flux<T> action) {
|
||||
return Objects.requireNonNull(this.transactionalOperatorReadOnly).transactional(action);
|
||||
}
|
||||
|
||||
private <T> Mono<T> execute(Mono<T> action) {
|
||||
return Objects.requireNonNull(this.transactionalOperator).transactional(action);
|
||||
}
|
||||
|
||||
private <T> Flux<T> execute(Flux<T> action) {
|
||||
return Objects.requireNonNull(this.transactionalOperator).transactional(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> findAll(Class<T> domainType) {
|
||||
|
||||
return transactionalOperatorReadOnly.transactional(doFindAll(domainType, null));
|
||||
return executeReadOnly(doFindAll(domainType, null));
|
||||
}
|
||||
|
||||
private <T> Flux<T> doFindAll(Class<T> domainType, @Nullable Class<?> resultType) {
|
||||
@@ -221,34 +240,34 @@ public final class ReactiveNeo4jTemplate implements
|
||||
@Override
|
||||
public <T> Flux<T> findAll(Statement statement, Class<T> domainType) {
|
||||
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, statement).flatMapMany(ExecutableQuery::getResults));
|
||||
return executeReadOnly(createExecutableQuery(domainType, statement).flatMapMany(ExecutableQuery::getResults));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> findAll(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
|
||||
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, null, statement, parameters).flatMapMany(ExecutableQuery::getResults));
|
||||
return executeReadOnly(createExecutableQuery(domainType, null, statement, parameters).flatMapMany(ExecutableQuery::getResults));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> findOne(Statement statement, Map<String, Object> parameters, Class<T> domainType) {
|
||||
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, null, statement, parameters).flatMap(ExecutableQuery::getSingleResult));
|
||||
return executeReadOnly(createExecutableQuery(domainType, null, statement, parameters).flatMap(ExecutableQuery::getSingleResult));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> findAll(String cypherQuery, Class<T> domainType) {
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, cypherQuery).flatMapMany(ExecutableQuery::getResults));
|
||||
return executeReadOnly(createExecutableQuery(domainType, cypherQuery).flatMapMany(ExecutableQuery::getResults));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> findAll(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, null, cypherQuery, parameters).flatMapMany(ExecutableQuery::getResults));
|
||||
return executeReadOnly(createExecutableQuery(domainType, null, cypherQuery, parameters).flatMapMany(ExecutableQuery::getResults));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> findOne(String cypherQuery, Map<String, Object> parameters, Class<T> domainType) {
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, null, cypherQuery, parameters).flatMap(ExecutableQuery::getSingleResult));
|
||||
return executeReadOnly(createExecutableQuery(domainType, null, cypherQuery, parameters).flatMap(ExecutableQuery::getSingleResult));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -265,15 +284,15 @@ public final class ReactiveNeo4jTemplate implements
|
||||
} else {
|
||||
Mono<ExecutableQuery<T>> executableQuery;
|
||||
if (queryFragmentsAndParameters == null) {
|
||||
executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
|
||||
executableQuery = createExecutableQuery(domainType, resultType, Objects.requireNonNull(cypherQuery),
|
||||
parameters == null ? Collections.emptyMap() : parameters);
|
||||
} else {
|
||||
executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters);
|
||||
}
|
||||
|
||||
intermediaResults = switch (fetchType) {
|
||||
case ALL -> transactionalOperatorReadOnly.transactional(executableQuery.flatMapMany(ExecutableQuery::getResults));
|
||||
case ONE -> transactionalOperatorReadOnly.transactional(executableQuery.flatMap(ExecutableQuery::getSingleResult).flux());
|
||||
case ALL -> executeReadOnly(executableQuery.flatMapMany(ExecutableQuery::getResults));
|
||||
case ONE -> executeReadOnly(executableQuery.flatMap(ExecutableQuery::getSingleResult).flux());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -296,7 +315,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
QueryFragmentsAndParameters fragmentsAndParameters = QueryFragmentsAndParameters
|
||||
.forExistsById(entityMetaData, convertIdValues(entityMetaData.getRequiredIdProperty(), id));
|
||||
.forExistsById(entityMetaData, TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id));
|
||||
|
||||
Statement statement = fragmentsAndParameters.getQueryFragments().toStatement();
|
||||
Map<String, Object> parameters = fragmentsAndParameters.getParameters();
|
||||
@@ -309,9 +328,9 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, null,
|
||||
return executeReadOnly(createExecutableQuery(domainType, null,
|
||||
QueryFragmentsAndParameters.forFindById(entityMetaData,
|
||||
convertIdValues(entityMetaData.getRequiredIdProperty(), id)))
|
||||
TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id)))
|
||||
.flatMap(ExecutableQuery::getSingleResult));
|
||||
}
|
||||
|
||||
@@ -320,9 +339,9 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
return transactionalOperatorReadOnly.transactional(createExecutableQuery(domainType, null,
|
||||
return executeReadOnly(createExecutableQuery(domainType, null,
|
||||
QueryFragmentsAndParameters.forFindByAllId(entityMetaData,
|
||||
convertIdValues(entityMetaData.getRequiredIdProperty(), ids)))
|
||||
TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), ids)))
|
||||
.flatMapMany(ExecutableQuery::getResults));
|
||||
}
|
||||
|
||||
@@ -333,36 +352,20 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return createExecutableQuery(domainType, null, queryFragmentsAndParameters);
|
||||
}
|
||||
|
||||
private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, @Nullable Object idValues) {
|
||||
|
||||
if (idProperty != null && ((Neo4jPersistentEntity<?>) idProperty.getOwner()).isUsingInternalIds()) {
|
||||
return idValues;
|
||||
}
|
||||
|
||||
if (idValues != null) {
|
||||
return neo4jMappingContext.getConversionService().writeValue(idValues, TypeInformation.of(idValues.getClass()), idProperty == null ? null : idProperty.getOptionalConverter());
|
||||
} else if (idProperty != null) {
|
||||
return neo4jMappingContext.getConversionService().writeValue(idValues, idProperty.getTypeInformation(), idProperty.getOptionalConverter());
|
||||
} else {
|
||||
// Not much we can convert here
|
||||
return Values.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> save(T instance) {
|
||||
|
||||
return transactionalOperator.transactional(saveImpl(instance, Collections.emptySet(), null));
|
||||
return execute(saveImpl(instance, Collections.emptySet(), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> saveAs(T instance, BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
|
||||
|
||||
if (instance == null) {
|
||||
return null;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return transactionalOperator.transactional(saveImpl(instance, TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, instance.getClass(), includeProperty), null));
|
||||
return execute(saveImpl(instance, TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, instance.getClass(), includeProperty), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -371,7 +374,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Assert.notNull(resultType, "ResultType must not be null");
|
||||
|
||||
if (instance == null) {
|
||||
return null;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (resultType.equals(instance.getClass())) {
|
||||
@@ -383,7 +386,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(instance.getClass(), resultType,
|
||||
localProjectionFactory, neo4jMappingContext);
|
||||
|
||||
Mono<T> savingPublisher = transactionalOperator.transactional(saveImpl(instance, pps, null));
|
||||
Mono<T> savingPublisher = execute(saveImpl(instance, pps, null));
|
||||
|
||||
if (!resultType.isInterface()) {
|
||||
return savingPublisher.map(savedInstance -> {
|
||||
@@ -399,9 +402,9 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return savingPublisher.flatMap(savedInstance -> {
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(savedInstance.getClass());
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getIdProperty();
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(savedInstance);
|
||||
return transactionalOperatorReadOnly.transactional(this.findById(propertyAccessor.getProperty(idProperty), savedInstance.getClass())
|
||||
return executeReadOnly(this.findById(Objects.requireNonNull(propertyAccessor.getProperty(idProperty)), savedInstance.getClass())
|
||||
.map(loadedValue -> localProjectionFactory.createProjection(resultType, loadedValue)));
|
||||
});
|
||||
}
|
||||
@@ -412,7 +415,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
Class<?> resultType = TemplateSupport.findCommonElementType(instances);
|
||||
Class<?> resultType = Objects.requireNonNull(TemplateSupport.findCommonElementType(instances), () -> "Could not find a common type element to store and then project multiple instances of type %s".formatted(domainType));
|
||||
|
||||
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(domainType, resultType,
|
||||
getProjectionFactory(), neo4jMappingContext);
|
||||
@@ -423,20 +426,24 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return Flux.fromIterable(instances)
|
||||
.concatMap(instance -> {
|
||||
T domainObject = converter.convert(instance);
|
||||
if (domainObject == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Mono<R> result = transactionalOperator.transactional(saveImpl(domainObject, pps, stateMachine, knownRelationshipsIds)
|
||||
Mono<R> result = execute(saveImpl(domainObject, pps, stateMachine, knownRelationshipsIds)
|
||||
.map(savedEntity -> (R) new DtoInstantiatingConverter(resultType, neo4jMappingContext).convertDirectly(savedEntity)));
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private <T> Mono<T> saveImpl(T instance, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
|
||||
private <T> Mono<T> saveImpl(T instance, Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
|
||||
return saveImpl(instance, includedProperties, stateMachine, new HashSet<>());
|
||||
}
|
||||
|
||||
private <T> Mono<T> saveImpl(T instance, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine, Collection<Object> knownRelationshipsIds) {
|
||||
@SuppressWarnings("deprecation")
|
||||
private <T> Mono<T> saveImpl(T instance, Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine, Collection<Object> knownRelationshipsIds) {
|
||||
|
||||
if (stateMachine != null && stateMachine.hasProcessedValue(instance)) {
|
||||
return Mono.just(instance);
|
||||
@@ -500,7 +507,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
ReactiveNeo4jClient.RunnableSpec runnableQuery = neo4jClient
|
||||
.query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData)))
|
||||
.bind(convertIdValues(idProperty, propertyAccessor.getProperty(idProperty)))
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, idProperty, propertyAccessor.getProperty(idProperty)))
|
||||
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels()).to(Constants.NAME_OF_STATIC_LABELS_PARAM);
|
||||
|
||||
if (entityMetaData.hasVersionProperty()) {
|
||||
@@ -518,13 +525,13 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> saveAll(Iterable<T> instances) {
|
||||
return transactionalOperator.transactional(saveAllImpl(instances, Collections.emptySet(), null));
|
||||
return execute(saveAllImpl(instances, Collections.emptySet(), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> saveAllAs(Iterable<T> instances, BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
|
||||
|
||||
return transactionalOperator.transactional(saveAllImpl(instances, null, includeProperty));
|
||||
return execute(saveAllImpl(instances, null, includeProperty));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -552,20 +559,21 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(commonElementType, resultType,
|
||||
localProjectionFactory, neo4jMappingContext);
|
||||
|
||||
Flux<T> savedInstances = transactionalOperator.transactional(saveAllImpl(instances, pps, null));
|
||||
Flux<T> savedInstances = execute(saveAllImpl(instances, pps, null));
|
||||
if (projectionInformation.isClosed()) {
|
||||
return savedInstances.map(instance -> localProjectionFactory.createProjection(resultType, instance));
|
||||
}
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(commonElementType);
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getIdProperty();
|
||||
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
|
||||
|
||||
return savedInstances.concatMap(savedInstance -> {
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(savedInstance);
|
||||
return transactionalOperatorReadOnly.transactional(findById(propertyAccessor.getProperty(idProperty), commonElementType));
|
||||
return executeReadOnly(findById(Objects.requireNonNull(propertyAccessor.getProperty(idProperty)), commonElementType));
|
||||
}).map(instance -> localProjectionFactory.createProjection(resultType, instance));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
|
||||
|
||||
Set<Class<?>> types = new HashSet<>();
|
||||
@@ -583,7 +591,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Class<?> domainClass = types.iterator().next();
|
||||
|
||||
Collection<PropertyFilter.ProjectedPath> pps = includeProperty == null ?
|
||||
includedProperties :
|
||||
Objects.requireNonNullElseGet(includedProperties, List::of) :
|
||||
TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, domainClass,
|
||||
includeProperty);
|
||||
|
||||
@@ -645,9 +653,9 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Condition condition = entityMetaData.getIdExpression().in(parameter(nameOfParameter));
|
||||
|
||||
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
|
||||
return transactionalOperator.transactional(Mono.defer(() ->
|
||||
return execute(Mono.defer(() ->
|
||||
this.neo4jClient.query(() -> renderer.render(statement))
|
||||
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), ids))
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), ids))
|
||||
.to(nameOfParameter).run().then()));
|
||||
}
|
||||
|
||||
@@ -661,15 +669,14 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Condition condition = entityMetaData.getIdExpression().isEqualTo(parameter(nameOfParameter));
|
||||
|
||||
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
|
||||
return transactionalOperator.transactional(Mono.defer(() ->
|
||||
return execute(Mono.defer(() ->
|
||||
this.neo4jClient.query(() -> renderer.render(statement))
|
||||
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), id))
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id))
|
||||
.to(nameOfParameter).run().then()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<Void> deleteByIdWithVersion(Object id, Class<T> domainType, Neo4jPersistentProperty versionProperty,
|
||||
Object versionValue) {
|
||||
public <T> Mono<Void> deleteByIdWithVersion(Object id, Class<T> domainType, Neo4jPersistentProperty versionProperty, @Nullable Object versionValue) {
|
||||
|
||||
String nameOfParameter = "id";
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
@@ -682,10 +689,10 @@ public final class ReactiveNeo4jTemplate implements
|
||||
.returning(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData)).build();
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put(nameOfParameter, convertIdValues(entityMetaData.getRequiredIdProperty(), id));
|
||||
parameters.put(nameOfParameter, TemplateSupport.convertIdValues(this.neo4jMappingContext, entityMetaData.getRequiredIdProperty(), id));
|
||||
parameters.put(Constants.NAME_OF_VERSION_PARAM, versionValue);
|
||||
|
||||
return transactionalOperator.transactional(Mono.defer(() ->
|
||||
return execute(Mono.defer(() ->
|
||||
this.neo4jClient.query(() -> renderer.render(statement))
|
||||
.bindAll(parameters)
|
||||
.fetch().one().switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -702,7 +709,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getRequiredPersistentEntity(domainType);
|
||||
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData);
|
||||
return transactionalOperator.transactional(Mono.defer(() -> this.neo4jClient.query(() -> renderer.render(statement)).run().then()));
|
||||
return execute(Mono.defer(() -> this.neo4jClient.query(() -> renderer.render(statement)).run().then()));
|
||||
}
|
||||
|
||||
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, Statement statement) {
|
||||
@@ -719,7 +726,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return createExecutableQuery(domainType, resultType, renderer.render(statement), TemplateSupport.mergeParameters(statement, parameters));
|
||||
}
|
||||
|
||||
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, @Nullable String cypherQuery,
|
||||
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, String cypherQuery,
|
||||
Map<String, Object> parameters) {
|
||||
|
||||
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction = TemplateSupport
|
||||
@@ -748,8 +755,9 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return createExecutableQuery(domainType, resultType, queryFragments.toStatement(), queryFragmentsAndParameters.getParameters());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Mono<NodesAndRelationshipsByIdStatementProvider> createNodesAndRelationshipsByIdStatementProvider(Neo4jPersistentEntity<?> entityMetaData,
|
||||
QueryFragments queryFragments, Map<String, Object> parameters) {
|
||||
QueryFragments queryFragments, Map<String, Object> parameters) {
|
||||
|
||||
return Mono.deferContextual(ctx -> {
|
||||
Class<?> rootClass = entityMetaData.getUnderlyingClass();
|
||||
@@ -776,10 +784,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return Tuples.of(newRelationshipIds, newRelatedNodeIds);
|
||||
})
|
||||
.one()
|
||||
.map((t) -> {
|
||||
//noinspection unchecked
|
||||
return (Tuple2<Collection<String>, Collection<String>>) t;
|
||||
})
|
||||
.map((t) -> (Tuple2<Collection<String>, Collection<String>>) t)
|
||||
.expand(iterateAndMapNextLevel(relationshipDescription, queryFragments, rootClass, PropertyPathWalkStep.empty()));
|
||||
})
|
||||
.then(Mono.fromSupplier(() -> new NodesAndRelationshipsByIdStatementProvider(rootNodeIds, relationshipsToRelatedNodeIds.keySet(), relationshipsToRelatedNodeIds.values().stream().flatMap(Collection::stream).toList(), queryFragments, elementIdOrIdFunction)));
|
||||
@@ -790,18 +795,23 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Flux<Tuple2<Collection<String>, Collection<String>>> iterateNextLevel(Collection<String> relatedNodeIds,
|
||||
RelationshipDescription sourceRelationshipDescription, QueryFragments queryFragments,
|
||||
Class<?> rootClass, PropertyPathWalkStep currentPathStep) {
|
||||
RelationshipDescription sourceRelationshipDescription, QueryFragments queryFragments,
|
||||
Class<?> rootClass, PropertyPathWalkStep currentPathStep) {
|
||||
|
||||
NodeDescription<?> target = sourceRelationshipDescription.getTarget();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
String fieldName = ((Association<Neo4jPersistentProperty>) sourceRelationshipDescription).getInverse().getFieldName();
|
||||
String fieldName = ((Association<@NonNull Neo4jPersistentProperty>) sourceRelationshipDescription).getInverse().getFieldName();
|
||||
|
||||
PropertyPathWalkStep nextPathStep = currentPathStep.with((sourceRelationshipDescription.hasRelationshipProperties() ?
|
||||
fieldName + "." + ((Neo4jPersistentEntity<?>) sourceRelationshipDescription.getRelationshipPropertiesEntity())
|
||||
.getPersistentProperty(TargetNode.class).getFieldName() : fieldName));
|
||||
PropertyPathWalkStep nextPathStep;
|
||||
if (sourceRelationshipDescription.hasRelationshipProperties()) {
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) sourceRelationshipDescription.getRequiredRelationshipPropertiesEntity();
|
||||
nextPathStep = currentPathStep.with(fieldName + "." + Objects.requireNonNull(relationshipPropertiesEntity.getPersistentProperty(TargetNode.class), () -> "Could not get target node property on %s".formatted(relationshipPropertiesEntity.getType())).getFieldName());
|
||||
} else {
|
||||
nextPathStep = currentPathStep.with(fieldName);
|
||||
}
|
||||
|
||||
return Flux.fromIterable(target
|
||||
.getRelationshipsInHierarchy(
|
||||
@@ -829,16 +839,12 @@ public final class ReactiveNeo4jTemplate implements
|
||||
return Tuples.of(newRelationshipIds, newRelatedNodeIds);
|
||||
})
|
||||
.one()
|
||||
.map((t) -> {
|
||||
//noinspection unchecked
|
||||
return (Tuple2<Collection<String>, Collection<String>>) t;
|
||||
})
|
||||
.map((t) -> (Tuple2<Collection<String>, Collection<String>>) t)
|
||||
.expand(object -> iterateAndMapNextLevel(relDe, queryFragments, rootClass, nextPathStep).apply(object));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Function<Tuple2<Collection<String>, Collection<String>>,
|
||||
Publisher<Tuple2<Collection<String>, Collection<String>>>> iterateAndMapNextLevel(
|
||||
RelationshipDescription relationshipDescription, QueryFragments queryFragments, Class<?> rootClass, PropertyPathWalkStep currentPathStep) {
|
||||
@@ -900,6 +906,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
stateMachine, knownRelationshipsIds, includeProperty, startingPropertyPath);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, PersistentPropertyAccessor<?> parentPropertyAccessor,
|
||||
boolean isParentObjectNew, NestedRelationshipProcessingStateMachine stateMachine,
|
||||
Collection<Object> knownRelationshipsIds,
|
||||
@@ -933,7 +940,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
idProperty = null;
|
||||
} else {
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity();
|
||||
idProperty = relationshipPropertiesEntity.getIdProperty();
|
||||
idProperty = relationshipPropertiesEntity == null ? null : relationshipPropertiesEntity.getIdProperty();
|
||||
}
|
||||
|
||||
// break recursive procession and deletion of previously created relationships
|
||||
@@ -950,12 +957,13 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
if (idProperty != null) {
|
||||
for (Object relatedValueToStore : relatedValuesToStore) {
|
||||
//noinspection ConstantValue
|
||||
if (relatedValueToStore == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object id = relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
|
||||
Object id = Objects.requireNonNull(relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore))
|
||||
.getProperty(idProperty);
|
||||
if (id != null) {
|
||||
knownRelationshipsIds.add(id);
|
||||
@@ -967,7 +975,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
relationshipDeleteMonos.add(
|
||||
neo4jClient.query(renderer.render(relationshipRemoveQuery))
|
||||
.bind(convertIdValues(sourceEntity.getIdProperty(), fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getIdProperty(), fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(knownRelationshipsIds) //
|
||||
.to(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM) //
|
||||
@@ -1016,7 +1024,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
queryOrSave = savedEntity
|
||||
.map(entity -> Tuples.of(new AtomicReference<>((Object) (TemplateSupport.rendererCanUseElementIdIfPresent(renderer, targetEntity) ? entity.elementId() : entity.id())), new AtomicReference<>(entity)))
|
||||
.doOnNext(t -> {
|
||||
var relatedInternalId = t.getT1().get();
|
||||
var relatedInternalId = Objects.requireNonNull(t.getT1().get(), "Related internal id is null");
|
||||
stateMachine.markEntityAsProcessed(relatedValueToStore, relatedInternalId);
|
||||
if (relatedValueToStore instanceof MappingSupport.RelationshipPropertiesWithEntityHolder) {
|
||||
Object entity = ((MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValueToStore).getRelatedEntity();
|
||||
@@ -1031,7 +1039,9 @@ public final class ReactiveNeo4jTemplate implements
|
||||
Neo4jPersistentProperty requiredIdProperty = targetEntity.getRequiredIdProperty();
|
||||
PersistentPropertyAccessor<?> targetPropertyAccessor = targetEntity.getPropertyAccessor(newRelatedObject);
|
||||
Object possibleInternalLongId = targetPropertyAccessor.getProperty(requiredIdProperty);
|
||||
//noinspection OptionalOfNullableMisuse
|
||||
relatedInternalId = TemplateSupport.retrieveOrSetRelatedId(targetEntity, targetPropertyAccessor, Optional.ofNullable(savedEntity), relatedInternalId);
|
||||
//noinspection ConstantValue
|
||||
if (savedEntity != null) {
|
||||
TemplateSupport.updateVersionPropertyIfPossible(targetEntity, targetPropertyAccessor, savedEntity);
|
||||
}
|
||||
@@ -1039,21 +1049,22 @@ public final class ReactiveNeo4jTemplate implements
|
||||
stateMachine.markRelationshipAsProcessed(possibleInternalLongId == null ? relatedInternalId : possibleInternalLongId,
|
||||
relationshipDescription.getRelationshipObverse());
|
||||
|
||||
Object idValue = idProperty != null
|
||||
? relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore).getProperty(idProperty)
|
||||
PersistentPropertyAccessor<?> relationshipPropertiesPropertyAccessor = relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore);
|
||||
Object idValue = (idProperty != null && relationshipPropertiesPropertyAccessor != null)
|
||||
? relationshipPropertiesPropertyAccessor.getProperty(idProperty)
|
||||
: null;
|
||||
|
||||
boolean isNewRelationship = idValue == null;
|
||||
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
|
||||
sourceEntity, relationshipDescription, relatedValueToStore, isNewRelationship, canUseElementId);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
|
||||
Map<String, @Nullable Object> properties = new HashMap<>();
|
||||
properties.put(Constants.FROM_ID_PARAMETER_NAME, TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getRequiredIdProperty(), fromId));
|
||||
properties.put(Constants.TO_ID_PARAMETER_NAME, relatedInternalId);
|
||||
properties.put(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM, idValue);
|
||||
var update = true;
|
||||
if (!relationshipDescription.isDynamic() && relationshipDescription.hasRelationshipProperties()) {
|
||||
if (!relationshipDescription.isDynamic() && relationshipDescription.hasRelationshipProperties() && fromId != null) {
|
||||
var hlp = ((MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValueToStore);
|
||||
var hasProcessedRelationshipEntity = stateMachine.hasProcessedRelationshipEntity(parentPropertyAccessor.getBean(), hlp.getRelatedEntity(), relationshipContext.getRelationship());
|
||||
if (hasProcessedRelationshipEntity) {
|
||||
@@ -1070,7 +1081,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
if (update) {
|
||||
return neo4jClient
|
||||
.query(renderer.render(statementHolder.getStatement()))
|
||||
.bind(convertIdValues(sourceEntity.getRequiredIdProperty(), fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, sourceEntity.getRequiredIdProperty(), fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(relatedInternalId) //
|
||||
.to(Constants.TO_ID_PARAMETER_NAME) //
|
||||
@@ -1081,9 +1092,8 @@ public final class ReactiveNeo4jTemplate implements
|
||||
.mappedBy((t, r) -> IdentitySupport.mapperForRelatedIdValues(idProperty).apply(r))
|
||||
.one()
|
||||
.flatMap(relationshipInternalId -> {
|
||||
if (idProperty != null && isNewRelationship) {
|
||||
relationshipContext
|
||||
.getRelationshipPropertiesPropertyAccessor(relatedValueToStore)
|
||||
if (idProperty != null && isNewRelationship && relationshipPropertiesPropertyAccessor != null) {
|
||||
relationshipPropertiesPropertyAccessor
|
||||
.setProperty(idProperty, relationshipInternalId);
|
||||
knownRelationshipsIds.add(relationshipInternalId);
|
||||
}
|
||||
@@ -1130,16 +1140,16 @@ public final class ReactiveNeo4jTemplate implements
|
||||
.thenMany(Flux.concat(relationshipCreationCreations))
|
||||
.doOnNext(objects -> objects.applyFinalResultToOwner(parentPropertyAccessor))
|
||||
.checkpoint()
|
||||
.then(stateMachine.updateRelationshipIds(this::getRelationshipId))
|
||||
.then(stateMachine.updateRelationshipIdsReactive(this::getRelationshipId))
|
||||
.then(Mono.fromSupplier(parentPropertyAccessor::getBean));
|
||||
return deleteAndThanCreateANew;
|
||||
|
||||
}
|
||||
|
||||
private Mono<Object> getRelationshipId(Statement statement, Neo4jPersistentProperty idProperty, Object fromId, Object toId) {
|
||||
private Mono<Object> getRelationshipId(Statement statement, @Nullable Neo4jPersistentProperty idProperty, Object fromId, Object toId) {
|
||||
|
||||
return neo4jClient.query(renderer.render(statement))
|
||||
.bind(convertIdValues(idProperty, fromId)) //
|
||||
.bind(TemplateSupport.convertIdValues(this.neo4jMappingContext, idProperty, fromId)) //
|
||||
.to(Constants.FROM_ID_PARAMETER_NAME) //
|
||||
.bind(toId) //
|
||||
.to(Constants.TO_ID_PARAMETER_NAME) //
|
||||
@@ -1150,10 +1160,10 @@ public final class ReactiveNeo4jTemplate implements
|
||||
|
||||
// The pendant to {@link #saveRelatedNode(Object, Neo4jPersistentEntity, PropertyFilter, PropertyFilter.RelaxedPropertyPath)}
|
||||
// We can't do without a query, as we need to refresh the internal id
|
||||
private Mono<Entity> loadRelatedNode(NodeDescription<?> targetNodeDescription, Object relatedInternalId) {
|
||||
private Mono<Entity> loadRelatedNode(NodeDescription<?> targetNodeDescription, @Nullable Object relatedInternalId) {
|
||||
|
||||
var targetPersistentEntity = (Neo4jPersistentEntity<?>) targetNodeDescription;
|
||||
var queryFragmentsAndParameters = QueryFragmentsAndParameters.forFindById(targetPersistentEntity, convertIdValues(targetPersistentEntity.getRequiredIdProperty(), relatedInternalId));
|
||||
var queryFragmentsAndParameters = QueryFragmentsAndParameters.forFindById(targetPersistentEntity, TemplateSupport.convertIdValues(this.neo4jMappingContext, targetPersistentEntity.getRequiredIdProperty(), relatedInternalId));
|
||||
var nodeName = Constants.NAME_OF_TYPED_ROOT_NODE.apply(targetNodeDescription).getValue();
|
||||
|
||||
return neo4jClient
|
||||
@@ -1175,14 +1185,14 @@ public final class ReactiveNeo4jTemplate implements
|
||||
DynamicLabels dynamicLabels = t.getT2();
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Object, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(entityType);
|
||||
String idPropertyName = targetNodeDescription.getIdProperty().getPropertyName();
|
||||
String idPropertyName = targetNodeDescription.getRequiredIdProperty().getPropertyName();
|
||||
IdDescription idDescription = targetNodeDescription.getIdDescription();
|
||||
boolean assignedId = idDescription.isAssignedId() || idDescription.isExternallyGeneratedId();
|
||||
boolean assignedId = idDescription != null && (idDescription.isAssignedId() || idDescription.isExternallyGeneratedId());
|
||||
binderFunction = binderFunction.andThen(tree -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
|
||||
|
||||
if (!includeProperty.isNotFiltering()) {
|
||||
if (properties != null && !includeProperty.isNotFiltering()) {
|
||||
properties.entrySet().removeIf(e -> {
|
||||
// we cannot skip the id property if it is an assigned id
|
||||
boolean isIdProperty = e.getKey().equals(idPropertyName);
|
||||
@@ -1219,7 +1229,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
boolean containsPossibleCircles = entityMetaData != null && entityMetaData.containsPossibleCircles(queryFragments::includeField);
|
||||
if (cypherQuery == null || containsPossibleCircles) {
|
||||
|
||||
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
|
||||
if (entityMetaData != null && containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
|
||||
return createNodesAndRelationshipsByIdStatementProvider(entityMetaData, queryFragments, finalParameters)
|
||||
.map(nodesAndRelationshipsById -> {
|
||||
var statement = nodesAndRelationshipsById.toStatement(entityMetaData);
|
||||
@@ -1256,7 +1266,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
this.eventSupport = ReactiveEventSupport.discoverCallbacks(neo4jMappingContext, beanFactory);
|
||||
|
||||
SpelAwareProxyProjectionFactory spelAwareProxyProjectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
spelAwareProxyProjectionFactory.setBeanClassLoader(beanClassLoader);
|
||||
spelAwareProxyProjectionFactory.setBeanClassLoader(Objects.requireNonNull(this.beanClassLoader));
|
||||
spelAwareProxyProjectionFactory.setBeanFactory(beanFactory);
|
||||
this.projectionFactory = spelAwareProxyProjectionFactory;
|
||||
|
||||
@@ -1324,7 +1334,7 @@ public final class ReactiveNeo4jTemplate implements
|
||||
@SuppressWarnings("unchecked")
|
||||
public Flux<T> getResults() {
|
||||
|
||||
return transactionalOperator.transactional(fetchSpec.all().switchOnFirst((signal, f) -> {
|
||||
return execute(fetchSpec.all().switchOnFirst((signal, f) -> {
|
||||
if (signal.hasValue() && preparedQuery.resultsHaveBeenAggregated()) {
|
||||
return f.concatMap(nested -> Flux.fromIterable((Collection<T>) nested).distinct()).distinct();
|
||||
}
|
||||
@@ -1337,14 +1347,14 @@ public final class ReactiveNeo4jTemplate implements
|
||||
* @throws IncorrectResultSizeDataAccessException if there is no or more than one result
|
||||
*/
|
||||
public Mono<T> getSingleResult() {
|
||||
return transactionalOperator.transactional(fetchSpec.one().map(t -> {
|
||||
return execute(fetchSpec.one().map(t -> {
|
||||
if (t instanceof LinkedHashSet) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T firstItem = (T) ((LinkedHashSet<?>) t).iterator().next();
|
||||
return firstItem;
|
||||
}
|
||||
return t;
|
||||
}).onErrorMap(IndexOutOfBoundsException.class, e -> new IncorrectResultSizeDataAccessException(e.getMessage(), 1)));
|
||||
}).onErrorMap(IndexOutOfBoundsException.class, e -> new IncorrectResultSizeDataAccessException(Objects.requireNonNull(e.getMessage()), 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
@@ -34,6 +35,8 @@ import java.util.Optional;
|
||||
@API(status = API.Status.INTERNAL, since = "6.1")
|
||||
final class RelationshipHandler {
|
||||
|
||||
private static final int DEFAULT_SIZE = 32;
|
||||
|
||||
enum Cardinality {
|
||||
|
||||
ONE_TO_ONE,
|
||||
@@ -42,7 +45,7 @@ final class RelationshipHandler {
|
||||
DYNAMIC_ONE_TO_MANY
|
||||
}
|
||||
|
||||
static RelationshipHandler forProperty(Neo4jPersistentProperty property, Object rawValue) {
|
||||
static RelationshipHandler forProperty(Neo4jPersistentProperty property, @Nullable Object rawValue) {
|
||||
|
||||
Cardinality cardinality;
|
||||
Collection<Object> newRelationshipObjectCollection = Collections.emptyList();
|
||||
@@ -51,13 +54,16 @@ final class RelationshipHandler {
|
||||
// Order is important here, all map based associations are dynamic, but not all dynamic associations are one to many
|
||||
if (property.isCollectionLike()) {
|
||||
cardinality = Cardinality.ONE_TO_MANY;
|
||||
newRelationshipObjectCollection = CollectionFactory.createCollection(property.getType(), ((Collection<?>) rawValue).size());
|
||||
var size = rawValue == null ? DEFAULT_SIZE : ((Collection<?>) rawValue).size();
|
||||
newRelationshipObjectCollection = CollectionFactory.createCollection(property.getType(), size);
|
||||
} else if (property.isDynamicOneToManyAssociation()) {
|
||||
cardinality = Cardinality.DYNAMIC_ONE_TO_MANY;
|
||||
newRelationshipObjectCollectionMap = CollectionFactory.createMap(property.getType(), ((Map<?, ?>) rawValue).size());
|
||||
var size = rawValue == null ? DEFAULT_SIZE : ((Map<?, ?>) rawValue).size();
|
||||
newRelationshipObjectCollectionMap = CollectionFactory.createMap(property.getType(), size);
|
||||
} else if (property.isDynamicAssociation()) {
|
||||
cardinality = Cardinality.DYNAMIC_ONE_TO_ONE;
|
||||
newRelationshipObjectCollectionMap = CollectionFactory.createMap(property.getType(), ((Map<?, ?>) rawValue).size());
|
||||
var size = rawValue == null ? DEFAULT_SIZE : ((Map<?, ?>) rawValue).size();
|
||||
newRelationshipObjectCollectionMap = CollectionFactory.createMap(property.getType(), size);
|
||||
} else {
|
||||
cardinality = Cardinality.ONE_TO_ONE;
|
||||
}
|
||||
@@ -69,6 +75,7 @@ final class RelationshipHandler {
|
||||
/**
|
||||
* The raw value as passed to the template.
|
||||
*/
|
||||
@Nullable
|
||||
private final Object rawValue;
|
||||
private final Cardinality cardinality;
|
||||
|
||||
@@ -76,7 +83,7 @@ final class RelationshipHandler {
|
||||
private final Map<Object, Object> newRelatedObjectsByType;
|
||||
|
||||
RelationshipHandler(Neo4jPersistentProperty property,
|
||||
Object rawValue, Cardinality cardinality,
|
||||
@Nullable Object rawValue, Cardinality cardinality,
|
||||
Collection<Object> newRelatedObjects,
|
||||
Map<Object, Object> newRelatedObjectsByType) {
|
||||
this.property = property;
|
||||
@@ -100,9 +107,12 @@ final class RelationshipHandler {
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> newCollection = (Collection<Object>) newRelatedObjectsByType
|
||||
.computeIfAbsent(key, k -> CollectionFactory.createCollection(
|
||||
property.getTypeInformation().getRequiredActualType().getType(),
|
||||
((Collection<?>) ((Map<?, ?>) rawValue).get(key)).size()));
|
||||
.computeIfAbsent(key, k -> {
|
||||
Collection<?> objects = rawValue == null ? null : (Collection<?>) ((Map<?, ?>) rawValue).get(key);
|
||||
return CollectionFactory.createCollection(
|
||||
property.getTypeInformation().getRequiredActualType().getType(),
|
||||
objects != null ? objects.size() : DEFAULT_SIZE);
|
||||
});
|
||||
newCollection.add(potentiallyRecreatedRelatedObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.NotificationClassification;
|
||||
import org.neo4j.driver.NotificationSeverity;
|
||||
import org.neo4j.driver.summary.InputPosition;
|
||||
@@ -29,7 +30,6 @@ import org.neo4j.driver.summary.Notification;
|
||||
import org.neo4j.driver.summary.Plan;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class for dealing with result summaries.
|
||||
|
||||
@@ -17,12 +17,12 @@ package org.springframework.data.neo4j.core;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Used to automatically map single valued records to a sensible Java type based on {@link Value#asObject()}.
|
||||
@@ -42,8 +42,8 @@ final class SingleValueMappingFunction<T> implements BiFunction<TypeSystem, Reco
|
||||
this.targetClass = targetClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@Nullable
|
||||
public T apply(TypeSystem typeSystem, Record record) {
|
||||
|
||||
if (record.size() == 0) {
|
||||
@@ -58,7 +58,7 @@ final class SingleValueMappingFunction<T> implements BiFunction<TypeSystem, Reco
|
||||
}
|
||||
|
||||
@Nullable
|
||||
T convertValue(@Nullable Value source) {
|
||||
T convertValue(Value source) {
|
||||
if (targetClass == Void.class || targetClass == void.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.Expression;
|
||||
import org.neo4j.cypherdsl.core.FunctionInvocation;
|
||||
@@ -45,6 +46,7 @@ import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.neo4j.cypherdsl.core.renderer.Dialect;
|
||||
import org.neo4j.cypherdsl.core.renderer.Renderer;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.Entity;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
@@ -62,7 +64,7 @@ import org.springframework.data.neo4j.core.mapping.PropertyFilter;
|
||||
import org.springframework.data.neo4j.core.mapping.PropertyTraverser;
|
||||
import org.springframework.data.neo4j.core.mapping.SpringDataCypherDsl;
|
||||
import org.springframework.data.neo4j.repository.query.QueryFragments;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -75,7 +77,6 @@ import org.springframework.util.Assert;
|
||||
@API(status = API.Status.INTERNAL, since = "6.0.9")
|
||||
public final class TemplateSupport {
|
||||
|
||||
|
||||
/**
|
||||
* Indicator for an empty collection
|
||||
*/
|
||||
@@ -98,7 +99,7 @@ public final class TemplateSupport {
|
||||
}
|
||||
|
||||
Collection<Class<?>> allClasses = StreamSupport.stream(collection.spliterator(), true)
|
||||
.filter(o -> o != null)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Object::getClass).collect(Collectors.toSet());
|
||||
|
||||
if (allClasses.isEmpty()) {
|
||||
@@ -156,8 +157,9 @@ public final class TemplateSupport {
|
||||
Entity newOrUpdatedNode
|
||||
) {
|
||||
if (entityMetaData.hasVersionProperty()) {
|
||||
var versionProperty = entityMetaData.getRequiredVersionProperty();
|
||||
propertyAccessor.setProperty(
|
||||
entityMetaData.getVersionProperty(), newOrUpdatedNode.get(entityMetaData.getVersionProperty().getPropertyName()).asLong());
|
||||
versionProperty, newOrUpdatedNode.get(versionProperty.getPropertyName()).asLong());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +170,7 @@ public final class TemplateSupport {
|
||||
* @param parameters The original parameters
|
||||
* @return Merged parameters
|
||||
*/
|
||||
static Map<String, Object> mergeParameters(Statement statement, @Nullable Map<String, Object> parameters) {
|
||||
static Map<String, Object> mergeParameters(Statement statement, Map<String, Object> parameters) {
|
||||
|
||||
Map<String, Object> mergedParameters = new HashMap<>(statement.getCatalog().getParameters());
|
||||
if (parameters != null) {
|
||||
@@ -205,7 +207,8 @@ public final class TemplateSupport {
|
||||
}
|
||||
|
||||
boolean hasRootNodeIds() {
|
||||
return parameters.get(ROOT_NODE_IDS).isEmpty();
|
||||
var ids = parameters.get(ROOT_NODE_IDS);
|
||||
return ids != null && !ids.isEmpty();
|
||||
}
|
||||
|
||||
Statement toStatement(NodeDescription<?> nodeDescription) {
|
||||
@@ -291,10 +294,10 @@ public final class TemplateSupport {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> properties = (Map<String, Object>) tree.get(Constants.NAME_OF_PROPERTIES_PARAM);
|
||||
|
||||
String idPropertyName = entityMetaData.getIdProperty().getPropertyName();
|
||||
String idPropertyName = entityMetaData.getRequiredIdProperty().getPropertyName();
|
||||
IdDescription idDescription = entityMetaData.getIdDescription();
|
||||
boolean assignedId = idDescription.isAssignedId() || idDescription.isExternallyGeneratedId();
|
||||
if (!includeProperty.isNotFiltering()) {
|
||||
boolean assignedId = idDescription != null && (idDescription.isAssignedId() || idDescription.isExternallyGeneratedId());
|
||||
if (!(includeProperty.isNotFiltering() || properties == null)) {
|
||||
properties.entrySet()
|
||||
.removeIf(e -> {
|
||||
// we cannot skip the id property if it is an assigned id
|
||||
@@ -317,7 +320,7 @@ public final class TemplateSupport {
|
||||
* @return A map as expected by the property filter.
|
||||
*/
|
||||
static <T> Collection<PropertyFilter.ProjectedPath> computeIncludedPropertiesFromPredicate(Neo4jMappingContext mappingContext,
|
||||
Class<T> domainType, @Nullable BiPredicate<PropertyPath, Neo4jPersistentProperty> predicate) {
|
||||
Class<T> domainType, BiPredicate<PropertyPath, Neo4jPersistentProperty> predicate) {
|
||||
if (predicate == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
@@ -357,6 +360,7 @@ public final class TemplateSupport {
|
||||
* @param databaseEntity A fallback entity to retrieve the deprecated internal long id
|
||||
* @param <T> The type of the entity
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
static <T> void setGeneratedIdIfNecessary(
|
||||
Neo4jPersistentEntity<?> entityMetaData,
|
||||
PersistentPropertyAccessor<T> propertyAccessor,
|
||||
@@ -388,10 +392,11 @@ public final class TemplateSupport {
|
||||
* @param <T> The type of the entity
|
||||
* @return The actual related internal id being used.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
static <T> Object retrieveOrSetRelatedId(
|
||||
Neo4jPersistentEntity<?> entityMetadata,
|
||||
PersistentPropertyAccessor<T> propertyAccessor,
|
||||
Optional<Entity> databaseEntity,
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional<Entity> databaseEntity,
|
||||
@Nullable Object relatedInternalId
|
||||
) {
|
||||
if (!entityMetadata.isUsingInternalIds()) {
|
||||
@@ -442,7 +447,11 @@ public final class TemplateSupport {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
static Object convertToLongIdOrStringElementId(Collection<String> ids) {
|
||||
@Nullable
|
||||
static Object convertToLongIdOrStringElementId(@Nullable Collection<String> ids) {
|
||||
if (ids == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ids.stream()
|
||||
.map(Long::valueOf).collect(Collectors.toSet());
|
||||
@@ -452,6 +461,22 @@ public final class TemplateSupport {
|
||||
}
|
||||
}
|
||||
|
||||
static Object convertIdValues(Neo4jMappingContext ctx, @Nullable Neo4jPersistentProperty idProperty, @Nullable Object idValues) {
|
||||
|
||||
if (idProperty != null && ((Neo4jPersistentEntity<?>) idProperty.getOwner()).isUsingInternalIds()) {
|
||||
return (idValues != null) ? idValues : Values.NULL;
|
||||
}
|
||||
|
||||
if (idValues != null) {
|
||||
return ctx.getConversionService().writeValue(idValues, TypeInformation.of(idValues.getClass()), idProperty == null ? null : idProperty.getOptionalConverter());
|
||||
} else if (idProperty != null) {
|
||||
return ctx.getConversionService().writeValue(idValues, idProperty.getTypeInformation(), idProperty.getOptionalConverter());
|
||||
} else {
|
||||
// Not much we can convert here
|
||||
return Values.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateSupport() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.data.neo4j.core;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,8 @@ public final class UserSelection {
|
||||
return new UserSelection(value);
|
||||
}
|
||||
|
||||
@Nullable private final String value;
|
||||
@Nullable
|
||||
private final String value;
|
||||
|
||||
private UserSelection(@Nullable String value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -33,10 +33,12 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.exceptions.value.LossyCoercion;
|
||||
@@ -267,7 +269,8 @@ final class AdditionalTypes {
|
||||
|
||||
@SuppressWarnings({"raw", "unchecked"}) // Due to dynamic enum retrieval
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
@Nullable
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (source == null) {
|
||||
return Value.class.isAssignableFrom(targetType.getType()) ? Values.NULL : null;
|
||||
@@ -299,6 +302,7 @@ final class AdditionalTypes {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return null;
|
||||
}
|
||||
@@ -315,12 +319,14 @@ final class AdditionalTypes {
|
||||
}
|
||||
|
||||
private static boolean describesSupportedEnumVariant(TypeDescriptor typeDescriptor) {
|
||||
var elementTypeDescriptor = typeDescriptor.getElementTypeDescriptor();
|
||||
return typeDescriptor.isArray()
|
||||
&& Enum.class.isAssignableFrom(typeDescriptor.getElementTypeDescriptor().getType());
|
||||
&& elementTypeDescriptor != null && Enum.class.isAssignableFrom(elementTypeDescriptor.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object object, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
@Nullable
|
||||
public Object convert(@Nullable Object object, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (object == null) {
|
||||
return Value.class.isAssignableFrom(targetType.getType()) ? Values.NULL : null;
|
||||
@@ -329,17 +335,17 @@ final class AdditionalTypes {
|
||||
if (Value.class.isAssignableFrom(sourceType.getType())) {
|
||||
Value source = (Value) object;
|
||||
|
||||
TypeDescriptor elementTypeDescriptor = targetType.getElementTypeDescriptor();
|
||||
TypeDescriptor elementTypeDescriptor = Objects.requireNonNull(targetType.getElementTypeDescriptor());
|
||||
Object[] targetArray = (Object[]) Array.newInstance(elementTypeDescriptor.getType(), source.size());
|
||||
|
||||
Arrays.setAll(targetArray,
|
||||
i -> delegate.convert(source.get(i), TypeDescriptor.valueOf(Value.class), elementTypeDescriptor));
|
||||
return targetArray;
|
||||
} else {
|
||||
Enum[] source = (Enum[]) object;
|
||||
Enum<?>[] source = (Enum<?>[]) object;
|
||||
|
||||
return Values.value(Arrays.stream(source)
|
||||
.map(e -> delegate.convert(e, sourceType.getElementTypeDescriptor(), TypeDescriptor.valueOf(Value.class)))
|
||||
.map(e -> delegate.convert(e, Objects.requireNonNull(sourceType.getElementTypeDescriptor()), TypeDescriptor.valueOf(Value.class)))
|
||||
.toArray());
|
||||
}
|
||||
}
|
||||
@@ -357,6 +363,7 @@ final class AdditionalTypes {
|
||||
return Values.value(aFloat.toString());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Locale asLocale(Value value) {
|
||||
|
||||
return StringUtils.parseLocale(value.asString());
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
|
||||
@@ -73,11 +74,13 @@ public @interface ConvertWith {
|
||||
*/
|
||||
final class UnsetConverter implements Neo4jPersistentPropertyConverter<Object> {
|
||||
|
||||
@Override public Value write(Object source) {
|
||||
@Override public Value write(@Nullable Object source) {
|
||||
return Values.NULL;
|
||||
}
|
||||
|
||||
@Override public Object read(Value source) {
|
||||
@Override
|
||||
@Nullable
|
||||
public Object read(@Nullable Value source) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.data.neo4j.core.convert;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -28,10 +27,9 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
final class DefaultNeo4jPersistentPropertyConverterFactory implements Neo4jPersistentPropertyConverterFactory {
|
||||
|
||||
@Nullable
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
DefaultNeo4jPersistentPropertyConverterFactory(@Nullable BeanFactory beanFactory) {
|
||||
DefaultNeo4jPersistentPropertyConverterFactory(BeanFactory beanFactory) {
|
||||
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
package org.springframework.data.neo4j.core.convert;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.springframework.dao.TypeMismatchDataAccessException;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This service orchestrates a standard Spring conversion service with {@link org.springframework.data.neo4j.core.convert.Neo4jConversions} registered.
|
||||
@@ -40,7 +40,8 @@ public interface Neo4jConversionService {
|
||||
* @param <T> The type to be returned
|
||||
* @return The converted value
|
||||
*/
|
||||
@Nullable <T> T convert(Object source, Class<T> targetType);
|
||||
@Nullable
|
||||
<T> T convert(Object source, Class<T> targetType);
|
||||
|
||||
/**
|
||||
* Returns whether we have a custom conversion registered to read {@code sourceType} into a native type. The returned
|
||||
@@ -66,9 +67,7 @@ public interface Neo4jConversionService {
|
||||
* @throws TypeMismatchDataAccessException In case the value cannot be converted to the target type
|
||||
*/
|
||||
@Nullable
|
||||
Object readValue(
|
||||
@Nullable Value source, TypeInformation<?> targetType, @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride
|
||||
);
|
||||
Object readValue(@Nullable Value source, TypeInformation<?> targetType, @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride);
|
||||
|
||||
/**
|
||||
* Converts an {@link Object} to a driver's value object.
|
||||
@@ -77,9 +76,7 @@ public interface Neo4jConversionService {
|
||||
* @param sourceType The type information describing the target type.
|
||||
* @return A driver compatible value object.
|
||||
*/
|
||||
Value writeValue(
|
||||
@Nullable Object value, TypeInformation<?> sourceType, @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride
|
||||
);
|
||||
Value writeValue(@Nullable Object value, TypeInformation<?> sourceType, @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride);
|
||||
|
||||
/**
|
||||
* @param type A type that should be checked whether it's simple or not.
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
package org.springframework.data.neo4j.core.convert;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This interface represents a pair of methods capable of converting values of type {@code T} to and from {@link Value values}.
|
||||
@@ -41,5 +41,5 @@ public interface Neo4jPersistentPropertyConverter<T> {
|
||||
* @param source The value to read, never null or {@link org.neo4j.driver.Values#NULL}
|
||||
* @return The converted value, maybe null if {@code source} was equals to {@link org.neo4j.driver.Values#NULL}.
|
||||
*/
|
||||
@Nullable T read(Value source);
|
||||
@Nullable T read(@Nullable Value source);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ package org.springframework.data.neo4j.core.convert;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* You need to provide an implementation of this interface in case you want to store a property of an entity as separate
|
||||
|
||||
@@ -23,12 +23,12 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.IsoDuration;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This generic converter has been introduced to augment the {@link TemporalAmountAdapter} with the type information passed
|
||||
@@ -53,14 +53,14 @@ final class TemporalAmountConverter implements GenericConverter {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object convert(@Nullable Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (TemporalAmount.class.isAssignableFrom(sourceType.getType())) {
|
||||
return Values.value(value);
|
||||
}
|
||||
|
||||
boolean valueIsLiteralNullOrNullValue = value == null || value == Values.NULL;
|
||||
Object convertedValue = valueIsLiteralNullOrNullValue ? null : adapter.apply(((Value) value).asIsoDuration());
|
||||
Object convertedValue = value == null || value == Values.NULL ? null : adapter.apply(((Value) value).asIsoDuration());
|
||||
|
||||
if (convertedValue instanceof IsoDuration && isZero((IsoDuration) convertedValue)) {
|
||||
if (Period.class.isAssignableFrom(targetType.getType())) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
converters.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.core.convert;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.data.neo4j.core.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* The {@link CreateRelationshipStatementHolder} holds the Cypher Statement to create a relationship as well as the optional
|
||||
@@ -40,7 +39,7 @@ public final class CreateRelationshipStatementHolder {
|
||||
private final Statement statement;
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
CreateRelationshipStatementHolder(@NonNull Statement statement, @NonNull Map<String, Object> properties) {
|
||||
CreateRelationshipStatementHolder(Statement statement, Map<String, Object> properties) {
|
||||
this.statement = statement;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
@@ -39,6 +40,7 @@ import java.util.regex.Pattern;
|
||||
import javax.lang.model.SourceVersion;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.Expression;
|
||||
@@ -55,6 +57,7 @@ import org.neo4j.cypherdsl.core.SortItem;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingMatchAndUpdate;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingWithoutWhere;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingUpdate;
|
||||
import org.neo4j.cypherdsl.core.SymbolicName;
|
||||
import org.neo4j.cypherdsl.core.renderer.Configuration;
|
||||
@@ -63,8 +66,6 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -131,15 +132,14 @@ public enum CypherGenerator {
|
||||
* @param condition Optional conditions to add
|
||||
* @return An ongoing match
|
||||
*/
|
||||
public StatementBuilder.OrderableOngoingReadingAndWith prepareMatchOf(NodeDescription<?> nodeDescription,
|
||||
@Nullable Condition condition) {
|
||||
@SuppressWarnings("deprecation")
|
||||
public StatementBuilder.OrderableOngoingReadingAndWith prepareMatchOf(NodeDescription<?> nodeDescription, @Nullable Condition condition) {
|
||||
|
||||
Node rootNode = createRootNode(nodeDescription);
|
||||
|
||||
List<IdentifiableElement> expressions = new ArrayList<>();
|
||||
expressions.add(rootNode.getRequiredSymbolicName());
|
||||
if (nodeDescription instanceof Neo4jPersistentEntity<?> entity && entity.isUsingDeprecatedInternalId()) {
|
||||
//noinspection deprecation
|
||||
expressions.add(rootNode.internalId().as(Constants.NAME_OF_INTERNAL_ID));
|
||||
}
|
||||
expressions.add(elementIdOrIdFunction.apply(rootNode).as(Constants.NAME_OF_ELEMENT_ID));
|
||||
@@ -148,11 +148,11 @@ public enum CypherGenerator {
|
||||
}
|
||||
|
||||
public StatementBuilder.OngoingReading prepareMatchOf(NodeDescription<?> nodeDescription,
|
||||
@Nullable List<PatternElement> initialMatchOn,
|
||||
List<PatternElement> initialMatchOn,
|
||||
@Nullable Condition condition) {
|
||||
Node rootNode = createRootNode(nodeDescription);
|
||||
|
||||
StatementBuilder.OngoingReadingWithoutWhere match = prepareMatchOfRootNode(rootNode, initialMatchOn);
|
||||
OngoingReadingWithoutWhere match = prepareMatchOfRootNode(rootNode, initialMatchOn);
|
||||
|
||||
List<IdentifiableElement> expressions = new ArrayList<>();
|
||||
expressions.add(Cypher.collect(elementIdOrIdFunction.apply(rootNode)).as(Constants.NAME_OF_SYNTHESIZED_ROOT_NODE));
|
||||
@@ -163,12 +163,11 @@ public enum CypherGenerator {
|
||||
}
|
||||
|
||||
public StatementBuilder.OngoingReading prepareMatchOf(NodeDescription<?> nodeDescription,
|
||||
RelationshipDescription relationshipDescription, @Nullable List<PatternElement> initialMatchOn,
|
||||
@Nullable Condition condition) {
|
||||
RelationshipDescription relationshipDescription, @Nullable List<PatternElement> initialMatchOn, @Nullable Condition condition) {
|
||||
|
||||
Node rootNode = createRootNode(nodeDescription);
|
||||
|
||||
StatementBuilder.OngoingReadingWithoutWhere match = prepareMatchOfRootNode(rootNode, initialMatchOn);
|
||||
OngoingReadingWithoutWhere match = prepareMatchOfRootNode(rootNode, initialMatchOn);
|
||||
|
||||
Node targetNode = node(relationshipDescription.getTarget().getPrimaryLabel(),
|
||||
relationshipDescription.getTarget().getAdditionalLabels())
|
||||
@@ -202,7 +201,6 @@ public enum CypherGenerator {
|
||||
.with(expressions.toArray(IdentifiableElement[]::new));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Node createRootNode(NodeDescription<?> nodeDescription) {
|
||||
String primaryLabel = nodeDescription.getPrimaryLabel();
|
||||
List<String> additionalLabels = nodeDescription.getAdditionalLabels();
|
||||
@@ -210,11 +208,11 @@ public enum CypherGenerator {
|
||||
return node(primaryLabel, additionalLabels).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription));
|
||||
}
|
||||
|
||||
private StatementBuilder.OngoingReadingWithoutWhere prepareMatchOfRootNode(
|
||||
private OngoingReadingWithoutWhere prepareMatchOfRootNode(
|
||||
Node rootNode, @Nullable List<PatternElement> initialMatchOn
|
||||
) {
|
||||
|
||||
StatementBuilder.OngoingReadingWithoutWhere match = null;
|
||||
OngoingReadingWithoutWhere match = null;
|
||||
if (initialMatchOn == null || initialMatchOn.isEmpty()) {
|
||||
match = Cypher.match(rootNode);
|
||||
} else {
|
||||
@@ -226,7 +224,7 @@ public enum CypherGenerator {
|
||||
}
|
||||
}
|
||||
}
|
||||
return match;
|
||||
return Objects.requireNonNull(match);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,8 +237,7 @@ public enum CypherGenerator {
|
||||
*/
|
||||
public Statement createStatementReturningDynamicLabels(NodeDescription<?> nodeDescription) {
|
||||
|
||||
IdDescription idDescription = nodeDescription.getIdDescription();
|
||||
Assert.notNull(idDescription, "Cannot load specific nodes by id without a corresponding attribute");
|
||||
IdDescription idDescription = Objects.requireNonNull(nodeDescription.getIdDescription(), "Cannot load specific nodes by id without a corresponding attribute");
|
||||
|
||||
final Node rootNode = createRootNode(nodeDescription);
|
||||
|
||||
@@ -290,7 +287,7 @@ public enum CypherGenerator {
|
||||
Neo4jPersistentProperty property = (Neo4jPersistentProperty) idProperty;
|
||||
|
||||
Condition result = Cypher.noCondition();
|
||||
for (String key : property.getOptionalConverter().write(null).keys()) {
|
||||
for (String key : Objects.requireNonNull(property.getOptionalConverter()).write(null).keys()) {
|
||||
Property expression = Cypher.property(containerName, key);
|
||||
result = result.and(expression.isEqualTo(actualParameter.property(key)));
|
||||
}
|
||||
@@ -319,7 +316,7 @@ public enum CypherGenerator {
|
||||
return bs.returning(rootNode).build();
|
||||
};
|
||||
|
||||
if (!idDescription.isInternallyGeneratedId()) {
|
||||
if (idDescription != null && !idDescription.isInternallyGeneratedId()) {
|
||||
GraphPropertyDescription idPropertyDescription = ((Neo4jPersistentEntity<?>) nodeDescription).getRequiredIdProperty();
|
||||
|
||||
if (((Neo4jPersistentEntity<?>) nodeDescription).hasVersionProperty()) {
|
||||
@@ -403,6 +400,7 @@ public enum CypherGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public Statement prepareSaveOfMultipleInstancesOf(NodeDescription<?> nodeDescription) {
|
||||
|
||||
Assert.isTrue(!nodeDescription.isUsingInternalIds(),
|
||||
@@ -413,7 +411,7 @@ public enum CypherGenerator {
|
||||
IdDescription idDescription = nodeDescription.getIdDescription();
|
||||
|
||||
@SuppressWarnings("ConstantConditions") // We now already that the node is using internal ids, and as such, an IdDescription must be present
|
||||
String nameOfIdProperty = idDescription.getOptionalGraphPropertyName()
|
||||
String nameOfIdProperty = Optional.ofNullable(idDescription).flatMap(IdDescription::getOptionalGraphPropertyName)
|
||||
.orElseThrow(() -> new MappingException("External id does not correspond to a graph property"));
|
||||
|
||||
List<Expression> expressions = new ArrayList<>();
|
||||
@@ -431,9 +429,8 @@ public enum CypherGenerator {
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Statement prepareSaveOfRelationship(Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
RelationshipDescription relationship, @Nullable String dynamicRelationshipType, boolean canUseElementId) {
|
||||
RelationshipDescription relationship, String dynamicRelationshipType, boolean canUseElementId) {
|
||||
final Node startNode = neo4jPersistentEntity.isUsingInternalIds()
|
||||
? anyNode(START_NODE_NAME)
|
||||
: node(neo4jPersistentEntity.getPrimaryLabel(), neo4jPersistentEntity.getAdditionalLabels())
|
||||
@@ -457,7 +454,8 @@ public enum CypherGenerator {
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Function<Node, Expression> getNodeIdFunction(@Nullable Neo4jPersistentEntity<?> entity, boolean canUseElementId) {
|
||||
@SuppressWarnings("deprecation")
|
||||
private static Function<Node, Expression> getNodeIdFunction(Neo4jPersistentEntity<?> entity, boolean canUseElementId) {
|
||||
|
||||
Function<Node, Expression> startNodeIdFunction;
|
||||
var idProperty = entity.getRequiredIdProperty();
|
||||
@@ -473,7 +471,8 @@ public enum CypherGenerator {
|
||||
return startNodeIdFunction;
|
||||
}
|
||||
|
||||
private static Function<Node, Expression> getEndNodeIdFunction(@Nullable Neo4jPersistentEntity<?> entity, boolean canUseElementId) {
|
||||
@SuppressWarnings("deprecation")
|
||||
private static Function<Node, Expression> getEndNodeIdFunction(Neo4jPersistentEntity<?> entity, boolean canUseElementId) {
|
||||
|
||||
Function<Node, Expression> startNodeIdFunction;
|
||||
if (entity == null) {
|
||||
@@ -505,7 +504,6 @@ public enum CypherGenerator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Statement prepareSaveOfRelationships(Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
RelationshipDescription relationship, @Nullable String dynamicRelationshipType, boolean canUseElementId) {
|
||||
|
||||
@@ -518,7 +516,7 @@ public enum CypherGenerator {
|
||||
|
||||
String type = relationship.isDynamic() ? dynamicRelationshipType : relationship.getType();
|
||||
Relationship relationshipFragment = (relationship.isOutgoing() ?
|
||||
startNode.relationshipTo(endNode, type) :
|
||||
startNode.relationshipTo(endNode, type) : // CypherDSL is fine with a null type
|
||||
startNode.relationshipFrom(endNode, type)).named(RELATIONSHIP_NAME);
|
||||
|
||||
String row = "relationship";
|
||||
@@ -534,7 +532,6 @@ public enum CypherGenerator {
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Statement prepareSaveOfRelationshipWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
RelationshipDescription relationship,
|
||||
boolean isNew,
|
||||
@@ -582,7 +579,6 @@ public enum CypherGenerator {
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Statement prepareUpdateOfRelationshipsWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
RelationshipDescription relationship, boolean isNew, boolean canUseElementId) {
|
||||
|
||||
@@ -638,7 +634,6 @@ public enum CypherGenerator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Statement prepareDeleteOf(
|
||||
Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
RelationshipDescription relationshipDescription,
|
||||
@@ -683,13 +678,14 @@ public enum CypherGenerator {
|
||||
* @param sort The {@link Sort sort} that should be turned into a valid Cypher {@code ORDER}-clause
|
||||
* @return An optional order clause. Will be {@literal null} on sorts that are {@literal null} or unsorted.
|
||||
*/
|
||||
public @Nullable String createOrderByFragment(@Nullable Sort sort) {
|
||||
@Nullable
|
||||
public String createOrderByFragment(@Nullable Sort sort) {
|
||||
|
||||
if (sort == null || sort.isUnsorted()) {
|
||||
return null;
|
||||
}
|
||||
Statement statement = match(anyNode()).returning("n")
|
||||
.orderBy(sort.stream().filter(Objects::nonNull).map(order -> {
|
||||
.orderBy(sort.stream().map(order -> {
|
||||
String property = order.getProperty().trim();
|
||||
Expression expression;
|
||||
if (LOOKS_LIKE_A_FUNCTION.matcher(property).matches()) {
|
||||
@@ -711,12 +707,12 @@ public enum CypherGenerator {
|
||||
try {
|
||||
Assert.isTrue(SourceVersion.isIdentifier(property), "Name must be a valid identifier.");
|
||||
expression = Cypher.name(property);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (e.getMessage().endsWith(".")) {
|
||||
throw new IllegalArgumentException(
|
||||
e.getMessage().substring(0, e.getMessage().length() - 1));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
var msg = Optional.ofNullable(ex.getMessage()).orElse("");
|
||||
if (msg.endsWith(".")) {
|
||||
throw new IllegalArgumentException(msg.substring(0, msg.length() - 1));
|
||||
}
|
||||
throw e;
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
if (order.isIgnoreCase()) {
|
||||
@@ -750,7 +746,6 @@ public enum CypherGenerator {
|
||||
nodeDescription,
|
||||
Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription),
|
||||
includeField,
|
||||
null,
|
||||
processedRelationships));
|
||||
Collections.addAll(returnContent, additionalExpressions);
|
||||
return returnContent;
|
||||
@@ -767,18 +762,18 @@ public enum CypherGenerator {
|
||||
}
|
||||
|
||||
|
||||
public StatementBuilder.OngoingReading prepareFindOf(NodeDescription<?> nodeDescription, @Nullable List<PatternElement> initialMatchOn, @Nullable Condition condition) {
|
||||
public StatementBuilder.OngoingReading prepareFindOf(NodeDescription<?> nodeDescription, List<PatternElement> initialMatchOn, @Nullable Condition condition) {
|
||||
var rootNode = createRootNode(nodeDescription);
|
||||
return prepareMatchOfRootNode(rootNode, initialMatchOn).where(conditionOrNoCondition(condition));
|
||||
}
|
||||
|
||||
private MapProjection projectPropertiesAndRelationships(PropertyFilter.RelaxedPropertyPath parentPath, Neo4jPersistentEntity<?> nodeDescription, SymbolicName nodeName,
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> includedProperties, @Nullable RelationshipDescription relationshipDescription, List<RelationshipDescription> processedRelationships, Expression... additionalExpressions) {
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> includedProperties, @Nullable List<RelationshipDescription> processedRelationships) {
|
||||
|
||||
Collection<RelationshipDescription> relationships = ((DefaultNeo4jPersistentEntity<?>) nodeDescription).getRelationshipsInHierarchy(includedProperties, parentPath);
|
||||
relationships.removeIf(r -> !includedProperties.test(parentPath.append(r.getFieldName())));
|
||||
|
||||
List<Object> propertiesProjection = projectNodeProperties(parentPath, nodeDescription, nodeName, relationshipDescription, includedProperties);
|
||||
List<Object> propertiesProjection = projectNodeProperties(parentPath, nodeDescription, nodeName, includedProperties);
|
||||
List<Object> contentOfProjection = new ArrayList<>(propertiesProjection);
|
||||
|
||||
contentOfProjection.addAll(generateListsFor(parentPath, nodeDescription, relationships, nodeName, includedProperties, processedRelationships));
|
||||
@@ -790,8 +785,9 @@ public enum CypherGenerator {
|
||||
* this list can also contain two "keys" in a row. The {@link MapProjection} will take care to handle them as
|
||||
* self-reflecting fields. Example with self-reflection and explicit value: {@code n {.id, name: n.name}}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private List<Object> projectNodeProperties(PropertyFilter.RelaxedPropertyPath parentPath, NodeDescription<?> nodeDescription, SymbolicName nodeName,
|
||||
@Nullable RelationshipDescription relationshipDescription, Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
|
||||
List<Object> nodePropertiesProjection = new ArrayList<>();
|
||||
Node node = anyNode(nodeName);
|
||||
@@ -827,7 +823,6 @@ public enum CypherGenerator {
|
||||
nodePropertiesProjection.add(Cypher.labels(node));
|
||||
if (nodeDescription instanceof Neo4jPersistentEntity<?> entity && entity.isUsingDeprecatedInternalId()) {
|
||||
nodePropertiesProjection.add(Constants.NAME_OF_INTERNAL_ID);
|
||||
//noinspection deprecation
|
||||
nodePropertiesProjection.add(node.internalId());
|
||||
}
|
||||
nodePropertiesProjection.add(Constants.NAME_OF_ELEMENT_ID);
|
||||
@@ -839,9 +834,10 @@ public enum CypherGenerator {
|
||||
* @see CypherGenerator#projectNodeProperties
|
||||
*/
|
||||
private List<Object> generateListsFor(PropertyFilter.RelaxedPropertyPath parentPath, Neo4jPersistentEntity<?> nodeDescription, Collection<RelationshipDescription> relationships, SymbolicName nodeName,
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> includedProperties, List<RelationshipDescription> processedRelationships) {
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> includedProperties, @Nullable List<RelationshipDescription> processedRelationships) {
|
||||
|
||||
List<Object> mapProjectionLists = new ArrayList<>();
|
||||
List<RelationshipDescription> processed = Objects.requireNonNullElseGet(processedRelationships, ArrayList::new);
|
||||
|
||||
for (RelationshipDescription relationshipDescription : relationships) {
|
||||
|
||||
@@ -850,11 +846,11 @@ public enum CypherGenerator {
|
||||
// if we already processed the other way before, do not try to jump in the infinite loop
|
||||
// unless it is a root node relationship
|
||||
if (relationshipDescription.hasRelationshipObverse()
|
||||
&& processedRelationships.contains(relationshipDescription.getRelationshipObverse())) {
|
||||
&& processed.contains(relationshipDescription.getRelationshipObverse())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
generateListFor(parentPath, nodeDescription, relationshipDescription, nodeName, processedRelationships, fieldName, mapProjectionLists, includedProperties);
|
||||
generateListFor(parentPath, nodeDescription, relationshipDescription, nodeName, processed, fieldName, mapProjectionLists, includedProperties);
|
||||
}
|
||||
|
||||
return mapProjectionLists;
|
||||
@@ -876,10 +872,15 @@ public enum CypherGenerator {
|
||||
Neo4jPersistentEntity<?> endNodeDescription = (Neo4jPersistentEntity<?>) relationshipDescription.getTarget();
|
||||
|
||||
processedRelationships.add(relationshipDescription);
|
||||
PropertyFilter.RelaxedPropertyPath newParentPath = relationshipDescription.hasRelationshipProperties()
|
||||
? parentPath.append(relationshipDescription.getFieldName()).append(((Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity())
|
||||
.getPersistentProperty(TargetNode.class).getFieldName())
|
||||
: parentPath.append(relationshipDescription.getFieldName());
|
||||
PropertyFilter.RelaxedPropertyPath newParentPath;
|
||||
newParentPath = parentPath.append(relationshipDescription.getFieldName());
|
||||
if (relationshipDescription.hasRelationshipProperties()) {
|
||||
var persistentProperty = ((Neo4jPersistentEntity<?>) relationshipDescription.getRequiredRelationshipPropertiesEntity()).getPersistentProperty(TargetNode.class);
|
||||
if (persistentProperty != null) {
|
||||
newParentPath = newParentPath
|
||||
.append(persistentProperty.getFieldName());
|
||||
}
|
||||
}
|
||||
|
||||
if (relationshipDescription.isDynamic()) {
|
||||
Relationship relationship = relationshipDescription.isOutgoing()
|
||||
@@ -888,7 +889,7 @@ public enum CypherGenerator {
|
||||
relationship = relationship.named(relationshipTargetName);
|
||||
|
||||
MapProjection mapProjection = projectPropertiesAndRelationships(newParentPath, endNodeDescription, relationshipFieldName,
|
||||
includedProperties, relationshipDescription, new ArrayList<>(processedRelationships));
|
||||
includedProperties, new ArrayList<>(processedRelationships));
|
||||
|
||||
if (relationshipDescription.hasRelationshipProperties()) {
|
||||
relationship = relationship.named(relationshipSymbolicName);
|
||||
@@ -906,7 +907,7 @@ public enum CypherGenerator {
|
||||
: startNode.relationshipFrom(endNode, relationshipType);
|
||||
|
||||
MapProjection mapProjection = projectPropertiesAndRelationships(newParentPath, endNodeDescription, relationshipFieldName,
|
||||
includedProperties, relationshipDescription, new ArrayList<>(processedRelationships));
|
||||
includedProperties, new ArrayList<>(processedRelationships));
|
||||
|
||||
if (relationshipDescription.hasRelationshipProperties()) {
|
||||
relationship = relationship.named(relationshipSymbolicName);
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
@@ -32,7 +34,6 @@ import org.springframework.data.neo4j.core.convert.Neo4jConversionService;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
@@ -68,13 +69,12 @@ final class DefaultNeo4jConversionService implements Neo4jConversionService {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object readValue(@Nullable Value source, TypeInformation<?> targetType,
|
||||
@Nullable Neo4jPersistentPropertyConverter<?> conversionOverride) {
|
||||
public Object readValue(@Nullable Value source, TypeInformation<?> targetType, @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride) {
|
||||
|
||||
BiFunction<Value, Class<?>, Object> conversion;
|
||||
boolean applyConversionToCompleteCollection = false;
|
||||
if (conversionOverride == null) {
|
||||
conversion = (v, t) -> conversionService.convert(v, t);
|
||||
conversion = conversionService::convert;
|
||||
} else {
|
||||
applyConversionToCompleteCollection = conversionOverride instanceof NullSafeNeo4jPersistentPropertyConverter
|
||||
&& ((NullSafeNeo4jPersistentPropertyConverter<?>) conversionOverride).isForCollection();
|
||||
@@ -94,8 +94,10 @@ final class DefaultNeo4jConversionService implements Neo4jConversionService {
|
||||
Class<?> rawType = type.getType();
|
||||
|
||||
if (!valueIsLiteralNullOrNullValue && isCollection(type) && !applyConversionToCompleteCollection) {
|
||||
// value can't be null at this point in time
|
||||
@SuppressWarnings("NullAway")
|
||||
Collection<Object> target = CollectionFactory
|
||||
.createCollection(rawType, type.getComponentType().getType(), value.size());
|
||||
.createCollection(rawType, Objects.requireNonNull(type.getComponentType()).getType(), value.size());
|
||||
value.values()
|
||||
.forEach(element -> target.add(conversion.apply(element, type.getComponentType().getType())));
|
||||
return target;
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -35,6 +36,8 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
@@ -57,8 +60,6 @@ import org.springframework.data.neo4j.core.mapping.callback.EventSupport;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -107,18 +108,20 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <R> R read(Class<R> targetType, MapAccessor mapAccessor) {
|
||||
|
||||
knownObjects.nextRecord();
|
||||
labelNodeCache.clear();
|
||||
|
||||
@SuppressWarnings("unchecked") // ¯\_(ツ)_/¯
|
||||
Neo4jPersistentEntity<R> rootNodeDescription = (Neo4jPersistentEntity<R>) nodeDescriptionStore.getNodeDescription(targetType);
|
||||
Neo4jPersistentEntity<R> rootNodeDescription = Objects.requireNonNull((Neo4jPersistentEntity<R>) nodeDescriptionStore.getNodeDescription(targetType), () -> "Can't read an entity of type %s without description".formatted(targetType));
|
||||
MapAccessor queryRoot = determineQueryRoot(mapAccessor, rootNodeDescription, true);
|
||||
if (queryRoot == null) {
|
||||
throw new IllegalStateException("No query root");
|
||||
}
|
||||
|
||||
try {
|
||||
return queryRoot == null ? null : map(queryRoot, queryRoot, rootNodeDescription);
|
||||
return map(queryRoot, queryRoot, rootNodeDescription);
|
||||
} catch (Exception e) {
|
||||
throw new MappingException("Error mapping " + mapAccessor, e);
|
||||
}
|
||||
@@ -229,10 +232,15 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
@Override
|
||||
public void write(Object source, Map<String, Object> parameters) {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
Neo4jPersistentEntity<?> nodeDescription = (Neo4jPersistentEntity<?>) nodeDescriptionStore
|
||||
.getNodeDescription(source.getClass());
|
||||
if (nodeDescription == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
if (nodeDescription.hasRelationshipPropertyPersistTypeInfoFlag()) {
|
||||
// add type info when write to the database
|
||||
properties.put(Constants.NAME_OF_RELATIONSHIP_TYPE, nodeDescription.getPrimaryLabel());
|
||||
@@ -275,7 +283,6 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
Neo4jPersistentProperty vectorProperty = nodeDescription.getRequiredVectorProperty();
|
||||
parameters.put(Constants.NAME_OF_VECTOR_PROPERTY, vectorProperty.getPropertyName());
|
||||
parameters.put(Constants.NAME_OF_VECTOR_VALUE, conversionService.writeValue(propertyAccessor.getProperty(vectorProperty), vectorProperty.getTypeInformation(), vectorProperty.getOptionalConverter()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +294,9 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
* @param record Record that should be merged
|
||||
* @return A map accessor combining a {@link Node} and an arbitrary record
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private static MapAccessor mergeRootNodeWithRecord(Node node, MapAccessor record) {
|
||||
Map<String, Object> mergedAttributes = new HashMap<>(node.size() + record.size() + 1);
|
||||
Map<String, @Nullable Object> mergedAttributes = new HashMap<>(node.size() + record.size() + 1);
|
||||
|
||||
mergedAttributes.put(Constants.NAME_OF_INTERNAL_ID, IdentitySupport.getInternalId(node));
|
||||
mergedAttributes.put(Constants.NAME_OF_ELEMENT_ID, node.elementId());
|
||||
@@ -312,8 +320,9 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
return map(queryResult, nodeDescription, nodeDescription, null, null, relationshipsFromResult, nodesFromResult);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescription, NodeDescription<?> genericTargetNodeDescription,
|
||||
@Nullable Object lastMappedEntity, @Nullable RelationshipDescription relationshipDescription, Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult) {
|
||||
@Nullable Object lastMappedEntity, @Nullable RelationshipDescription relationshipDescription, @Nullable Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult) {
|
||||
|
||||
// prior to SDN 7 local `getInternalId` didn't check relationships, so in that case, they have never been a known
|
||||
// object. The centralized methods checks those too now. The condition is to recreate the old behaviour without
|
||||
@@ -339,7 +348,8 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, instance, lastMappedEntity, relationshipsFromResult, nodesFromResult, false);
|
||||
|
||||
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(getMostCurrentInstance(internalId, instance));
|
||||
var mostCurrentInstance = Objects.requireNonNull(getMostCurrentInstance(internalId, instance), "Could not get the most current instance for the internal id %s".formatted(internalId));
|
||||
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(mostCurrentInstance);
|
||||
ET bean = propertyAccessor.getBean();
|
||||
bean = eventSupport.maybeCallAfterConvert(bean, concreteNodeDescription, queryResult);
|
||||
|
||||
@@ -367,7 +377,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
|
||||
}
|
||||
// due to a needed side effect in `populateProperties`, the entity might have been changed
|
||||
return getMostCurrentInstance(internalId, mappedObject);
|
||||
return Objects.requireNonNull(getMostCurrentInstance(internalId, mappedObject), "Could not get mapped instance for internal id %s".formatted(internalId));
|
||||
}
|
||||
|
||||
private boolean hasMoreFields(Map<String, Object> currentQueryResult, Set<Map<String, Object>> savedQueryResults) {
|
||||
@@ -384,35 +394,37 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
return !currentFields.isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <ET> ET getMostCurrentInstance(String internalId, ET fallbackInstance) {
|
||||
return (ET) (knownObjects.getObject(internalId) != null ? knownObjects.getObject(internalId) : fallbackInstance);
|
||||
private <ET> ET getMostCurrentInstance(@Nullable String internalId, @Nullable ET fallbackInstance) {
|
||||
return (ET) (internalId != null && knownObjects.getObject(internalId) != null ? knownObjects.getObject(internalId) : fallbackInstance);
|
||||
}
|
||||
|
||||
|
||||
private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEntity<ET> baseNodeDescription, Neo4jPersistentEntity<ET> moreConcreteNodeDescription, String internalId,
|
||||
private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEntity<ET> baseNodeDescription, Neo4jPersistentEntity<ET> moreConcreteNodeDescription, @Nullable String internalId,
|
||||
ET mappedObject, @Nullable Object lastMappedEntity,
|
||||
Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult, boolean objectAlreadyMapped) {
|
||||
@Nullable Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult, boolean objectAlreadyMapped) {
|
||||
|
||||
List<String> allLabels = getLabels(queryResult, moreConcreteNodeDescription);
|
||||
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
|
||||
.deriveConcreteNodeDescription(moreConcreteNodeDescription, allLabels);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Neo4jPersistentEntity<ET> concreteNodeDescription = (Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels
|
||||
.getNodeDescription();
|
||||
Neo4jPersistentEntity<ET> concreteNodeDescription = Objects.requireNonNull((Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels
|
||||
.getNodeDescription(), "Couldn't find required node description");
|
||||
|
||||
if (!concreteNodeDescription.requiresPropertyPopulation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(mappedObject);
|
||||
Predicate<Neo4jPersistentProperty> isConstructorParameter = concreteNodeDescription
|
||||
.getInstanceCreatorMetadata()::isCreatorParameter;
|
||||
Predicate<Neo4jPersistentProperty> isConstructorParameter = parameter -> {
|
||||
var metadata = concreteNodeDescription.getInstanceCreatorMetadata();
|
||||
return metadata != null && metadata.isCreatorParameter(parameter);
|
||||
};
|
||||
|
||||
boolean isKotlinType = KotlinDetector.isKotlinType(concreteNodeDescription.getType());
|
||||
// Fill simple properties
|
||||
PropertyHandler<Neo4jPersistentProperty> handler = populateFrom(queryResult, propertyAccessor,
|
||||
PropertyHandler<@NonNull Neo4jPersistentProperty> handler = populateFrom(queryResult, propertyAccessor,
|
||||
isConstructorParameter, nodeDescriptionAndLabels.getDynamicLabels(), lastMappedEntity, isKotlinType, objectAlreadyMapped);
|
||||
PropertyHandlerSupport.of(concreteNodeDescription).doWithProperties(handler);
|
||||
// in a cyclic graph / with bidirectional relationships, we could end up in a state in which we
|
||||
@@ -422,10 +434,9 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
knownObjects.mappedWithQueryResult(internalId, queryResult);
|
||||
|
||||
AssociationHandlerSupport.of(concreteNodeDescription).doWithAssociations(
|
||||
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult, internalId));
|
||||
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Neo4jPersistentEntity<?> getMostConcreteTargetNodeDescription(
|
||||
Neo4jPersistentEntity<?> genericTargetNodeDescription, MapAccessor possibleValueNode) {
|
||||
|
||||
@@ -444,18 +455,16 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
* @param queryResult The complete query result
|
||||
* @return The list of labels defined by the query variable {@link Constants#NAME_OF_LABELS}.
|
||||
*/
|
||||
@NonNull
|
||||
private List<String> getLabels(MapAccessor queryResult, @Nullable NodeDescription<?> nodeDescription) {
|
||||
Value labelsValue = queryResult.get(Constants.NAME_OF_LABELS);
|
||||
List<String> labels = new ArrayList<>();
|
||||
if (!labelsValue.isNull()) {
|
||||
labels = labelsValue.asList(Value::asString);
|
||||
} else if (queryResult instanceof Node) {
|
||||
Node nodeRepresentation = (Node) queryResult;
|
||||
} else if (queryResult instanceof Node nodeRepresentation) {
|
||||
nodeRepresentation.labels().forEach(labels::add);
|
||||
} else if (queryResult instanceof Relationship) {
|
||||
Value value = queryResult.get(Constants.NAME_OF_RELATIONSHIP_TYPE);
|
||||
if (value.isNull()) {
|
||||
if (value.isNull() && nodeDescription != null) {
|
||||
labels.addAll(nodeDescription.getStaticLabels());
|
||||
} else {
|
||||
labels.add(value.asString());
|
||||
@@ -484,14 +493,16 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
private <ET> ET instantiate(Neo4jPersistentEntity<ET> nodeDescription, NodeDescription<?> genericNodeDescription, MapAccessor values,
|
||||
Collection<String> surplusLabels, @Nullable Object lastMappedEntity,
|
||||
Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult) {
|
||||
@Nullable Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult) {
|
||||
|
||||
ParameterValueProvider<Neo4jPersistentProperty> parameterValueProvider = new ParameterValueProvider<Neo4jPersistentProperty>() {
|
||||
ParameterValueProvider<@NonNull Neo4jPersistentProperty> parameterValueProvider = new ParameterValueProvider<>() {
|
||||
|
||||
@SuppressWarnings("unchecked") // Needed for the last cast. It's easier that way than using the parameter type info and checking for primitives
|
||||
@SuppressWarnings("unchecked")
|
||||
// Needed for the last cast. It's easier that way than using the parameter type info and checking for primitives
|
||||
@Override
|
||||
public <T> T getParameterValue(Parameter<T, Neo4jPersistentProperty> parameter) {
|
||||
Neo4jPersistentProperty matchingProperty = nodeDescription.getRequiredPersistentProperty(parameter.getName());
|
||||
@Nullable
|
||||
public <T> T getParameterValue(Parameter<T, @NonNull Neo4jPersistentProperty> parameter) {
|
||||
Neo4jPersistentProperty matchingProperty = nodeDescription.getRequiredPersistentProperty(Objects.requireNonNull(parameter.getName(), "Parameter names are not available"));
|
||||
|
||||
Object result;
|
||||
if (matchingProperty.isRelationship()) {
|
||||
@@ -499,7 +510,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
.filter(r -> {
|
||||
String propertyFieldName = matchingProperty.getFieldName();
|
||||
return r.getFieldName().equals(propertyFieldName);
|
||||
}).findFirst().get();
|
||||
}).findFirst().orElseThrow();
|
||||
// If we cannot find any value it does not mean that there isn't any.
|
||||
// The result set might contain associations not named CONCRETE_TYPE_TARGET but ABSTRACT_TYPE_TARGET.
|
||||
// For this we bubble up the hierarchy of NodeDescriptions.
|
||||
@@ -522,7 +533,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
} else if (matchingProperty.isEntityWithRelationshipProperties()) {
|
||||
result = lastMappedEntity;
|
||||
} else {
|
||||
result = conversionService.readValue(extractValueOf(matchingProperty, values), parameter.getType(), matchingProperty.getOptionalConverter());
|
||||
result = conversionService.readValue(extractValueOf(matchingProperty, values), parameter.getType(), matchingProperty.getOptionalConverter());
|
||||
}
|
||||
return (T) result;
|
||||
}
|
||||
@@ -531,7 +542,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
return entityInstantiators.getInstantiatorFor(nodeDescription).createInstance(nodeDescription, parameterValueProvider);
|
||||
}
|
||||
|
||||
private PropertyHandler<Neo4jPersistentProperty> populateFrom(MapAccessor queryResult,
|
||||
private PropertyHandler<@NonNull Neo4jPersistentProperty> populateFrom(MapAccessor queryResult,
|
||||
PersistentPropertyAccessor<?> propertyAccessor, Predicate<Neo4jPersistentProperty> isConstructorParameter,
|
||||
Collection<String> surplusLabels, @Nullable Object targetNode, boolean ownerIsKotlinType, boolean objectAlreadyMapped) {
|
||||
|
||||
@@ -561,15 +572,15 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object getValueOrDefault(boolean ownerIsKotlinType, Class<?> rawType, @Nullable Object value) {
|
||||
private static Object getValueOrDefault(boolean ownerIsKotlinType, Class<?> rawType, Object value) {
|
||||
|
||||
return value == null && !ownerIsKotlinType && rawType.isPrimitive() ? ReflectionUtils.getPrimitiveDefault(rawType) : value;
|
||||
}
|
||||
|
||||
private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor queryResult, NodeDescription<?> baseDescription,
|
||||
PersistentPropertyAccessor<?> propertyAccessor, Predicate<Neo4jPersistentProperty> isConstructorParameter,
|
||||
boolean objectAlreadyMapped, Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult, String internalId) {
|
||||
private AssociationHandler<@NonNull Neo4jPersistentProperty> populateFrom(MapAccessor queryResult, NodeDescription<?> baseDescription,
|
||||
PersistentPropertyAccessor<?> propertyAccessor, Predicate<Neo4jPersistentProperty> isConstructorParameter,
|
||||
boolean objectAlreadyMapped, @Nullable Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult
|
||||
) {
|
||||
|
||||
return association -> {
|
||||
|
||||
@@ -590,41 +601,35 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
Object propertyValue = propertyAccessor.getProperty(persistentProperty);
|
||||
|
||||
boolean propertyValueNotNull = propertyValue != null;
|
||||
if (propertyValue != null) {
|
||||
|
||||
boolean populatedCollection = objectAlreadyMapped && persistentProperty.isCollectionLike()
|
||||
&& propertyValueNotNull
|
||||
&& !((Collection<?>) propertyValue).isEmpty();
|
||||
boolean populatedCollection = objectAlreadyMapped && persistentProperty.isCollectionLike() && !((Collection<?>) propertyValue).isEmpty();
|
||||
boolean populatedMap = objectAlreadyMapped && persistentProperty.isMap() && !((Map<?, ?>) propertyValue).isEmpty();
|
||||
boolean populatedScalarValue = objectAlreadyMapped && !persistentProperty.isCollectionLike() && !persistentProperty.isMap();
|
||||
|
||||
boolean populatedMap = objectAlreadyMapped && persistentProperty.isMap()
|
||||
&& propertyValueNotNull
|
||||
&& !((Map<?, ?>) propertyValue).isEmpty();
|
||||
if (populatedCollection) {
|
||||
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult, false)
|
||||
.ifPresent(value -> {
|
||||
Collection<?> providedCollection = (Collection<?>) value;
|
||||
Collection<?> existingValue = (Collection<?>) propertyValue;
|
||||
Collection<Object> newValue = CollectionFactory.createCollection(existingValue.getClass(), providedCollection.size() + existingValue.size());
|
||||
|
||||
boolean populatedScalarValue = objectAlreadyMapped && !persistentProperty.isCollectionLike() && !persistentProperty.isMap()
|
||||
&& propertyValueNotNull;
|
||||
RelationshipDescription relationshipDescription = (RelationshipDescription) association;
|
||||
Map<Object, Object> mergedValues = new HashMap<>();
|
||||
mergeCollections(relationshipDescription, existingValue, mergedValues);
|
||||
mergeCollections(relationshipDescription, providedCollection, mergedValues);
|
||||
|
||||
if (populatedCollection) {
|
||||
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult, false)
|
||||
.ifPresent(value -> {
|
||||
Collection<?> providedCollection = (Collection<?>) value;
|
||||
Collection<?> existingValue = (Collection<?>) propertyValue;
|
||||
Collection<Object> newValue = CollectionFactory.createCollection(existingValue.getClass(), providedCollection.size() + existingValue.size());
|
||||
newValue.addAll(mergedValues.values());
|
||||
propertyAccessor.setProperty(persistentProperty, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
RelationshipDescription relationshipDescription = (RelationshipDescription) association;
|
||||
Map<Object, Object> mergedValues = new HashMap<>();
|
||||
mergeCollections(relationshipDescription, existingValue, mergedValues);
|
||||
mergeCollections(relationshipDescription, providedCollection, mergedValues);
|
||||
boolean propertyAlreadyPopulated = populatedCollection || populatedMap || populatedScalarValue;
|
||||
|
||||
newValue.addAll(mergedValues.values());
|
||||
propertyAccessor.setProperty(persistentProperty, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
boolean propertyAlreadyPopulated = populatedCollection || populatedMap || populatedScalarValue;
|
||||
|
||||
// avoid unnecessary re-assignment of values
|
||||
if (propertyAlreadyPopulated) {
|
||||
return;
|
||||
// avoid unnecessary re-assignment of values
|
||||
if (propertyAlreadyPopulated) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult)
|
||||
@@ -636,15 +641,17 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
private void mergeCollections(RelationshipDescription relationshipDescription, Collection<?> values, Map<Object, Object> mergedValues) {
|
||||
for (Object existingValueInCollection : values) {
|
||||
if (relationshipDescription.hasRelationshipProperties()) {
|
||||
Object existingIdPropertyValue = ((Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity())
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRequiredRelationshipPropertiesEntity();
|
||||
Object existingIdPropertyValue = relationshipPropertiesEntity
|
||||
.getPropertyAccessor(existingValueInCollection)
|
||||
.getProperty(((Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity()).getIdProperty());
|
||||
.getProperty(relationshipPropertiesEntity.getRequiredIdProperty());
|
||||
|
||||
mergedValues.put(existingIdPropertyValue, existingValueInCollection);
|
||||
} else if (!relationshipDescription.isDynamic()) { // should not happen because this is all inside populatedCollection (but better safe than sorry)
|
||||
Object existingIdPropertyValue = ((Neo4jPersistentEntity<?>) relationshipDescription.getTarget())
|
||||
Neo4jPersistentEntity<?> target = (Neo4jPersistentEntity<?>) relationshipDescription.getTarget();
|
||||
Object existingIdPropertyValue = target
|
||||
.getPropertyAccessor(existingValueInCollection)
|
||||
.getProperty(((Neo4jPersistentEntity<?>) relationshipDescription.getTarget()).getIdProperty());
|
||||
.getProperty(target.getRequiredIdProperty());
|
||||
|
||||
mergedValues.put(existingIdPropertyValue, existingValueInCollection);
|
||||
}
|
||||
@@ -652,14 +659,15 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
}
|
||||
|
||||
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
|
||||
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
|
||||
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, @Nullable Collection<Relationship> relationshipsFromResult,
|
||||
Collection<Node> nodesFromResult) {
|
||||
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, relationshipsFromResult, nodesFromResult, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
|
||||
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
|
||||
Collection<Node> nodesFromResult, boolean fetchMore) {
|
||||
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, @Nullable Collection<Relationship> relationshipsFromResult,
|
||||
Collection<Node> nodesFromResult, boolean fetchMore) {
|
||||
|
||||
String typeOfRelationship = relationshipDescription.getType();
|
||||
String targetLabel = relationshipDescription.getTarget().getPrimaryLabel();
|
||||
@@ -672,8 +680,9 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
BiConsumer<String, Object> mappedObjectHandler;
|
||||
Function<String, ?> keyTransformer;
|
||||
if (persistentProperty.isDynamicAssociation() && persistentProperty.getComponentType().isEnum()) {
|
||||
keyTransformer = f -> conversionService.convert(f, persistentProperty.getComponentType());
|
||||
Class<?> componentType = persistentProperty.getComponentType();
|
||||
if (persistentProperty.isDynamicAssociation() && (componentType != null && componentType.isEnum())) {
|
||||
keyTransformer = f -> conversionService.convert(f, componentType);
|
||||
} else {
|
||||
keyTransformer = Function.identity();
|
||||
}
|
||||
@@ -747,7 +756,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
// and therefor should not be in the list.
|
||||
// Otherwise, for highly linked data it could potentially cause a StackOverflowError.
|
||||
String direction = relationshipDescription.getDirection().name();
|
||||
if (knownObjects.hasProcessedRelationshipCompletely("R" + direction + IdentitySupport.getElementId(possibleRelationship))) {
|
||||
if (relationshipsFromResult != null && knownObjects.hasProcessedRelationshipCompletely("R" + direction + IdentitySupport.getElementId(possibleRelationship))) {
|
||||
relationshipsFromResult.remove(possibleRelationship);
|
||||
}
|
||||
// If the target is the same(equal) node, get the related object from the cache.
|
||||
@@ -769,7 +778,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
if (relationshipDescription.hasRelationshipProperties()) {
|
||||
Object relationshipProperties;
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity();
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRequiredRelationshipPropertiesEntity();
|
||||
if (fetchMore) {
|
||||
relationshipProperties = map(possibleRelationship, relationshipPropertiesEntity, relationshipPropertiesEntity, mappedObject, relationshipDescription, relationshipsFromResult, nodesFromResult);
|
||||
} else {
|
||||
@@ -813,7 +822,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
.asRelationship();
|
||||
|
||||
Object relationshipProperties;
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity();
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationshipDescription.getRequiredRelationshipPropertiesEntity();
|
||||
if (fetchMore) {
|
||||
relationshipProperties = map(relatedEntityRelationship, relationshipPropertiesEntity, relationshipPropertiesEntity, valueEntry, relationshipDescription, relationshipsFromResult, nodesFromResult);
|
||||
} else {
|
||||
@@ -834,7 +843,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
if (persistentProperty.getTypeInformation().isCollectionLike()) {
|
||||
List<Object> returnedValues = relationshipDescription.hasRelationshipProperties() ? relationshipsAndProperties : value;
|
||||
Collection<Object> target = CollectionFactory.createCollection(persistentProperty.getRawType(), persistentProperty.getComponentType(), returnedValues.size());
|
||||
Collection<Object> target = CollectionFactory.createCollection(persistentProperty.getRawType(), componentType, returnedValues.size());
|
||||
target.addAll(returnedValues);
|
||||
return Optional.of(target);
|
||||
} else {
|
||||
@@ -874,14 +883,14 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
return allNodesInResult;
|
||||
}
|
||||
|
||||
private Collection<Relationship> extractMatchingRelationships(Collection<Relationship> relationshipsFromResult,
|
||||
private Collection<Relationship> extractMatchingRelationships(@Nullable Collection<Relationship> relationshipsFromResult,
|
||||
RelationshipDescription relationshipDescription, String typeOfRelationship,
|
||||
Predicate<Relationship> relationshipPredicate) {
|
||||
|
||||
Predicate<Relationship> onlyWithMatchingType = r -> r.type().equals(typeOfRelationship) || relationshipDescription.isDynamic();
|
||||
return relationshipsFromResult.stream()
|
||||
return (relationshipsFromResult != null) ? relationshipsFromResult.stream()
|
||||
.filter(onlyWithMatchingType.and(relationshipPredicate))
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toList()) : List.of();
|
||||
}
|
||||
|
||||
private Collection<Relationship> extractRelationships(MapAccessor allValues) {
|
||||
@@ -898,6 +907,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
return allRelationshipsInResult;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static Value extractValueOf(Neo4jPersistentProperty property, MapAccessor propertyContainer) {
|
||||
if (property.isInternalIdProperty()) {
|
||||
if (Neo4jPersistentEntity.DEPRECATED_GENERATED_ID_TYPES.contains(property.getType())) {
|
||||
@@ -974,7 +984,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInCreation(@Nullable String internalId) {
|
||||
private boolean isInCreation(String internalId) {
|
||||
if (internalId == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -1037,7 +1047,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
|
||||
read.lock();
|
||||
|
||||
return previousRecords.contains(internalId) || internalCurrentRecord.get(internalId);
|
||||
return previousRecords.contains(internalId) || Optional.ofNullable(internalCurrentRecord.get(internalId)).orElse(Boolean.FALSE);
|
||||
|
||||
} finally {
|
||||
read.unlock();
|
||||
@@ -1074,20 +1084,25 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
|
||||
internalCurrentRecord.clear();
|
||||
}
|
||||
|
||||
private void mappedWithQueryResult(String internalId, MapAccessor queryResult) {
|
||||
try {
|
||||
write.lock();
|
||||
mappedQueryResults.computeIfAbsent(internalId, id -> ConcurrentHashMap.newKeySet())
|
||||
.add(queryResult.asMap());
|
||||
} finally {
|
||||
write.unlock();
|
||||
private void mappedWithQueryResult(@Nullable String internalId, MapAccessor queryResult) {
|
||||
if (internalId != null) {
|
||||
try {
|
||||
write.lock();
|
||||
mappedQueryResults.computeIfAbsent(internalId, id -> ConcurrentHashMap.newKeySet())
|
||||
.add(queryResult.asMap());
|
||||
} finally {
|
||||
write.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Map<String, Object>> getQueryResultsFor(String internalId) {
|
||||
private Set<Map<String, Object>> getQueryResultsFor(@Nullable String internalId) {
|
||||
if (internalId == null) {
|
||||
return Set.of();
|
||||
}
|
||||
try {
|
||||
read.lock();
|
||||
return mappedQueryResults.get(internalId);
|
||||
return Objects.requireNonNullElseGet(mappedQueryResults.get(internalId), Set::of);
|
||||
} finally {
|
||||
read.unlock();
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.data.support.IsNewStrategy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -53,7 +53,7 @@ final class DefaultNeo4jIsNewStrategy implements IsNewStrategy {
|
||||
|
||||
Assert.notNull(entityMetaData, "Entity meta data must not be null");
|
||||
|
||||
IdDescription idDescription = entityMetaData.getIdDescription();
|
||||
IdDescription idDescription = Objects.requireNonNull(entityMetaData.getIdDescription(), () -> "Cannot determine id description for entity %s".formatted(entityMetaData.getType()));
|
||||
Class<?> valueType = entityMetaData.getRequiredIdProperty().getType();
|
||||
|
||||
if (idDescription.isExternallyGeneratedId() && valueType.isPrimitive()) {
|
||||
@@ -84,7 +84,7 @@ final class DefaultNeo4jIsNewStrategy implements IsNewStrategy {
|
||||
|
||||
private final Class<?> valueType;
|
||||
|
||||
private @Nullable final Function<Object, Object> valueLookup;
|
||||
private final Function<Object, Object> valueLookup;
|
||||
|
||||
private DefaultNeo4jIsNewStrategy(IdDescription idDescription, Class<?> valueType,
|
||||
Function<Object, Object> valueLookup) {
|
||||
@@ -104,7 +104,7 @@ final class DefaultNeo4jIsNewStrategy implements IsNewStrategy {
|
||||
if (idDescription.isInternallyGeneratedId()) {
|
||||
|
||||
boolean isNew;
|
||||
if (value != null && valueType.isPrimitive() && Number.class.isInstance(value)) {
|
||||
if (value != null && valueType.isPrimitive() && value instanceof Number) {
|
||||
isNew = ((Number) value).longValue() < 0;
|
||||
} else {
|
||||
isNew = value == null;
|
||||
@@ -118,7 +118,7 @@ final class DefaultNeo4jIsNewStrategy implements IsNewStrategy {
|
||||
return value == null;
|
||||
}
|
||||
|
||||
if (Number.class.isInstance(value)) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
@@ -32,6 +33,8 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
@@ -49,8 +52,6 @@ import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.data.support.IsNewStrategy;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -85,6 +86,7 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
|
||||
private final Lazy<Boolean> isRelationshipPropertiesEntity;
|
||||
|
||||
@Nullable
|
||||
private NodeDescription<?> parentNodeDescription;
|
||||
|
||||
private List<NodeDescription<?>> childNodeDescriptionsInHierarchy;
|
||||
@@ -255,14 +257,14 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
|
||||
private void verifyDynamicAssociations() {
|
||||
|
||||
Set<Class> targetEntities = new HashSet<>();
|
||||
AssociationHandlerSupport.of(this).doWithAssociations((Association<Neo4jPersistentProperty> association) -> {
|
||||
Set<Class<?>> targetEntities = new HashSet<>();
|
||||
AssociationHandlerSupport.of(this).doWithAssociations((Association<@NonNull Neo4jPersistentProperty> association) -> {
|
||||
Neo4jPersistentProperty inverse = association.getInverse();
|
||||
if (inverse.isDynamicAssociation()) {
|
||||
Relationship relationship = inverse.findAnnotation(Relationship.class);
|
||||
Assert.state(relationship == null || relationship.type().isEmpty(),
|
||||
() -> "Dynamic relationships cannot be used with a fixed type; omit @Relationship or use @Relationship(direction = "
|
||||
+ relationship.direction().name() + ") without a type in " + this.getUnderlyingClass() + " on field "
|
||||
+ Optional.ofNullable(relationship).map(Relationship::direction).orElse(Relationship.Direction.OUTGOING).name() + ") without a type in " + this.getUnderlyingClass() + " on field "
|
||||
+ inverse.getFieldName());
|
||||
|
||||
Assert.state(!targetEntities.contains(inverse.getAssociationTargetType()),
|
||||
@@ -329,7 +331,6 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
* @param type the type of the underlying class
|
||||
* @return computed primary label
|
||||
*/
|
||||
@Nullable
|
||||
static String computePrimaryLabel(Class<?> type) {
|
||||
|
||||
Node nodeAnnotation = AnnotatedElementUtils.findMergedAnnotation(type, Node.class);
|
||||
@@ -365,7 +366,6 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
*
|
||||
* @return computed additional labels of the concrete class
|
||||
*/
|
||||
@NonNull
|
||||
private List<String> computeOwnAdditionalLabels() {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
@@ -398,7 +398,6 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<String> computeParentLabels() {
|
||||
List<String> parentLabels = new ArrayList<>();
|
||||
Neo4jPersistentEntity<?> parentNodeDescriptionCalculated = (Neo4jPersistentEntity<?>) parentNodeDescription;
|
||||
@@ -430,9 +429,10 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
|
||||
@Override
|
||||
public boolean hasVectorProperty() {
|
||||
return Optional.ofNullable(getVectorProperty()).map(v -> true).orElse(false);
|
||||
return getVectorProperty() != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Neo4jPersistentProperty getVectorProperty() {
|
||||
return this.vectorProperty.getNullable();
|
||||
}
|
||||
@@ -511,13 +511,11 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
return Collections.unmodifiableCollection(relationships);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Collection<RelationshipDescription> getRelationshipsInHierarchy(Predicate<PropertyFilter.RelaxedPropertyPath> propertyFilter) {
|
||||
|
||||
return getRelationshipsInHierarchy(propertyFilter, PropertyFilter.RelaxedPropertyPath.withRootType(this.getUnderlyingClass()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Collection<RelationshipDescription> getRelationshipsInHierarchy(Predicate<PropertyFilter.RelaxedPropertyPath> propertyFilter, PropertyFilter.RelaxedPropertyPath path) {
|
||||
|
||||
Collection<RelationshipDescription> relationships = new HashSet<>(getRelationships());
|
||||
@@ -599,7 +597,7 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentNodeDescription(NodeDescription<?> parent) {
|
||||
public void setParentNodeDescription(@Nullable NodeDescription<?> parent) {
|
||||
this.parentNodeDescription = parent;
|
||||
}
|
||||
|
||||
@@ -634,9 +632,14 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
visitedNodes.add(targetNode);
|
||||
|
||||
// we don't care about the other content of relationship properties and jump straight into the `TargetNode`
|
||||
String relationshipPropertiesPrefix = relationship.hasRelationshipProperties()
|
||||
? "." + ((Neo4jPersistentEntity<?>) relationship.getRelationshipPropertiesEntity()).getPersistentProperty(TargetNode.class).getFieldName()
|
||||
: "";
|
||||
String relationshipPropertiesPrefix;
|
||||
if (!relationship.hasRelationshipProperties()) {
|
||||
relationshipPropertiesPrefix = "";
|
||||
} else {
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationship.getRequiredRelationshipPropertiesEntity();
|
||||
var targetNodeProperty = Objects.requireNonNull(relationshipPropertiesEntity.getPersistentProperty(TargetNode.class), () -> "Could not get target node property on %s".formatted(relationshipPropertiesEntity.getType()));
|
||||
relationshipPropertiesPrefix = "." + targetNodeProperty.getFieldName();
|
||||
}
|
||||
PropertyFilter.RelaxedPropertyPath nextPath = relaxedPropertyPath.append(relationship.getFieldName() + relationshipPropertiesPrefix);
|
||||
if (calculatePossibleCircles(targetNode, visitedNodes, includeField, nextPath)) {
|
||||
return true;
|
||||
@@ -661,8 +664,14 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
|
||||
// but don't (!) add them to the visitedNodes yet.
|
||||
// Otherwise, the same "parallel" defined target nodes will report a false circle.
|
||||
branchedVisitedNodes.add(targetNode);
|
||||
String relationshipPropertiesPrefix = relationship.hasRelationshipProperties() ? "." + ((Neo4jPersistentEntity<?>) relationship.getRelationshipPropertiesEntity())
|
||||
.getPersistentProperty(TargetNode.class).getFieldName() : "";
|
||||
String relationshipPropertiesPrefix;
|
||||
if (!relationship.hasRelationshipProperties()) {
|
||||
relationshipPropertiesPrefix = "";
|
||||
} else {
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationship.getRequiredRelationshipPropertiesEntity();
|
||||
var targetNodeProperty = Objects.requireNonNull(relationshipPropertiesEntity.getPersistentProperty(TargetNode.class), () -> "Could not get target node property on %s".formatted(relationshipPropertiesEntity.getType()));
|
||||
relationshipPropertiesPrefix = "." + targetNodeProperty.getFieldName();
|
||||
}
|
||||
if (calculatePossibleCircles(targetNode, branchedVisitedNodes, includeField, path.append(relationship.getFieldName() + relationshipPropertiesPrefix))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,11 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.mapping.Association;
|
||||
@@ -37,8 +40,6 @@ import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -63,7 +64,8 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
|
||||
private final Lazy<Neo4jPersistentPropertyConverter<?>> customConversion;
|
||||
|
||||
private final @Nullable PersistentPropertyCharacteristics optionalCharacteristics;
|
||||
@Nullable
|
||||
private final PersistentPropertyCharacteristics optionalCharacteristics;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationBasedPersistentProperty}.
|
||||
@@ -110,7 +112,7 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Association<Neo4jPersistentProperty> createAssociation() {
|
||||
protected Association<@NonNull Neo4jPersistentProperty> createAssociation() {
|
||||
|
||||
Neo4jPersistentEntity<?> obverseOwner;
|
||||
|
||||
@@ -120,31 +122,29 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
|
||||
if (this.hasActualTypeAnnotation(RelationshipProperties.class)) {
|
||||
TypeInformation<?> typeInformation = getRelationshipPropertiesTargetType(getActualType());
|
||||
obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).get();
|
||||
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(TypeInformation.of(getActualType())).get();
|
||||
obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).orElseThrow();
|
||||
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(TypeInformation.of(getActualType())).orElseThrow();
|
||||
} else {
|
||||
Class<?> associationTargetType = this.getAssociationTargetType();
|
||||
Class<?> associationTargetType = Objects.requireNonNull(this.getAssociationTargetType());
|
||||
obverseOwner = this.mappingContext.addPersistentEntity(TypeInformation.of(associationTargetType)).orElse(null);
|
||||
Assert.notNull(obverseOwner, "Obverse owner could not be added");
|
||||
if (dynamicAssociation) {
|
||||
|
||||
TypeInformation<?> mapValueType = this.getTypeInformation().getMapValueType();
|
||||
TypeInformation<?> mapValueType = Objects.requireNonNull(this.getTypeInformation().getMapValueType());
|
||||
TypeInformation<?> componentType = mapValueType.getComponentType();
|
||||
if (componentType != null) {
|
||||
TypeInformation<?> actualType = mapValueType.getActualType();
|
||||
|
||||
boolean relationshipPropertiesCollection =
|
||||
this.mappingContext.getPersistentEntity(mapValueType.getActualType().getType())
|
||||
.isRelationshipPropertiesEntity();
|
||||
if (actualType != null && this.mappingContext.getRequiredPersistentEntity(actualType.getType())
|
||||
.isRelationshipPropertiesEntity()) {
|
||||
TypeInformation<?> typeInformation = getRelationshipPropertiesTargetType(actualType.getType());
|
||||
obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).orElseThrow();
|
||||
relationshipPropertiesClass = this.mappingContext
|
||||
.addPersistentEntity(componentType).orElseThrow();
|
||||
|
||||
boolean relationshipPropertiesScalar =
|
||||
mapValueType.getType().isAnnotationPresent(RelationshipProperties.class);
|
||||
|
||||
if (relationshipPropertiesCollection) {
|
||||
TypeInformation<?> typeInformation = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType());
|
||||
obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).get();
|
||||
relationshipPropertiesClass = this.mappingContext
|
||||
.addPersistentEntity(mapValueType.getComponentType()).get();
|
||||
|
||||
} else if (relationshipPropertiesScalar) {
|
||||
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(mapValueType.getComponentType()).get();
|
||||
} else if (mapValueType.getType().isAnnotationPresent(RelationshipProperties.class)) {
|
||||
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(componentType).orElseThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +164,8 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
|
||||
// Try to determine if there is a relationship definition that expresses logically the same relationship
|
||||
// on the other end.
|
||||
// At this point, obverseOwner can't be null
|
||||
@SuppressWarnings("NullAway")
|
||||
Optional<RelationshipDescription> obverseRelationshipDescription = obverseOwner.getRelationships().stream()
|
||||
.filter(rel -> rel.getType().equals(type)
|
||||
&& rel.getTarget().equals(this.getOwner())
|
||||
@@ -180,7 +182,6 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
return relationshipDescription;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private TypeInformation<?> getRelationshipPropertiesTargetType(Class<?> relationshipPropertiesType) {
|
||||
|
||||
Field targetNodeField = ReflectionUtils.findField(relationshipPropertiesType,
|
||||
@@ -190,8 +191,8 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
throw new MappingException("Missing @TargetNode declaration in " + relationshipPropertiesType);
|
||||
}
|
||||
TypeInformation<?> relationshipPropertiesTypeInformation = TypeInformation.of(relationshipPropertiesType);
|
||||
Class<?> type = relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName()).getType();
|
||||
if (Object.class == type && this.getField().getGenericType() instanceof ParameterizedType pt && pt.getActualTypeArguments().length == 1) {
|
||||
Class<?> type = Objects.requireNonNull(relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName())).getType();
|
||||
if (Object.class == type && this.getRequiredField().getGenericType() instanceof ParameterizedType pt && pt.getActualTypeArguments().length == 1) {
|
||||
return TypeInformation.of(ResolvableType.forType(pt.getActualTypeArguments()[0]));
|
||||
}
|
||||
return TypeInformation.of(type);
|
||||
@@ -231,6 +232,7 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Neo4jPersistentPropertyConverter<?> getOptionalConverter() {
|
||||
return isEntity() ? null : customConversion.getOptional()
|
||||
.map(Neo4jPersistentPropertyConverter.class::cast)
|
||||
@@ -253,8 +255,7 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp
|
||||
.findAnnotation(org.springframework.data.neo4j.core.schema.Property.class);
|
||||
|
||||
String targetName = this.getName();
|
||||
if (propertyAnnotation != null && !propertyAnnotation.name().isEmpty()
|
||||
&& propertyAnnotation.name().trim().length() != 0) {
|
||||
if (propertyAnnotation != null && !propertyAnnotation.name().trim().isEmpty()) {
|
||||
targetName = propertyAnnotation.name().trim();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,16 +17,17 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.neo4j.core.schema.Relationship;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
* @author Gerrit Meier
|
||||
* @since 6.0
|
||||
*/
|
||||
final class DefaultRelationshipDescription extends Association<Neo4jPersistentProperty> implements RelationshipDescription {
|
||||
final class DefaultRelationshipDescription extends Association<@NonNull Neo4jPersistentProperty> implements RelationshipDescription {
|
||||
|
||||
private final String type;
|
||||
|
||||
@@ -40,8 +41,10 @@ final class DefaultRelationshipDescription extends Association<Neo4jPersistentPr
|
||||
|
||||
private final Relationship.Direction direction;
|
||||
|
||||
@Nullable
|
||||
private final NodeDescription<?> relationshipPropertiesClass;
|
||||
|
||||
@Nullable
|
||||
private RelationshipDescription relationshipObverse;
|
||||
|
||||
private final boolean cascadeUpdates;
|
||||
@@ -97,6 +100,7 @@ final class DefaultRelationshipDescription extends Association<Neo4jPersistentPr
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public NodeDescription<?> getRelationshipPropertiesEntity() {
|
||||
return relationshipPropertiesClass;
|
||||
}
|
||||
@@ -107,11 +111,12 @@ final class DefaultRelationshipDescription extends Association<Neo4jPersistentPr
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRelationshipObverse(RelationshipDescription relationshipObverse) {
|
||||
public void setRelationshipObverse(@Nullable RelationshipDescription relationshipObverse) {
|
||||
this.relationshipObverse = relationshipObverse;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RelationshipDescription getRelationshipObverse() {
|
||||
return relationshipObverse;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
@@ -35,7 +37,6 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -73,8 +74,7 @@ public final class DtoInstantiatingConverter implements Converter<EntityInstance
|
||||
Neo4jPersistentEntity<?> sourceEntity = context.getRequiredPersistentEntity(entityInstance.getClass());
|
||||
PersistentPropertyAccessor<Object> sourceAccessor = sourceEntity.getPropertyAccessor(entityInstance);
|
||||
|
||||
Neo4jPersistentEntity<?> targetEntity = context.addPersistentEntity(TypeInformation.of(targetType)).orElse(null);
|
||||
Assert.notNull(targetEntity, "Target entity could not be created for a DTO");
|
||||
Neo4jPersistentEntity<?> targetEntity = context.addPersistentEntity(TypeInformation.of(targetType)).orElseThrow(() -> new IllegalStateException("Target entity could not be created for a DTO"));
|
||||
InstanceCreatorMetadata<?> creator = targetEntity.getInstanceCreatorMetadata();
|
||||
|
||||
Object dto = context.getInstantiatorFor(targetEntity)
|
||||
@@ -98,7 +98,6 @@ public final class DtoInstantiatingConverter implements Converter<EntityInstance
|
||||
return dto;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
Object getPropertyValueDirectlyFor(PersistentProperty<?> targetProperty, PersistentEntity<?, ?> sourceEntity,
|
||||
PersistentPropertyAccessor<?> sourceAccessor) {
|
||||
@@ -111,18 +110,15 @@ public final class DtoInstantiatingConverter implements Converter<EntityInstance
|
||||
}
|
||||
|
||||
Object result = sourceAccessor.getProperty(sourceProperty);
|
||||
if (targetProperty.isEntity() && !targetProperty.getTypeInformation().isAssignableFrom(sourceProperty.getTypeInformation())) {
|
||||
if (result != null && targetProperty.isEntity() && !targetProperty.getTypeInformation().isAssignableFrom(sourceProperty.getTypeInformation())) {
|
||||
return new DtoInstantiatingConverter(targetProperty.getType(), this.context).convertDirectly(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(@Nullable EntityInstanceWithSource entityInstanceAndSource) {
|
||||
|
||||
if (entityInstanceAndSource == null) {
|
||||
return null;
|
||||
}
|
||||
@Nullable
|
||||
public Object convert(EntityInstanceWithSource entityInstanceAndSource) {
|
||||
|
||||
Object entityInstance = entityInstanceAndSource.getEntityInstance();
|
||||
if (targetType.isInterface() || targetType.isInstance(entityInstance)) {
|
||||
@@ -135,7 +131,7 @@ public final class DtoInstantiatingConverter implements Converter<EntityInstance
|
||||
Neo4jPersistentEntity<?> targetEntity = context.addPersistentEntity(TypeInformation.of(targetType))
|
||||
.orElseThrow(() -> new MappingException(
|
||||
"Could not add a persistent entity for the projection target type '" + targetType.getName() + "'"));
|
||||
InstanceCreatorMetadata<? extends PersistentProperty<?>> creator = targetEntity.getInstanceCreatorMetadata();
|
||||
InstanceCreatorMetadata<@NonNull ? extends PersistentProperty<?>> creator = targetEntity.getInstanceCreatorMetadata();
|
||||
|
||||
Object dto = context.getInstantiatorFor(targetEntity)
|
||||
.createInstance(targetEntity,
|
||||
|
||||
@@ -17,9 +17,11 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.mapping.InstanceCreatorMetadata;
|
||||
@@ -33,7 +35,6 @@ import org.springframework.data.neo4j.core.convert.Neo4jConversionService;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,7 @@ public final class EntityFromDtoInstantiatingConverter<T> implements Converter<O
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T convert(Object dtoInstance) {
|
||||
|
||||
if (dtoInstance == null) {
|
||||
@@ -72,18 +74,19 @@ public final class EntityFromDtoInstantiatingConverter<T> implements Converter<O
|
||||
}
|
||||
|
||||
PersistentEntity<?, ?> sourceEntity = context.addPersistentEntity(TypeInformation.of(dtoInstance.getClass()))
|
||||
.get();
|
||||
.orElseThrow();
|
||||
PersistentPropertyAccessor<Object> sourceAccessor = sourceEntity.getPropertyAccessor(dtoInstance);
|
||||
|
||||
PersistentEntity<?, ?> targetEntity = context.getPersistentEntity(targetEntityType);
|
||||
InstanceCreatorMetadata<?> creator = targetEntity.getInstanceCreatorMetadata();
|
||||
PersistentEntity<?, ?> targetEntity = Objects.requireNonNull(context.getPersistentEntity(targetEntityType));
|
||||
InstanceCreatorMetadata<?> creator = Objects.requireNonNull(targetEntity.getInstanceCreatorMetadata());
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
T entity = (T) context.getInstantiatorFor(targetEntity)
|
||||
.createInstance(targetEntity, new ParameterValueProvider() {
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getParameterValue(Parameter parameter) {
|
||||
PersistentProperty<?> targetProperty = targetEntity.getPersistentProperty(parameter.getName());
|
||||
PersistentProperty<?> targetProperty = targetEntity.getPersistentProperty(Objects.requireNonNull(parameter.getName(), "Parameter names are not available"));
|
||||
if (targetProperty == null) {
|
||||
throw new MappingException("Cannot map constructor parameter " + parameter.getName()
|
||||
+ " to a property of class " + targetEntityType);
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.Expression;
|
||||
import org.neo4j.cypherdsl.core.Node;
|
||||
@@ -25,7 +26,6 @@ import org.neo4j.cypherdsl.core.SymbolicName;
|
||||
import org.springframework.data.neo4j.core.schema.GeneratedValue;
|
||||
import org.springframework.data.neo4j.core.schema.IdGenerator;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -40,17 +40,20 @@ public final class IdDescription {
|
||||
/**
|
||||
* The class representing a generator for new ids or null for assigned ids.
|
||||
*/
|
||||
private @Nullable final Class<? extends IdGenerator<?>> idGeneratorClass;
|
||||
@Nullable
|
||||
private final Class<? extends IdGenerator<?>> idGeneratorClass;
|
||||
|
||||
/**
|
||||
* A reference to an ID generator.
|
||||
*/
|
||||
private @Nullable final String idGeneratorRef;
|
||||
@Nullable
|
||||
private final String idGeneratorRef;
|
||||
|
||||
/**
|
||||
* The property that stores the id if applicable.
|
||||
*/
|
||||
private @Nullable final String graphPropertyName;
|
||||
@Nullable
|
||||
private final String graphPropertyName;
|
||||
private final boolean isDeprecated;
|
||||
|
||||
private final Lazy<Expression> idExpression;
|
||||
@@ -70,8 +73,8 @@ public final class IdDescription {
|
||||
}
|
||||
|
||||
public static IdDescription forExternallyGeneratedIds(SymbolicName symbolicName,
|
||||
@Nullable Class<? extends IdGenerator<?>> idGeneratorClass,
|
||||
@Nullable String idGeneratorRef, String graphPropertyName) {
|
||||
Class<? extends IdGenerator<?>> idGeneratorClass,
|
||||
String idGeneratorRef, String graphPropertyName) {
|
||||
|
||||
Assert.notNull(graphPropertyName, "Graph property name is required");
|
||||
try {
|
||||
@@ -87,8 +90,9 @@ public final class IdDescription {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private IdDescription(SymbolicName symbolicName, @Nullable Class<? extends IdGenerator<?>> idGeneratorClass,
|
||||
@Nullable String idGeneratorRef, @Nullable String graphPropertyName, boolean isDeprecated) {
|
||||
@Nullable String idGeneratorRef, @Nullable String graphPropertyName, boolean isDeprecated) {
|
||||
|
||||
this.idGeneratorClass = idGeneratorClass;
|
||||
this.idGeneratorRef = idGeneratorRef != null && idGeneratorRef.isEmpty() ? null : idGeneratorRef;
|
||||
@@ -98,7 +102,6 @@ public final class IdDescription {
|
||||
this.idExpression = Lazy.of(() -> {
|
||||
final Node rootNode = Cypher.anyNode(symbolicName);
|
||||
if (this.isInternallyGeneratedId()) {
|
||||
//noinspection deprecation
|
||||
return isDeprecated ? rootNode.internalId() : rootNode.elementId();
|
||||
} else {
|
||||
return this.getOptionalGraphPropertyName()
|
||||
@@ -118,14 +121,14 @@ public final class IdDescription {
|
||||
* @param nodeName use this name as the symbolic name of the node in the query
|
||||
* @return An expression that represents the right identifier type.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public Expression asIdExpression(String nodeName) {
|
||||
final Node rootNode = Cypher.anyNode(nodeName);
|
||||
if (this.isInternallyGeneratedId()) {
|
||||
//noinspection deprecation
|
||||
return isDeprecated ? rootNode.internalId() : rootNode.elementId();
|
||||
} else {
|
||||
return this.getOptionalGraphPropertyName()
|
||||
.map(propertyName -> Cypher.property(nodeName, propertyName)).get();
|
||||
.map(propertyName -> Cypher.property(nodeName, propertyName)).orElseThrow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,13 @@ import static org.apiguardian.api.API.Status.INTERNAL;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.types.Entity;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.Node;
|
||||
import org.neo4j.driver.types.Relationship;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This class is <strong>not</strong> part of any public API and will be changed without further notice as needed. It's
|
||||
@@ -61,7 +60,7 @@ public final class IdentitySupport {
|
||||
* @return An internal id
|
||||
*/
|
||||
@Nullable
|
||||
public static String getElementId(@NonNull MapAccessor row) {
|
||||
public static String getElementId(MapAccessor row) {
|
||||
if (row instanceof Entity entity) {
|
||||
return getElementId(entity);
|
||||
}
|
||||
@@ -77,9 +76,10 @@ public final class IdentitySupport {
|
||||
return value.asString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
@Deprecated
|
||||
public static Long getInternalId(@NonNull MapAccessor row) {
|
||||
@Nullable
|
||||
public static Long getInternalId(MapAccessor row) {
|
||||
if (row instanceof Entity entity) {
|
||||
return entity.id();
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public final class IdentitySupport {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getPrefixedElementId(@NonNull MapAccessor queryResult, @Nullable String seed) {
|
||||
public static String getPrefixedElementId(MapAccessor queryResult, @Nullable String seed) {
|
||||
if (queryResult instanceof Node) {
|
||||
return "N" + getElementId(queryResult);
|
||||
} else if (queryResult instanceof Relationship) {
|
||||
|
||||
@@ -27,13 +27,13 @@ import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.types.Node;
|
||||
import org.neo4j.driver.types.Relationship;
|
||||
import org.neo4j.driver.types.Type;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
@@ -174,10 +174,10 @@ public final class MappingSupport {
|
||||
Object relationshipPropertiesValue = entityHolder.getRelationshipProperties();
|
||||
|
||||
Neo4jPersistentEntity<?> persistentEntity =
|
||||
neo4jMappingContext.getPersistentEntity(relationshipPropertiesValue.getClass());
|
||||
Objects.requireNonNull(neo4jMappingContext.getPersistentEntity(relationshipPropertiesValue.getClass()));
|
||||
|
||||
PersistentPropertyAccessor<Object> relationshipPropertiesAccessor = persistentEntity.getPropertyAccessor(relationshipPropertiesValue);
|
||||
relationshipPropertiesAccessor.setProperty(persistentEntity.getPersistentProperty(TargetNode.class), newRelationshipObject);
|
||||
relationshipPropertiesAccessor.setProperty(Objects.requireNonNull(persistentEntity.getPersistentProperty(TargetNode.class)), newRelationshipObject);
|
||||
newRelationshipObject = relationshipPropertiesAccessor.getBean();
|
||||
|
||||
// If we recreate or manipulate the object including it's accessor, we must update it in the holder as well.
|
||||
|
||||
@@ -31,12 +31,14 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -67,7 +69,6 @@ import org.springframework.data.neo4j.core.schema.Node;
|
||||
import org.springframework.data.neo4j.core.schema.PostLoad;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
@@ -109,11 +110,12 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
|
||||
private final Neo4jConversionService conversionService;
|
||||
|
||||
private final Map<Neo4jPersistentEntity, Set<MethodHolder>> postLoadMethods = new ConcurrentHashMap<>();
|
||||
private final Map<Neo4jPersistentEntity<?>, Set<MethodHolder>> postLoadMethods = new ConcurrentHashMap<>();
|
||||
|
||||
private EventSupport eventSupport;
|
||||
|
||||
private @Nullable AutowireCapableBeanFactory beanFactory;
|
||||
@Nullable
|
||||
private AutowireCapableBeanFactory beanFactory;
|
||||
|
||||
private boolean strict = false;
|
||||
|
||||
@@ -127,7 +129,6 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
|
||||
private Neo4jConversions neo4jConversions;
|
||||
|
||||
@Nullable
|
||||
private TypeSystem typeSystem;
|
||||
|
||||
@Nullable
|
||||
@@ -139,25 +140,25 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
|
||||
private Builder(Neo4jConversions neo4jConversions, @Nullable TypeSystem typeSystem, @Nullable PersistentPropertyCharacteristicsProvider persistentPropertyCharacteristicsProvider) {
|
||||
this.neo4jConversions = neo4jConversions;
|
||||
this.typeSystem = typeSystem;
|
||||
this.typeSystem = Objects.requireNonNullElseGet(typeSystem, TypeSystem::getDefault);
|
||||
this.persistentPropertyCharacteristicsProvider = persistentPropertyCharacteristicsProvider;
|
||||
}
|
||||
|
||||
@SuppressWarnings("HiddenField")
|
||||
public Builder withNeo4jConversions(@Nullable Neo4jConversions neo4jConversions) {
|
||||
public Builder withNeo4jConversions(Neo4jConversions neo4jConversions) {
|
||||
this.neo4jConversions = neo4jConversions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("HiddenField")
|
||||
public Builder withPersistentPropertyCharacteristicsProvider(@Nullable PersistentPropertyCharacteristicsProvider persistentPropertyCharacteristicsProvider) {
|
||||
public Builder withPersistentPropertyCharacteristicsProvider(PersistentPropertyCharacteristicsProvider persistentPropertyCharacteristicsProvider) {
|
||||
this.persistentPropertyCharacteristicsProvider = persistentPropertyCharacteristicsProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("HiddenField")
|
||||
public Builder withTypeSystem(@Nullable TypeSystem typeSystem) {
|
||||
this.typeSystem = typeSystem;
|
||||
public Builder withTypeSystem(TypeSystem typeSystem) {
|
||||
this.typeSystem = Objects.requireNonNullElseGet(typeSystem, TypeSystem::getDefault);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -180,20 +181,6 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
this(new Builder(neo4jConversions, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 type system. If this is null, we use the default one without accessing the driver.
|
||||
* @deprecated Use {@link Neo4jMappingContext#builder()}
|
||||
*/
|
||||
@API(status = API.Status.INTERNAL, since = "6.0")
|
||||
@Deprecated(since = "6.3.7", forRemoval = true)
|
||||
public Neo4jMappingContext(Neo4jConversions neo4jConversions, @Nullable TypeSystem typeSystem) {
|
||||
this(new Builder(neo4jConversions, typeSystem, null));
|
||||
}
|
||||
|
||||
private Neo4jMappingContext(Builder builder) {
|
||||
|
||||
this.conversionService = new DefaultNeo4jConversionService(builder.neo4jConversions);
|
||||
@@ -254,9 +241,9 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
if (!newEntity.describesInterface()) {
|
||||
if (this.nodeDescriptionStore.containsKey(primaryLabel)) {
|
||||
|
||||
Neo4jPersistentEntity existingEntity = (Neo4jPersistentEntity) this.nodeDescriptionStore.get(
|
||||
Neo4jPersistentEntity<?> existingEntity = (Neo4jPersistentEntity<?>) this.nodeDescriptionStore.get(
|
||||
primaryLabel);
|
||||
if (!existingEntity.getTypeInformation().getRawTypeInformation()
|
||||
if (existingEntity != null && !existingEntity.getTypeInformation().getRawTypeInformation()
|
||||
.equals(typeInformation.getRawTypeInformation())) {
|
||||
String message = String.format(Locale.ENGLISH,
|
||||
"The schema already contains a node description under the primary label %s", primaryLabel);
|
||||
@@ -318,7 +305,7 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
private static boolean isValidParentNode(@Nullable Class<?> parentClass) {
|
||||
private static boolean isValidParentNode(Class<?> parentClass) {
|
||||
if (parentClass == null || parentClass.equals(Object.class)) {
|
||||
return false;
|
||||
}
|
||||
@@ -355,6 +342,7 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public NodeDescription<?> getNodeDescription(Class<?> underlyingClass) {
|
||||
return doGetPersistentEntity(underlyingClass);
|
||||
}
|
||||
@@ -418,7 +406,12 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
if (this.beanFactory == null) {
|
||||
idGenerator = BeanUtils.instantiateClass(t);
|
||||
} else {
|
||||
idGenerator = this.beanFactory.getBeanProvider(t).getIfUnique(() -> this.beanFactory.createBean(t));
|
||||
idGenerator = this.beanFactory.getBeanProvider(t).getIfUnique(() -> {
|
||||
// The beanFactory can't actually be reassigned, so doing a whole double lock check is a bit overkill
|
||||
@SuppressWarnings("NullAway")
|
||||
var result = this.beanFactory.createBean(t);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
return idGenerator;
|
||||
}
|
||||
@@ -510,10 +503,10 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
|
||||
} else {
|
||||
converterClass = customConverter.getClass();
|
||||
}
|
||||
Map<String, Type> typeVariableMap = GenericTypeResolver.getTypeVariableMap(converterClass)
|
||||
Map<String, Type> typeVariableMap = (converterClass != null) ? GenericTypeResolver.getTypeVariableMap(converterClass)
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(e -> e.getKey().getName(), Map.Entry::getValue));
|
||||
.collect(Collectors.toMap(e -> e.getKey().getName(), Map.Entry::getValue)) : Map.of();
|
||||
Type propertyType = null;
|
||||
if (typeVariableMap.containsKey("T")) {
|
||||
propertyType = typeVariableMap.get("T");
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.MutablePersistentEntity;
|
||||
@@ -76,8 +77,21 @@ public interface Neo4jPersistentEntity<T>
|
||||
return isUsingInternalIds() && Neo4jPersistentEntity.DEPRECATED_GENERATED_ID_TYPES.contains(getRequiredIdProperty().getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this entity spots a vector property
|
||||
*/
|
||||
boolean hasVectorProperty();
|
||||
|
||||
/**
|
||||
* Will return the single supported vector property if {@link #hasVectorProperty()} returns {@literal true}, otherwise {@literal null}.
|
||||
* @return an optional vector property on this entity
|
||||
*/
|
||||
@Nullable
|
||||
Neo4jPersistentProperty getVectorProperty();
|
||||
|
||||
/**
|
||||
* Will return the single supported vector property if {@link #hasVectorProperty()} returns {@literal true}, otherwise it will throw an {@link IllegalStateException}.
|
||||
* @return the vector property on this entity.
|
||||
*/
|
||||
Neo4jPersistentProperty getRequiredVectorProperty();
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.domain.Vector;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter;
|
||||
import org.springframework.data.neo4j.core.schema.CompositeProperty;
|
||||
import org.springframework.data.neo4j.core.schema.DynamicLabels;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.data.mapping.PersistentProperty} interface with additional methods for metadata related
|
||||
@@ -45,7 +45,8 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
|
||||
*/
|
||||
default boolean isDynamicAssociation() {
|
||||
|
||||
return isRelationship() && isMap() && (getComponentType() == String.class || getComponentType().isEnum());
|
||||
Class<?> componentType = getComponentType();
|
||||
return isRelationship() && isMap() && (componentType == String.class || (componentType != null && componentType.isEnum()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,14 +20,16 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Working on nested relationships happens in a certain algorithmic context. This context enables a tight cohesion
|
||||
@@ -42,6 +44,7 @@ import org.springframework.lang.Nullable;
|
||||
@API(status = API.Status.INTERNAL, since = "6.0")
|
||||
public final class NestedRelationshipContext {
|
||||
private final Neo4jPersistentProperty inverse;
|
||||
@Nullable
|
||||
private final Object value;
|
||||
private final RelationshipDescription relationship;
|
||||
|
||||
@@ -82,10 +85,9 @@ public final class NestedRelationshipContext {
|
||||
|
||||
public Object identifyAndExtractRelationshipTargetNode(Object relatedValue) {
|
||||
Object valueToBeSaved = relatedValue;
|
||||
if (relatedValue instanceof Map.Entry) {
|
||||
Map.Entry<?, ?> relatedValueMapEntry = (Map.Entry<?, ?>) relatedValue;
|
||||
if (relatedValue instanceof Map.Entry<?, ?> relatedValueMapEntry) {
|
||||
if (this.hasRelationshipWithProperties()) {
|
||||
Object mapValue = ((Map.Entry<?, ?>) relatedValue).getValue();
|
||||
Object mapValue = relatedValueMapEntry.getValue();
|
||||
// it can be either a scalar entity holder or a list of it
|
||||
mapValue = mapValue instanceof List ? ((List<?>) mapValue).get(0) : mapValue;
|
||||
valueToBeSaved = ((MappingSupport.RelationshipPropertiesWithEntityHolder) mapValue).getRelatedEntity();
|
||||
@@ -100,7 +102,8 @@ public final class NestedRelationshipContext {
|
||||
return valueToBeSaved;
|
||||
}
|
||||
|
||||
public @Nullable PersistentPropertyAccessor<?> getRelationshipPropertiesPropertyAccessor(@Nullable Object relatedValue) {
|
||||
@Nullable
|
||||
public PersistentPropertyAccessor<?> getRelationshipPropertiesPropertyAccessor(Object relatedValue) {
|
||||
|
||||
if (!this.hasRelationshipWithProperties() || relatedValue == null) {
|
||||
return null;
|
||||
@@ -115,7 +118,7 @@ public final class NestedRelationshipContext {
|
||||
}
|
||||
}
|
||||
|
||||
public static NestedRelationshipContext of(Association<Neo4jPersistentProperty> handler,
|
||||
public static NestedRelationshipContext of(Association<@NonNull Neo4jPersistentProperty> handler,
|
||||
PersistentPropertyAccessor<?> propertyAccessor, Neo4jPersistentEntity<?> neo4jPersistentEntity) {
|
||||
|
||||
Neo4jPersistentProperty inverse = handler.getInverse();
|
||||
@@ -130,7 +133,7 @@ public final class NestedRelationshipContext {
|
||||
neo4jPersistentEntity.getName() + " does not define a relationship for " + inverse.getFieldName()));
|
||||
|
||||
if (relationship.hasRelationshipProperties() && value != null) {
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationship.getRelationshipPropertiesEntity();
|
||||
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationship.getRequiredRelationshipPropertiesEntity();
|
||||
|
||||
// If this is dynamic relationship (Map<Object, Object>), extract the keys as relationship names
|
||||
// and the map values as values.
|
||||
@@ -187,7 +190,8 @@ public final class NestedRelationshipContext {
|
||||
private static Object getTargetNode(Neo4jPersistentEntity<?> relationshipPropertiesEntity, Object object) {
|
||||
|
||||
PersistentPropertyAccessor<Object> propertyAccessor = relationshipPropertiesEntity.getPropertyAccessor(object);
|
||||
return propertyAccessor.getProperty(relationshipPropertiesEntity.getPersistentProperty(TargetNode.class));
|
||||
var targetNodeProperty = Objects.requireNonNull(relationshipPropertiesEntity.getPersistentProperty(TargetNode.class), () -> "Could not get target node property on %s".formatted(relationshipPropertiesEntity.getType()));
|
||||
return Objects.requireNonNull(propertyAccessor.getProperty(targetNodeProperty));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.Set;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -101,7 +101,7 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
* @param valuesToStore Check whether all the values in the collection have been processed
|
||||
* @return The state of things processed
|
||||
*/
|
||||
public ProcessState getStateOf(@Nullable Object fromId, RelationshipDescription relationshipDescription, @Nullable Collection<?> valuesToStore) {
|
||||
public ProcessState getStateOf(@Nullable Object fromId, RelationshipDescription relationshipDescription, Collection<?> valuesToStore) {
|
||||
if (fromId == null) {
|
||||
return ProcessState.PROCESSED_BOTH;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
|
||||
private record RelationshipIdUpdateContext(Statement cypher, Object fromId, Object toId,
|
||||
NestedRelationshipContext relationshipContext,
|
||||
Object relatedValueToStore, Neo4jPersistentProperty idProperty) {
|
||||
Object relatedValueToStore, @Nullable Neo4jPersistentProperty idProperty) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +147,7 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RelationshipIdSupplier {
|
||||
Optional<Object> getId(Statement statement, Neo4jPersistentProperty idProperty, Object fromId, Object toId);
|
||||
Optional<Object> getId(Statement statement, @Nullable Neo4jPersistentProperty idProperty, Object fromId, Object toId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +155,7 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveRelationshipIdSupplier {
|
||||
Mono<Object> getId(Statement statement, Neo4jPersistentProperty idProperty, Object fromId, Object toId);
|
||||
Mono<Object> getId(Statement statement, @Nullable Neo4jPersistentProperty idProperty, Object fromId, Object toId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,8 +163,8 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
*
|
||||
* @param relationshipDescription To be marked as processed
|
||||
*/
|
||||
public void markRelationshipAsProcessed(Object fromId, @Nullable RelationshipDescription relationshipDescription) {
|
||||
if (relationshipDescription == null) {
|
||||
public void markRelationshipAsProcessed(@Nullable Object fromId, @Nullable RelationshipDescription relationshipDescription) {
|
||||
if (fromId == null || relationshipDescription == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -218,16 +218,20 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
if (!processed && mappingContext.hasPersistentEntityFor(typeOfValue)) {
|
||||
Neo4jPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(typeOfValue);
|
||||
Neo4jPersistentProperty idProperty = entity.getIdProperty();
|
||||
Object id = idProperty == null ? null : entity.getPropertyAccessor(valueToCheck).getProperty(idProperty);
|
||||
Object id;
|
||||
Optional<Object> alreadyProcessedObject = Optional.empty();
|
||||
if (idProperty != null) {
|
||||
// After the lookup by system.identityHashCode failed for a processed object alias,
|
||||
// we must traverse or iterate over all value with the matching type and compare the domain ids
|
||||
// to figure out if the logical object has already been processed through a different object instance.
|
||||
// The type check is needed to avoid relationship ids <> node id conflicts.
|
||||
id = entity.getPropertyAccessor(valueToCheck).getProperty(idProperty);
|
||||
alreadyProcessedObject = processedObjectsAlias.values().stream()
|
||||
.filter(typeOfValue::isInstance)
|
||||
.filter(processedObject -> id != null && id.equals(entity.getPropertyAccessor(processedObject).getProperty(idProperty)))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
// After the lookup by system.identityHashCode failed for a processed object alias,
|
||||
// we must traverse or iterate over all value with the matching type and compare the domain ids
|
||||
// to figure out if the logical object has already been processed through a different object instance.
|
||||
// The type check is needed to avoid relationship ids <> node id conflicts.
|
||||
Optional<Object> alreadyProcessedObject = id == null ? Optional.empty() : processedObjectsAlias.values().stream()
|
||||
.filter(typeOfValue::isInstance)
|
||||
.filter(processedObject -> id.equals(entity.getPropertyAccessor(processedObject).getProperty(idProperty)))
|
||||
.findAny();
|
||||
if (alreadyProcessedObject.isPresent()) { // Skip the show the next time around.
|
||||
processed = true;
|
||||
Object internalId = getObjectId(alreadyProcessedObject.get());
|
||||
@@ -249,16 +253,17 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
* @param relationshipDescription the relationship that should be looked for in the registry.
|
||||
* @return processed yes (true) / no (false)
|
||||
*/
|
||||
public boolean hasProcessedRelationship(Object fromId, @Nullable RelationshipDescription relationshipDescription) {
|
||||
if (relationshipDescription != null) {
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
return processedRelationshipDescriptions.contains(new RelationshipDescriptionWithSourceId(fromId, relationshipDescription));
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
public boolean hasProcessedRelationship(@Nullable Object fromId, @Nullable RelationshipDescription relationshipDescription) {
|
||||
if (fromId == null || relationshipDescription == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
return processedRelationshipDescriptions.contains(new RelationshipDescriptionWithSourceId(fromId, relationshipDescription));
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void storeProcessRelationshipEntity(MappingSupport.RelationshipPropertiesWithEntityHolder id, Object source, Object target, RelationshipDescription type) {
|
||||
@@ -284,7 +289,7 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
}
|
||||
|
||||
public void requireIdUpdate(Neo4jPersistentEntity<?> sourceEntity, RelationshipDescription relationshipDescription, boolean canUseElementId,
|
||||
Object fromId, Object toId, NestedRelationshipContext relationshipContext, Object relatedValueToStore, Neo4jPersistentProperty idProperty) {
|
||||
Object fromId, Object toId, NestedRelationshipContext relationshipContext, Object relatedValueToStore, @Nullable Neo4jPersistentProperty idProperty) {
|
||||
|
||||
Statement relationshipCreationQuery = CypherGenerator.INSTANCE.prepareSaveOfRelationshipWithProperties(
|
||||
sourceEntity, relationshipDescription, false,
|
||||
@@ -297,24 +302,6 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<RelationshipIdUpdateContext> getRequiresIdUpdate() {
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
return Set.copyOf(requiresIdUpdate);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
public void markAsUpdated(RelationshipIdUpdateContext context) {
|
||||
final long stamp = lock.writeLock();
|
||||
try {
|
||||
requiresIdUpdate.remove(context);
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRelationshipIds(RelationshipIdSupplier idSupplier) {
|
||||
final long stamp = lock.writeLock();
|
||||
try {
|
||||
@@ -322,10 +309,12 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
while (it.hasNext()) {
|
||||
var requiredIdUpdate = it.next();
|
||||
idSupplier.getId(requiredIdUpdate.cypher(), requiredIdUpdate.idProperty(), requiredIdUpdate.fromId(), requiredIdUpdate.toId()).ifPresent(anId -> {
|
||||
requiredIdUpdate.relationshipContext()
|
||||
.getRelationshipPropertiesPropertyAccessor(requiredIdUpdate.relatedValueToStore())
|
||||
.setProperty(requiredIdUpdate.idProperty(), anId);
|
||||
it.remove();
|
||||
PersistentPropertyAccessor<?> relationshipPropertiesPropertyAccessor = requiredIdUpdate.relationshipContext()
|
||||
.getRelationshipPropertiesPropertyAccessor(requiredIdUpdate.relatedValueToStore());
|
||||
if (relationshipPropertiesPropertyAccessor != null && requiredIdUpdate.idProperty() != null) {
|
||||
relationshipPropertiesPropertyAccessor.setProperty(requiredIdUpdate.idProperty(), anId);
|
||||
it.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
@@ -333,17 +322,19 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<Void> updateRelationshipIds(ReactiveRelationshipIdSupplier idSupplier) {
|
||||
public Mono<Void> updateRelationshipIdsReactive(ReactiveRelationshipIdSupplier idSupplier) {
|
||||
return Flux.defer(() -> {
|
||||
final long stamp = lock.writeLock();
|
||||
return Flux.fromIterable(requiresIdUpdate)
|
||||
.flatMap(requiredIdUpdate -> Mono.just(requiredIdUpdate).zipWith(idSupplier.getId(requiredIdUpdate.cypher(), requiredIdUpdate.idProperty(), requiredIdUpdate.fromId(), requiredIdUpdate.toId())))
|
||||
.doOnNext(t -> {
|
||||
var requiredIdUpdate = t.getT1();
|
||||
requiredIdUpdate.relationshipContext()
|
||||
.getRelationshipPropertiesPropertyAccessor(requiredIdUpdate.relatedValueToStore())
|
||||
.setProperty(requiredIdUpdate.idProperty(), t.getT2());
|
||||
requiresIdUpdate.remove(requiredIdUpdate);
|
||||
PersistentPropertyAccessor<?> relationshipPropertiesPropertyAccessor = requiredIdUpdate.relationshipContext()
|
||||
.getRelationshipPropertiesPropertyAccessor(requiredIdUpdate.relatedValueToStore());
|
||||
if (relationshipPropertiesPropertyAccessor != null && requiredIdUpdate.idProperty() != null) {
|
||||
relationshipPropertiesPropertyAccessor.setProperty(requiredIdUpdate.idProperty(), t.getT2());
|
||||
requiresIdUpdate.remove(requiredIdUpdate);
|
||||
}
|
||||
}).doOnTerminate(() -> lock.unlock(stamp));
|
||||
}).then();
|
||||
}
|
||||
@@ -387,14 +378,13 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getProcessedObjectIds(@Nullable Object entity) {
|
||||
private Object getProcessedObjectIds(Object entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return processedObjectsIds.get(System.identityHashCode(entity));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Object extractRelatedValueFromRelationshipProperties(Object valueToStore) {
|
||||
Object value;
|
||||
if (valueToStore instanceof MappingSupport.RelationshipPropertiesWithEntityHolder) {
|
||||
@@ -424,8 +414,9 @@ public final class NestedRelationshipProcessingStateMachine {
|
||||
return processedObjectsAlias.containsKey(System.identityHashCode(entity));
|
||||
}
|
||||
|
||||
private boolean hasProcessedAllOf(@Nullable Collection<?> entities) {
|
||||
private boolean hasProcessedAllOf(Collection<?> entities) {
|
||||
// there can be null elements in the unified collection of values to store.
|
||||
//noinspection ConstantValue
|
||||
if (entities == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Formattable;
|
||||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
|
||||
@@ -36,9 +38,15 @@ import org.springframework.data.mapping.MappingException;
|
||||
@API(status = API.Status.INTERNAL, since = "6.0.2")
|
||||
public final class NoRootNodeMappingException extends MappingException implements Formattable {
|
||||
|
||||
private MapAccessor mapAccessor;
|
||||
private Neo4jPersistentEntity<?> entity;
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5742846435191601546L;
|
||||
|
||||
@Nullable
|
||||
private final transient MapAccessor mapAccessor;
|
||||
@Nullable
|
||||
private final transient Neo4jPersistentEntity<?> entity;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public NoRootNodeMappingException(MapAccessor mapAccessor, Neo4jPersistentEntity<?> entity) {
|
||||
super(String.format("Could not find mappable nodes or relationships inside %s for %s", mapAccessor, entity));
|
||||
this.mapAccessor = mapAccessor;
|
||||
@@ -47,9 +55,13 @@ public final class NoRootNodeMappingException extends MappingException implement
|
||||
|
||||
@Override
|
||||
public void formatTo(Formatter formatter, int flags, int width, int precision) {
|
||||
String className = entity.getUnderlyingClass().getSimpleName();
|
||||
formatter.format("Could not find mappable nodes or relationships inside %s for %s:%s", mapAccessor,
|
||||
className.substring(0, 1).toLowerCase(
|
||||
Locale.ROOT), String.join(":", entity.getStaticLabels()));
|
||||
if (mapAccessor != null && entity != null) {
|
||||
String className = entity.getUnderlyingClass().getSimpleName();
|
||||
formatter.format("Could not find mappable nodes or relationships inside %s for %s:%s", mapAccessor,
|
||||
className.substring(0, 1).toLowerCase(
|
||||
Locale.ROOT), String.join(":", entity.getStaticLabels()));
|
||||
} else {
|
||||
formatter.format("Could not find mappable nodes or relationships inside a record");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Expression;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Describes how a class is mapped to a node inside the database. It provides navigable links to relationships and
|
||||
@@ -143,14 +144,15 @@ public interface NodeDescription<T> {
|
||||
*/
|
||||
default Expression getIdExpression() {
|
||||
|
||||
if (this.getIdDescription().getOptionalGraphPropertyName()
|
||||
var idDescription = Objects.requireNonNull(this.getIdDescription(), "No id description available, cannot compute a Cypher expression for retrieving or storing the id");
|
||||
if (idDescription.getOptionalGraphPropertyName()
|
||||
.flatMap(this::getGraphProperty)
|
||||
.filter(GraphPropertyDescription::isComposite)
|
||||
.isPresent()) {
|
||||
throw new IllegalStateException("A composite id property cannot be used as ID expression.");
|
||||
}
|
||||
|
||||
return this.getIdDescription().asIdExpression();
|
||||
return idDescription.asIdExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,12 +23,13 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This class is more or less just a wrapper around the node description lookup map. It ensures that there is no cyclic
|
||||
@@ -102,7 +103,7 @@ final class NodeDescriptionStore {
|
||||
return nodeDescriptionAndLabels.apply(entityDescription, labels);
|
||||
}
|
||||
|
||||
private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<?> entityDescription, @Nullable List<String> labels) {
|
||||
private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription<?> entityDescription, List<String> labels) {
|
||||
|
||||
boolean isConcreteClassThatFulfillsEverything = !Modifier.isAbstract(entityDescription.getUnderlyingClass().getModifiers()) && entityDescription.getStaticLabels().containsAll(labels);
|
||||
|
||||
@@ -148,14 +149,19 @@ final class NodeDescriptionStore {
|
||||
}
|
||||
|
||||
unmatchedLabelsCache.put(nd, unmatchedLabelsCount);
|
||||
if (mostMatchingNodeDescription == null || unmatchedLabelsCount < unmatchedLabelsCache.get(mostMatchingNodeDescription)) {
|
||||
if (mostMatchingNodeDescription == null || unmatchedLabelsCount < Objects.requireNonNullElse(unmatchedLabelsCache.get(mostMatchingNodeDescription), Integer.MAX_VALUE)) {
|
||||
mostMatchingNodeDescription = nd;
|
||||
mostMatchingStaticLabels = matchingLabels;
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> surplusLabels = new HashSet<>(labels);
|
||||
mostMatchingStaticLabels.forEach(surplusLabels::remove);
|
||||
if (mostMatchingStaticLabels != null) {
|
||||
mostMatchingStaticLabels.forEach(surplusLabels::remove);
|
||||
}
|
||||
if (mostMatchingNodeDescription == null) {
|
||||
throw new IllegalStateException("Could not compute a concrete node description for entity %s and labels %s".formatted(entityDescription, labels));
|
||||
}
|
||||
return new NodeDescriptionAndLabels(mostMatchingNodeDescription, surplusLabels);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* All property converters will be wrapped by this class. It adds the information if a converter needs to be applied to
|
||||
@@ -60,7 +60,8 @@ final class NullSafeNeo4jPersistentPropertyConverter<T> implements Neo4jPersiste
|
||||
return delegate.write(source);
|
||||
}
|
||||
|
||||
@Override @Nullable
|
||||
@Override
|
||||
@Nullable
|
||||
public T read(@Nullable Value source) {
|
||||
return source == null || source.isNull() ? null : delegate.read(source);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.data.neo4j.core.mapping;
|
||||
import static org.apiguardian.api.API.Status.STABLE;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The characteristics of a {@link Neo4jPersistentProperty} can diverge from what is by default derived from the annotated
|
||||
|
||||
@@ -22,8 +22,8 @@ import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.neo4j.core.schema.Property;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ public abstract class PropertyFilter {
|
||||
|
||||
public abstract boolean isNotFiltering();
|
||||
|
||||
static String toDotPath(RelaxedPropertyPath propertyPath, @Nullable String lastSegment) {
|
||||
static String toDotPath(RelaxedPropertyPath propertyPath, String lastSegment) {
|
||||
|
||||
if (lastSegment == null) {
|
||||
return propertyPath.toDotPath();
|
||||
@@ -167,7 +167,7 @@ public abstract class PropertyFilter {
|
||||
return dotPath;
|
||||
}
|
||||
|
||||
public String toDotPath(@Nullable String lastSegment) {
|
||||
public String toDotPath(String lastSegment) {
|
||||
|
||||
if (lastSegment == null) {
|
||||
return this.toDotPath();
|
||||
|
||||
@@ -23,10 +23,10 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.neo4j.core.schema.TargetNode;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A strategy for traversing all properties (including association) once, without going in circles with cyclic mappings.
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.neo4j.core.schema.Relationship;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Description of a relationship. Those descriptions always describe outgoing relationships. The inverse direction is
|
||||
@@ -92,6 +92,11 @@ public interface RelationshipDescription {
|
||||
@Nullable
|
||||
NodeDescription<?> getRelationshipPropertiesEntity();
|
||||
|
||||
default NodeDescription<?> getRequiredRelationshipPropertiesEntity() {
|
||||
|
||||
return Objects.requireNonNull(getRelationshipPropertiesEntity(), () -> "Relationship entity %s does not point to an entity holding the relationships' properties".formatted(this.getType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if this relationship is a relationship with additional properties. In such cases
|
||||
* {@code getRelationshipPropertiesClass} will return the type of the properties holding class.
|
||||
@@ -102,7 +107,7 @@ public interface RelationshipDescription {
|
||||
|
||||
default boolean hasInternalIdProperty() {
|
||||
|
||||
return hasRelationshipProperties() && Optional.ofNullable(getRelationshipPropertiesEntity().getIdDescription())
|
||||
return hasRelationshipProperties() && Optional.ofNullable(getRelationshipPropertiesEntity()).map(NodeDescription::getIdDescription)
|
||||
.filter(IdDescription::isInternallyGeneratedId).isPresent();
|
||||
}
|
||||
|
||||
@@ -114,7 +119,6 @@ public interface RelationshipDescription {
|
||||
return Relationship.Direction.INCOMING.equals(this.getDirection());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
default String generateRelatedNodesCollectionName(NodeDescription<?> mostAbstractNodeDescription) {
|
||||
|
||||
return this.getSource().getMostAbstractParentLabel(mostAbstractNodeDescription) + "_" + this.getType() + "_" + this.getTarget().getPrimaryLabel() + "_" + this.isOutgoing();
|
||||
@@ -125,11 +129,12 @@ public interface RelationshipDescription {
|
||||
*
|
||||
* @param relationshipObverse logically same relationship definition in the target entity
|
||||
*/
|
||||
void setRelationshipObverse(RelationshipDescription relationshipObverse);
|
||||
void setRelationshipObverse(@Nullable RelationshipDescription relationshipObverse);
|
||||
|
||||
/**
|
||||
* @return logically same relationship definition in the target entity
|
||||
*/
|
||||
@Nullable
|
||||
RelationshipDescription getRelationshipObverse();
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,11 +22,11 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.neo4j.core.schema.IdGenerator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Contains the descriptions of all nodes, their properties and relationships known to SDN.
|
||||
@@ -93,7 +93,13 @@ public interface Schema {
|
||||
throw new UnknownEntityException(targetClass);
|
||||
}
|
||||
Neo4jEntityConverter entityConverter = getEntityConverter();
|
||||
return (typeSystem, record) -> entityConverter.read(targetClass, record);
|
||||
return (typeSystem, record) -> {
|
||||
try {
|
||||
return entityConverter.read(targetClass, record);
|
||||
} catch (IllegalStateException ex) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
|
||||
@@ -28,6 +30,8 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
@API(status = API.Status.STABLE, since = "6.0")
|
||||
public final class UnknownEntityException extends InvalidDataAccessApiUsageException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -1769937352513022599L;
|
||||
private final Class<?> targetClass;
|
||||
|
||||
public UnknownEntityException(Class<?> targetClass) {
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class that orchestrates {@link EntityCallbacks}. Not to be used outside the framework.
|
||||
@@ -74,8 +73,7 @@ public final class EventSupport {
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T maybeCallBeforeBind(@Nullable T object) {
|
||||
public <T> T maybeCallBeforeBind(T object) {
|
||||
|
||||
if (object == null) {
|
||||
return object;
|
||||
@@ -90,8 +88,7 @@ public final class EventSupport {
|
||||
* @param <T> Expected type
|
||||
* @return The instance to which the callback was applied to
|
||||
*/
|
||||
@Nullable
|
||||
public <T> T maybeCallAfterConvert(@Nullable T object, Neo4jPersistentEntity<T> entity, MapAccessor source) {
|
||||
public <T> T maybeCallAfterConvert(T object, Neo4jPersistentEntity<T> entity, MapAccessor source) {
|
||||
|
||||
if (object == null) {
|
||||
return object;
|
||||
|
||||
@@ -15,7 +15,4 @@
|
||||
* <!-- end::intent[] -->
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.data.neo4j.core.mapping.callback;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
* <!-- 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.
|
||||
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
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
the imperative and reactive variants of the client and the template.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -25,14 +25,18 @@ import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
@@ -45,7 +49,6 @@ import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyToMapC
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.core.schema.CompositeProperty.Phase;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -62,7 +65,7 @@ import org.springframework.util.StringUtils;
|
||||
* @since 6.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD })
|
||||
@Target({ElementType.FIELD})
|
||||
@Inherited
|
||||
@ConvertWith(converterFactory = CompositePropertyConverterFactory.class)
|
||||
@API(status = API.Status.STABLE, since = "6.0")
|
||||
@@ -207,7 +210,7 @@ final class CompositePropertyConverter<K, P> implements Neo4jPersistentPropertyC
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value write(P property) {
|
||||
public Value write(@Nullable P property) {
|
||||
|
||||
Map<K, Value> source = delegate.decompose(property, neo4jConversionService);
|
||||
Map<String, Object> temp = new HashMap<>();
|
||||
@@ -216,7 +219,12 @@ final class CompositePropertyConverter<K, P> implements Neo4jPersistentPropertyC
|
||||
}
|
||||
|
||||
@Override
|
||||
public P read(Value source) {
|
||||
@Nullable
|
||||
public P read(@Nullable Value source) {
|
||||
|
||||
if (source == null || TypeSystem.getDefault().NULL().isTypeOf(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<K, Value> temp = new HashMap<>();
|
||||
source.keys().forEach(k -> {
|
||||
@@ -230,6 +238,7 @@ final class CompositePropertyConverter<K, P> implements Neo4jPersistentPropertyC
|
||||
|
||||
/**
|
||||
* Internally used via reflection.
|
||||
*
|
||||
* @return The type of the underlying delegate.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@@ -249,7 +258,7 @@ final class CompositePropertyConverterFactory implements Neo4jPersistentProperty
|
||||
private final BeanFactory beanFactory;
|
||||
private final Neo4jConversionService conversionServiceDelegate;
|
||||
|
||||
CompositePropertyConverterFactory(@Nullable BeanFactory beanFactory, @Nullable Neo4jConversionService conversionServiceDelegate) {
|
||||
CompositePropertyConverterFactory(BeanFactory beanFactory, Neo4jConversionService conversionServiceDelegate) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.conversionServiceDelegate = conversionServiceDelegate;
|
||||
}
|
||||
@@ -306,15 +315,16 @@ final class CompositePropertyConverterFactory implements Neo4jPersistentProperty
|
||||
}
|
||||
|
||||
if (persistentProperty.getActualType() != type) {
|
||||
var typeName = Optional.ofNullable(type).map(Type::getTypeName).orElse("n/a");
|
||||
throw new IllegalArgumentException(
|
||||
"The property type `" + typeVariableMap.get(PROPERTY_TYPE_KEY).getTypeName() + "` created by `"
|
||||
"The property type `" + typeName + "` created by `"
|
||||
+ delegateClass.getName() + "` " + generateLocation(persistentProperty)
|
||||
+ " doesn't match the actual property type");
|
||||
}
|
||||
componentType = (Class<?>) typeVariableMap.get(KEY_TYPE_KEY);
|
||||
}
|
||||
|
||||
boolean isEnum = componentType.isEnum();
|
||||
boolean isEnum = componentType != null && componentType.isEnum();
|
||||
if (!(componentType == String.class || isEnum)) {
|
||||
throw new IllegalArgumentException("@" + CompositeProperty.class.getSimpleName()
|
||||
+ " can only be used on Map properties with a key type of String or enum. Was " + generateLocation(
|
||||
@@ -343,7 +353,7 @@ final class CompositePropertyConverterFactory implements Neo4jPersistentProperty
|
||||
|
||||
String prefixWithDelimiter = persistentProperty.computePrefixWithDelimiter();
|
||||
return new CompositePropertyConverter(
|
||||
delegate, prefixWithDelimiter, conversionServiceDelegate, componentType, keyWriter, keyReader);
|
||||
delegate, prefixWithDelimiter, conversionServiceDelegate, Objects.requireNonNull(componentType), keyWriter, keyReader);
|
||||
}
|
||||
|
||||
private static String generateLocation(Neo4jPersistentProperty persistentProperty) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.core.schema;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.support;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -24,7 +26,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Don't use outside SDN code. You have been warned.
|
||||
@@ -35,18 +36,20 @@ public final class BookmarkManagerReference implements ApplicationContextAware {
|
||||
|
||||
private final Supplier<Neo4jBookmarkManager> defaultBookmarkManagerSupplier;
|
||||
|
||||
private ObjectProvider<Neo4jBookmarkManager> neo4jBookmarkManagers = new ObjectProvider<Neo4jBookmarkManager>() {
|
||||
private ObjectProvider<Neo4jBookmarkManager> neo4jBookmarkManagers = new ObjectProvider<>() {
|
||||
@Override
|
||||
public Neo4jBookmarkManager getObject(Object... args) throws BeansException {
|
||||
throw new BeanCreationException("This provider can't create new beans");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Neo4jBookmarkManager getIfAvailable() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Neo4jBookmarkManager getIfUnique() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
@@ -60,6 +63,7 @@ public final class BookmarkManagerReference implements ApplicationContextAware {
|
||||
@Nullable
|
||||
private volatile Neo4jBookmarkManager bookmarkManager;
|
||||
|
||||
@Nullable
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public BookmarkManagerReference(Supplier<Neo4jBookmarkManager> defaultBookmarkManagerSupplier, @Nullable Neo4jBookmarkManager bookmarkManager) {
|
||||
@@ -73,7 +77,8 @@ public final class BookmarkManagerReference implements ApplicationContextAware {
|
||||
this.neo4jBookmarkManagers = applicationContext.getBeanProvider(Neo4jBookmarkManager.class);
|
||||
this.applicationEventPublisher = applicationContext;
|
||||
if (this.bookmarkManager != null) {
|
||||
this.bookmarkManager.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
Objects.requireNonNull(this.bookmarkManager)
|
||||
.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +89,7 @@ public final class BookmarkManagerReference implements ApplicationContextAware {
|
||||
result = this.bookmarkManager;
|
||||
if (result == null) {
|
||||
this.bookmarkManager = neo4jBookmarkManagers.getIfAvailable(this.defaultBookmarkManagerSupplier);
|
||||
//noinspection DataFlowIssue
|
||||
this.bookmarkManager.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
result = this.bookmarkManager;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ import java.lang.annotation.Target;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.neo4j.core.convert.ConvertWith;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter;
|
||||
|
||||
@@ -47,12 +49,13 @@ public @interface DateLong {
|
||||
final class DateLongConverter implements Neo4jPersistentPropertyConverter<Date> {
|
||||
|
||||
@Override
|
||||
public Value write(Date source) {
|
||||
return Values.value(source.getTime());
|
||||
public Value write(@Nullable Date source) {
|
||||
return source == null ? Values.NULL : Values.value(source.getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date read(Value source) {
|
||||
return new Date(source.asLong());
|
||||
@Nullable
|
||||
public Date read(@Nullable Value source) {
|
||||
return source == null || TypeSystem.getDefault().NULL().isTypeOf(source) ? null : new Date(source.asLong());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.neo4j.core.convert.ConvertWith;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverterFactory;
|
||||
@@ -100,14 +102,15 @@ final class DateStringConverter implements Neo4jPersistentPropertyConverter<Date
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value write(Date source) {
|
||||
return Values.value(getFormat().format(source));
|
||||
public Value write(@Nullable Date source) {
|
||||
return source == null ? Values.NULL : Values.value(getFormat().format(source));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date read(Value source) {
|
||||
@Nullable
|
||||
public Date read(@Nullable Value source) {
|
||||
try {
|
||||
return getFormat().parse(source.asString());
|
||||
return source == null || TypeSystem.getDefault().NULL().isTypeOf(source) ? null : getFormat().parse(source.asString());
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.support;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Representation of a user agent containing sensible information to identify queries generated by or executed via Spring Data Neo4j.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
indicating that some transaction may be retried and additional converters and id generators.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.core.support;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Default bookmark manager.
|
||||
|
||||
@@ -20,9 +20,9 @@ import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Responsible for storing, updating and retrieving the bookmarks of Neo4j's transaction.
|
||||
@@ -58,7 +58,7 @@ public sealed interface Neo4jBookmarkManager permits AbstractBookmarkManager, No
|
||||
* bookmarks on each call.
|
||||
* @return A bookmark manager
|
||||
*/
|
||||
static Neo4jBookmarkManager create(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
|
||||
static Neo4jBookmarkManager create(Supplier<Set<Bookmark>> bookmarksSupplier) {
|
||||
return new DefaultBookmarkManager(bookmarksSupplier);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed interface Neo4jBookmarkManager permits AbstractBookmarkManager, No
|
||||
* bookmarks on each call.
|
||||
* @return A reactive bookmark manager
|
||||
*/
|
||||
static Neo4jBookmarkManager createReactive(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
|
||||
static Neo4jBookmarkManager createReactive(Supplier<Set<Bookmark>> bookmarksSupplier) {
|
||||
return new ReactiveDefaultBookmarkManager(bookmarksSupplier);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@@ -33,7 +35,10 @@ import org.springframework.context.ApplicationEvent;
|
||||
@API(status = API.Status.STABLE, since = "6.1.1")
|
||||
public final class Neo4jBookmarksUpdatedEvent extends ApplicationEvent {
|
||||
|
||||
private final Set<Bookmark> bookmarks;
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2143476552056698819L;
|
||||
@Nullable
|
||||
private transient final Set<Bookmark> bookmarks;
|
||||
|
||||
Neo4jBookmarksUpdatedEvent(Set<Bookmark> bookmarks) {
|
||||
super(bookmarks);
|
||||
@@ -45,6 +50,6 @@ public final class Neo4jBookmarksUpdatedEvent extends ApplicationEvent {
|
||||
*/
|
||||
public Set<Bookmark> getBookmarks() {
|
||||
|
||||
return Collections.unmodifiableSet(this.bookmarks);
|
||||
return this.bookmarks == null ? Set.of() : Collections.unmodifiableSet(this.bookmarks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.Transaction;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.support.RetryExceptionPredicate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.support.ResourceHolderSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
@@ -33,7 +36,6 @@ import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.UserSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
@@ -43,7 +45,6 @@ import org.springframework.transaction.support.SmartTransactionObject;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Dedicated {@link org.springframework.transaction.PlatformTransactionManager} for native Neo4j transactions. This
|
||||
@@ -55,6 +56,9 @@ import org.springframework.util.StringUtils;
|
||||
@API(status = API.Status.STABLE, since = "6.0")
|
||||
public final class Neo4jTransactionManager extends AbstractPlatformTransactionManager implements ApplicationContextAware {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 7971369288503005574L;
|
||||
|
||||
/**
|
||||
* Start building a new transaction manager for the given driver instance.
|
||||
* @param driver A fixed driver instance.
|
||||
@@ -114,7 +118,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBookmarkManager(@Nullable Neo4jBookmarkManager bookmarkManager) {
|
||||
public Builder withBookmarkManager(Neo4jBookmarkManager bookmarkManager) {
|
||||
this.bookmarkManager = bookmarkManager;
|
||||
return this;
|
||||
}
|
||||
@@ -127,19 +131,19 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
/**
|
||||
* The underlying driver, which is also the synchronisation object.
|
||||
*/
|
||||
private final Driver driver;
|
||||
private transient final Driver driver;
|
||||
|
||||
/**
|
||||
* Database name provider.
|
||||
*/
|
||||
private final DatabaseSelectionProvider databaseSelectionProvider;
|
||||
private transient final DatabaseSelectionProvider databaseSelectionProvider;
|
||||
|
||||
/**
|
||||
* Provider for user impersonation.
|
||||
*/
|
||||
private final UserSelectionProvider userSelectionProvider;
|
||||
private transient final UserSelectionProvider userSelectionProvider;
|
||||
|
||||
private final BookmarkManagerReference bookmarkManager;
|
||||
private transient final BookmarkManagerReference bookmarkManager;
|
||||
|
||||
/**
|
||||
* This will create a transaction manager for the default database.
|
||||
@@ -193,20 +197,6 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
this.bookmarkManager.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param driver The driver that has been used as a synchronization object.
|
||||
* @param targetDatabase The target database
|
||||
* @return An optional managed transaction or {@literal null} if the method hasn't been called inside an ongoing
|
||||
* Spring transaction
|
||||
* @see #retrieveTransaction(Driver, DatabaseSelection, UserSelection)
|
||||
* @deprecated since 6.2, use #retrieveTransaction(Driver, DatabaseSelection, UserSelection)
|
||||
*/
|
||||
@Deprecated
|
||||
public static @Nullable Transaction retrieveTransaction(final Driver driver, @Nullable final String targetDatabase) {
|
||||
|
||||
return retrieveTransaction(driver, StringUtils.hasText(targetDatabase) ? DatabaseSelection.byName(targetDatabase) : DatabaseSelection.undecided(), UserSelection.connectedUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method provides a native Neo4j transaction to be used from within a {@link org.springframework.data.neo4j.core.Neo4jClient}.
|
||||
* In most cases this the native transaction will be controlled from the Neo4j specific {@link org.springframework.transaction.PlatformTransactionManager}.
|
||||
@@ -219,7 +209,8 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
* @return An optional managed transaction or {@literal null} if the method hasn't been called inside an ongoing
|
||||
* Spring transaction
|
||||
*/
|
||||
public static @Nullable Transaction retrieveTransaction(
|
||||
@Nullable
|
||||
public static Transaction retrieveTransaction(
|
||||
final Driver driver,
|
||||
final DatabaseSelection targetDatabase,
|
||||
final UserSelection asUser
|
||||
@@ -257,7 +248,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
.registerSynchronization(new Neo4jSessionSynchronization(connectionHolder, driver));
|
||||
|
||||
TransactionSynchronizationManager.bindResource(driver, connectionHolder);
|
||||
return connectionHolder.getTransaction(targetDatabase, asUser);
|
||||
return Objects.requireNonNull(connectionHolder.getTransaction(targetDatabase, asUser));
|
||||
}
|
||||
|
||||
private static Neo4jTransactionObject extractNeo4jTransaction(Object transaction) {
|
||||
@@ -285,7 +276,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
@Override
|
||||
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
|
||||
|
||||
return extractNeo4jTransaction(transaction).hasResourceHolder();
|
||||
return extractNeo4jTransaction(transaction).getResourceHolder() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -331,7 +322,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
@Override
|
||||
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(transaction);
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(Objects.requireNonNull(transaction));
|
||||
transactionObject.setResourceHolder((Neo4jTransactionHolder) suspendedResources);
|
||||
|
||||
TransactionSynchronizationManager.bindResource(driver, suspendedResources);
|
||||
@@ -347,7 +338,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
this.bookmarkManager.resolve().updateBookmarks(transactionHolder.getBookmarks(), newBookmarks);
|
||||
} catch (Neo4jException ex) {
|
||||
if (ex instanceof RetryableException) {
|
||||
throw new TransactionSystemException(ex.getMessage(), ex);
|
||||
throw new TransactionSystemException(Objects.requireNonNullElse(ex.getMessage(), "Caught a retryable exception"), ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
@@ -383,7 +374,8 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
// The resource holder is null when the call to TransactionSynchronizationManager.getResource
|
||||
// in Neo4jTransactionManager.doGetTransaction didn't return a corresponding resource holder.
|
||||
// If it is null, there's no existing session / transaction.
|
||||
@Nullable private Neo4jTransactionHolder resourceHolder;
|
||||
@Nullable
|
||||
private Neo4jTransactionHolder resourceHolder;
|
||||
|
||||
Neo4jTransactionObject(@Nullable Neo4jTransactionHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
@@ -393,23 +385,19 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
* Usually called in {@link #doBegin(Object, TransactionDefinition)} which is called when there's no existing
|
||||
* transaction.
|
||||
*
|
||||
* @param resourceHolder A newly created resource holder with a fresh drivers session,
|
||||
* @param resourceHolder A newly created resource holder with a fresh drivers' session,
|
||||
*/
|
||||
void setResourceHolder(@Nullable Neo4jTransactionHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a {@link Neo4jTransactionHolder} is set.
|
||||
*/
|
||||
boolean hasResourceHolder() {
|
||||
return resourceHolder != null;
|
||||
@Nullable Neo4jTransactionHolder getResourceHolder() {
|
||||
return resourceHolder;
|
||||
}
|
||||
|
||||
Neo4jTransactionHolder getRequiredResourceHolder() {
|
||||
|
||||
Assert.state(hasResourceHolder(), RESOURCE_HOLDER_NOT_PRESENT_MESSAGE);
|
||||
return resourceHolder;
|
||||
return Objects.requireNonNull(resourceHolder, RESOURCE_HOLDER_NOT_PRESENT_MESSAGE);
|
||||
}
|
||||
|
||||
void setRollbackOnly() {
|
||||
@@ -419,7 +407,7 @@ public final class Neo4jTransactionManager extends AbstractPlatformTransactionMa
|
||||
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return this.hasResourceHolder() && this.resourceHolder.isRollbackOnly();
|
||||
return this.resourceHolder != null && this.resourceHolder.isRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.AccessMode;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.neo4j.driver.SessionConfig;
|
||||
@@ -39,6 +40,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public final class Neo4jTransactionUtils {
|
||||
|
||||
@Nullable
|
||||
private static final Method WITH_IMPERSONATED_USER
|
||||
= ReflectionUtils.findMethod(SessionConfig.Builder.class, "withImpersonatedUser", String.class);
|
||||
|
||||
@@ -46,7 +48,7 @@ public final class Neo4jTransactionUtils {
|
||||
return WITH_IMPERSONATED_USER != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
@SuppressWarnings({"UnusedReturnValue", "NullAway"})
|
||||
public static SessionConfig.Builder withImpersonatedUser(SessionConfig.Builder builder, String user) {
|
||||
|
||||
if (driverSupportsImpersonation()) {
|
||||
|
||||
@@ -22,9 +22,9 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Default bookmark manager.
|
||||
|
||||
@@ -20,12 +20,12 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Bookmark;
|
||||
import org.neo4j.driver.reactivestreams.ReactiveSession;
|
||||
import org.neo4j.driver.reactivestreams.ReactiveTransaction;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.support.ResourceHolderSupport;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Objects;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.TransactionConfig;
|
||||
import org.neo4j.driver.exceptions.RetryableException;
|
||||
@@ -32,7 +36,6 @@ import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.ReactiveUserSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.UserSelection;
|
||||
import org.springframework.data.neo4j.core.support.BookmarkManagerReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.NoTransactionException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
@@ -52,6 +55,9 @@ import org.springframework.util.Assert;
|
||||
@API(status = API.Status.STABLE, since = "6.0")
|
||||
public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTransactionManager implements ApplicationContextAware {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 204661696798919944L;
|
||||
|
||||
/**
|
||||
* Start building a new transaction manager for the given driver instance.
|
||||
* @param driver A fixed driver instance.
|
||||
@@ -124,19 +130,19 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
/**
|
||||
* The underlying driver, which is also the synchronisation object.
|
||||
*/
|
||||
private final Driver driver;
|
||||
private transient final Driver driver;
|
||||
|
||||
/**
|
||||
* Database name provider.
|
||||
*/
|
||||
private final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
|
||||
private transient final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
|
||||
|
||||
/**
|
||||
* Provider for user impersonation.
|
||||
*/
|
||||
private final ReactiveUserSelectionProvider userSelectionProvider;
|
||||
private transient final ReactiveUserSelectionProvider userSelectionProvider;
|
||||
|
||||
private final BookmarkManagerReference bookmarkManager;
|
||||
private transient final BookmarkManagerReference bookmarkManager;
|
||||
|
||||
/**
|
||||
* This will create a transaction manager for the default database.
|
||||
@@ -272,7 +278,7 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
@Override
|
||||
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
|
||||
|
||||
return extractNeo4jTransaction(transaction).hasResourceHolder();
|
||||
return extractNeo4jTransaction(transaction).getResourceHolder() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -328,7 +334,7 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
.getRequiredResourceHolder();
|
||||
return holder.commit()
|
||||
.doOnNext(bookmark -> bookmarkManager.resolve().updateBookmarks(holder.getBookmarks(), bookmark))
|
||||
.onErrorMap(e -> e instanceof RetryableException, e -> new TransactionSystemException(e.getMessage(), e))
|
||||
.onErrorMap(e -> e instanceof RetryableException, ex -> new TransactionSystemException(Objects.requireNonNullElse(ex.getMessage(), "Caught a retryable exception"), ex))
|
||||
.then();
|
||||
}
|
||||
|
||||
@@ -350,10 +356,10 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> doResume(TransactionSynchronizationManager synchronizationManager, Object transaction,
|
||||
protected Mono<Void> doResume(TransactionSynchronizationManager synchronizationManager, @Nullable Object transaction,
|
||||
Object suspendedResources) throws TransactionException {
|
||||
|
||||
return Mono.just(extractNeo4jTransaction(transaction))
|
||||
return Mono.just(extractNeo4jTransaction(Objects.requireNonNull(transaction)))
|
||||
.doOnNext(r -> r.setResourceHolder((ReactiveNeo4jTransactionHolder) suspendedResources))
|
||||
.then(Mono.fromRunnable(() -> synchronizationManager.bindResource(driver, suspendedResources)));
|
||||
}
|
||||
@@ -379,7 +385,8 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
// The resource holder is null when the call to TransactionSynchronizationManager.getResource
|
||||
// in Neo4jTransactionManager.doGetTransaction didn't return a corresponding resource holder.
|
||||
// If it is null, there's no existing session / transaction.
|
||||
@Nullable private ReactiveNeo4jTransactionHolder resourceHolder;
|
||||
@Nullable
|
||||
private ReactiveNeo4jTransactionHolder resourceHolder;
|
||||
|
||||
ReactiveNeo4jTransactionObject(@Nullable ReactiveNeo4jTransactionHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
@@ -389,33 +396,24 @@ public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTrans
|
||||
* Usually called in {@link #doBegin(TransactionSynchronizationManager, Object, TransactionDefinition)} which is
|
||||
* called when there's no existing transaction.
|
||||
*
|
||||
* @param resourceHolder A newly created resource holder with a fresh drivers session,
|
||||
* @param resourceHolder A newly created resource holder with a fresh drivers' session,
|
||||
*/
|
||||
void setResourceHolder(@Nullable ReactiveNeo4jTransactionHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a {@link Neo4jTransactionHolder} is set.
|
||||
*/
|
||||
boolean hasResourceHolder() {
|
||||
return resourceHolder != null;
|
||||
}
|
||||
|
||||
ReactiveNeo4jTransactionHolder getRequiredResourceHolder() {
|
||||
|
||||
Assert.state(hasResourceHolder(), RESOURCE_HOLDER_NOT_PRESENT_MESSAGE);
|
||||
return resourceHolder;
|
||||
return Objects.requireNonNull(this.resourceHolder, RESOURCE_HOLDER_NOT_PRESENT_MESSAGE);
|
||||
}
|
||||
|
||||
void setRollbackOnly() {
|
||||
|
||||
getRequiredResourceHolder().setRollbackOnly();
|
||||
@Nullable ReactiveNeo4jTransactionHolder getResourceHolder() {
|
||||
return resourceHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return this.hasResourceHolder() && this.resourceHolder.isRollbackOnly();
|
||||
return this.resourceHolder != null && this.resourceHolder.isRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
both the imperative and reactive `TransactionManager` as `Neo4jTransactionManager` and `ReactiveNeo4jTransactionManager`.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
|
||||
@@ -26,8 +28,10 @@ import org.springframework.dao.EmptyResultDataAccessException;
|
||||
* @since 6.0
|
||||
*/
|
||||
@API(status = API.Status.STABLE, since = "6.0")
|
||||
public class NoResultException extends EmptyResultDataAccessException {
|
||||
public final class NoResultException extends EmptyResultDataAccessException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -1508370436250180391L;
|
||||
private final String query;
|
||||
|
||||
public NoResultException(int expectedNumberOfResults, String query) {
|
||||
|
||||
@@ -132,7 +132,7 @@ public final class Neo4jRepositoryConfigurationExtension extends RepositoryConfi
|
||||
source.getAttribute("transactionManagerRef").orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
|
||||
builder.addPropertyReference("neo4jOperations",
|
||||
source.getAttribute("neo4jTemplateRef").orElse(DEFAULT_NEO4J_TEMPLATE_BEAN_NAME));
|
||||
builder.addPropertyReference("neo4jMappingContext",
|
||||
builder.addPropertyReference("mappingContext",
|
||||
source.getAttribute("neo4jMappingContextRef").orElse(DEFAULT_MAPPING_CONTEXT_BEAN_NAME));
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public final class ReactiveNeo4jRepositoryConfigurationExtension extends Reposit
|
||||
source.getAttribute("transactionManagerRef").orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
|
||||
builder.addPropertyReference("neo4jOperations",
|
||||
source.getAttribute("neo4jTemplateRef").orElse(DEFAULT_NEO4J_TEMPLATE_BEAN_NAME));
|
||||
builder.addPropertyReference("neo4jMappingContext",
|
||||
builder.addPropertyReference("mappingContext",
|
||||
source.getAttribute("neo4jMappingContextRef").orElse(DEFAULT_MAPPING_CONTEXT_BEAN_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
and reactive Spring Data Neo4j repositories.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.repository.config;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This package provides the Neo4j imperative and reactive repository API.
|
||||
* <!-- end::intent[] -->
|
||||
*/
|
||||
@NonNullApi
|
||||
@NullMarked
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -48,7 +49,6 @@ import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -94,13 +94,14 @@ abstract class AbstractNeo4jQuery extends Neo4jQuerySupport implements Repositor
|
||||
|
||||
if (Iterable.class.isAssignableFrom(returnType)) {
|
||||
TypeInformation<?> from = TypeInformation.fromReturnTypeOf(repositoryMethod);
|
||||
return GeoResult.class.equals(from.getComponentType().getType());
|
||||
return from.getComponentType() != null && GeoResult.class.equals(from.getComponentType().getType());
|
||||
}
|
||||
|
||||
return GeoPage.class.isAssignableFrom(returnType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public final Object execute(Object[] parameters) {
|
||||
|
||||
boolean incrementLimit = queryMethod.incrementLimit();
|
||||
@@ -122,8 +123,10 @@ abstract class AbstractNeo4jQuery extends Neo4jQuerySupport implements Repositor
|
||||
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), mappingContext);
|
||||
|
||||
// Neo4jQuerySupport ensure we will get an EntityInstanceWithSource in the projecting case
|
||||
preparingConverter = source -> converter.convert(
|
||||
(EntityInstanceWithSource) OptionalUnwrappingConverter.INSTANCE.convert(source));
|
||||
preparingConverter = source -> {
|
||||
var unwrapped = (EntityInstanceWithSource) OptionalUnwrappingConverter.INSTANCE.convert(source);
|
||||
return (unwrapped == null) ? null : converter.convert(unwrapped);
|
||||
};
|
||||
}
|
||||
|
||||
if (queryMethod.isPageQuery()) {
|
||||
@@ -180,11 +183,11 @@ abstract class AbstractNeo4jQuery extends Neo4jQuerySupport implements Repositor
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
|
||||
protected abstract <T> PreparedQuery<T> prepareQuery(Class<T> returnedType,
|
||||
Collection<PropertyFilter.ProjectedPath> includedProperties, Neo4jParameterAccessor parameterAccessor,
|
||||
@Nullable Neo4jQueryType queryType,
|
||||
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
|
||||
@Nullable UnaryOperator<Integer> limitModifier);
|
||||
UnaryOperator<Integer> limitModifier);
|
||||
|
||||
protected Optional<PreparedQuery<Long>> getCountQuery(Neo4jParameterAccessor parameterAccessor) {
|
||||
return Optional.empty();
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -37,7 +38,6 @@ import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -84,13 +84,15 @@ abstract class AbstractReactiveNeo4jQuery extends Neo4jQuerySupport implements R
|
||||
|
||||
if (Flux.class.isAssignableFrom(returnType)) {
|
||||
TypeInformation<?> from = TypeInformation.fromReturnTypeOf(repositoryMethod);
|
||||
return GeoResult.class.equals(from.getComponentType().getType());
|
||||
TypeInformation<?> componentType = from.getComponentType();
|
||||
return componentType != null && GeoResult.class.equals(componentType.getType());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public final Object execute(Object[] parameters) {
|
||||
|
||||
boolean incrementLimit = queryMethod.incrementLimit();
|
||||
@@ -111,8 +113,10 @@ abstract class AbstractReactiveNeo4jQuery extends Neo4jQuerySupport implements R
|
||||
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), mappingContext);
|
||||
|
||||
// Neo4jQuerySupport ensure we will get an EntityInstanceWithSource in the projecting case
|
||||
preparingConverter = source -> converter.convert(
|
||||
(EntityInstanceWithSource) OptionalUnwrappingConverter.INSTANCE.convert(source));
|
||||
preparingConverter = source -> {
|
||||
var intermediate = (EntityInstanceWithSource) OptionalUnwrappingConverter.INSTANCE.convert(source);
|
||||
return (intermediate == null) ? null : converter.convert(intermediate);
|
||||
};
|
||||
}
|
||||
|
||||
if (queryMethod.isScrollQuery()) {
|
||||
@@ -125,6 +129,6 @@ abstract class AbstractReactiveNeo4jQuery extends Neo4jQuerySupport implements R
|
||||
|
||||
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
|
||||
Collection<PropertyFilter.ProjectedPath> includedProperties, Neo4jParameterAccessor parameterAccessor,
|
||||
@Nullable Neo4jQueryType queryType, @Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
|
||||
@Nullable UnaryOperator<Integer> limitModifier);
|
||||
@Nullable Neo4jQueryType queryType, Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
|
||||
UnaryOperator<Integer> limitModifier);
|
||||
}
|
||||
|
||||
@@ -20,21 +20,21 @@ import static org.neo4j.cypherdsl.core.Cypher.property;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.Expression;
|
||||
import org.neo4j.cypherdsl.core.SortItem;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder;
|
||||
import org.neo4j.cypherdsl.core.SymbolicName;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.ScrollPosition.Direction;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversionService;
|
||||
@@ -161,7 +161,7 @@ public final class CypherAdapterUtils {
|
||||
expression = entity.getIdExpression();
|
||||
var comparatorFunction = getComparatorFunction(scrollPosition.scrollsForward() ? Sort.Direction.ASC : Sort.Direction.DESC, scrollDirection);
|
||||
allEqualsWithArtificialSort = allEqualsWithArtificialSort.and(comparatorFunction.apply(expression, parameter));
|
||||
} else {
|
||||
} else if (propertyAndDirection.containsKey(k)) {
|
||||
var p = propertyAndDirection.get(k);
|
||||
expression = p.property.isIdProperty() ? entity.getIdExpression() : root.property(k);
|
||||
|
||||
@@ -188,22 +188,14 @@ public final class CypherAdapterUtils {
|
||||
* @param sort The sort object to convert
|
||||
* @return An of sort items. It will be empty when sort is unsorted.
|
||||
*/
|
||||
public static Collection<SortItem> toSortItems(NodeDescription<?> nodeDescription, Sort sort) {
|
||||
public static Collection<SortItem> toSortItems(@Nullable NodeDescription<?> nodeDescription, Sort sort) {
|
||||
|
||||
if (nodeDescription == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return sort.stream().map(sortAdapterFor(nodeDescription)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static StatementBuilder.BuildableStatement addPagingParameter(NodeDescription<?> nodeDescription,
|
||||
Pageable pageable, StatementBuilder.OngoingReadingAndReturn returning) {
|
||||
|
||||
Sort sort = pageable.getSort();
|
||||
|
||||
long skip = pageable.getOffset();
|
||||
|
||||
int pageSize = pageable.getPageSize();
|
||||
|
||||
return returning.orderBy(toSortItems(nodeDescription, sort)).skip(skip).limit(pageSize);
|
||||
}
|
||||
|
||||
private CypherAdapterUtils() {}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.neo4j.cypherdsl.core.Expression;
|
||||
@@ -64,8 +65,6 @@ import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A Cypher-DSL based implementation of the {@link AbstractQueryCreator} that eventually creates Cypher queries as
|
||||
@@ -94,11 +93,13 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
|
||||
|
||||
private final Pageable pagingParameter;
|
||||
|
||||
@Nullable
|
||||
private final ScrollPosition scrollPosition;
|
||||
|
||||
/**
|
||||
* Stores the number of max results, if the {@link PartTree tree} is limiting.
|
||||
*/
|
||||
@Nullable
|
||||
private final Number maxResults;
|
||||
|
||||
/**
|
||||
@@ -188,7 +189,6 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
|
||||
return new QueryFragmentsAndParameters(nodeDescription, queryFragments, convertedParameters, theSort);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private QueryFragments createQueryFragments(@Nullable Condition condition, Sort sort) {
|
||||
QueryFragments queryFragments = new QueryFragments();
|
||||
|
||||
@@ -234,7 +234,9 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
|
||||
// Enforce sorting by something that is hopefully stable comparable (looking at Neo4j's id() with tears in my eyes).
|
||||
theSort = theSort.and(Sort.by(entity.getRequiredIdProperty().getName()).ascending());
|
||||
|
||||
queryFragments.setLimit(limitModifier.apply(maxResults.intValue()));
|
||||
if (maxResults != null) {
|
||||
queryFragments.setLimit(limitModifier.apply(maxResults.intValue()));
|
||||
}
|
||||
if (!keysetScrollPosition.isInitial()) {
|
||||
conditionFragment = conditionFragment.and(CypherAdapterUtils.combineKeysetIntoCondition(entity, keysetScrollPosition, theSort, mappingContext.getConversionService()));
|
||||
}
|
||||
@@ -244,7 +246,8 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
|
||||
if (!offsetScrollPosition.isInitial()) {
|
||||
queryFragments.setSkip(offsetScrollPosition.getOffset() + 1);
|
||||
}
|
||||
queryFragments.setLimit(limitModifier.apply(pagingParameter.isUnpaged() ? maxResults.intValue() : pagingParameter.getPageSize()));
|
||||
|
||||
queryFragments.setLimit(limitModifier.apply((pagingParameter.isUnpaged() && maxResults != null) ? maxResults.intValue() : pagingParameter.getPageSize()));
|
||||
}
|
||||
|
||||
var finalSortItems = new ArrayList<>(this.sortItems);
|
||||
@@ -580,7 +583,8 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
|
||||
|
||||
final Object value;
|
||||
|
||||
final @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride;
|
||||
@Nullable
|
||||
final Neo4jPersistentPropertyConverter<?> conversionOverride;
|
||||
|
||||
Parameter(String nameOrIndex, Object value, @Nullable Neo4jPersistentPropertyConverter<?> conversionOverride) {
|
||||
this.nameOrIndex = nameOrIndex;
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Statement;
|
||||
import org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingAndReturn;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
@@ -64,8 +65,8 @@ final class CypherdslBasedQuery extends AbstractNeo4jQuery {
|
||||
@Override
|
||||
protected <T> PreparedQuery<T> prepareQuery(Class<T> returnedType,
|
||||
Collection<PropertyFilter.ProjectedPath> includedProperties,
|
||||
Neo4jParameterAccessor parameterAccessor, Neo4jQueryType queryType,
|
||||
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
|
||||
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
|
||||
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
|
||||
UnaryOperator<Integer> limitModifier) {
|
||||
|
||||
Object[] parameters = parameterAccessor.getValues();
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
@@ -30,13 +37,6 @@ import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Immutable implementation of a {@link FetchableFluentQuery}. All
|
||||
@@ -124,6 +124,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public R oneValue() {
|
||||
|
||||
return findOperation.find(example.getProbeType())
|
||||
@@ -134,6 +135,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public R firstValue() {
|
||||
|
||||
List<R> all = all();
|
||||
@@ -166,7 +168,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
@Override
|
||||
public Window<R> scroll(ScrollPosition scrollPosition) {
|
||||
Class<S> domainType = this.example.getProbeType();
|
||||
Neo4jPersistentEntity<?> entity = mappingContext.getPersistentEntity(domainType);
|
||||
Neo4jPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
var skip = scrollPosition.isInitial()
|
||||
? 0
|
||||
@@ -174,7 +176,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
: 0;
|
||||
|
||||
Condition condition = scrollPosition instanceof KeysetScrollPosition keysetScrollPosition
|
||||
? CypherAdapterUtils.combineKeysetIntoCondition(mappingContext.getPersistentEntity(example.getProbeType()), keysetScrollPosition, sort, mappingContext.getConversionService())
|
||||
? CypherAdapterUtils.combineKeysetIntoCondition(mappingContext.getRequiredPersistentEntity(example.getProbeType()), keysetScrollPosition, sort, mappingContext.getConversionService())
|
||||
: null;
|
||||
|
||||
List<R> rawResult = findOperation.find(domainType)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user