Skip to content

Commit 2a06578

Browse files
committed
DATACOUCH-620 - Fix nested object id/@id issue.
Only treat the id (or @id) field in top-level objects as the id.
1 parent 946e835 commit 2a06578

File tree

6 files changed

+360
-12
lines changed

6 files changed

+360
-12
lines changed

src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ public void write(final Object source, final CouchbaseDocument target) {
402402
typeMapper.writeType(type, target);
403403
}
404404

405-
writeInternal(source, target, type);
405+
writeInternal(source, target, type, true);
406406
if (target.getId() == null) {
407407
throw new MappingException("An ID property is needed, but not found/could not be generated on this entity.");
408408
}
@@ -416,7 +416,8 @@ public void write(final Object source, final CouchbaseDocument target) {
416416
* @param typeHint the type information for the source.
417417
*/
418418
@SuppressWarnings("unchecked")
419-
protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation<?> typeHint) {
419+
protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation<?> typeHint,
420+
boolean withId) {
420421
if (source == null) {
421422
return;
422423
}
@@ -437,7 +438,7 @@ protected void writeInternal(final Object source, CouchbaseDocument target, fina
437438
}
438439

439440
CouchbasePersistentEntity<?> entity = mappingContext.getPersistentEntity(source.getClass());
440-
writeInternal(source, target, entity);
441+
writeInternal(source, target, entity, withId);
441442
addCustomTypeKeyIfNecessary(typeHint, source, target);
442443
}
443444

