diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java index 5e8f0e4ebe..9b75fcb8f2 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java @@ -627,4 +627,11 @@ private boolean calculatePossibleCircles(NodeDescription nodeDescription, Set visitedNodes.addAll(visitedTargetNodes); return false; } + + @Override + public String toString() { + return "DefaultNeo4jPersistentEntity{" + + "primaryLabel='" + primaryLabel + '\'' + + '}'; + } } diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescriptionStore.java b/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescriptionStore.java index 8f197b7059..704e9f71ef 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescriptionStore.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescriptionStore.java @@ -102,7 +102,7 @@ public NodeDescriptionAndLabels deriveConcreteNodeDescription(NodeDescription return nodeDescriptionAndLabels.apply(entityDescription, labels); } - private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription entityDescription, List labels) { + private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription entityDescription, @Nullable List labels) { boolean isConcreteClassThatFulfillsEverything = !Modifier.isAbstract(entityDescription.getUnderlyingClass().getModifiers()) && entityDescription.getStaticLabels().containsAll(labels); @@ -139,9 +139,9 @@ private NodeDescriptionAndLabels computeConcreteNodeDescription(NodeDescription< int unmatchedLabelsCount = 0; List matchingLabels = new ArrayList<>(); - for (String staticLabel : staticLabels) { - if (labels.contains(staticLabel)) { - matchingLabels.add(staticLabel); + for (String label : labels) { + if (staticLabels.contains(label)) { + matchingLabels.add(label); } else { unmatchedLabelsCount++; } diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java index 38af06af2e..70941baa26 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/IssuesIT.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.assertj.core.api.InstanceOfAssertFactories; @@ -147,6 +148,9 @@ import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Repository; import org.springframework.data.neo4j.integration.issues.gh2858.GH2858; import org.springframework.data.neo4j.integration.issues.gh2858.GH2858Repository; +import org.springframework.data.neo4j.integration.issues.gh2886.Apple; +import org.springframework.data.neo4j.integration.issues.gh2886.FruitRepository; +import org.springframework.data.neo4j.integration.issues.gh2886.Orange; import org.springframework.data.neo4j.integration.issues.qbe.A; import org.springframework.data.neo4j.integration.issues.qbe.ARepository; import org.springframework.data.neo4j.integration.issues.qbe.B; @@ -1162,6 +1166,32 @@ void hydrateProjectionReachableViaMultiplePaths(@Autowired GH2858Repository repo } + @Test + @Tag("GH-2886") + void dynamicLabels(@Autowired FruitRepository repository) { + + var f1 = new Apple(); + f1.setVolume(1.0); + f1.setColor("Red"); + f1.setLabels(Set.of("X")); + + var f2 = new Apple(); + f2.setColor("Blue"); + + var f3 = new Orange(); + f2.setVolume(3.0); + f3.setColor("Red"); + f3.setLabels(Set.of("Y")); + + var f4 = new Orange(); + f4.setColor("Yellow"); + + repository.saveAll(List.of(f1, f2, f3, f4)); + + var fruits = repository.findAllFruits(); + assertThat(fruits).allMatch(f -> f instanceof Apple || f instanceof Orange); + } + @Configuration @EnableTransactionManagement @EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties") diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java new file mode 100644 index 0000000000..8734ef33a8 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Apple.java @@ -0,0 +1,25 @@ +/* + * Copyright 2011-2024 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.gh2886; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "Apple") +public class Apple extends MagicalFruit { +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java new file mode 100644 index 0000000000..56be5cf541 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Fruit.java @@ -0,0 +1,51 @@ +/* + * Copyright 2011-2024 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.gh2886; + +import java.util.Set; + +import org.springframework.data.neo4j.core.schema.DynamicLabels; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "Fruit") +public abstract class Fruit { + + @Id + protected String id; + + @DynamicLabels + protected Set labels = Set.of(); + + public String getId() { + return this.id; + } + + public Set getLabels() { + return this.labels; + } + + public void setId(String id) { + this.id = id; + } + + public void setLabels(Set labels) { + this.labels = labels; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.java new file mode 100644 index 0000000000..dff4f612a0 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/FruitRepository.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011-2024 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.gh2886; + +import java.util.List; + +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.stereotype.Repository; + +/** + * GH-2886 + */ +@Repository +public interface FruitRepository extends Neo4jRepository { + @Query("MATCH (f:Fruit) RETURN f") + List findAllFruits(); +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java new file mode 100644 index 0000000000..3e462ea1c8 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/MagicalFruit.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011-2024 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.gh2886; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "MagicalFruit") +public class MagicalFruit extends Fruit { + + private double volume; + + private String color; + + public double getVolume() { + return this.volume; + } + + public String getColor() { + return this.color; + } + + public void setVolume(double volume) { + this.volume = volume; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java new file mode 100644 index 0000000000..1ba8265833 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2886/Orange.java @@ -0,0 +1,25 @@ +/* + * Copyright 2011-2024 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.gh2886; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * GH-2886 + */ +@Node(primaryLabel = "Orange") +public class Orange extends MagicalFruit { +}