Skip to content

Commit f58a090

Browse files
GH-2294 - Support @ReadOnlyProperty.
Closes #2294.
1 parent ef63722 commit f58a090

File tree

9 files changed

+310
-10
lines changed

9 files changed

+310
-10
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,9 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
641641

642642
// create context to bundle parameters
643643
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, propertyAccessor, sourceEntity);
644+
if (relationshipContext.isReadOnly()) {
645+
return;
646+
}
644647

645648
Object rawValue = relationshipContext.getValue();
646649
Collection<?> relatedValuesToStore = MappingSupport.unifyRelationshipValue(relationshipContext.getInverse(),

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,8 +725,10 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
725725
sourceEntity.doWithAssociations((AssociationHandler<Neo4jPersistentProperty>) association -> {
726726

727727
// create context to bundle parameters
728-
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, parentPropertyAccessor,
729-
sourceEntity);
728+
NestedRelationshipContext relationshipContext = NestedRelationshipContext.of(association, parentPropertyAccessor, sourceEntity);
729+
if (relationshipContext.isReadOnly()) {
730+
return;
731+
}
730732

731733
Object rawValue = relationshipContext.getValue();
732734
Collection<?> relatedValuesToStore = MappingSupport.unifyRelationshipValue(relationshipContext.getInverse(),

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jEntityConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.neo4j.driver.types.Type;
4343
import org.neo4j.driver.types.TypeSystem;
4444
import org.springframework.core.CollectionFactory;
45+
import org.springframework.data.annotation.ReadOnlyProperty;
4546
import org.springframework.data.mapping.AssociationHandler;
4647
import org.springframework.data.mapping.MappingException;
4748
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -184,7 +185,7 @@ public void write(Object source, Map<String, Object> parameters) {
184185
nodeDescription.doWithProperties((Neo4jPersistentProperty p) -> {
185186

186187
// Skip the internal properties, we don't want them to end up stored as properties
187-
if (p.isInternalIdProperty() || p.isDynamicLabels() || p.isEntity() || p.isVersionProperty()) {
188+
if (p.isInternalIdProperty() || p.isDynamicLabels() || p.isEntity() || p.isVersionProperty() || p.isAnnotationPresent(ReadOnlyProperty.class)) {
188189
return;
189190
}
190191

src/main/java/org/springframework/data/neo4j/core/mapping/NestedRelationshipContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Map;
2323

2424
import org.apiguardian.api.API;
25+
import org.springframework.data.annotation.ReadOnlyProperty;
2526
import org.springframework.data.mapping.Association;
2627
import org.springframework.data.mapping.PersistentPropertyAccessor;
2728
import org.springframework.data.neo4j.core.schema.TargetNode;
@@ -53,6 +54,10 @@ private NestedRelationshipContext(Neo4jPersistentProperty inverse, @Nullable Obj
5354
this.inverseValueIsEmpty = inverseValueIsEmpty;
5455
}
5556

57+
public boolean isReadOnly() {
58+
return inverse.isAnnotationPresent(ReadOnlyProperty.class);
59+
}
60+
5661
public Neo4jPersistentProperty getInverse() {
5762
return inverse;
5863
}

src/test/java/org/springframework/data/neo4j/integration/issues/gh2289/GH2289IT.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
*/
1616
package org.springframework.data.neo4j.integration.issues.gh2289;
1717

18-
import org.assertj.core.api.Assertions;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
1920
import org.junit.jupiter.api.BeforeAll;
2021
import org.junit.jupiter.api.RepeatedTest;
2122
import org.neo4j.driver.Driver;
2223
import org.neo4j.driver.Session;
2324
import org.neo4j.driver.Transaction;
25+
import org.neo4j.driver.Values;
2426
import org.springframework.beans.factory.annotation.Autowired;
2527
import org.springframework.context.annotation.Bean;
2628
import org.springframework.context.annotation.Configuration;
@@ -47,6 +49,10 @@ protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
4749
Transaction transaction = session.beginTransaction();
4850
) {
4951
transaction.run("MATCH (n) detach delete n");
52+
for (int i = 0; i < 4; ++i) {
53+
transaction.run("CREATE (s:SKU_RO {number: $i, name: $n})",
54+
Values.parameters("i", i, "n", new String(new char[] { (char) ('A' + i) })));
55+
}
5056
transaction.commit();
5157
bookmarkCapture.seedWith(session.lastBookmark());
5258
}
@@ -64,20 +70,53 @@ void testNewRelation(@Autowired SkuRepository skuRepo) {
6470
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
6571
a = skuRepo.save(a);
6672

67-
Assertions.assertThat(a.getRangeRelationsOut()).hasSize(3);
73+
assertThat(a.getRangeRelationsOut()).hasSize(3);
6874
b = skuRepo.findById(b.getId()).get();
69-
Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1);
75+
assertThat(b.getRangeRelationsIn()).hasSize(1);
7076

7177
b.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
7278
b = skuRepo.save(b);
73-
Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1);
74-
Assertions.assertThat(b.getRangeRelationsOut()).hasSize(1);
79+
assertThat(b.getRangeRelationsIn()).hasSize(1);
80+
assertThat(b.getRangeRelationsOut()).hasSize(1);
81+
}
82+
83+
@RepeatedTest(5) // GH-2294
84+
void testNewRelationRo(@Autowired SkuRORepository skuRepo) {
85+
SkuRO a = skuRepo.findOneByName("A");
86+
SkuRO b = skuRepo.findOneByName("B");
87+
SkuRO c = skuRepo.findOneByName("C");
88+
SkuRO d = skuRepo.findOneByName("D");
89+
90+
a.rangeRelationTo(b, 1, 1, RelationType.MULTIPLICATIVE);
91+
a.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
92+
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
93+
a.setName("a new name");
94+
a = skuRepo.save(a);
95+
assertThat(a.getRangeRelationsOut()).hasSize(3);
96+
assertThat(a.getName()).isEqualTo("a new name");
97+
98+
assertThat(skuRepo.findOneByName("a new name")).isNull();
99+
100+
b = skuRepo.findOneByName("B");
101+
assertThat(b.getRangeRelationsIn()).hasSize(1);
102+
assertThat(b.getRangeRelationsOut()).hasSizeLessThanOrEqualTo(1);
103+
104+
b.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
105+
b = skuRepo.save(b);
106+
assertThat(b.getRangeRelationsIn()).hasSize(1);
107+
assertThat(b.getRangeRelationsOut()).hasSize(1);
75108
}
76109

77110
@Repository
78111
public interface SkuRepository extends Neo4jRepository<Sku, Long> {
79112
}
80113

114+
@Repository
115+
public interface SkuRORepository extends Neo4jRepository<SkuRO, Long> {
116+
117+
SkuRO findOneByName(String name);
118+
}
119+
81120
@Configuration
82121
@EnableTransactionManagement
83122
@EnableNeo4jRepositories(considerNestedRepositories = true)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2289;
17+
18+
import lombok.Data;
19+
import lombok.EqualsAndHashCode;
20+
21+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
22+
import org.springframework.data.neo4j.core.schema.Id;
23+
import org.springframework.data.neo4j.core.schema.Property;
24+
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
25+
import org.springframework.data.neo4j.core.schema.TargetNode;
26+
27+
/**
28+
* @author Michael J. Simons
29+
*/
30+
@Data // lombok
31+
@RelationshipProperties
32+
public class RangeRelationRO {
33+
34+
@EqualsAndHashCode.Exclude
35+
@Id @GeneratedValue private Long id;
36+
37+
@Property private double minDelta;
38+
@Property private double maxDelta;
39+
@Property private RelationType relationType;
40+
41+
@TargetNode private SkuRO targetSku;
42+
43+
public RangeRelationRO(SkuRO targetSku, double minDelta, double maxDelta, RelationType relationType) {
44+
this.targetSku = targetSku;
45+
this.minDelta = minDelta;
46+
this.maxDelta = maxDelta;
47+
this.relationType = relationType;
48+
}
49+
}

src/test/java/org/springframework/data/neo4j/integration/issues/gh2289/ReactiveGH2289IT.java

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@
1515
*/
1616
package org.springframework.data.neo4j.integration.issues.gh2289;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import reactor.core.publisher.Mono;
1821
import reactor.test.StepVerifier;
1922

2023
import java.util.concurrent.atomic.AtomicLong;
2124
import java.util.concurrent.atomic.AtomicReference;
2225

23-
import org.assertj.core.api.Assertions;
2426
import org.junit.jupiter.api.BeforeAll;
2527
import org.junit.jupiter.api.RepeatedTest;
2628
import org.junit.jupiter.api.Tag;
2729
import org.neo4j.driver.Driver;
2830
import org.neo4j.driver.Session;
2931
import org.neo4j.driver.Transaction;
32+
import org.neo4j.driver.Values;
3033
import org.springframework.beans.factory.annotation.Autowired;
3134
import org.springframework.context.annotation.Bean;
3235
import org.springframework.context.annotation.Configuration;
@@ -54,6 +57,10 @@ protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
5457
Transaction transaction = session.beginTransaction();
5558
) {
5659
transaction.run("MATCH (n) detach delete n");
60+
for (int i = 0; i < 4; ++i) {
61+
transaction.run("CREATE (s:SKU_RO {number: $i, name: $n})",
62+
Values.parameters("i", i, "n", new String(new char[] { (char) ('A' + i) })));
63+
}
5764
transaction.commit();
5865
bookmarkCapture.seedWith(session.lastBookmark());
5966
}
@@ -84,7 +91,7 @@ void testNewRelation(@Autowired SkuRepository skuRepo) {
8491
.verifyComplete();
8592

8693
skuRepo.findById(bId.get())
87-
.doOnNext(b -> Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1))
94+
.doOnNext(b -> assertThat(b.getRangeRelationsIn()).hasSize(1))
8895
.flatMap(b -> {
8996
b.rangeRelationTo(cRef.get(), 1, 1, RelationType.MULTIPLICATIVE);
9097
return skuRepo.save(b);
@@ -94,10 +101,62 @@ void testNewRelation(@Autowired SkuRepository skuRepo) {
94101
.verifyComplete();
95102
}
96103

104+
@RepeatedTest(5) // GH-2294
105+
void testNewRelationRo(@Autowired SkuRORepository skuRepo) {
106+
107+
AtomicLong bId = new AtomicLong();
108+
AtomicReference<SkuRO> cRef = new AtomicReference<>();
109+
skuRepo.findOneByName("A")
110+
.zipWith(skuRepo.findOneByName("B"))
111+
.zipWith(skuRepo.findOneByName("C"))
112+
.zipWith(skuRepo.findOneByName("D"))
113+
.flatMap(t -> {
114+
SkuRO a = t.getT1().getT1().getT1();
115+
SkuRO b = t.getT1().getT1().getT2();
116+
SkuRO c = t.getT1().getT2();
117+
SkuRO d = t.getT2();
118+
119+
bId.set(b.getId());
120+
cRef.set(c);
121+
a.rangeRelationTo(b, 1, 1, RelationType.MULTIPLICATIVE);
122+
a.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
123+
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
124+
125+
a.setName("a new name");
126+
127+
return skuRepo.save(a);
128+
}).as(StepVerifier::create)
129+
.expectNextMatches(a -> a.getRangeRelationsOut().size() == 3 && "a new name".equals(a.getName()))
130+
.verifyComplete();
131+
132+
skuRepo.findOneByName("a new name")
133+
.as(StepVerifier::create)
134+
.verifyComplete();
135+
136+
skuRepo.findOneByName("B")
137+
.doOnNext(b -> {
138+
assertThat(b.getRangeRelationsIn()).hasSize(1);
139+
assertThat(b.getRangeRelationsOut()).hasSizeLessThanOrEqualTo(1);
140+
})
141+
.flatMap(b -> {
142+
b.rangeRelationTo(cRef.get(), 1, 1, RelationType.MULTIPLICATIVE);
143+
return skuRepo.save(b);
144+
})
145+
.as(StepVerifier::create)
146+
.expectNextMatches(b -> b.getRangeRelationsIn().size() == 1 && b.getRangeRelationsOut().size() == 1)
147+
.verifyComplete();
148+
}
149+
97150
@Repository
98151
public interface SkuRepository extends ReactiveNeo4jRepository<Sku, Long> {
99152
}
100153

154+
@Repository
155+
public interface SkuRORepository extends ReactiveNeo4jRepository<SkuRO, Long> {
156+
157+
Mono<SkuRO> findOneByName(String name);
158+
}
159+
101160
@Configuration
102161
@EnableTransactionManagement
103162
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2289;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Getter;
20+
import lombok.Setter;
21+
22+
import java.util.HashSet;
23+
import java.util.Set;
24+
25+
import org.springframework.data.annotation.ReadOnlyProperty;
26+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
27+
import org.springframework.data.neo4j.core.schema.Id;
28+
import org.springframework.data.neo4j.core.schema.Node;
29+
import org.springframework.data.neo4j.core.schema.Property;
30+
import org.springframework.data.neo4j.core.schema.Relationship;
31+
32+
/**
33+
* @author Michael J. Simons
34+
*/
35+
@Node("SKU_RO")
36+
@Getter // lombok
37+
@Setter
38+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
39+
public class SkuRO {
40+
41+
@Id @GeneratedValue
42+
@EqualsAndHashCode.Include
43+
private Long id;
44+
45+
@Property("number")
46+
@EqualsAndHashCode.Include
47+
private Long number;
48+
49+
@ReadOnlyProperty
50+
@Property("name")
51+
@EqualsAndHashCode.Include
52+
private String name;
53+
54+
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.OUTGOING)
55+
private Set<RangeRelationRO> rangeRelationsOut = new HashSet<>();
56+
57+
@ReadOnlyProperty
58+
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.INCOMING)
59+
private Set<RangeRelationRO> rangeRelationsIn = new HashSet<>();
60+
61+
public SkuRO(Long number, String name) {
62+
this.number = number;
63+
this.name = name;
64+
}
65+
66+
public RangeRelationRO rangeRelationTo(SkuRO sku, double minDelta, double maxDelta, RelationType relationType) {
67+
RangeRelationRO relationOut = new RangeRelationRO(sku, minDelta, maxDelta, relationType);
68+
rangeRelationsOut.add(relationOut);
69+
return relationOut;
70+
}
71+
}

0 commit comments

Comments
 (0)