@@ -471,9 +472,10 @@ private String convertToString(Object propertyObj) {
471472
* @param source the source object.
472473
* @param target the target document.
473474
* @param entity the persistent entity to convert from.
475+
* @param withId one of the top-level properties is the id for the document
474476
*/
475477
protected void writeInternal(final Object source, final CouchbaseDocument target,
476-
final CouchbasePersistentEntity<?> entity) {
478+
final CouchbasePersistentEntity<?> entity, boolean withId) {
477479
if (source == null) {
478480
return;
479481
}
@@ -483,7 +485,7 @@ protected void writeInternal(final Object source, final CouchbaseDocument target
483485
}
484486

485487
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(source);
486-
final CouchbasePersistentProperty idProperty = entity.getIdProperty();
488+
final CouchbasePersistentProperty idProperty = withId ? entity.getIdProperty() : null;
487489
final CouchbasePersistentProperty versionProperty = entity.getVersionProperty();
488490

489491
GeneratedValue generatedValueInfo = null;
@@ -525,7 +527,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
525527
}
526528

527529
if (!conversions.isSimpleType(propertyObj.getClass())) {
528-
writePropertyInternal(propertyObj, target, prop);
530+
writePropertyInternal(propertyObj, target, prop, false);
529531
} else {
530532
writeSimpleInternal(propertyObj, target, prop.getFieldName());
531533
}
@@ -552,7 +554,7 @@ public void doWithAssociation(final Association<CouchbasePersistentProperty> ass
552554
Class<?> type = inverseProp.getType();
553555
Object propertyObj = accessor.getProperty(inverseProp, type);
554556
if (null != propertyObj) {
555-
writePropertyInternal(propertyObj, target, inverseProp);
557+
writePropertyInternal(propertyObj, target, inverseProp, false);
556558
}
557559
}
558560
});
@@ -568,7 +570,7 @@ public void doWithAssociation(final Association<CouchbasePersistentProperty> ass
568570
*/
569571
@SuppressWarnings("unchecked")
570572
private void writePropertyInternal(final Object source, final CouchbaseDocument target,
571-
final CouchbasePersistentProperty prop) {
573+
final CouchbasePersistentProperty prop, boolean withId) {
572574
if (source == null) {
573575
return;
574576
}
@@ -617,7 +619,7 @@ private void writePropertyInternal(final Object source, final CouchbaseDocument
617619
CouchbasePersistentEntity<?> entity = isSubtype(prop.getType(), source.getClass())
618620
? mappingContext.getRequiredPersistentEntity(source.getClass())
619621
: mappingContext.getRequiredPersistentEntity(type);
620-
writeInternal(source, propertyDoc, entity);
622+
writeInternal(source, propertyDoc, entity, false);
621623
target.put(name, propertyDoc);
622624
}
623625

@@ -660,7 +662,7 @@ private CouchbaseDocument writeMapInternal(final Map<Object, Object> source, fin
660662
} else {
661663
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
662664
TypeInformation<?> valueTypeInfo = type.isMap() ? type.getMapValueType() : ClassTypeInformation.OBJECT;
663-
writeInternal(val, embeddedDoc, valueTypeInfo);
665+
writeInternal(val, embeddedDoc, valueTypeInfo, false);
664666
target.put(simpleKey, embeddedDoc);
665667
}
666668
} else {
@@ -706,7 +708,7 @@ private CouchbaseList writeCollectionInternal(final Collection<?> source, final
706708
} else {
707709

708710
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
709-
writeInternal(element, embeddedDoc, componentType);
711+
writeInternal(element, embeddedDoc, componentType, false);
710712
target.put(embeddedDoc);
711713
}
712714

@@ -937,7 +939,7 @@ public <R> R getPropertyValue(final CouchbasePersistentProperty property) {
937939
String expression = property.getSpelExpression();
938940
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName());
939941

940-
if (property.isIdProperty()) {
942+
if (property.isIdProperty() && parent == null) {
941943
return (R) source.getId();
942944
}
943945
if (value == null) {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2020 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+
17+
package org.springframework.data.couchbase.domain;
18+
19+
import org.springframework.data.annotation.Id;
20+
21+
import java.lang.reflect.Field;
22+
23+
/**
24+
* Course entity for tests
25+
*
26+
* @author Michael Reiche
27+
*/
28+
public class Course {
29+
@Id private final String id;
30+
private final String userId;
31+
private final String room;
32+
33+
public Course(String id, String userId, String room) {
34+
this.id = id;
35+
this.userId = userId;
36+
this.room = room;
37+
}
38+
39+
public String getId() {
40+
return id;
41+
}
42+
43+
public String toString() {
44+
StringBuffer sb = new StringBuffer("Course(");
45+
sb.append("id=");
46+
sb.append(id);
47+
sb.append(", userId=");
48+
sb.append(userId);
49+
sb.append(", room=");
50+
sb.append(room);
51+
sb.append(")");
52+
return sb.toString();
53+
}
54+
55+
@Override
56+
public boolean equals(Object that) throws RuntimeException {
57+
if (this == that)
58+
return true;
59+
if (that == null || getClass() != that.getClass())
60+
return false;
61+
for (Field f : getClass().getFields()) {
62+
if (!same(f, this, that))
63+
return false;
64+
}
65+
for (Field f : getClass().getDeclaredFields()) {
66+
if (!same(f, this, that))
67+
return false;
68+
}
69+
return true;
70+
}
71+
72+
private static boolean same(Field f, Object a, Object b) {
73+
Object thisField = null;
74+
Object thatField = null;
75+
76+
try {
77+
f.get(a);
78+
f.get(b);
79+
} catch (IllegalAccessException e) {
80+
throw new RuntimeException(e);
81+
}
82+
if (thisField == null && thatField == null) {
83+
return true;
84+
}
85+
if (thisField == null && thatField != null) {
86+
return false;
87+
}
88+
if (!thisField.equals(thatField)) {
89+
return false;
90+
}
91+
return true;
92+
}
93+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2020 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+
17+
package org.springframework.data.couchbase.domain;
18+
19+
import java.lang.reflect.Field;
20+
21+
/**
22+
* Submission entity for tests
23+
*
24+
* @author Michael Reiche
25+
*/
26+
public class Submission {
27+
private final String id;
28+
private final String userId;
29+
private final String talkId;
30+
private final String status;
31+
private final long number;
32+
33+
public Submission(String id, String userId, String talkId, String status, long number) {
34+
this.id = id;
35+
this.userId = userId;
36+
this.talkId = talkId;
37+
this.status = status;
38+
this.number = number;
39+
}
40+
41+
public String getId() {
42+
return id;
43+
}
44+
45+
public String toString() {
46+
StringBuffer sb = new StringBuffer("Submission(");
47+
sb.append("id=");
48+
sb.append(id);
49+
sb.append(", userId=");
50+
sb.append(userId);
51+
sb.append(", talkId=");
52+
sb.append(talkId);
53+
sb.append(", status=");
54+
sb.append(status);
55+
sb.append(", number=");
56+
sb.append(number);
57+
sb.append(")");
58+
return sb.toString();
59+
}
60+
61+
@Override
62+
public boolean equals(Object that) throws RuntimeException {
63+
if (this == that)
64+
return true;
65+
if (that == null || getClass() != that.getClass())
66+
return false;
67+
for (Field f : getClass().getFields()) {
68+
if (!same(f, this, that))
69+
return false;
70+
}
71+
for (Field f : getClass().getDeclaredFields()) {
72+
if (!same(f, this, that))
73+
return false;
74+
}
75+
return true;
76+
}
77+
78+
private static boolean same(Field f, Object a, Object b) {
79+
Object thisField = null;
80+
Object thatField = null;
81+
82+
try {
83+
f.get(a);
84+
f.get(b);
85+
} catch (IllegalAccessException e) {
86+
throw new RuntimeException(e);
87+
}
88+
if (thisField == null && thatField == null) {
89+
return true;
90+
}
91+
if (thisField == null && thatField != null) {
92+
return false;
93+
}
94+
if (!thisField.equals(thatField)) {
95+
return false;
96+
}
97+
return true;
98+
}
99+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2012-2020 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+
17+
package org.springframework.data.couchbase.domain;
18+
19+
import lombok.Data;
20+
import org.springframework.data.annotation.TypeAlias;
21+
import org.springframework.data.couchbase.core.index.CompositeQueryIndex;
22+
import org.springframework.data.couchbase.core.mapping.Document;
23+
24+
import java.lang.reflect.Field;
25+
import java.util.List;
26+
27+
/**
28+
* UserSubmission entity for tests
29+
*
30+
* @author Michael Reiche
31+
*/
32+
@Data
33+
@Document
34+
@TypeAlias("user")
35+
@CompositeQueryIndex(fields = { "id", "username", "email" })
36+
public class UserSubmission {
37+
private String id;
38+
private String username;
39+
private String email;
40+
private String password;
41+
private List<String> roles;
42+
private Address address;
43+
private int credits;
44+
private List<Submission> submissions;
45+
private List<Course> courses;
46+
47+
public void setSubmissions(List<Submission> submissions) {
48+
this.submissions = submissions;
49+
}
50+
51+
public void setCourses(List<Course> courses) {
52+
this.courses = courses;
53+
}
54+
55+
@Override
56+
public boolean equals(Object that) throws RuntimeException {
57+
if (this == that)
58+
return true;
59+
if (that == null || getClass() != that.getClass())
60+
return false;
61+
for (Field f : getClass().getFields()) {
62+
if (!same(f, this, that))
63+
return false;
64+
}
65+
for (Field f : getClass().getDeclaredFields()) {
66+
if (!same(f, this, that))
67+
return false;
68+
}
69+
return true;
70+
}
71+
72+
private static boolean same(Field f, Object a, Object b) {
73+
Object thisField = null;
74+
Object thatField = null;
75+
76+
try {
77+
f.get(a);
78+
f.get(b);
79+
} catch (IllegalAccessException e) {
80+
throw new RuntimeException(e);
81+
}
82+
if (thisField == null && thatField == null) {
83+
return true;
84+
}
85+
if (thisField == null && thatField != null) {
86+
return false;
87+
}
88+
if (!thisField.equals(thatField)) {
89+
return false;
90+
}
91+
return true;
92+
}
93+
}

0 commit comments

Comments
 (0)