From 808eb875076d2db793c95acf7f22a88b7e0fc0c8 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Tue, 21 May 2019 18:54:30 +0200 Subject: [PATCH] Drop the notion of the NodeManager. This removes the NodeManager and related infrastructure and introduces some configuration support. See more about the reasoning in ADR-004. Co-authored-by: Gerrit Meier --- README.adoc | 48 +---- etc/adr/adr-004.adoc | 22 ++ .../AbstractNeo4jConfig.java} | 26 ++- .../config/Neo4jConfigurationSupport.java | 146 +++++++++++++ .../data/neo4j/core/DefaultNodeManager.java | 136 ------------ .../data/neo4j/core/NodeManager.java | 66 ------ .../data/neo4j/core/NodeManagerFactory.java | 123 ----------- .../data/neo4j/core/PersistenceException.java | 68 ------ .../context/DefaultPersistenceContext.java | 115 ---------- .../core/context/PersistenceContext.java | 65 ------ .../data/neo4j/core/context/package-info.java | 9 - .../context/tracking/EntityChangeEvent.java | 39 ---- .../tracking/EntityComparisonStrategy.java | 170 --------------- .../tracking/EntityTrackingStrategy.java | 74 ------- .../core/mapping/Neo4jMappingContext.java | 7 - .../data/neo4j/core/schema/Schema.java | 11 +- .../transaction/Neo4jTransactionManager.java | 50 +---- .../transaction/Neo4jTransactionUtils.java | 39 +--- .../core/transaction/NodeManagerHolder.java | 47 ----- .../NodeManagerSynchronization.java | 42 ---- .../NoResultException.java | 17 +- .../config/EnableNeo4jRepositories.java | 11 +- ...Neo4jRepositoryConfigurationExtension.java | 77 +------ .../repository/query/AbstractNeo4jQuery.java | 14 +- .../repository/query/Neo4jQueryExecution.java | 12 +- .../query/Neo4jQueryLookupStrategy.java | 10 +- .../repository/query/PartTreeNeo4jQuery.java | 9 +- .../query/StringBasedNeo4jQuery.java | 20 +- .../support/DefaultExecutableQuery.java | 61 ++++++ .../support}/DefaultPreparedQuery.java | 6 +- .../support}/ExecutableQuery.java | 25 ++- .../support/Neo4jRepositoryFactory.java | 12 +- .../support/Neo4jRepositoryFactoryBean.java | 10 +- .../support/NodeManagerFactoryBean.java | 62 ------ .../support}/PreparedQuery.java | 11 +- .../support/SharedNodeManagerCreator.java | 127 ----------- .../support/SimpleNeo4jRepository.java | 55 +++-- .../DefaultPersistenceContextTest.java | 140 ------------ .../EntityComparisonStrategyTest.java | 199 ------------------ .../data/neo4j/integration/RepositoryIT.java | 21 +- .../repository/query/RepositoryQueryTest.java | 16 +- .../support/SimpleNeo4jRepositoryTest.java | 96 --------- 42 files changed, 406 insertions(+), 1908 deletions(-) create mode 100644 etc/adr/adr-004.adoc rename spring-data-neo4j/src/main/java/org/springframework/data/neo4j/{core/NonUniqueResultException.java => config/AbstractNeo4jConfig.java} (50%) create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PersistenceException.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContext.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/package-info.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityChangeEvent.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategy.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityTrackingStrategy.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java rename spring-data-neo4j/src/main/java/org/springframework/data/neo4j/{core => repository}/NoResultException.java (70%) create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultExecutableQuery.java rename spring-data-neo4j/src/main/java/org/springframework/data/neo4j/{core => repository/support}/DefaultPreparedQuery.java (92%) rename spring-data-neo4j/src/main/java/org/springframework/data/neo4j/{core => repository/support}/ExecutableQuery.java (55%) delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java rename spring-data-neo4j/src/main/java/org/springframework/data/neo4j/{core => repository/support}/PreparedQuery.java (82%) delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java delete mode 100644 spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContextTest.java delete mode 100644 spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategyTest.java delete mode 100644 spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepositoryTest.java diff --git a/README.adoc b/README.adoc index 9f21575ab..50e64fd18 100644 --- a/README.adoc +++ b/README.adoc @@ -49,14 +49,7 @@ Each property of a class that is not identified as a simple type by `org.springf NOTE: Context in this sections refers especially to dirty tracking and dealing with state of entities. -It is unlikely that we can do completely without context. -While it's easy enough to update on entity as is with the <>, it's unlikely that we can deal this way with collections of relationships. -Spring Data JDBC's approach is as follow -____ -If the aggregate root is not new, all referenced entities get deleted, the aggregate root gets updated, and all referenced entities get inserted again. -____ - -That sounds good in theory, but the Graph itself being more optimized for reads than ongoing large chunks of writes probably won't like that. +We decided against a context for tracking changes, much like Spring Data JDBC did. === Other principles @@ -182,7 +175,8 @@ It is only meant to be a basic for discussions. note "Implementation of Spring Data Commons SPI" as SDC_note package "org.springframework.data.neo4j" { package "core" { - interface Neo4jTemplate + interface Neo4jClient + interface ReactiveNeo4jClient package "schema" { package "internal" { note "Schema description" as schemaDescription @@ -194,8 +188,8 @@ package "core" { interface Neo4jPersistentEntity interface Neo4jPersistentProperty } - package "session" { - interface Neo4jSessionFactory + package "transaction" { + class Neo4jTransactionManager } package "convert" { note "conversion support" as conversionNote @@ -234,7 +228,6 @@ core-[hidden]--->repository |`Neo4jTemplate` and related classes. |core.schema |Annotations for marking classes as nodes to be saved as well as internal schema description. -|context |Infrastructure for dirty tracking etc. |core.mapping |Spring mapping information. @@ -291,10 +284,10 @@ The other execution paths are only drafts right now and marked with a `*`. From our previous experience and handling in other Spring Data stores this would branch off in two (technical three) directions: . `StringBasedNeo4jQuery` for custom Cypher queries that are provided with the `@Query` annotation. -. `*` `StringBasedNeo4jQuery` for named queries that are outsourced in property files. -. `*` `PartTreeNeo4jQuery` for derived finder methods. +. `StringBasedNeo4jQuery` for named queries that are outsourced in property files. +. `PartTreeNeo4jQuery` for derived finder methods. -All three of them will get a custom `Neo4jQueryMethod` besides `Neo4jOperations` and `QueryMethodEvaluationContextProvider` (not used yet) provided. +All three of them will get a custom `Neo4jQueryMethod` besides `Neo4jClient` and `QueryMethodEvaluationContextProvider` (not used yet) provided. This is a wrapper around the `java.lang.reflect.Method` passed into the `resolveQuery` method of the `Neo4jQueryLookupStrategy` to provide additional metadata. ==== `StringBasedNeo4jQuery` execution @@ -314,30 +307,7 @@ We considered several approaches of dirty tracking in SDN-RX: . Shallow copy of objects to get compared on save. _A full copy of the objects will occupy twice the memory._ -Picking the third option for now (_using some kind of event / listener_) would allow us to track single field / relationship changes per object. -An entity will get tracked after it gets initially saved into or loaded from the database. - -[NOTE] -.Architecture evaluation fragment (not current anymore but should get archived) -==== -As the technical solution we decided to use proxies to listen for the changes. -It will then be required for the user to work with the returned (proxy) object after calling `save` to set additional fields etc. -The event tracking and event triggering through the proxies should communicate via an API to have the option of using another tracking approach in the future. - -Moved back to evaluation: It is not possible to simply intercept arbitrary property changes in Java. -We would need some kind of byte code enhancement/maven plugin to achieve this. -==== - -The information about object changes will be based on an object comparison. -We will store the saved or loaded object state and compare it with the actual version that should get saved. -To keep the eventing idea up this will create an one-shot event containing multiple changed properties. -This will also be needed if we implement the support for property changes through the byte code enhancement, -because a change / domain interaction could affect more than one property at a time. -The events will then get used to construct the matching cypher for the next save interaction. - -==== Side-effect on "flush mode" -We can also track repository interactions like `save` and defer them depending on the flush mode. -This would make it possible to remove the need for calling `save` explicitly but be more JPA-ish. +We have settled with option 1 (See ADR-004), analogue to Spring Data JDBC. [[starter]] == Spring Boot Starter diff --git a/etc/adr/adr-004.adoc b/etc/adr/adr-004.adoc new file mode 100644 index 000000000..38bc47ee2 --- /dev/null +++ b/etc/adr/adr-004.adoc @@ -0,0 +1,22 @@ +== ADR 4: Drop the notion of the `NodeManager` + +=== Status + +accepted + +=== Context + +We introduced the `NodeManager` as a pendan to Hibernates `EntityManager` and with it, a concept of a persistence context, tracking changes. +This setup is required for updating only changed properties and also having implicit saves. + +=== Decision + +The previous versions of SDN and OGM all copied the concept of having a tracking of entities. +We decided against it this time to remove complexity. +We will update all properties each time a node is save, relying on the database to do this in an efficient way. + +Relationships will be updated via smart queries. + +=== Consequences + +The biggest impact will probably more network traffic with models having a huge number of properties on a single domain object. diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NonUniqueResultException.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java similarity index 50% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NonUniqueResultException.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java index 3bd6174d8..b9640bf56 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NonUniqueResultException.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/AbstractNeo4jConfig.java @@ -16,22 +16,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.core; +package org.springframework.data.neo4j.config; import org.apiguardian.api.API; -import org.springframework.data.neo4j.core.PersistenceException.IllegalResultSizeException; +import org.neo4j.driver.Driver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.core.Neo4jClient; /** - * Throw when a query doesn't return a unique result. + * Base class for imperative SDN-RX configuration using JavaConfig. + * This can be included in all scenarios in which Spring Boot is not an option. * * @author Michael J. Simons - * @soundtrack Deichkind - Niveau weshalb warum + * @author Gerrit Meier * @since 1.0 */ +@Configuration @API(status = API.Status.STABLE, since = "1.0") -public class NonUniqueResultException extends IllegalResultSizeException { +public abstract class AbstractNeo4jConfig extends Neo4jConfigurationSupport { - public NonUniqueResultException(String query, long actualNumberOfResults) { - super(1L, actualNumberOfResults, query); + /** + * The driver used here should be the driver resulting from {@link #driver()}, which is the default. + * + * @param driver The driver to connect with. + * @return A imperative Neo4j client. + */ + @Bean + public Neo4jClient neo4jClient(Driver driver) { + return Neo4jClient.create(driver); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java new file mode 100644 index 000000000..716208906 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfigurationSupport.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.config; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apiguardian.api.API; +import org.neo4j.driver.Driver; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Internal support class for basic configuration. The support infrastructure here is basically all around finding out about + * which classes are to be mapped and which not. The driver is part of the configuration support, as Neo4js Java driver + * contains both imperative and reactive components. + * + * @author Michael J. Simons + * @author Gerrit Meier + * @since 1.0 + */ +@API(status = API.Status.STABLE, since = "1.0") +public abstract class Neo4jConfigurationSupport { + + /** + * The driver to be used for interacting with Neo4j. + * + * @return + */ + public abstract Driver driver(); + + /** + * Creates a {@link org.springframework.data.neo4j.core.mapping.Neo4jMappingContext} equipped with entity classes + * scanned from the mapping base package. + * + * @return + * @see #getMappingBasePackages() + */ + @Bean + public Neo4jMappingContext neo4jMappingContext() throws ClassNotFoundException { + + Neo4jMappingContext mappingContext = new Neo4jMappingContext(); + mappingContext.setInitialEntitySet(getInitialEntitySet()); + + return mappingContext; + } + + /** + * Provides a {@link PlatformTransactionManager} for Neo4j based on the driver resulting from {@link #driver()}. + * + * @param driver The driver to synchronize against + * @return A platform transaction manager + */ + @Bean + public PlatformTransactionManager transactionManager(Driver driver) { + + return new Neo4jTransactionManager(driver); + } + + /** + * Returns the base packages to scan for Neo4j mapped entities at startup. Will return the package name of the + * configuration class' (the concrete class, not this one here) by default. So if you have a + * {@code com.acme.AppConfig} extending {@link Neo4jConfigurationSupport} the base package will be considered + * {@code com.acme} unless the method is overridden to implement alternate behavior. + * + * @return the base packages to scan for mapped {@link Node} classes + * or an empty collection to not enable scanning for entities. + */ + protected final Collection getMappingBasePackages() { + + Package mappingBasePackage = getClass().getPackage(); + return Collections.singleton(mappingBasePackage == null ? null : mappingBasePackage.getName()); + } + + /** + * Scans the mapping base package for classes annotated with {@link Node}. + * By default, it scans for entities in all packages returned by {@link #getMappingBasePackages()}. + * + * @return + * @throws ClassNotFoundException + * @see #getMappingBasePackages() + */ + protected final Set> getInitialEntitySet() throws ClassNotFoundException { + + Set> initialEntitySet = new HashSet>(); + + for (String basePackage : getMappingBasePackages()) { + initialEntitySet.addAll(scanForEntities(basePackage)); + } + + return initialEntitySet; + } + + /** + * Scans the given base package for entities, i.e. Neo4j specific types annotated with {@link Node}. + * + * @param basePackage must not be {@literal null}. + * @return + * @throws ClassNotFoundException + */ + protected final Set> scanForEntities(String basePackage) throws ClassNotFoundException { + + if (!StringUtils.hasText(basePackage)) { + return Collections.emptySet(); + } + + Set> initialEntitySet = new HashSet>(); + + ClassPathScanningCandidateComponentProvider componentProvider = + new ClassPathScanningCandidateComponentProvider(false); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Node.class)); + + ClassLoader classLoader = Neo4jConfigurationSupport.class.getClassLoader(); + for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { + initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), classLoader)); + } + + return initialEntitySet; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java deleted file mode 100644 index 0792accaf..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core; - -import static java.util.stream.Collectors.*; - -import lombok.RequiredArgsConstructor; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import org.apiguardian.api.API; -import org.neo4j.driver.Transaction; -import org.neo4j.driver.exceptions.NoSuchRecordException; -import org.springframework.data.neo4j.core.Neo4jClient.MappingSpec; -import org.springframework.data.neo4j.core.Neo4jClient.RecordFetchSpec; -import org.springframework.data.neo4j.core.context.DefaultPersistenceContext; -import org.springframework.data.neo4j.core.context.PersistenceContext; -import org.springframework.data.neo4j.core.schema.NodeDescription; -import org.springframework.data.neo4j.core.schema.Schema; -import org.springframework.lang.Nullable; - -/** - * @author Michael J. Simons - * @author Gerrit Meier - * @since 1.0 - */ -@API(status = API.Status.INTERNAL, since = "1.0") -class DefaultNodeManager implements NodeManager { - - private final Schema schema; - - private final Neo4jClient neo4jClient; - - private final Transaction transaction; - - private final PersistenceContext persistenceContext; - - DefaultNodeManager(Schema schema, Neo4jClient neo4jClient, @Nullable Transaction transaction) { - - this.schema = schema; - this.neo4jClient = neo4jClient; - this.transaction = transaction; - - this.persistenceContext = new DefaultPersistenceContext(); - } - - @Override - @Nullable - public Transaction getTransaction() { - return transaction; - } - - @Override - public ExecutableQuery toExecutableQuery(PreparedQuery preparedQuery) { - - Class resultType = preparedQuery.getResultType(); - MappingSpec, Collection, T> mappingSpec = neo4jClient.newQuery(preparedQuery.getCypherQuery()) - .bindAll(preparedQuery.getParameters()) - .fetchAs(resultType); - RecordFetchSpec, Collection, T> fetchSpec = preparedQuery.getOptionalMappingFunction() - .map(mappingFunction -> mappingSpec.mappedBy(mappingFunction)) - .orElse(mappingSpec); - - return new DefaultExecutableQuery(preparedQuery, schema.getNodeDescription(resultType), fetchSpec); - } - - @Override - public T save(T entityWithUnknownState) { - - // TODO if already registered, here or in the context? - this.persistenceContext - .register(entityWithUnknownState, schema.getRequiredNodeDescription(entityWithUnknownState.getClass())); - - throw new UnsupportedOperationException("Not there yet."); - } - - @Override - public void delete(Object managedEntity) { - - this.persistenceContext.deregister(managedEntity); - - throw new UnsupportedOperationException("Not there yet."); - } - - @RequiredArgsConstructor - class DefaultExecutableQuery implements ExecutableQuery { - - private final PreparedQuery preparedQuery; - private final Optional> optionalNodeDescription; - private final RecordFetchSpec, Collection, T> fetchSpec; - - @Override - public List getResults() { - return fetchSpec.all().stream().map(this::register).collect(toList()); - } - - @Override - public Optional getSingleResult() { - try { - return fetchSpec.one().map(this::register); - } catch (NoSuchRecordException e) { - return Optional.empty(); - } - } - - @Override - public T getRequiredSingleResult() { - return fetchSpec.one().map(this::register) - .orElseThrow(() -> new NoResultException(1L, preparedQuery.getCypherQuery())); - } - - private T register(T entity) { - this.optionalNodeDescription.ifPresent( - nodeDescription -> DefaultNodeManager.this.persistenceContext.register(entity, nodeDescription)); - return entity; - } - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java deleted file mode 100644 index 38c30e96b..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core; - -import org.apiguardian.api.API; -import org.neo4j.driver.Transaction; -import org.springframework.lang.Nullable; - -/** - * Entry point for creating queries that return managed Nodes. The node manager is not supposed to be kept around - * for longer than necessary. Try to keep your transactions short to avoid memory pressure due to keeping track of - * managed nodes. - * - * @author Michael J. Simons - */ -@API(status = API.Status.STABLE, since = "1.0") -public interface NodeManager { - - /** - * Clears all managed entities and flushes any open state to the underlying storage. - * TODO reflect if we really, really want to have this. - * If an user flushes the node manager, we will end up in a state where we do not have any information about - * the nodes / entities currently processed in the ongoing transaction. - */ - default void flush() { - } - - @Nullable - Transaction getTransaction(); - - ExecutableQuery toExecutableQuery(PreparedQuery preparedQuery); - - /** - * Saves an entity. When the entity is not yet managed in this instance of the NodeManager, and will be registered as - * a managed instance. In either way, the state of the entity will be written to the underlying store afterwards. - * It is recommended to use the returned, managed instance of the object. This is especially true when dealing with - * immutable entities, where SDN RX has to return new instances to fill in generated keys and the like. - * - * @param entityWithUnknownState An entity that is either managed or unmanaged - * @return A managed object - */ - T save(T entityWithUnknownState); - - /** - * Delete an object from the persistence context and the underlying store. - * - * @param managedEntity Object to be removed - */ - void delete(Object managedEntity); -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java deleted file mode 100644 index 5fb402d6f..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core; - -import lombok.extern.slf4j.Slf4j; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apiguardian.api.API; -import org.neo4j.driver.Driver; -import org.springframework.data.neo4j.core.schema.Schema; -import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils; -import org.springframework.lang.Nullable; - -/** - * Creates ready to use instances of {@link NodeManager}. - * - * @author Michael J. Simons - */ -@API(status = API.Status.STABLE, since = "1.0") -@Slf4j -public final class NodeManagerFactory { - - private final AtomicBoolean initialized = new AtomicBoolean(false); - - /** - * Driver that is used to create new sessions, either by directly invoking it or through Springs transactional utils. - */ - private final Driver driver; - - /** - * The initial set of classes that will be registered with the schema {@link #initialize()} to build the schema for node managers - * belonging to this factory. - */ - private final Set> initialPersistentClasses; - - @Nullable - private Schema schema; - - /** - * Creates a new instance of a factory producing {@link NodeManager node managers}. When used in a transactional setup, - * i.e. with the {@link org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager}, make sure to use - * the same {@link Driver driver instance} for both the node and the transaction manager. - *

- * Spring Boots autoconfiguration for SDN RX will make sure that the same driver is used for both concerns. - * - * @param driver The driver used to obtain statement runners from when creating instances of node managers. - * @param initialPersistentClasses The set of classes that should be initially scanned - */ - public NodeManagerFactory(Driver driver, Class... initialPersistentClasses) { - this(driver, Arrays.asList(initialPersistentClasses)); - } - - /** - * @see NodeManagerFactory#NodeManagerFactory(Driver, Class...) - */ - public NodeManagerFactory(Driver driver, Collection> initialPersistentClasses) { - this.driver = driver; - this.initialPersistentClasses = new HashSet<>(initialPersistentClasses); - } - - /** - * Creates a new node manager. The returned manager is supposed to have a short lifetime. When used in a Spring setup, - * this method should not called directly by client code. Instead the client code should use an injected instance of - * {@link NodeManager}, which will participate in Springs application based transaction or in JTA transactions. - * - * @return A new node manager - */ - public NodeManager createNodeManager() { - - if (!initialized.get()) { - throw new IllegalStateException( - "This factory has not been correctly initialized. Please provider a schema and register your persistent classes."); - } - // The call here to our Spring transaction shim has to be rethought in case we move this out of a Spring scope. - // I dropped all the methods to configure that in an effort to make the setup more simple. ^mjs - return new DefaultNodeManager(schema, Neo4jClient.create(driver), - Neo4jTransactionUtils.retrieveTransaction(driver, null).orElse(null)); - } - - /** - * Provides the schema for this node manager factory. The schemas has to be set before a node manager is retrieved from this factory. - * - * @param schema - */ - public void setSchema(@Nullable Schema schema) { - this.schema = schema; - } - - /** - * This initializes this factory and is usually called by Springs infrastructure and only useful as standalone call - * when the node manager factory is used without Spring. - */ - public void initialize() { - - if (this.initialized.compareAndSet(false, true)) { - Objects.requireNonNull(schema, "A schema is required. Did you provide one with #setSchema() beforehand?"); - log.info("Initializing schema with {} persistent classes", this.initialPersistentClasses.size()); - this.schema.register(this.initialPersistentClasses); - } - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PersistenceException.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PersistenceException.java deleted file mode 100644 index 2a192b794..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PersistenceException.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core; - -import org.apiguardian.api.API; - -/** - * Shared base class for exceptions during persistence operations of a {@link NodeManager}. - * - * @author Michael J. Simons - * @soundtrack Deichkind - Niveau weshalb warum - * @since 1.0 - */ -@API(status = API.Status.STABLE, since = "1.0") -public abstract class PersistenceException extends RuntimeException { - - protected PersistenceException(String message) { - super(message); - } - - protected PersistenceException(String message, Throwable cause) { - super(message, cause); - } - - abstract static class IllegalResultSizeException extends PersistenceException { - - private final long expectedNumberOfResults; - private final long actualNumberOfResults; - private final String query; - - IllegalResultSizeException(long expectedNumberOfResults, long actualNumberOfResults, String query) { - - super(String.format("Expected %d results, got %d", expectedNumberOfResults, actualNumberOfResults)); - - this.query = query; - this.expectedNumberOfResults = expectedNumberOfResults; - this.actualNumberOfResults = actualNumberOfResults; - } - - public long getExpectedNumberOfResults() { - return expectedNumberOfResults; - } - - public long getActualNumberOfResults() { - return actualNumberOfResults; - } - - public String getQuery() { - return query; - } - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java deleted file mode 100644 index 0034ff0a9..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import org.springframework.data.neo4j.core.context.tracking.EntityChangeEvent; -import org.springframework.data.neo4j.core.context.tracking.EntityComparisonStrategy; -import org.springframework.data.neo4j.core.context.tracking.EntityTrackingStrategy; -import org.springframework.data.neo4j.core.schema.NodeDescription; - -/** - * TODO explain what the persistence context should do other than being the plural of EntityTrackingStrategy :D - * - * @author Michael J. Simons - * @author Gerrit Meier - */ -@Slf4j -public class DefaultPersistenceContext implements PersistenceContext { - - private final EntityTrackingStrategy entityTrackingStrategy; - - private final Set registeredObjectIds = new HashSet<>(); - - public DefaultPersistenceContext() { - this.entityTrackingStrategy = getEntityTrackingStrategy(); - } - - @Override - public void register(Object entity, NodeDescription description) { - - Objects.requireNonNull(entity, "Cannot track null-entity!"); - - int identityOfEntity = getIdentityOf(entity); - - if (registeredObjectIds.contains(identityOfEntity)) { - log.debug("Object " + entity + " was already registered"); - return; - } - - registeredObjectIds.add(identityOfEntity); - entityTrackingStrategy.track(entity, description); - } - - @Override - public void deregister(Object managedEntity) { - - int identityOfEntity = getIdentityOf(managedEntity); - if (!registeredObjectIds.contains(identityOfEntity)) { - log.info("Cannot deregister " + managedEntity + " because it were never registered"); - return; - } - - entityTrackingStrategy.untrack(managedEntity); - } - - @Override - public Collection getEntityChanges(Object... objects) { - - List entityChanges = new ArrayList<>(); - - for (Object object : objects) { - Collection changeEvents = entityTrackingStrategy.getAggregatedEntityChangeEvents(object); - entityChanges.add(new EntityChanges(object, changeEvents)); - } - - return entityChanges; - } - - EntityTrackingStrategy getEntityTrackingStrategy() { - // todo choose the right / fitting implementation - return new EntityComparisonStrategy(); - } - - private int getIdentityOf(Object entity) { - return entityTrackingStrategy.getObjectIdentifier(entity); - } - - @Getter - static class EntityChanges { - private final Object entity; - - private final Collection changeEvents; - - EntityChanges(Object entity, Collection changeEvents) { - this.entity = entity; - this.changeEvents = changeEvents; - } - } - -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContext.java deleted file mode 100644 index e5c42fe5e..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContext.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context; - -import java.util.Collection; - -import org.apiguardian.api.API; -import org.springframework.data.neo4j.core.schema.NodeDescription; - -/** - * Represents the state of varies instances being tracked. Those include: - *

    - *
  • All nodes
  • - *
  • All relationships
  • - *
- * including properties of them. - * - * @author Michael J. Simons - * @author Gerrit Meier - */ -@API(status = API.Status.INTERNAL, since = "1.0") -public interface PersistenceContext { - - /** - * Registers an entity within the context and starts tracking the entities state. - * - * @param entity The entity to register - * @param description The schema description of the entities class - */ - void register(Object entity, NodeDescription description); - - /** - * Removes an entity from this context and stops tracking the entitys state. - * - * @param managedEntity The entity to remove - */ - void deregister(Object managedEntity); - - /** - * Calculates the deltas for each registered object and returns them grouped by the object identifying function - * defined in - * {@link org.springframework.data.neo4j.core.context.tracking.EntityTrackingStrategy#getObjectIdentifier(Object)} or - * one of its implementations. - * - * @param objects to calculate and return changes for - * @return All change events that got registered since registration. - */ - Collection getEntityChanges(Object... objects); -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/package-info.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/package-info.java deleted file mode 100644 index 0673137de..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Contains all necessary infrastructure for the context of a session, i.e. dirty tracking and related. - * - * @author Michael J. Simons - */ -@NonNullApi -package org.springframework.data.neo4j.core.context; - -import org.springframework.lang.NonNullApi; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityChangeEvent.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityChangeEvent.java deleted file mode 100644 index 4cce80dea..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityChangeEvent.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context.tracking; - -import lombok.Getter; - -import org.apiguardian.api.API; - -/** - * @author Gerrit Meier - * @author Michael J. Simons - */ -@API(status = API.Status.INTERNAL, since = "1.0") -@Getter -public final class EntityChangeEvent { - private final String propertyField; - private final Object value; - - EntityChangeEvent(String propertyField, Object value) { - this.propertyField = propertyField; - this.value = value; - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategy.java deleted file mode 100644 index 160af229d..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategy.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context.tracking; - -import static org.springframework.data.neo4j.core.context.tracking.EntityTrackingStrategy.*; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import org.springframework.data.neo4j.core.schema.GraphPropertyDescription; -import org.springframework.data.neo4j.core.schema.NodeDescription; - -/** - * Dirty tracking strategy based on property comparison. The strategy can compare two states of the same instance on - * attribute level but uses hashes for collection-like fields. Only fields that are considered to be node properties - * will get compared. - * - * @author Gerrit Meier - * @author Michael J. Simons - */ -public class EntityComparisonStrategy implements EntityTrackingStrategy { - - private final Map statesOfEntities = new HashMap<>(); - - @Override - public void track(Object entity, NodeDescription nodeDescription) { - statesOfEntities.put(getObjectIdentifier(entity), - new EntityState(nodeDescription, entity, getDefaultObjectIdentifier())); - } - - @Override - public void untrack(Object entity) { - statesOfEntities.remove(getObjectIdentifier(entity)); - } - - @Override - public Collection getAggregatedEntityChangeEvents(Object entity) { - int objectIdentifier = getObjectIdentifier(entity); - return statesOfEntities.get(objectIdentifier).aggregateChanges(entity); - } - - /** - * Compares two entities of the same instance and creates a delta {@link EntityChangeEvent}. - */ - private static class EntityState { - - private final Map oldState; - private final List objectFields; - private final Object identifier; - private final Function objectIdentifyingFuction; - - EntityState(NodeDescription nodeDescription, Object entity, Function objectIdentifyingFuction) { - - Collection properties = nodeDescription.getGraphProperties(); - this.objectFields = getFieldsFromProperties(entity, properties); - - this.objectIdentifyingFuction = objectIdentifyingFuction; - this.identifier = this.objectIdentifyingFuction.apply(entity); - this.oldState = retrieveStateFrom(entity); - } - - Set aggregateChanges(Object newObject) { - - if (!sameObject(objectIdentifyingFuction.apply(newObject))) { - throw new IllegalArgumentException("The objects to compare are not the same."); - } - Set changes = new HashSet<>(); - - for (Field field : objectFields) { - Object newValue = getFieldValueOrHashCode(field, newObject); - Object oldValue = oldState.get(field.getName()); - - // check oldValue null first to avoid NPE in `equals` calls - if (oldValue == null && newValue == null) { - continue; - } - if (oldValue == null || !oldValue.equals(newValue)) { - changes.add(new EntityChangeEvent(field.getName(), newValue)); - } - } - - return changes; - } - - private List getFieldsFromProperties(Object entity, Collection properties) { - - List fields = new ArrayList<>(); - - Class entityClass = entity.getClass(); - Map classFields = retrieveAllFields(entityClass); - for (GraphPropertyDescription property : properties) { - fields.add(classFields.get(property.getFieldName())); - } - - return fields; - } - - private Map retrieveAllFields(Class entityClass) { - - Map classFields = new HashMap<>(); - - Class classToScan = entityClass; - - do { - for (Field field : classToScan.getDeclaredFields()) { - classFields.put(field.getName(), field); - } - classToScan = classToScan.getSuperclass(); - } while (classToScan != null); - - return classFields; - } - - private boolean sameObject(Integer objectIdentifier) { - return this.identifier.equals(objectIdentifier); - } - - private Map retrieveStateFrom(Object entity) { - - Map state = new HashMap<>(); - - for (Field field : objectFields) { - state.put(field.getName(), getFieldValueOrHashCode(field, entity)); - } - - return Collections.unmodifiableMap(state); - } - - private Object getFieldValueOrHashCode(Field field, Object entity) { - try { - field.setAccessible(true); - Object value = field.get(entity); - field.setAccessible(false); - if (value == null) { - return null; - } else if (!Collection.class.isAssignableFrom(value.getClass())) { - return value; - } else { - return value.hashCode(); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot determine field value", e); - } - } - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityTrackingStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityTrackingStrategy.java deleted file mode 100644 index a569cf65d..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/tracking/EntityTrackingStrategy.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context.tracking; - -import java.util.Collection; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.springframework.data.neo4j.core.schema.NodeDescription; - -/** - * A tracking strategy is used to determine if an entity has changed it state over time in a transaction. It is also - * responsible to generate and return a complete list of all these changes on an entity's attribute level. - * - * @author Gerrit Meier - */ -@API(status = API.Status.INTERNAL, since = "1.0") -public interface EntityTrackingStrategy { - - static Function getDefaultObjectIdentifier() { - return System::identityHashCode; - } - - /** - * Register an entity in the entity tracking strategy. {@link NodeDescription} is needed to determine the fields that - * are considered mapping relevant. - * - * @param entity the object that should get tracked. - * @param nodeDescription the "rich" entity's class description. - */ - void track(Object entity, NodeDescription nodeDescription); - - /** - * Remove an entity from tracking. This method should get called after an entity was deleted to have a clean change - * history state. - * - * @param entity to get removed from entity tracking - */ - void untrack(Object entity); - - /** - * Aggregates all changes that were registered for this entity since the start of tracking. - * - * @param entity for which a summarized collection of {@EntityChangeEvent}s should get created. - * @return all events that occurred since tracking started. - */ - Collection getAggregatedEntityChangeEvents(Object entity); - - /** - * Returns an object identifier for a given entity. - * - * @param entity the entity to get an unique identifier for. - * @return hash or something similar that represents the entity. - */ - default int getObjectIdentifier(Object entity) { - return getDefaultObjectIdentifier().apply(entity); - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java index 838ff7061..5b956047b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java @@ -138,13 +138,6 @@ public class Neo4jMappingContext return new DefaultNeo4jPersistentProperty(property, neo4jPersistentProperties, simpleTypeHolder); } - @Override - public synchronized void register(Set> entityClasses) { - - this.setInitialEntitySet(entityClasses); - this.initialize(); - } - @Override public Optional> getNodeDescription(String primaryLabel) { return Optional.ofNullable(this.nodeDescriptionsByPrimaryLabel.get(primaryLabel)); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java index 7eb9f469c..bbd8777c9 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java @@ -40,11 +40,16 @@ import org.springframework.data.neo4j.core.cypher.StatementBuilder.OngoingMatchA public interface Schema { /** - * Registers and scans the given set of classes to be available as Neo4j domain entities. + * Registers the given set of classes to be available as Neo4j domain entities. * - * @param entityClasses The additional set of classes to register with this schema + * @param initialEntitySet The set of classes to register with this schema */ - void register(Set> entityClasses); + void setInitialEntitySet(Set> initialEntitySet); + + /** + * Triggers the scanning of the registered, initial entity set. + */ + void initialize(); /** * Retrieves a nodes description by its primary label. diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java index facd2a9cb..f6459e6ec 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java @@ -23,18 +23,12 @@ import lombok.extern.slf4j.Slf4j; import java.time.Duration; import java.util.Collections; import java.util.List; -import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Driver; import org.neo4j.driver.Session; import org.neo4j.driver.TransactionConfig; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.data.neo4j.core.NodeManagerFactory; import org.springframework.lang.Nullable; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.InvalidIsolationLevelException; @@ -57,32 +51,14 @@ import org.springframework.util.Assert; */ @API(status = API.Status.STABLE, since = "1.0") @Slf4j -public class Neo4jTransactionManager extends AbstractPlatformTransactionManager implements BeanFactoryAware { +public class Neo4jTransactionManager extends AbstractPlatformTransactionManager { private final Driver driver; - @Nullable - private NodeManagerFactory nodeManagerFactory; - public Neo4jTransactionManager(Driver driver) { this.driver = driver; } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - try { - this.setNodeManagerFactory(beanFactory.getBean(NodeManagerFactory.class)); - } catch (NoSuchBeanDefinitionException ex) { - log.warn("Found no instance of {}", NodeManagerFactory.class); - } - } - - public void setNodeManagerFactory(@Nullable NodeManagerFactory nodeManagerFactory) { - - // TODO Check if the NodeManager uses the same datasource aka driver as we do - this.nodeManagerFactory = nodeManagerFactory; - } - @Override protected Object doGetTransaction() throws TransactionException { @@ -118,13 +94,6 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager connectionHolder.setSynchronizedWithTransaction(true); transactionObject.setResourceHolder(connectionHolder); TransactionSynchronizationManager.bindResource(this.driver, connectionHolder); - - if (this.nodeManagerFactory != null) { - NodeManagerHolder nodeManagerHolder = new NodeManagerHolder(this.nodeManagerFactory.createNodeManager()); - nodeManagerHolder.setSynchronizedWithTransaction(true); - transactionObject.setNodeManagerHolder(nodeManagerHolder); - TransactionSynchronizationManager.bindResource(this.nodeManagerFactory, nodeManagerHolder); - } } catch (Exception ex) { ex.printStackTrace(); throw new TransactionSystemException(String.format("Could not open a new Neo4j session: %s", ex.getMessage())); @@ -173,14 +142,8 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager Neo4jTransactionObject transactionObject = extractNeo4jTransaction(transaction); transactionObject.getRequiredResourceHolder().close(); - transactionObject.getNodeManagerHolder() - .map(NodeManagerHolder::getNodeManager) - .ifPresent(nodeManager -> nodeManager.flush()); TransactionSynchronizationManager.unbindResource(driver); - if (this.nodeManagerFactory != null) { - TransactionSynchronizationManager.unbindResource(this.nodeManagerFactory); - } } private static TransactionConfig createTransactionConfigFrom(TransactionDefinition definition) { @@ -227,9 +190,6 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager @Nullable private Neo4jConnectionHolder resourceHolder; - @Nullable - private NodeManagerHolder nodeManagerHolder; - Neo4jTransactionObject(@Nullable Neo4jConnectionHolder resourceHolder) { this.resourceHolder = resourceHolder; } @@ -244,10 +204,6 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager this.resourceHolder = resourceHolder; } - void setNodeManagerHolder(@Nullable NodeManagerHolder nodeManagerHolder) { - this.nodeManagerHolder = nodeManagerHolder; - } - /** * @return {@literal true} if a {@link Neo4jConnectionHolder} is set. */ @@ -261,10 +217,6 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager return resourceHolder; } - Optional getNodeManagerHolder() { - return Optional.ofNullable(nodeManagerHolder); - } - void setRollbackOnly() { getRequiredResourceHolder().setRollbackOnly(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java index fff533aa4..237c2abc9 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java @@ -29,8 +29,6 @@ import org.neo4j.driver.Driver; import org.neo4j.driver.SessionParametersTemplate; import org.neo4j.driver.Transaction; import org.neo4j.driver.reactive.RxTransaction; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.NodeManagerFactory; import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -48,12 +46,10 @@ public final class Neo4jTransactionUtils { * @return Session parameters to configure the default session used */ public static Consumer defaultSessionParameters(@Nullable String databaseName) { - return t -> { - t - .withDefaultAccessMode(AccessMode.WRITE) - .withBookmarks(Collections.EMPTY_LIST) - .withDatabase(Optional.ofNullable(databaseName).orElse(DEFAULT_DATABASE_NAME)); - }; + return t -> t + .withDefaultAccessMode(AccessMode.WRITE) + .withBookmarks(Collections.EMPTY_LIST) + .withDatabase(Optional.ofNullable(databaseName).orElse(DEFAULT_DATABASE_NAME)); } /** @@ -105,33 +101,6 @@ public final class Neo4jTransactionUtils { return Mono.empty(); } - public static NodeManager retrieveTransactionalNodeManager(NodeManagerFactory nodeManagerFactory) { - - if (!TransactionSynchronizationManager.isSynchronizationActive()) { - return nodeManagerFactory.createNodeManager(); - } - - // Try existing transaction - NodeManagerHolder nodeManagerHolder = (NodeManagerHolder) TransactionSynchronizationManager - .getResource(nodeManagerFactory); - - if (nodeManagerHolder != null) { - - return nodeManagerHolder.getNodeManager(); - } - - // Manually create a new synchronization - nodeManagerHolder = new NodeManagerHolder(nodeManagerFactory.createNodeManager()); - nodeManagerHolder.setSynchronizedWithTransaction(true); - - TransactionSynchronizationManager.registerSynchronization( - new NodeManagerSynchronization(nodeManagerHolder, nodeManagerFactory)); - - TransactionSynchronizationManager.bindResource(nodeManagerFactory, nodeManagerHolder); - - return nodeManagerHolder.getNodeManager(); - } - private Neo4jTransactionUtils() { } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java deleted file mode 100644 index d077d77b3..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.transaction; - -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.lang.Nullable; -import org.springframework.transaction.support.ResourceHolderSupport; -import org.springframework.util.Assert; - -/** - * Dedicated holder for storing a NodeManager inside a transaction. - *

- * Note: Intended for internal usage only. - * - * @author Michael J. Simons - */ -class NodeManagerHolder extends ResourceHolderSupport { - - @Nullable - private final NodeManager nodeManager; - - NodeManagerHolder(@Nullable NodeManager nodeManager) { - this.nodeManager = nodeManager; - } - - public NodeManager getNodeManager() { - Assert.state(this.nodeManager != null, "No NodeManager available"); - return this.nodeManager; - } - -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java deleted file mode 100644 index d2a81bd6c..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.transaction; - -import org.springframework.data.neo4j.core.NodeManagerFactory; -import org.springframework.transaction.support.ResourceHolderSynchronization; - -/** - * @author Michael J. Simons - */ -public class NodeManagerSynchronization - extends ResourceHolderSynchronization { - - private final NodeManagerHolder localNodeManagerHolder; - - NodeManagerSynchronization(NodeManagerHolder nodeManagerHolder, NodeManagerFactory nodeManagerFactory) { - - super(nodeManagerHolder, nodeManagerFactory); - this.localNodeManagerHolder = nodeManagerHolder; - } - - @Override - protected void flushResource(NodeManagerHolder resourceHolder) { - resourceHolder.getNodeManager().flush(); - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NoResultException.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/NoResultException.java similarity index 70% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NoResultException.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/NoResultException.java index f26205a98..c26f63500 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NoResultException.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/NoResultException.java @@ -16,10 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.core; +package org.springframework.data.neo4j.repository; import org.apiguardian.api.API; -import org.springframework.data.neo4j.core.PersistenceException.IllegalResultSizeException; +import org.springframework.dao.EmptyResultDataAccessException; /** * Throw when a query doesn't return a required result. @@ -29,9 +29,16 @@ import org.springframework.data.neo4j.core.PersistenceException.IllegalResultSiz * @since 1.0 */ @API(status = API.Status.STABLE, since = "1.0") -public class NoResultException extends IllegalResultSizeException { +public class NoResultException extends EmptyResultDataAccessException { - NoResultException(long expectedNumberOfResults, String query) { - super(expectedNumberOfResults, 0L, query); + private final String query; + + public NoResultException(int expectedNumberOfResults, String query) { + super(expectedNumberOfResults); + this.query = query; + } + + public String getQuery() { + return query; } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java index 0e358a883..17bedd3c1 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java @@ -31,9 +31,7 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.core.annotation.AliasFor; -import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean; -import org.springframework.transaction.PlatformTransactionManager; /** * Annotation to activate Neo4j repositories. If no base package is configured through either {@link #value()}, @@ -78,15 +76,14 @@ public @interface EnableNeo4jRepositories { Class repositoryFactoryBeanClass() default Neo4jRepositoryFactoryBean.class; /** - * Configures the name of the {@link NodeManager} bean to be used with the repositories detected. + * Configures the name of the {@link org.springframework.data.neo4j.core.mapping.Neo4jMappingContext} bean to be used with the repositories detected. */ - String nodeManagerFactoryRef() default DEFAULT_NODE_MANAGER_FACTORY_BEAN_NAME; + String neo4jMappingContextRef() default DEFAULT_MAPPING_CONTEXT_BEAN_NAME; /** - * Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories - * discovered through this annotation. Defaults to {@code transactionManager}. + * Configures the name of the {@link org.springframework.data.neo4j.core.Neo4jClient} bean to be used with the repositories detected. */ - String transactionManagerRef() default DEFAULT_TRANSACTION_MANAGER_BEAN_NAME; + String neo4jClientRef() default DEFAULT_NEO4J_CLIENT_NAME; /** * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java index 34a6aa0a7..bcd9bc16c 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java @@ -24,12 +24,9 @@ import java.util.Collections; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean; -import org.springframework.data.neo4j.repository.support.NodeManagerFactoryBean; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; import org.springframework.data.repository.config.RepositoryConfigurationSource; @@ -49,20 +46,12 @@ public class Neo4jRepositoryConfigurationExtension extends RepositoryConfigurati /** * See {@link AbstractBeanDefinition#INFER_METHOD}. */ - static final String GENERATE_BEAN_NAME = "(generated)"; - - static final String DEFAULT_NODE_MANAGER_FACTORY_BEAN_NAME = "nodeManagerFactory"; - static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; + static final String DEFAULT_NEO4J_CLIENT_NAME = "neo4jClient"; /** - * Holds the name of the shared NodeManagerBean created from the factory with the configured name. + * See {@link AbstractBeanDefinition#INFER_METHOD}. */ - private String generatedNodeManagerBeanName; - - /** - * Holds the name of the shared Neo4j mapping context.. - */ - private String generatedMappingContextBeanName; + static final String DEFAULT_MAPPING_CONTEXT_BEAN_NAME = "neo4jMappingContext"; /* * (non-Javadoc) @@ -99,61 +88,9 @@ public class Neo4jRepositoryConfigurationExtension extends RepositoryConfigurati @Override public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) { - builder.addPropertyValue("transactionManager", - source.getAttribute("transactionManagerRef").orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)); - - builder.addPropertyReference("nodeManager", this.generatedNodeManagerBeanName); - builder.addPropertyReference("neo4jMappingContext", this.generatedMappingContextBeanName); - } - - @Override - public void registerBeansForRoot(BeanDefinitionRegistry registry, - RepositoryConfigurationSource config) { - - // Mapping context - AbstractBeanDefinition neo4jMappingContextBeanDefinition = BeanDefinitionBuilder - .rootBeanDefinition(Neo4jMappingContext.class) - .getBeanDefinition(); - this.generatedMappingContextBeanName = registerWithSourceAndGeneratedBeanName( - neo4jMappingContextBeanDefinition, registry, config); - - // Augmented node manager factory (creating injectable, shared instances of NodeManager that are aware of the mapping context). - String nameOfNodeManagerFactory = config.getAttribute("nodeManagerFactoryRef") - .orElse(DEFAULT_NODE_MANAGER_FACTORY_BEAN_NAME); - AbstractBeanDefinition sharedSessionCreatorBeanDefinition = BeanDefinitionBuilder - .rootBeanDefinition(NodeManagerFactoryBean.class) - .addConstructorArgReference(nameOfNodeManagerFactory) - .addConstructorArgReference(generatedMappingContextBeanName) - .getBeanDefinition(); - this.generatedNodeManagerBeanName = registerWithSourceAndGeneratedBeanName( - sharedSessionCreatorBeanDefinition, registry, config); - } - - /** - * Uses a generated bean name if {@code configuredBeanName} is equal to {@link #GENERATE_BEAN_NAME}, otherwise uses - * the configured bean name to register the new bean. Does not check if there's already a bean under the configured - * name but throws a {@link org.springframework.beans.factory.BeanDefinitionStoreException}. Checks whether a bean is - * already registered under {@code configuredBeanName} in the given {@link BeanDefinitionRegistry} and uses a - * generated name for registering the bean instead. If not, the suggested bean name is used. - * - * @param bean must not be {@literal null}. - * @param registry must not be {@literal null}. - * @param configuredBeanName must not be {@literal null} or empty. - * @param source must not be {@literal null}. - * @return the bean name used for registering the given {@link AbstractBeanDefinition} - * @throws org.springframework.beans.factory.BeanDefinitionStoreException if the BeanDefinition is invalid or if there - * is already a BeanDefinition for the specified bean name * (and we are not allowed to override it) - */ - private static String registerWithGeneratedNameOrUseConfigured(AbstractBeanDefinition bean, - BeanDefinitionRegistry registry, String configuredBeanName, Object source) { - - String registeredBeanName = configuredBeanName; - if (GENERATE_BEAN_NAME.equals(configuredBeanName)) { - registeredBeanName = registerWithSourceAndGeneratedBeanName(bean, registry, source); - } else { - bean.setSource(source); - registry.registerBeanDefinition(configuredBeanName, bean); - } - return registeredBeanName; + builder.addPropertyReference("neo4jClient", + source.getAttribute("neo4jClientRef").orElse(DEFAULT_NEO4J_CLIENT_NAME)); + builder.addPropertyReference("neo4jMappingContext", + source.getAttribute("neo4jMappingContextRef").orElse(DEFAULT_MAPPING_CONTEXT_BEAN_NAME)); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/AbstractNeo4jQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/AbstractNeo4jQuery.java index b058db391..e76807dd2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/AbstractNeo4jQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/AbstractNeo4jQuery.java @@ -25,9 +25,9 @@ import org.springframework.data.domain.Range; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.PreparedQuery; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.repository.support.PreparedQuery; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.util.Assert; @@ -41,19 +41,19 @@ import org.springframework.util.Assert; */ abstract class AbstractNeo4jQuery implements RepositoryQuery { - protected final NodeManager nodeManager; + protected final Neo4jClient neo4jClient; protected final Neo4jMappingContext mappingContext; protected final Neo4jQueryMethod queryMethod; protected final Class domainType; - AbstractNeo4jQuery(NodeManager nodeManager, + AbstractNeo4jQuery(Neo4jClient neo4jClient, Neo4jMappingContext mappingContext, Neo4jQueryMethod queryMethod) { - Assert.notNull(nodeManager, "The node manager is required."); + Assert.notNull(neo4jClient, "The Neo4j client is required."); Assert.notNull(mappingContext, "The mapping context is required."); Assert.notNull(queryMethod, "Query method must not be null!"); - this.nodeManager = nodeManager; + this.neo4jClient = neo4jClient; this.mappingContext = mappingContext; this.queryMethod = queryMethod; this.domainType = queryMethod.getReturnedObjectType(); @@ -66,7 +66,7 @@ abstract class AbstractNeo4jQuery implements RepositoryQuery { @Override public final Object execute(Object[] parameters) { - return new Neo4jQueryExecution.DefaultQueryExecution(nodeManager) + return new Neo4jQueryExecution.DefaultQueryExecution(neo4jClient) .execute(prepareQuery(parameters), queryMethod.isCollectionQuery()); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryExecution.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryExecution.java index b291f3af9..5eb5d1215 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryExecution.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryExecution.java @@ -20,9 +20,9 @@ package org.springframework.data.neo4j.repository.query; import lombok.RequiredArgsConstructor; -import org.springframework.data.neo4j.core.ExecutableQuery; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.PreparedQuery; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.repository.support.ExecutableQuery; +import org.springframework.data.neo4j.repository.support.PreparedQuery; /** * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a @@ -40,12 +40,12 @@ interface Neo4jQueryExecution { @RequiredArgsConstructor class DefaultQueryExecution implements Neo4jQueryExecution { - private final NodeManager nodeManager; + private final Neo4jClient neo4jClient; @Override - public Object execute(PreparedQuery preparedQuery, boolean asCollectionQuery) { + public Object execute(PreparedQuery description, boolean asCollectionQuery) { - ExecutableQuery executableQuery = nodeManager.toExecutableQuery(preparedQuery); + ExecutableQuery executableQuery = ExecutableQuery.create(description, neo4jClient); if (asCollectionQuery) { return executableQuery.getResults(); } else { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java index 1f44ab431..f3300d364 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java @@ -23,7 +23,7 @@ import lombok.RequiredArgsConstructor; import java.lang.reflect.Method; import org.apiguardian.api.API; -import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -43,7 +43,7 @@ import org.springframework.data.repository.query.RepositoryQuery; @RequiredArgsConstructor public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy { - private final NodeManager nodeManager; + private final Neo4jClient neo4jClient; private final Neo4jMappingContext mappingContext; private final QueryMethodEvaluationContextProvider evaluationContextProvider; @@ -58,12 +58,12 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy { String namedQueryName = queryMethod.getNamedQueryName(); if (namedQueries.hasQuery(namedQueryName)) { - return StringBasedNeo4jQuery.create(nodeManager, mappingContext, evaluationContextProvider, queryMethod, + return StringBasedNeo4jQuery.create(neo4jClient, mappingContext, evaluationContextProvider, queryMethod, namedQueries.getQuery(namedQueryName)); } else if (queryMethod.hasQueryAnnotation()) { - return StringBasedNeo4jQuery.create(nodeManager, mappingContext, evaluationContextProvider, queryMethod); + return StringBasedNeo4jQuery.create(neo4jClient, mappingContext, evaluationContextProvider, queryMethod); } else { - return new PartTreeNeo4jQuery(nodeManager, mappingContext, queryMethod); + return new PartTreeNeo4jQuery(neo4jClient, mappingContext, queryMethod); } } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java index 9c0565418..394ce588c 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java @@ -35,10 +35,10 @@ import java.util.Map; import java.util.Set; import org.neo4j.driver.types.Point; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.PreparedQuery; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.repository.query.Neo4jQueryMethod.Neo4jParameters; +import org.springframework.data.neo4j.repository.support.PreparedQuery; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; @@ -75,11 +75,11 @@ final class PartTreeNeo4jQuery extends AbstractNeo4jQuery { private final PartTree tree; PartTreeNeo4jQuery( - NodeManager nodeManager, + Neo4jClient neo4jClient, Neo4jMappingContext mappingContext, Neo4jQueryMethod queryMethod ) { - super(nodeManager, mappingContext, queryMethod); + super(neo4jClient, mappingContext, queryMethod); this.tree = new PartTree(queryMethod.getName(), domainType); @@ -97,7 +97,6 @@ final class PartTreeNeo4jQuery extends AbstractNeo4jQuery { mappingContext, domainType, tree, formalParameters, actualParameters ); - String cypherQuery = queryCreator.createQuery(); Map boundedParameters = formalParameters .getBindableParameters().stream() diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java index 2de60721b..a6742e60f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java @@ -23,9 +23,9 @@ import java.util.Map; import java.util.Optional; import org.springframework.data.mapping.MappingException; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.PreparedQuery; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.repository.support.PreparedQuery; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; @@ -92,13 +92,13 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery { * Create a {@link StringBasedNeo4jQuery} for a query method that is annotated with {@link Query @Query}. The annotation * is expected to have a value. * - * @param nodeManager + * @param neo4jClient * @param mappingContext * @param evaluationContextProvider * @param queryMethod * @return A new instance of a String based Neo4j query. */ - static StringBasedNeo4jQuery create(NodeManager nodeManager, Neo4jMappingContext mappingContext, + static StringBasedNeo4jQuery create(Neo4jClient neo4jClient, Neo4jMappingContext mappingContext, QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod) { @@ -109,36 +109,36 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery { .filter(StringUtils::hasText) .orElseThrow(() -> new MappingException("Expected @Query annotation to have a value, but it did not.")); - return new StringBasedNeo4jQuery(nodeManager, mappingContext, evaluationContextProvider, queryMethod, + return new StringBasedNeo4jQuery(neo4jClient, mappingContext, evaluationContextProvider, queryMethod, cypherTemplate, queryAnnotation.count(), queryAnnotation.exists(), queryAnnotation.delete()); } /** * Create a {@link StringBasedNeo4jQuery} based on an explicit Cypher template. * - * @param nodeManager + * @param neo4jClient * @param mappingContext * @param evaluationContextProvider * @param queryMethod * @param cypherTemplate The template to use. * @return A new instance of a String based Neo4j query. */ - static StringBasedNeo4jQuery create(NodeManager nodeManager, Neo4jMappingContext mappingContext, + static StringBasedNeo4jQuery create(Neo4jClient neo4jClient, Neo4jMappingContext mappingContext, QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod, String cypherTemplate) { Assert.hasText(cypherTemplate, "Cannot create String based Neo4j query without a cypher template."); - return new StringBasedNeo4jQuery(nodeManager, mappingContext, evaluationContextProvider, queryMethod, + return new StringBasedNeo4jQuery(neo4jClient, mappingContext, evaluationContextProvider, queryMethod, cypherTemplate, false, false, false); } - private StringBasedNeo4jQuery(NodeManager nodeManager, + private StringBasedNeo4jQuery(Neo4jClient neo4jClient, Neo4jMappingContext mappingContext, QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod, String cypherTemplate, boolean countQuery, boolean existsQuery, boolean deleteQuery) { - super(nodeManager, mappingContext, queryMethod); + super(neo4jClient, mappingContext, queryMethod); this.countQuery = countQuery; this.existsQuery = existsQuery; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultExecutableQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultExecutableQuery.java new file mode 100644 index 000000000..3fd62c1e3 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultExecutableQuery.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.repository.support; + +import static java.util.stream.Collectors.*; + +import lombok.RequiredArgsConstructor; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.neo4j.driver.exceptions.NoSuchRecordException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.repository.NoResultException; + +@RequiredArgsConstructor public +final class DefaultExecutableQuery implements ExecutableQuery { + + private final PreparedQuery preparedQuery; + private final Neo4jClient.RecordFetchSpec, Collection, T> fetchSpec; + + @Override + public List getResults() { + return fetchSpec.all().stream().collect(toList()); + } + + @Override + public Optional getSingleResult() { + try { + return fetchSpec.one(); + } catch (NoSuchRecordException e) { + // This exception is thrown by the driver in both cases when there are 0 or 1+n records + // So there has been an incorrect result size, but not to few results but to many. + throw new IncorrectResultSizeDataAccessException(1); + } + } + + @Override + public T getRequiredSingleResult() { + return fetchSpec.one() + .orElseThrow(() -> new NoResultException(1, preparedQuery.getCypherQuery())); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultPreparedQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultPreparedQuery.java similarity index 92% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultPreparedQuery.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultPreparedQuery.java index e473311ea..e2d254e25 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultPreparedQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/DefaultPreparedQuery.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.core; +package org.springframework.data.neo4j.repository.support; import java.util.Map; import java.util.Optional; @@ -27,9 +27,9 @@ import org.neo4j.driver.types.TypeSystem; import org.springframework.lang.Nullable; /** - * Typed preparation of a query that is used to create an {@link org.springframework.data.neo4j.core.ExecutableQuery} of the same type. + * Typed preparation of a query that is used to create an {@link ExecutableQuery} of the same type. *

- * When no mapping function is provided, the node manager will assume a simple type to be returned. Otherwise make sure + * When no mapping function is provided, the Neo4j client will assume a simple type to be returned. Otherwise make sure * that the query fits to the mapping function, that is: It must return all nodes, relationships and paths that is expected * by the mapping function to work correctly. * diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/ExecutableQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/ExecutableQuery.java similarity index 55% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/ExecutableQuery.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/ExecutableQuery.java index 74a23c2ac..387411776 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/ExecutableQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/ExecutableQuery.java @@ -16,12 +16,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.core; +package org.springframework.data.neo4j.repository.support; +import java.util.Collection; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.repository.NoResultException; /** * Interface for controlling query execution. @@ -31,9 +35,23 @@ import org.apiguardian.api.API; * @soundtrack Deichkind - Niveau weshalb warum * @since 1.0 */ -@API(status = API.Status.STABLE, since = "1.0") +@API(status = API.Status.INTERNAL, since = "1.0") public interface ExecutableQuery { + static ExecutableQuery create(PreparedQuery description, Neo4jClient neo4jClient) { + + Class resultType = description.getResultType(); + Neo4jClient.MappingSpec, Collection, T> mappingSpec = neo4jClient + .newQuery(description.getCypherQuery()) + .bindAll(description.getParameters()) + .fetchAs(resultType); + Neo4jClient.RecordFetchSpec, Collection, T> fetchSpec = description.getOptionalMappingFunction() + .map(mappingFunction -> mappingSpec.mappedBy(mappingFunction)) + .orElse(mappingSpec); + + return new DefaultExecutableQuery<>(description, fetchSpec); + } + /** * @return All results returned by this query. */ @@ -41,13 +59,12 @@ public interface ExecutableQuery { /** * @return A single result - * @throws NonUniqueResultException + * @throws IncorrectResultSizeDataAccessException */ Optional getSingleResult(); /** * @return A single result - * @throws NonUniqueResultException * @throws NoResultException */ T getRequiredSingleResult(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java index 910a403ce..40f9447f2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java @@ -20,7 +20,7 @@ package org.springframework.data.neo4j.repository.support; import java.util.Optional; -import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.query.Neo4jQueryLookupStrategy; @@ -41,12 +41,12 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro */ final class Neo4jRepositoryFactory extends RepositoryFactorySupport { - private final NodeManager nodeManager; + private final Neo4jClient neo4jClient; private final Neo4jMappingContext mappingContext; - Neo4jRepositoryFactory(NodeManager nodeManager, Neo4jMappingContext mappingContext) { - this.nodeManager = nodeManager; + Neo4jRepositoryFactory(Neo4jClient neo4jClient, Neo4jMappingContext mappingContext) { + this.neo4jClient = neo4jClient; this.mappingContext = mappingContext; } @@ -57,7 +57,7 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport { @Override protected Object getTargetRepository(RepositoryInformation metadata) { - return getTargetRepositoryViaReflection(metadata, nodeManager, mappingContext, metadata.getDomainType()); + return getTargetRepositoryViaReflection(metadata, neo4jClient, mappingContext, metadata.getDomainType()); } @Override @@ -73,6 +73,6 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport { protected Optional getQueryLookupStrategy(Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new Neo4jQueryLookupStrategy(nodeManager, mappingContext, evaluationContextProvider)); + return Optional.of(new Neo4jQueryLookupStrategy(neo4jClient, mappingContext, evaluationContextProvider)); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java index 3ee4eb141..8b853649e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java @@ -20,7 +20,7 @@ package org.springframework.data.neo4j.repository.support; import java.io.Serializable; -import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -36,7 +36,7 @@ import org.springframework.data.repository.core.support.TransactionalRepositoryF public class Neo4jRepositoryFactoryBean, S, ID extends Serializable> extends TransactionalRepositoryFactoryBeanSupport { - private NodeManager nodeManager; + private Neo4jClient neo4jClient; private Neo4jMappingContext neo4jMappingContext; @@ -49,8 +49,8 @@ public class Neo4jRepositoryFactoryBean, S, ID exten super(repositoryInterface); } - public void setNodeManager(NodeManager nodeManager) { - this.nodeManager = nodeManager; + public void setNeo4jClient(Neo4jClient neo4jClient) { + this.neo4jClient = neo4jClient; } public void setNeo4jMappingContext(Neo4jMappingContext neo4jMappingContext) { @@ -59,7 +59,7 @@ public class Neo4jRepositoryFactoryBean, S, ID exten @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { - return new Neo4jRepositoryFactory(nodeManager, neo4jMappingContext); + return new Neo4jRepositoryFactory(neo4jClient, neo4jMappingContext); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java deleted file mode 100644 index 5a265f7a2..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.repository.support; - -import lombok.RequiredArgsConstructor; - -import org.apiguardian.api.API; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.NodeManagerFactory; -import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; - -/** - * This is a shim that integrates a {@link NodeManagerFactory} with Spring Datas infrastructure. It takes in the factory - * that needs to be provided by the user and the mapping context provided by our infrastructure. Springs mapping context - * replaces the default noop scanner. - * - * @author Gerrit Meier - * @author Michael J. Simons - */ -@API(status = API.Status.INTERNAL, since = "1.0") -@RequiredArgsConstructor -public final class NodeManagerFactoryBean implements InitializingBean, FactoryBean { - - private final NodeManagerFactory target; - - private final Neo4jMappingContext neo4jMappingContext; - - @Override - public NodeManager getObject() { - return SharedNodeManagerCreator.createSharedNodeManager(this.target); - } - - @Override - public Class getObjectType() { - return NodeManager.class; - } - - @Override - public void afterPropertiesSet() { - - target.setSchema(this.neo4jMappingContext); - target.initialize(); - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PreparedQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/PreparedQuery.java similarity index 82% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PreparedQuery.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/PreparedQuery.java index f9d1782ef..fcbe4864a 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/PreparedQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/PreparedQuery.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.neo4j.core; +package org.springframework.data.neo4j.repository.support; import java.util.Collections; import java.util.HashMap; @@ -30,12 +30,19 @@ import org.neo4j.driver.types.TypeSystem; import org.springframework.lang.Nullable; /** + * Typed preparation of a query that is used to create an {@link ExecutableQuery} of the same type. + *

+ * When no mapping function is provided, the Neo4j client will assume a simple type to be returned. Otherwise make sure + * that the query fits to the mapping function, that is: It must return all nodes, relationships and paths that is expected + * by the mapping function to work correctly. + * * @param The type of the objects returned by this query. + * @see ExecutableQuery#create(PreparedQuery, org.springframework.data.neo4j.core.Neo4jClient) * @author Michael J. Simons * @soundtrack Deichkind - Arbeit nervt * @since 1.0 */ -@API(status = API.Status.STABLE, since = "1.0") +@API(status = API.Status.INTERNAL, since = "1.0") public interface PreparedQuery { static RequiredBuildStep queryFor(Class resultType) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java deleted file mode 100644 index afca68a4b..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.repository.support; - -import lombok.extern.slf4j.Slf4j; - -import java.io.Serializable; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; - -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.NodeManagerFactory; -import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils; -import org.springframework.lang.Nullable; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.ReflectionUtils; - -/** - * @author Michael J. Simons - */ -@Slf4j -final class SharedNodeManagerCreator { - - private static final Set TRANSACTION_REQUIRING_METHODS; - - static { - Set tmp = new HashSet<>(); - - tmp.add("save"); - tmp.add("delete"); - - TRANSACTION_REQUIRING_METHODS = Collections.unmodifiableSet(tmp); - } - - public static NodeManager createSharedNodeManager(NodeManagerFactory nodeManagerFactory) { - - return (NodeManager) Proxy.newProxyInstance(SharedNodeManagerCreator.class.getClassLoader(), - new Class[] { NodeManager.class }, new SharedNodeManagerInvocationHandler(nodeManagerFactory)); - } - - private static class SharedNodeManagerInvocationHandler implements InvocationHandler, Serializable { - - private final NodeManagerFactory targetFactory; - - SharedNodeManagerInvocationHandler(NodeManagerFactory targetFactory) { - this.targetFactory = targetFactory; - } - - @Override - @Nullable - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - - String methodName = method.getName(); - switch (methodName) { - case "equals": - // Only consider equal when proxies are identical. - return (proxy == args[0]); - case "hashCode": - // Use hashCode of proxy. - return hashCode(); - case "toString": - // Deliver toString without touching a target Session. - return "Shared Session proxy for target factory [" + this.targetFactory + "]"; - default: - Function methodCall = - targetSession -> ReflectionUtils.invokeMethod(method, targetSession, args); - return invokeInTransaction(methodName, methodCall); - } - } - - private Object invokeInTransaction(String methodName, Function methodCall) { - - // Determine current Session: either the transactional one - // managed by the factory or a temporary one for the given invocation. - NodeManager target = Neo4jTransactionUtils.retrieveTransactionalNodeManager(this.targetFactory); - - if (TRANSACTION_REQUIRING_METHODS.contains(methodName)) { - if (target == null || (!TransactionSynchronizationManager.isActualTransactionActive() - && target.getTransaction() != null)) { - throw new IllegalStateException("No NodeManager with actual transaction available " - + "for current thread - cannot reliably process '" + methodName + "' call"); - } - } - - // Regular Session operations. - boolean isNewSession = false; - if (target == null) { - log.debug("Creating new Session for shared Session invocation"); - target = this.targetFactory.createNodeManager(); - isNewSession = true; - } - - // Invoke method on current Session. - try { - return methodCall.apply(target); - } finally { - if (isNewSession) { - target.flush(); - } - } - } - } - - private SharedNodeManagerCreator() { - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java index c5044578f..1a3263468 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java @@ -19,6 +19,7 @@ package org.springframework.data.neo4j.repository.support; import static java.util.Collections.*; +import static java.util.stream.Collectors.*; import static lombok.AccessLevel.*; import static org.springframework.data.neo4j.core.cypher.Cypher.*; import static org.springframework.data.neo4j.core.schema.NodeDescription.*; @@ -45,8 +46,7 @@ import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.PreparedQuery; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.cypher.Condition; import org.springframework.data.neo4j.core.cypher.Conditions; import org.springframework.data.neo4j.core.cypher.Cypher; @@ -81,7 +81,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { private static final Renderer renderer = CypherRenderer.create(); - private final NodeManager nodeManager; + private final Neo4jClient neo4jClient; private final Neo4jMappingContext mappingContext; @@ -91,9 +91,9 @@ class SimpleNeo4jRepository implements Neo4jRepository { private final BiFunction mappingFunction; private Expression idExpression; - SimpleNeo4jRepository(NodeManager nodeManager, Neo4jMappingContext mappingContext, Class nodeClass) { + SimpleNeo4jRepository(Neo4jClient neo4jClient, Neo4jMappingContext mappingContext, Class nodeClass) { - this.nodeManager = nodeManager; + this.neo4jClient = neo4jClient; this.mappingContext = mappingContext; this.nodeClass = nodeClass; @@ -124,7 +124,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .orderBy(toSortItems(nodeDescription, sort)) .build(); - return nodeManager.toExecutableQuery(prepareQuery(statement)).getResults(); + return createExecutableQuery(statement).getResults(); } @Override @@ -137,7 +137,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { Statement statement = returningWithPaging.build(); - List allResult = nodeManager.toExecutableQuery(prepareQuery(statement)).getResults(); + List allResult = createExecutableQuery(statement).getResults(); LongSupplier totalCountSupplier = this::count; return PageableExecutionUtils.getPage(allResult, pageable, totalCountSupplier); } @@ -153,8 +153,8 @@ class SimpleNeo4jRepository implements Neo4jRepository { Statement statement = returningWithPaging.build(); - List page = nodeManager - .toExecutableQuery(prepareQuery(example.getProbeType(), statement, predicate.parameters)).getResults(); + List page = createExecutableQuery(example.getProbeType(), statement, predicate.parameters) + .getResults(); LongSupplier totalCountSupplier = this::count; return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier); } @@ -173,7 +173,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { @Override @Transactional public S save(S entity) { - return this.nodeManager.save(entity); + throw new UnsupportedOperationException("Not there yet."); } @Override @@ -189,7 +189,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .prepareMatchOf(nodeDescription, Optional.of(idExpression.isEqualTo(literalOf(id)))) .returning(asterisk()) .build(); - return nodeManager.toExecutableQuery(prepareQuery(statement)).getSingleResult(); + return createExecutableQuery(statement).getSingleResult(); } @Override @@ -200,9 +200,9 @@ class SimpleNeo4jRepository implements Neo4jRepository { @Override public Iterable findAll() { - Statement statement = mappingContext.prepareMatchOf(nodeDescription, Optional.empty()).returning(asterisk()) - .build(); - return nodeManager.toExecutableQuery(prepareQuery(statement)).getResults(); + Statement statement = mappingContext.prepareMatchOf(nodeDescription, Optional.empty()) + .returning(asterisk()).build(); + return createExecutableQuery(statement).getResults(); } @Override @@ -213,7 +213,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .returning(asterisk()) .build(); - return nodeManager.toExecutableQuery(prepareQuery(nodeClass, statement, singletonMap("ids", ids))).getResults(); + return createExecutableQuery(nodeClass, statement, singletonMap("ids", ids)).getResults(); } @Override @@ -222,8 +222,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { Statement statement = mappingContext.prepareMatchOf(nodeDescription, Optional.empty()) .returning(Functions.count(asterisk())).build(); - return nodeManager.toExecutableQuery(prepareQuery(Long.class, statement, Collections.emptyMap())) - .getRequiredSingleResult(); + return createExecutableQuery(Long.class, statement, Collections.emptyMap()).getRequiredSingleResult(); } @Override @@ -263,8 +262,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .returning(asterisk()) .build(); - PreparedQuery preparedQuery = prepareQuery(example.getProbeType(), statement, predicate.parameters); - return nodeManager.toExecutableQuery(preparedQuery).getSingleResult(); + return createExecutableQuery(example.getProbeType(), statement, predicate.parameters).getSingleResult(); } @Override @@ -275,8 +273,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .returning(asterisk()) .build(); - PreparedQuery preparedQuery = prepareQuery(example.getProbeType(), statement, predicate.parameters); - return nodeManager.toExecutableQuery(preparedQuery).getResults(); + return createExecutableQuery(example.getProbeType(), statement, predicate.parameters).getResults(); } @Override @@ -288,8 +285,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .returning(asterisk()) .orderBy(toSortItems(nodeDescription, sort)).build(); - PreparedQuery preparedQuery = prepareQuery(example.getProbeType(), statement, predicate.parameters); - return nodeManager.toExecutableQuery(preparedQuery).getResults(); + return createExecutableQuery(example.getProbeType(), statement, predicate.parameters).getResults(); } @Override @@ -301,8 +297,7 @@ class SimpleNeo4jRepository implements Neo4jRepository { .returning(Functions.count(asterisk())) .build(); - PreparedQuery preparedQuery = prepareQuery(Long.class, statement, predicate.parameters); - return nodeManager.toExecutableQuery(preparedQuery).getRequiredSingleResult(); + return createExecutableQuery(Long.class, statement, predicate.parameters).getRequiredSingleResult(); } @Override @@ -419,11 +414,11 @@ class SimpleNeo4jRepository implements Neo4jRepository { return predicate; } - private PreparedQuery prepareQuery(Statement statement) { - return prepareQuery(nodeClass, statement, Collections.emptyMap()); + private ExecutableQuery createExecutableQuery(Statement statement) { + return createExecutableQuery(nodeClass, statement, Collections.emptyMap()); } - private PreparedQuery prepareQuery(Class resultType, Statement statement, + private ExecutableQuery createExecutableQuery(Class resultType, Statement statement, Map parameters) { BiFunction mappingFunctionToUse = this.mappingFunction; @@ -431,10 +426,12 @@ class SimpleNeo4jRepository implements Neo4jRepository { mappingFunctionToUse = mappingContext.getMappingFunctionFor(resultType).orElse(null); } - return PreparedQuery.queryFor(resultType) + PreparedQuery queryDescription = PreparedQuery.queryFor(resultType) .withCypherQuery(renderer.render(statement)) .withParameters(parameters) .usingMappingFunction(mappingFunctionToUse) .build(); + + return ExecutableQuery.create(queryDescription, neo4jClient); } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContextTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContextTest.java deleted file mode 100644 index 1560594c9..000000000 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContextTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context; - -import static org.mockito.Mockito.*; - -import java.util.Arrays; -import java.util.HashSet; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.data.neo4j.core.context.tracking.EntityTrackingStrategy; -import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; -import org.springframework.data.neo4j.core.schema.Id; -import org.springframework.data.neo4j.core.schema.NodeDescription; -import org.springframework.data.neo4j.core.schema.Schema; - -/** - * @author Gerrit Meier - * @author Michael J. Simons - */ -class DefaultPersistenceContextTest { - - private Schema schema; - private PersistenceContext context; - private EntityTrackingStrategy entityTrackingStrategy; - - @BeforeEach - void setup() { - entityTrackingStrategy = mock(EntityTrackingStrategy.class); - when(entityTrackingStrategy.getObjectIdentifier(any())) - .thenAnswer(invocation -> System.identityHashCode(invocation.getArguments()[0])); - - Neo4jMappingContext mappingContext = new Neo4jMappingContext(); - mappingContext.setInitialEntitySet(new HashSet>(Arrays.asList(Something.class))); - mappingContext.initialize(); - schema = mappingContext; - - // override method to return a verifiable mock - context = new DefaultPersistenceContext() { - @Override - EntityTrackingStrategy getEntityTrackingStrategy() { - return entityTrackingStrategy; - } - }; - } - - @Test - void registerAddsEntityToTrackingStrategy() { - - NodeDescription nodeDescription = schema.getRequiredNodeDescription(Something.class); - - context.register(new Something(), nodeDescription); - - verify(entityTrackingStrategy).track(any(), any()); - } - - @Test - void registerTheSameObjectMultipleTimesCallsTrackJustOnce() { - - NodeDescription nodeDescription = schema.getRequiredNodeDescription(Something.class); - Something entity = new Something(); - - context.register(entity, nodeDescription); - context.register(entity, nodeDescription); - - verify(entityTrackingStrategy).track(any(), any()); - } - - @Test - void registerTwoObjectOfTheSameTypeCallsTrackTwice() { - - NodeDescription nodeDescription = schema.getRequiredNodeDescription(Something.class); - Something entity1 = new Something(); - Something entity2 = new Something(); - - context.register(entity1, nodeDescription); - context.register(entity2, nodeDescription); - - verify(entityTrackingStrategy, times(2)).track(any(), any()); - } - - @Test - void triggersDeltaCalculationOnDeltaCall() { - - NodeDescription nodeDescription = schema.getRequiredNodeDescription(Something.class); - Something entity = new Something(); - - context.register(entity, nodeDescription); - context.getEntityChanges(entity); - - verify(entityTrackingStrategy).getAggregatedEntityChangeEvents(entity); - } - - @Test - void deregisterRemovesEntityFromTracking() { - - NodeDescription nodeDescription = schema.getRequiredNodeDescription(Something.class); - Something entity = new Something(); - - context.register(entity, nodeDescription); - context.deregister(entity); - - verify(entityTrackingStrategy).untrack(entity); - } - - @Test - void deregisterUnknownEntityDoesNotCallUntrack() { - Something entity = new Something(); - - context.deregister(entity); - - verify(entityTrackingStrategy, never()).untrack(entity); - } - - class Something { - - @Id - private Long id; - - String value; - } - -} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategyTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategyTest.java deleted file mode 100644 index 588ecac9b..000000000 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/context/tracking/EntityComparisonStrategyTest.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.context.tracking; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; - -import org.junit.jupiter.api.Test; -import org.springframework.data.neo4j.core.schema.GraphPropertyDescription; -import org.springframework.data.neo4j.core.schema.NodeDescription; - -/** - * @author Gerrit Meier - */ -class EntityComparisonStrategyTest { - - private final GraphPropertyDescription valuePropertyDescription; - - private final GraphPropertyDescription informationPropertyDescription; - - private final GraphPropertyDescription parentPropertyDescription; - - private final NodeDescription description; - - EntityComparisonStrategyTest() { - this.valuePropertyDescription = mock(GraphPropertyDescription.class); - when(this.valuePropertyDescription.getFieldName()).thenReturn("value"); - when(this.valuePropertyDescription.getPropertyName()).thenReturn("value"); - - this.informationPropertyDescription = mock(GraphPropertyDescription.class); - when(this.informationPropertyDescription.getFieldName()).thenReturn("information"); - when(this.informationPropertyDescription.getPropertyName()).thenReturn("information"); - - this.parentPropertyDescription = mock(GraphPropertyDescription.class); - when(this.parentPropertyDescription.getFieldName()).thenReturn("parentValue"); - when(this.parentPropertyDescription.getPropertyName()).thenReturn("parentValue"); - - this.description = mock(NodeDescription.class); - when(this.description.getPrimaryLabel()).thenReturn("Something"); - when(this.description.getGraphProperties()).thenReturn(Arrays - .asList(this.valuePropertyDescription, this.informationPropertyDescription, - this.parentPropertyDescription)); - when(this.description.getUnderlyingClass()).thenReturn(Something.class); - } - - @Test - void trackSimplePropertyChange() { - EntityComparisonStrategy strategy = new EntityComparisonStrategy(); - Something something = new Something("oldValue"); - - strategy.track(something, description); - - String fieldName = "value"; - String newValue = "newValue"; - something.value = newValue; - - Collection changeEvents = strategy.getAggregatedEntityChangeEvents(something); - - EntityChangeEvent changeEvent = changeEvents.iterator().next(); - assertThat(changeEvent.getPropertyField()).isEqualTo(fieldName); - assertThat(changeEvent.getValue()).isEqualTo(newValue); - } - - @Test - void trackCollectionPropertyChange() { - EntityComparisonStrategy strategy = new EntityComparisonStrategy(); - Something something = new Something("oldValue"); - - strategy.track(something, description); - - String fieldName = "information"; - something.information.add("additional entry"); - - Collection changeEvents = strategy.getAggregatedEntityChangeEvents(something); - - EntityChangeEvent changeEvent = changeEvents.iterator().next(); - assertThat(changeEvent.getPropertyField()).isEqualTo(fieldName); - assertThat(changeEvent.getValue()).isInstanceOf(Integer.class); - } - - @Test - void trackCollectionPropertyReorderChange() { - EntityComparisonStrategy strategy = new EntityComparisonStrategy(); - Something something = new Something("blubb"); - something.information.add("entry 1"); - something.information.add("entry 2"); - - strategy.track(something, description); - - something.information.sort(Comparator.reverseOrder()); - - String fieldName = "information"; - - Collection changeEvents = strategy.getAggregatedEntityChangeEvents(something); - - EntityChangeEvent changeEvent = changeEvents.iterator().next(); - assertThat(changeEvent.getPropertyField()).isEqualTo(fieldName); - assertThat(changeEvent.getValue()).isInstanceOf(Integer.class); - } - - @Test - void trackParentClassPropertyChange() { - EntityComparisonStrategy strategy = new EntityComparisonStrategy(); - Something something = new Something("oldValue"); - - strategy.track(something, description); - - String fieldName = "parentValue"; - String newValue = "newValue"; - something.parentValue = newValue; - - Collection changeEvents = strategy.getAggregatedEntityChangeEvents(something); - - EntityChangeEvent changeEvent = changeEvents.iterator().next(); - assertThat(changeEvent.getPropertyField()).isEqualTo(fieldName); - assertThat(changeEvent.getValue()).isEqualTo(newValue); - } - - @Test - void trackMultipleObjects() { - EntityComparisonStrategy strategy = new EntityComparisonStrategy(); - Something something1 = new Something("oldValue"); - Something something2 = new Something("oldValue"); - - strategy.track(something1, description); - strategy.track(something2, description); - - String fieldName = "value"; - String newValue1 = "newValue1"; - String newValue2 = "newValue2"; - something1.value = newValue1; - something2.value = newValue2; - - Collection changeEvents1 = strategy.getAggregatedEntityChangeEvents(something1); - Collection changeEvents2 = strategy.getAggregatedEntityChangeEvents(something2); - - EntityChangeEvent changeEvent1 = changeEvents1.iterator().next(); - assertThat(changeEvent1.getPropertyField()).isEqualTo(fieldName); - assertThat(changeEvent1.getValue()).isEqualTo(newValue1); - - EntityChangeEvent changeEvent2 = changeEvents2.iterator().next(); - assertThat(changeEvent2.getPropertyField()).isEqualTo(fieldName); - assertThat(changeEvent2.getValue()).isEqualTo(newValue2); - } - - class ParentClass { - String parentValue; - } - - class Something extends ParentClass { - final List information = new ArrayList<>(); - String value; - - Something(String value) { - this.value = value; - } - - // create own equals and hashCode that should not get used in any technical parts of the dirty tracking - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Something something = (Something) o; - return value.equals(something.value) && information.equals(something.information); - } - - @Override - public int hashCode() { - return Objects.hash(value, information); - } - } -} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java index 32c2f56c6..bf523c328 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java @@ -50,14 +50,12 @@ import org.springframework.data.domain.Sort; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; -import org.springframework.data.neo4j.core.NodeManagerFactory; -import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.data.neo4j.config.AbstractNeo4jConfig; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.data.neo4j.test.Neo4jExtension; import org.springframework.data.neo4j.test.Neo4jExtension.Neo4jConnectionSupport; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; /** @@ -830,25 +828,12 @@ class RepositoryIT { @Configuration @EnableNeo4jRepositories @EnableTransactionManagement - static class Config { + static class Config extends AbstractNeo4jConfig { @Bean - public static Driver driver() { + public Driver driver() { return neo4jConnectionSupport.openConnection(); } - - @Bean - public NodeManagerFactory nodeManagerFactory(Driver driver) { - - return new NodeManagerFactory(driver, PersonWithAllConstructor.class, PersonWithNoConstructor.class, - PersonWithWither.class, ThingWithAssignedId.class, KotlinPerson.class); - } - - @Bean - public PlatformTransactionManager transactionManager(Driver driver) { - - return new Neo4jTransactionManager(driver); - } } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java index e9f8b628b..40b7b681c 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java @@ -35,7 +35,7 @@ import org.neo4j.driver.Values; import org.neo4j.driver.types.Point; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.MappingException; -import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.Neo4jClient; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; @@ -70,7 +70,7 @@ final class RepositoryQueryTest { private static final ProjectionFactory PROJECTION_FACTORY = new SpelAwareProxyProjectionFactory(); @Mock - NodeManager nodeManager; + Neo4jClient neo4jClient; @Mock Neo4jMappingContext schema; @@ -97,7 +97,7 @@ final class RepositoryQueryTest { @Test void shouldSelectPartTreeNeo4jQuery() { - final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(NodeManager.class), mock( + final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(Neo4jClient.class), mock( Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT); RepositoryQuery query = lookupStrategy @@ -109,7 +109,7 @@ final class RepositoryQueryTest { @Test void shouldSelectStringBasedNeo4jQuery() { - final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(NodeManager.class), mock( + final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(Neo4jClient.class), mock( Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT); RepositoryQuery query = lookupStrategy @@ -125,7 +125,7 @@ final class RepositoryQueryTest { when(namedQueries.hasQuery(namedQueryName)).thenReturn(true); when(namedQueries.getQuery(namedQueryName)).thenReturn("MATCH (n) RETURN n"); - final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(NodeManager.class), mock( + final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(Neo4jClient.class), mock( Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT); RepositoryQuery query = lookupStrategy @@ -180,7 +180,7 @@ final class RepositoryQueryTest { assertThatExceptionOfType(MappingException.class) .isThrownBy( - () -> StringBasedNeo4jQuery.create(mock(NodeManager.class), mock(Neo4jMappingContext.class), + () -> StringBasedNeo4jQuery.create(mock(Neo4jClient.class), mock(Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT, method)) .withMessage("Expected @Query annotation to have a value, but it did not."); } @@ -191,7 +191,7 @@ final class RepositoryQueryTest { Neo4jQueryMethod method = RepositoryQueryTest .neo4jQueryMethod("annotatedQueryWithValidTemplate", String.class, String.class); - StringBasedNeo4jQuery repositoryQuery = StringBasedNeo4jQuery.create(mock(NodeManager.class), + StringBasedNeo4jQuery repositoryQuery = StringBasedNeo4jQuery.create(mock(Neo4jClient.class), mock(Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT, method); @@ -210,7 +210,7 @@ final class RepositoryQueryTest { .neo4jQueryMethod("findByDontDoThisInRealLiveNamed", org.neo4j.driver.types.Point.class, String.class, String.class); - StringBasedNeo4jQuery repositoryQuery = StringBasedNeo4jQuery.create(mock(NodeManager.class), + StringBasedNeo4jQuery repositoryQuery = StringBasedNeo4jQuery.create(mock(Neo4jClient.class), mock(Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT, method); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepositoryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepositoryTest.java deleted file mode 100644 index 7b6abd1b2..000000000 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepositoryTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [https://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.repository.support; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -import java.util.Collections; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.data.neo4j.core.NodeManager; -import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; -import org.springframework.data.neo4j.core.schema.Id; -import org.springframework.data.neo4j.core.schema.IdDescription; -import org.springframework.data.neo4j.core.schema.Node; -import org.springframework.data.neo4j.core.schema.NodeDescription; - -/** - * @author Gerrit Meier - * @author Michael J. Simons - */ -class SimpleNeo4jRepositoryTest { - - private NodeManager nodeManager; - - private Neo4jMappingContext mappingContext; - - private NodeDescription nodeDescription; - - private SimpleNeo4jRepository repository; - - @BeforeEach - void setupMock() { - nodeManager = mock(NodeManager.class); - - nodeDescription = mock(NodeDescription.class); - when(nodeDescription.getPrimaryLabel()).thenReturn("TestNode"); - when(nodeDescription.getIdDescription()).thenReturn(new IdDescription()); - - mappingContext = mock(Neo4jMappingContext.class); - when(mappingContext.getRequiredNodeDescription(TestNode.class)).thenReturn(nodeDescription); - repository = new SimpleNeo4jRepository(this.nodeManager, this.mappingContext, TestNode.class); - } - - @Test - void saveNotImplemented() { - repository.save(null); // todo this should throw an exception upfront - verify(nodeManager).save(any()); - } - - @Test - void saveAllNotImplemented() { - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> repository.saveAll(null)); - } - - @Test - void deleteByIdNotImplemented() { - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> repository.deleteById(null)); - } - - @Test - void deleteNotImplemented() { - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> repository.delete(null)); - } - - @Test - void deleteAll1NotImplemented() { - assertThatExceptionOfType(UnsupportedOperationException.class) - .isThrownBy(() -> repository.deleteAll(Collections.emptyList())); - } - - @Node - class TestNode { - @Id - private Long id; - - private String value; - } -}