diff --git a/etc/jqassistant/api.adoc b/etc/jqassistant/api.adoc index 114ecaec2..bdb164fb8 100644 --- a/etc/jqassistant/api.adoc +++ b/etc/jqassistant/api.adoc @@ -13,7 +13,7 @@ and annotations. ---- MATCH (c:Java)-[:ANNOTATED_BY]->(a)-[:OF_TYPE]->(t:Type {fqn: 'org.apiguardian.api.API'}), (p)-[:DECLARES]->(c) -WHERE c:Member AND NOT c:Constructor +WHERE c:Member AND NOT (c:Constructor OR c:Method) RETURN p.fqn, c.name ---- 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 b5a30f5e6..bd532ca71 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -330,7 +330,7 @@ public final class Neo4jTemplate implements return null; } - if (resultType.isInstance(instance)) { + if (resultType.equals(instance.getClass())) { return (R) save(instance); } 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 754dd2b31..e30bb02ad 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -314,7 +314,7 @@ public final class ReactiveNeo4jTemplate implements return null; } - if (resultType.isInstance(instance)) { + if (resultType.equals(instance.getClass())) { return (Mono) save(instance); } diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java b/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java index 08de8bbb2..4a87d2f75 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java @@ -135,6 +135,18 @@ public final class QueryFragmentsAndParameters { return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null); } + /** + * Utility method for creating a query fragment including parameters for a given condition. + * + * @param entityMetaData The metadata of a given and known entity + * @param condition A Cypher-DSL condition + * @return Fully populated fragments and parameter + */ + @API(status = API.Status.EXPERIMENTAL, since = "6.1.7") + public static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity entityMetaData, Condition condition) { + return forCondition(entityMetaData, condition, null, null); + } + static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity entityMetaData, Condition condition, @Nullable Pageable pageable, diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java index a3b4e2097..ebffc983e 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java @@ -51,6 +51,9 @@ import org.springframework.data.neo4j.core.DatabaseSelectionProvider; import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; +import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity; +import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity; +import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials; import org.springframework.data.neo4j.integration.shared.common.Person; import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor; import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId; @@ -111,6 +114,14 @@ class Neo4jTemplateIT { transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})"); transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})"); + transaction.run( + "CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " + + "CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " + + "CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " + + "CREATE (company)-[:CHILD_OF]->(root) " + + "CREATE (root)-[:HAS_CREDENTIAL]->(cred) " + + "CREATE (company)-[:WITH_CREDENTIAL]->(cred)"); + transaction.commit(); bookmarkCapture.seedWith(session.lastBookmark()); } @@ -799,6 +810,23 @@ class Neo4jTemplateIT { assertThat(people).extracting(Person::getLastName).containsExactly("Schnitzel"); } + @Test // GH-2415 + void saveWithProjectionImplementedByEntity() { + + NodeEntity nodeEntity = neo4jTemplate + .find(BaseNodeEntity.class) + .as(NodeEntity.class) + .matching( + "MATCH p=(n:BaseNodeEntity)-[r]-(t) WHERE n.nodeId = $nodeId WITH n, collect([x in relationships(p) |x]) AS r, collect(t) AS o RETURN n, r, o", + Collections.singletonMap("nodeId", "root") + ) + .one().get(); + neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class); + + nodeEntity = neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class).get(); + assertThat(nodeEntity.getChildren()).hasSize(1); + } + @Configuration @EnableTransactionManagement static class Config extends AbstractNeo4jConfig { diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/BaseNodeEntity.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/BaseNodeEntity.java new file mode 100644 index 000000000..e7ce0255f --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/BaseNodeEntity.java @@ -0,0 +1,54 @@ +/* + * 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.issues.gh2415; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.NonFinal; +import lombok.experimental.SuperBuilder; + +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.support.UUIDStringGenerator; + +/** + * @author Andreas Berger + */ +@Node +@NonFinal +@Data +@Setter(AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@SuperBuilder(toBuilder = true) +public class BaseNodeEntity { + + @Id + @GeneratedValue(UUIDStringGenerator.class) + @EqualsAndHashCode.Include + private String nodeId; + + private String name; + + @Override + public String toString() { + return getClass().getSimpleName() + " - " + getName() + " (" + getNodeId() + ")"; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/Credential.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/Credential.java new file mode 100644 index 000000000..95c06e128 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/Credential.java @@ -0,0 +1,51 @@ +/* + * 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.issues.gh2415; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; + +import org.springframework.data.annotation.Immutable; +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.support.UUIDStringGenerator; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author Andreas Berger + */ +@Node +@Value +@With +@AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Immutable +@Builder(toBuilder = true) +public class Credential { + + @JsonIgnore + @Id + @GeneratedValue(UUIDStringGenerator.class) + @EqualsAndHashCode.Include + String id; + + String name; +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/NodeEntity.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/NodeEntity.java new file mode 100644 index 000000000..5d547d64f --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/NodeEntity.java @@ -0,0 +1,54 @@ +/* + * 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.issues.gh2415; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +import java.util.Set; + +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author Andreas Berger + */ +@Node +@Data +@Setter(AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) +@SuperBuilder(toBuilder = true) +public class NodeEntity extends BaseNodeEntity implements NodeWithDefinedCredentials { + + @JsonIgnore + @Relationship(type = "CHILD_OF", direction = Relationship.Direction.INCOMING) + private Set children; + + @Relationship(type = "HAS_CREDENTIAL") + private Set definedCredentials; + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/NodeWithDefinedCredentials.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/NodeWithDefinedCredentials.java new file mode 100644 index 000000000..331d28fe5 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2415/NodeWithDefinedCredentials.java @@ -0,0 +1,30 @@ +/* + * 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.issues.gh2415; + +import java.util.Set; + +/** + * @author Andreas Berger + */ +public interface NodeWithDefinedCredentials { + + String getNodeId(); + + String getName(); + + Set getDefinedCredentials(); +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java b/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java index 34045a7a2..9d128c211 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java @@ -53,6 +53,9 @@ import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider; import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate; import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager; +import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity; +import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity; +import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials; import org.springframework.data.neo4j.integration.shared.common.Person; import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor; import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId; @@ -116,6 +119,14 @@ class ReactiveNeo4jTemplateIT { transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})"); transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})"); + transaction.run( + "CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " + + "CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " + + "CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " + + "CREATE (company)-[:CHILD_OF]->(root) " + + "CREATE (root)-[:HAS_CREDENTIAL]->(cred) " + + "CREATE (company)-[:WITH_CREDENTIAL]->(cred)"); + transaction.commit(); bookmarkCapture.seedWith(session.lastBookmark()); @@ -803,6 +814,24 @@ class ReactiveNeo4jTemplateIT { .verifyComplete(); } + @Test // GH-2415 + void saveWithProjectionImplementedByEntity() { + + neo4jTemplate + .find(BaseNodeEntity.class) + .as(NodeEntity.class) + .matching( + "MATCH p=(n:BaseNodeEntity)-[r]-(t) WHERE n.nodeId = $nodeId WITH n, collect([x in relationships(p) |x]) AS r, collect(t) AS o RETURN n, r, o", + Collections.singletonMap("nodeId", "root") + ) + .one() + .flatMap(nodeEntity -> neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class)) + .flatMap(nodeEntity -> neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class)) + .as(StepVerifier::create) + .consumeNextWith(nodeEntity -> assertThat(nodeEntity.getChildren()).hasSize(1)) + .verifyComplete(); + } + @Configuration @EnableTransactionManagement static class Config extends AbstractReactiveNeo4jConfig {