diff --git a/src/main/java/org/springframework/data/neo4j/core/NamedParameters.java b/src/main/java/org/springframework/data/neo4j/core/NamedParameters.java index cfa4ac6f6..3e686dfda 100644 --- a/src/main/java/org/springframework/data/neo4j/core/NamedParameters.java +++ b/src/main/java/org/springframework/data/neo4j/core/NamedParameters.java @@ -15,9 +15,11 @@ */ package org.springframework.data.neo4j.core; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -68,13 +70,45 @@ final class NamedParameters { if (Constants.NAME_OF_PROPERTIES_PARAM.equals(name) && value != null) { this.parameters.put(name, unwrapMapValueWrapper((Map) value)); + } else if (Constants.NAME_OF_ENTITY_LIST_PARAM.equals(name) && value != null) { + this.parameters.put(name, unwrapMapValueWrapperInListOfEntities((List>) value)); } else { this.parameters.put(name, value); } } + @SuppressWarnings("unchecked") + private List> unwrapMapValueWrapperInListOfEntities(List> entityList) { + boolean requiresChange = entityList.stream().anyMatch( + entity -> + entity.containsKey(Constants.NAME_OF_PROPERTIES_PARAM) && + ((Map) entity.get(Constants.NAME_OF_PROPERTIES_PARAM)).values().stream() + .anyMatch(MapValueWrapper.class::isInstance) + ); + + if (!requiresChange) { + return entityList; + } + + List> newEntityList = new ArrayList<>(entityList.size()); + for (Map entity : entityList) { + if (entity.containsKey(Constants.NAME_OF_PROPERTIES_PARAM)) { + Map newEntity = new HashMap<>(entity); + newEntity.put(Constants.NAME_OF_PROPERTIES_PARAM, unwrapMapValueWrapper((Map) entity.get(Constants.NAME_OF_PROPERTIES_PARAM))); + newEntityList.add(newEntity); + } else { + newEntityList.add(entity); + } + } + return newEntityList; + } + private static Map unwrapMapValueWrapper(Map properties) { + if (properties.values().stream().noneMatch(MapValueWrapper.class::isInstance)) { + return properties; + } + Map newProperties = new HashMap<>(properties.size()); properties.forEach((k, v) -> { if (v instanceof MapValueWrapper) { diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/Gh2493IT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/Gh2493IT.java new file mode 100644 index 000000000..72b414345 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/Gh2493IT.java @@ -0,0 +1,125 @@ +/* + * Copyright 2011-2022 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.issues.gh2493; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.neo4j.driver.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.config.AbstractNeo4jConfig; +import org.springframework.data.neo4j.core.DatabaseSelectionProvider; +import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.data.neo4j.test.BookmarkCapture; +import org.springframework.data.neo4j.test.Neo4jExtension; +import org.springframework.data.neo4j.test.Neo4jIntegrationTest; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * @author Michael J. Simons + */ +@Neo4jIntegrationTest +public class Gh2493IT { + + protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport; + + @BeforeAll + protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) { + + try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig()); + Transaction transaction = session.beginTransaction(); + ) { + transaction.run("MATCH (n) DETACH DELETE n").consume(); + transaction.commit(); + bookmarkCapture.seedWith(session.lastBookmark()); + } + } + + @Test + void saveOneShouldWork(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture, + @Autowired TestObjectRepository repository) { + + TestObject testObject = new TestObject(new TestData(4711, "Foobar")); + testObject = repository.save(testObject); + + assertThat(testObject.getId()).isNotNull(); + assertThatTestObjectHasBeenCreated(driver, bookmarkCapture, testObject); + } + + @Test + void saveAllShouldWork(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture, + @Autowired TestObjectRepository repository) { + + TestObject testObject = new TestObject(new TestData(4711, "Foobar")); + testObject = repository.saveAll(Collections.singletonList(testObject)).get(0); + + assertThat(testObject.getId()).isNotNull(); + assertThatTestObjectHasBeenCreated(driver, bookmarkCapture, testObject); + } + + private static void assertThatTestObjectHasBeenCreated(Driver driver, BookmarkCapture bookmarkCapture, + TestObject testObject) { + try (Session session = driver.session(bookmarkCapture.createSessionConfig())) { + Map arguments = new HashMap<>(); + arguments.put("id", testObject.getId()); + arguments.put("num", testObject.getData().getNum()); + arguments.put("string", testObject.getData().getString()); + long cnt = session.run( + "MATCH (n:TestObject) WHERE n.id = $id AND n.dataNum = $num AND n.dataString = $string RETURN count(n)", + arguments) + .single().get(0).asLong(); + assertThat(cnt).isOne(); + } + } + + @Configuration + @EnableTransactionManagement + @EnableNeo4jRepositories + static class Config extends AbstractNeo4jConfig { + + @Bean + public Driver driver() { + + return neo4jConnectionSupport.getDriver(); + } + + @Bean + public BookmarkCapture bookmarkCapture() { + return new BookmarkCapture(); + } + + @Override + public PlatformTransactionManager transactionManager(Driver driver, + DatabaseSelectionProvider databaseNameProvider) { + + BookmarkCapture bookmarkCapture = bookmarkCapture(); + return new Neo4jTransactionManager(driver, databaseNameProvider, + Neo4jBookmarkManager.create(bookmarkCapture)); + } + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestConverter.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestConverter.java new file mode 100644 index 000000000..79b56199b --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestConverter.java @@ -0,0 +1,61 @@ +/* + * Copyright 2011-2022 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.issues.gh2493; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.springframework.data.neo4j.core.convert.Neo4jConversionService; +import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyToMapConverter; + +/** + * @author Michael J. Simons + */ +public class TestConverter implements Neo4jPersistentPropertyToMapConverter { + + static final String NUM = "Num"; + static final String STRING = "String"; + + @Override + public Map decompose(TestData property, + Neo4jConversionService neo4jConversionService) { + + if (property == null) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + result.put(NUM, Values.value(property.getNum())); + result.put(STRING, Values.value(property.getString())); + return result; + } + + @Override + public TestData compose(Map source, + Neo4jConversionService neo4jConversionService) { + TestData data = new TestData(); + if (source.get(NUM) != null) { + data.setNum(source.get(NUM).asInt()); + } + if (source.get(STRING) != null) { + data.setString(source.get(STRING).asString()); + } + return data; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestData.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestData.java new file mode 100644 index 000000000..e787a6b7c --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestData.java @@ -0,0 +1,39 @@ +/* + * Copyright 2011-2022 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.issues.gh2493; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author Michael J. Simons + */ +@Getter +@Setter +public class TestData { + + private int num; + private String string; + + public TestData() { + super(); + } + + public TestData(int num, String string) { + this.num = num; + this.string = string; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestObject.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestObject.java new file mode 100644 index 000000000..61482d467 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestObject.java @@ -0,0 +1,51 @@ +/* + * Copyright 2011-2022 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.issues.gh2493; + +import lombok.Getter; +import lombok.Setter; + +import org.springframework.data.neo4j.core.schema.CompositeProperty; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; +import org.springframework.data.neo4j.core.support.UUIDStringGenerator; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author Michael J. Simons + */ +@Node +@Getter +@Setter +public class TestObject { + + @Id + @Property(name = "id") + @GeneratedValue(value = UUIDStringGenerator.class) + protected String id; + + @JsonIgnore + @CompositeProperty(delimiter = "", converter = TestConverter.class) + private TestData data; + + public TestObject(TestData aData) { + super(); + data = aData; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestObjectRepository.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestObjectRepository.java new file mode 100644 index 000000000..1de1a4a85 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2493/TestObjectRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011-2022 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.issues.gh2493; + +import org.springframework.data.neo4j.repository.Neo4jRepository; + +/** + * @author Michael J. Simons + */ +public interface TestObjectRepository extends Neo4jRepository { +}