diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index eee94aace..590ca0334 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -217,6 +217,10 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware { private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Object idValues) { + if (((Neo4jPersistentEntity) idProperty.getOwner()).isUsingInternalIds()) { + return idValues; + } + return neo4jMappingContext.getConversionService().writeValue(idValues, ClassTypeInformation.from(idValues.getClass()), idProperty == null ? null : idProperty.getOptionalWritingConverter()); @@ -269,9 +273,10 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware { return entityMetaData.getDynamicLabelsProperty().map(p -> { PersistentPropertyAccessor propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved); + Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty(); Neo4jClient.RunnableSpecTightToDatabase runnableQuery = neo4jClient .query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData))) - .in(inDatabase).bind(propertyAccessor.getProperty(entityMetaData.getRequiredIdProperty())) + .in(inDatabase).bind(convertIdValues(idProperty, propertyAccessor.getProperty(idProperty))) .to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels()) .to(Constants.NAME_OF_STATIC_LABELS_PARAM); diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java index 56ac75e5b..aaef73270 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -220,6 +220,10 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Object idValues) { + if (((Neo4jPersistentEntity) idProperty.getOwner()).isUsingInternalIds()) { + return idValues; + } + return neo4jMappingContext.getConversionService().writeValue(idValues, ClassTypeInformation.from(idValues.getClass()), idProperty == null ? null : idProperty.getOptionalWritingConverter()); } @@ -267,9 +271,10 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea return entityMetaData.getDynamicLabelsProperty().map(p -> { PersistentPropertyAccessor propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved); + Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty(); ReactiveNeo4jClient.RunnableSpecTightToDatabase runnableQuery = neo4jClient .query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData))) - .in(inDatabase).bind(propertyAccessor.getProperty(entityMetaData.getRequiredIdProperty())) + .in(inDatabase).bind(convertIdValues(idProperty, propertyAccessor.getProperty(idProperty))) .to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels()).to(Constants.NAME_OF_STATIC_LABELS_PARAM); if (entityMetaData.hasVersionProperty()) { diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/DynamicLabelsIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/DynamicLabelsIT.java index 0eb3ba6d7..161a5da7f 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/DynamicLabelsIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/DynamicLabelsIT.java @@ -54,6 +54,7 @@ import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDyna import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels.SimpleDynamicLabelsWithBusinessIdAndVersion; import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels.SimpleDynamicLabelsWithVersion; import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels.SuperNode; +import org.springframework.data.neo4j.integration.shared.common.EntityWithDynamicLabelsAndIdThatNeedsToBeConverted; import org.springframework.data.neo4j.test.Neo4jExtension; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -357,6 +358,20 @@ public class DynamicLabelsIT { entity -> assertThat(entity.moreLabels).containsExactlyInAnyOrder("SimpleDynamicLabels", "Baz", "Foobar")); } + @Test // GH-2296 + void shouldConvertIds(@Autowired Neo4jTemplate template) { + + template.deleteAll(EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.class); + EntityWithDynamicLabelsAndIdThatNeedsToBeConverted savedInstance = template + .save(new EntityWithDynamicLabelsAndIdThatNeedsToBeConverted("value_1")); + + assertThat(savedInstance.getValue()).isEqualTo("value_1"); + assertThat(savedInstance.getExtraLabels()).containsExactlyInAnyOrder("value_1"); + + Optional optionalReloadedInstance = + template.findById(savedInstance.getId(), EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.class); + assertThat(optionalReloadedInstance).hasValueSatisfying(v -> v.getExtraLabels().contains("value_1")); + } } @Nested diff --git a/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveDynamicLabelsIT.java b/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveDynamicLabelsIT.java index 1f45081ab..02aec25bf 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveDynamicLabelsIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveDynamicLabelsIT.java @@ -26,6 +26,8 @@ import reactor.test.StepVerifier; import java.util.Collections; import java.util.HashSet; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -56,6 +58,7 @@ import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDyna import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels.SimpleDynamicLabelsWithBusinessIdAndVersion; import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels.SimpleDynamicLabelsWithVersion; import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels.SuperNode; +import org.springframework.data.neo4j.integration.shared.common.EntityWithDynamicLabelsAndIdThatNeedsToBeConverted; import org.springframework.data.neo4j.test.Neo4jExtension; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -349,6 +352,27 @@ public class ReactiveDynamicLabelsIT { .flatMapMany(entity -> Flux.fromIterable(entity.moreLabels)).sort().as(StepVerifier::create) .expectNext("Baz", "Foobar", "SimpleDynamicLabels"); } + + @Test // GH-2296 + void shouldConvertIds(@Autowired ReactiveNeo4jTemplate template) { + + String label = "value_1"; + Predicate expectatations = savedInstance -> + label.equals(savedInstance.getValue()) && savedInstance.getExtraLabels().contains(label); + + AtomicReference generatedUUID = new AtomicReference<>(); + template.deleteAll(EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.class) + .then(template.save(new EntityWithDynamicLabelsAndIdThatNeedsToBeConverted(label))) + .doOnNext(s -> generatedUUID.set(s.getId())) + .as(StepVerifier::create) + .expectNextMatches(expectatations) + .verifyComplete(); + + template.findById(generatedUUID.get(), EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.class) + .as(StepVerifier::create) + .expectNextMatches(expectatations) + .verifyComplete(); + } } @Nested diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.java new file mode 100644 index 000000000..1ad552785 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/EntityWithDynamicLabelsAndIdThatNeedsToBeConverted.java @@ -0,0 +1,68 @@ +/* + * Copyright 2011-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.shared.common; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import org.springframework.data.neo4j.core.schema.DynamicLabels; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * Provided via Github as reproducer for entities with dynamic labels and ids that are subject to conversion. Needed for GH-2296. + * + * @author Michael J. Simons + */ +@Node +public class EntityWithDynamicLabelsAndIdThatNeedsToBeConverted { + @Id + @GeneratedValue + private UUID id; + + @DynamicLabels + private Set extraLabels; + + private String value; + + public EntityWithDynamicLabelsAndIdThatNeedsToBeConverted(String value) { + setValue(value); + } + + public void setValue(String value) { + this.value = value; + + if (Objects.isNull(extraLabels)) { + extraLabels = new HashSet<>(); + } + extraLabels.add(value); + } + + public String getValue() { + return value; + } + + public Set getExtraLabels() { + return extraLabels; + } + + public UUID getId() { + return id; + } +}