diff --git a/core/src/main/java/org/neo4j/ogm/context/EntityGraphMapper.java b/core/src/main/java/org/neo4j/ogm/context/EntityGraphMapper.java index bf656030a6..242418fe3e 100644 --- a/core/src/main/java/org/neo4j/ogm/context/EntityGraphMapper.java +++ b/core/src/main/java/org/neo4j/ogm/context/EntityGraphMapper.java @@ -398,7 +398,7 @@ private void mapEntityReferences(final Object entity, NodeBuilder nodeBuilder, i ClassInfo endNodeTypeClassInfo = metaData.classInfo(endNodeType); if (endNodeTypeClassInfo != null) { - endNodeTypeClassInfo.directSubclasses() + endNodeTypeClassInfo.allSubclasses() .stream().map(ClassInfo::getUnderlyingClass).forEach(potentiallyRelated::add); } diff --git a/core/src/main/java/org/neo4j/ogm/metadata/ClassInfo.java b/core/src/main/java/org/neo4j/ogm/metadata/ClassInfo.java index 8a14a38520..7d1d167571 100644 --- a/core/src/main/java/org/neo4j/ogm/metadata/ClassInfo.java +++ b/core/src/main/java/org/neo4j/ogm/metadata/ClassInfo.java @@ -68,6 +68,7 @@ public class ClassInfo { private static final Logger LOGGER = LoggerFactory.getLogger(ClassInfo.class); private final List directSubclasses = new ArrayList<>(); + private volatile Set allSubclasses; private final List directInterfaces = new ArrayList<>(); private final List directImplementingClasses = new ArrayList<>(); /** @@ -281,6 +282,39 @@ public List directSubclasses() { return directSubclasses; } + /** + * @return A list of all implementing and extending subclasses. + * @since 3.1.20 + */ + public Collection allSubclasses() { + + Set computedSubclasses = this.allSubclasses; + if (computedSubclasses == null) { + synchronized (this) { + computedSubclasses = this.allSubclasses; + if (computedSubclasses == null) { + this.allSubclasses = computeSubclasses(); + computedSubclasses = this.allSubclasses; + } + } + } + return computedSubclasses; + } + + private Set computeSubclasses() { + + Set computedSubclasses = new HashSet<>(); + for (ClassInfo classInfo : this.directSubclasses()) { + computedSubclasses.add(classInfo); + computedSubclasses.addAll(classInfo.allSubclasses()); + } + for (ClassInfo classInfo : this.directImplementingClasses()) { + computedSubclasses.add(classInfo); + computedSubclasses.addAll(classInfo.allSubclasses()); + } + return Collections.unmodifiableSet(computedSubclasses); + } + List directImplementingClasses() { return directImplementingClasses; } diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/Container.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/Container.java index e9307c1118..39fb638cbc 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/Container.java +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/Container.java @@ -39,6 +39,9 @@ public class Container { @Relationship(type = "RELATES_TO", direction = "INCOMING") Set element; + @Relationship(type = "RELATES_TO_AS_WELL", direction = "INCOMING") + Set element2; + /** * This is added to ensure we don't delete unrelated relationships. */ @@ -84,4 +87,12 @@ public void setElementsOfAnotherRelationship( Set elementsOfAnotherRelationship) { this.elementsOfAnotherRelationship = elementsOfAnotherRelationship; } + + public Set getElement2() { + return element2; + } + + public void setElement2(Set element2) { + this.element2 = element2; + } } diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/EvenMoreConcreteElement.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/EvenMoreConcreteElement.java new file mode 100644 index 0000000000..ed8b927ff3 --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/EvenMoreConcreteElement.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://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 + * + * http://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.neo4j.ogm.domain.gh806; + +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * @author Michael J. Simons + */ +@NodeEntity +public class EvenMoreConcreteElement extends VeryConcreteElementA { + + public EvenMoreConcreteElement() { + } + + public EvenMoreConcreteElement(String globalName) { + super(globalName); + } +} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElement.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElement.java new file mode 100644 index 0000000000..8f3ae6f7ca --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElement.java @@ -0,0 +1,4 @@ +package org.neo4j.ogm.domain.gh806; + +public interface IElement { +} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElementImpl1.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElementImpl1.java new file mode 100644 index 0000000000..58274dffbe --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElementImpl1.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://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 + * + * http://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.neo4j.ogm.domain.gh806; + +import org.neo4j.ogm.annotation.GeneratedValue; +import org.neo4j.ogm.annotation.Id; +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +/** + * @author Michael J. Simons + */ +@NodeEntity +public class IElementImpl1 implements IElement{ + + @Id @GeneratedValue + Long id; + + String name; + + @Relationship(type = "RELATES_TO") + Container container; + + public IElementImpl1() { + } + + public IElementImpl1(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Container getContainer() { + return container; + } + + public void setContainer(Container container) { + this.container = container; + } +} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElementImpl1A.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElementImpl1A.java new file mode 100644 index 0000000000..abe4d53a37 --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/IElementImpl1A.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://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 + * + * http://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.neo4j.ogm.domain.gh806; + +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * @author Michael J. Simons + */ +@NodeEntity +public class IElementImpl1A extends IElementImpl1 { + + public IElementImpl1A() { + } + + public IElementImpl1A(String globalName) { + super(globalName); + } +} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/VeryConcreteElementA.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/VeryConcreteElementA.java new file mode 100644 index 0000000000..bc6260bd4c --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/VeryConcreteElementA.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://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 + * + * http://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.neo4j.ogm.domain.gh806; + +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * @author Michael J. Simons + */ +@NodeEntity +public class VeryConcreteElementA extends ConcreteElement { + + public VeryConcreteElementA() { + } + + public VeryConcreteElementA(String globalName) { + super(globalName); + } +} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/VeryConcreteElementB.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/VeryConcreteElementB.java new file mode 100644 index 0000000000..64b3494f96 --- /dev/null +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/gh806/VeryConcreteElementB.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://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 + * + * http://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.neo4j.ogm.domain.gh806; + +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * @author Michael J. Simons + */ +@NodeEntity +public class VeryConcreteElementB extends ConcreteElement { + + public VeryConcreteElementB() { + } + + public VeryConcreteElementB(String globalName) { + super(globalName); + } +} diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/metadata/DomainInfoTest.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/metadata/DomainInfoTest.java index 2e50221fe9..f944faf392 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/metadata/DomainInfoTest.java +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/metadata/DomainInfoTest.java @@ -35,7 +35,7 @@ public class DomainInfoTest { @Before public void setUp() { - domainInfo = DomainInfo.create("org.neo4j.ogm.domain.forum"); + domainInfo = DomainInfo.create("org.neo4j.ogm.domain.forum", "org.neo4j.ogm.domain.gh806"); } @Test @@ -47,6 +47,32 @@ public void testInterfaceClassIMembership() { assertThat(classInfo.directImplementingClasses()).hasSize(5); } + @Test // GH-806 + public void shouldFindAllSubclasses() { + + ClassInfo classInfo = domainInfo.getClassSimpleName("Element"); + + assertThat(classInfo.allSubclasses()) + .containsExactlyInAnyOrder( + domainInfo.getClassSimpleName("ConcreteElement"), + domainInfo.getClassSimpleName("VeryConcreteElementA"), + domainInfo.getClassSimpleName("VeryConcreteElementB"), + domainInfo.getClassSimpleName("EvenMoreConcreteElement") + ); + } + + @Test // GH-806 + public void shouldFindAllImplementingClasses() { + + ClassInfo classInfo = domainInfo.getClassSimpleName("IElement"); + + assertThat(classInfo.allSubclasses()) + .containsExactlyInAnyOrder( + domainInfo.getClassSimpleName("IElementImpl1"), + domainInfo.getClassSimpleName("IElementImpl1A") + ); + } + @Test public void testAbstractClassMembership() { diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/persistence/relationships/HierarchyRelsTest.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/persistence/relationships/HierarchyRelsTest.java index b7aba8d55f..4bc9f2c10b 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/persistence/relationships/HierarchyRelsTest.java +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/persistence/relationships/HierarchyRelsTest.java @@ -25,6 +25,10 @@ import java.util.Collections; import java.util.Date; import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.StreamSupport; @@ -38,6 +42,10 @@ import org.neo4j.ogm.domain.gh806.ConcreteElement; import org.neo4j.ogm.domain.gh806.Container; import org.neo4j.ogm.domain.gh806.Element; +import org.neo4j.ogm.domain.gh806.EvenMoreConcreteElement; +import org.neo4j.ogm.domain.gh806.IElementImpl1; +import org.neo4j.ogm.domain.gh806.IElementImpl1A; +import org.neo4j.ogm.domain.gh806.VeryConcreteElementA; import org.neo4j.ogm.domain.hierarchy.relations.BaseEntity; import org.neo4j.ogm.domain.hierarchy.relations.Type1; import org.neo4j.ogm.domain.hierarchy.relations.Type2; @@ -66,27 +74,32 @@ public void init() throws IOException { @After public void cleanup() { - session.purgeDatabase(); + //r session.purgeDatabase(); session.clear(); } @Test // GH-806 public void relationshipsToSubclassesShouldBeClearedAsWell() { - inheritanceImpl(s -> new Element(s)); - inheritanceImpl(s -> new ConcreteElement(s)); + inheritanceImpl(s -> new Element(s), Container::setElement); + inheritanceImpl(s -> new ConcreteElement(s), Container::setElement); + inheritanceImpl(s -> new VeryConcreteElementA(s), Container::setElement); + inheritanceImpl(s -> new EvenMoreConcreteElement(s), Container::setElement); + + inheritanceImpl(s -> new IElementImpl1(s), Container::setElement2); + inheritanceImpl(s -> new IElementImpl1A(s), Container::setElement2); } - void inheritanceImpl(Function portProvider) { + void inheritanceImpl(Function portProvider, BiConsumer> elementConsumer) { session.query("MATCH (n) DETACH DELETE n", Collections.emptyMap()); session.clear(); // Setup initial relationships in one tx Container card = new Container("container"); - Element port1 = portProvider.apply("e1"); - Element port2 = portProvider.apply("e2"); - card.setElement(new HashSet<>(Arrays.asList(port1, port2))); + T port1 = portProvider.apply("e1"); + T port2 = portProvider.apply("e2"); + elementConsumer.accept(card, new HashSet<>(Arrays.asList(port1, port2))); card.setElementsOfAnotherRelationship(Collections.singleton(new ConcreteElement("oe"))); @@ -94,22 +107,26 @@ void inheritanceImpl(Function portProvider) { session.clear(); // Verify state - String verificationQuery = "match (c:Container) <- [:RELATES_TO|RELATES_TO_TOO] - (p:Element) return c.name as c, p.name as p"; + String verificationQuery = "match (c:Container) <- [:RELATES_TO|RELATES_TO_TOO|RELATES_TO_AS_WELL] - (p) " + + "where any (label in labels(p) where label in $expectedLabels) return c.name as c, p.name as p"; + Set expectedLabels = new HashSet<>(Arrays.asList(ConcreteElement.class.getSimpleName(), port1.getClass().getSimpleName())); + Map parameters = Collections.singletonMap("expectedLabels", expectedLabels); + Result r; - r = session.query(verificationQuery, Collections.emptyMap()); + r = session.query(verificationQuery, parameters); assertThat(r.queryResults()).hasSize(3); assertThat(r.queryResults()).extracting(m -> m.get("p")).containsExactlyInAnyOrder("e1", "e2", "oe"); // Reload in cleared session for fresh tx card = session.load(Container.class, card.getId()); - Element port3 = portProvider.apply("e3"); + T port3 = portProvider.apply("e3"); // Replace associations - card.setElement(Collections.singleton(port3)); + elementConsumer.accept(card, new HashSet<>(Arrays.asList(port3))); session.save(card); session.clear(); - r = session.query(verificationQuery, Collections.emptyMap()); + r = session.query(verificationQuery, parameters); assertThat(r.queryResults()).hasSize(2); assertThat(r.queryResults()).extracting(m -> m.get("p")).containsExactlyInAnyOrder("e3", "oe"); }