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 f1860dee2..62bd52449 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 @@ -16,6 +16,7 @@ package org.springframework.data.neo4j.core.mapping; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -121,15 +122,12 @@ final class NodeDescriptionStore { Map, Integer> unmatchedLabelsCache = new HashMap<>(); List mostMatchingStaticLabels = null; - // Remove is faster than "stream, filter, count". - BiFunction, List, Integer> unmatchedLabelsCount = - (nodeDescription, staticLabels) -> { - Set staticLabelsClone = new HashSet<>(staticLabels); - labels.forEach(staticLabelsClone::remove); - return staticLabelsClone.size(); - }; - for (NodeDescription nd : haystack) { + + if (Modifier.isAbstract(nd.getUnderlyingClass().getModifiers())) { + continue; + } + List staticLabels = nd.getStaticLabels(); if (staticLabels.containsAll(labels)) { @@ -138,15 +136,20 @@ final class NodeDescriptionStore { return new NodeDescriptionAndLabels(nd, surplusLabels); } - unmatchedLabelsCache.put(nd, unmatchedLabelsCount.apply(nd, staticLabels)); - if (mostMatchingNodeDescription == null) { - mostMatchingNodeDescription = nd; - mostMatchingStaticLabels = staticLabels; - continue; + int unmatchedLabelsCount = 0; + List matchingLabels = new ArrayList<>(); + for (String staticLabel : staticLabels) { + if (labels.contains(staticLabel)) { + matchingLabels.add(staticLabel); + } else { + unmatchedLabelsCount++; + } } - if (unmatchedLabelsCache.get(nd) < unmatchedLabelsCache.get(mostMatchingNodeDescription)) { + unmatchedLabelsCache.put(nd, unmatchedLabelsCount); + if (mostMatchingNodeDescription == null || unmatchedLabelsCount < unmatchedLabelsCache.get(mostMatchingNodeDescription)) { mostMatchingNodeDescription = nd; + mostMatchingStaticLabels = matchingLabels; } } 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 74644086a..afb9a7d15 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 @@ -19,12 +19,23 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.neo4j.cypherdsl.core.Conditions.not; import static org.neo4j.cypherdsl.core.Predicates.exists; +import org.junit.jupiter.api.RepeatedTest; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.integration.shared.common.CounterMetric; +import org.springframework.data.neo4j.integration.shared.common.GaugeMetric; +import org.springframework.data.neo4j.integration.shared.common.HistogramMetric; +import org.springframework.data.neo4j.integration.shared.common.Metric; +import org.springframework.data.neo4j.integration.shared.common.SummaryMetric; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; @@ -80,6 +91,38 @@ public class ReactiveDynamicLabelsIT { protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport; + + @Nested + class DynamicLabelsAndOrderOfClassesBeingLoaded extends SpringTestBase { + + @Override + Long createTestEntity(Transaction t) { + return t.run("CREATE (m:Metric:Counter:A:B:C:D {timestamp: datetime()}) RETURN id(m)").single().get(0).asLong(); + + } + + @RepeatedTest(100) // GH-2619 + void ownLabelsShouldNotEndUpWithDynamicLabels(@Autowired Neo4jMappingContext mappingContext, @Autowired ReactiveNeo4jTemplate template) { + + List> metrics = Arrays.asList(GaugeMetric.class, SummaryMetric.class, HistogramMetric.class, CounterMetric.class); + Collections.shuffle(metrics); + for (Class type : metrics) { + assertThat(mappingContext.getPersistentEntity(type)).isNotNull(); + } + + Map args = new HashMap<>(); + args.put("agentIdLabel", "B"); + template.findAll("MATCH (m:Metric) WHERE $agentIdLabel in labels(m) RETURN m ORDER BY m.timestamp DESC", args, Metric.class) + .as(StepVerifier::create) + .assertNext(cm -> { + assertThat(cm).isInstanceOf(CounterMetric.class); + assertThat(cm.getId()).isEqualTo(existingEntityId); + assertThat(cm.getDynamicLabels()).containsExactlyInAnyOrder("A", "B", "C", "D"); + }) + .verifyComplete(); + } + } + @Nested class EntityWithSingleStaticLabelAndGeneratedId extends SpringTestBase { diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/CounterMetric.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/CounterMetric.java new file mode 100644 index 000000000..2a0740aa7 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/CounterMetric.java @@ -0,0 +1,29 @@ +/* + * 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.shared.common; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * @author Michael J. Simons + */ +@Node("Counter") +public class CounterMetric extends Metric { + + public CounterMetric(String name) { + super(name); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/GaugeMetric.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/GaugeMetric.java new file mode 100644 index 000000000..b0eda806e --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/GaugeMetric.java @@ -0,0 +1,30 @@ +/* + * 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.shared.common; + +import org.springframework.data.neo4j.core.schema.Node; + + +/** + * @author Michael J. Simons + */ +@Node("Gauge") +public class GaugeMetric extends Metric { + + public GaugeMetric(String name) { + super(name); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/HistogramMetric.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/HistogramMetric.java new file mode 100644 index 000000000..f9520f98a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/HistogramMetric.java @@ -0,0 +1,29 @@ +/* + * 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.shared.common; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * @author Michael J. Simons + */ +@Node("Histogram") +public class HistogramMetric extends Metric { + + public HistogramMetric(String name) { + super(name); + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/Metric.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/Metric.java new file mode 100644 index 000000000..84b853ac7 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/Metric.java @@ -0,0 +1,60 @@ +/* + * 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.shared.common; + +import java.util.ArrayList; +import java.util.List; + +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; + +/** + * @author Michael J. Simons + */ +@Node("Metric") +public abstract class Metric { + + @Id + @GeneratedValue + Long id; + + @DynamicLabels + public List dynamicLabels = new ArrayList<>(); + + public Long getId() { + return id; + } + + public List getDynamicLabels() { + return dynamicLabels; + } + + private String name; + + public Metric(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/SummaryMetric.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/SummaryMetric.java new file mode 100644 index 000000000..8fd9fa9f3 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/SummaryMetric.java @@ -0,0 +1,29 @@ +/* + * 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.shared.common; + +import org.springframework.data.neo4j.core.schema.Node; + +/** + * @author Michael J. Simons + */ +@Node("Summary") +public class SummaryMetric extends Metric { + + public SummaryMetric(String name) { + super(name); + } +}