Skip to content

Commit 182d45a

Browse files
authored
DATACOUCH-625 - Save doesn't return updated entity if immutable. (#274)
Modified applyUpdatedCas() to return accessor.getBean() which will occur if the version property is Immutable. Created applyUpdateId() that is analogous to applyUpdatedCas() Factored out the equals() method that was used by a number of test entity objects. Co-authored-by: mikereiche <[email protected]>
1 parent a85720c commit 182d45a

12 files changed

+241
-134
lines changed

src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,28 @@ public <T> T decodeEntity(String id, String source, long cas, Class<T> entityCla
8989
return accessor.getBean();
9090
}
9191

92-
public void applyUpdatedCas(final Object entity, final long cas) {
92+
public Object applyUpdatedCas(final Object entity, final long cas) {
9393
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
9494
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
9595
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
9696

9797
if (versionProperty != null) {
9898
accessor.setProperty(versionProperty, cas);
99+
return accessor.getBean();
99100
}
101+
return entity;
102+
}
103+
104+
public Object applyUpdatedId(final Object entity, Object id) {
105+
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
106+
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
107+
final CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty();
108+
109+
if (idProperty != null) {
110+
accessor.setProperty(idProperty, id);
111+
return accessor.getBean();
112+
}
113+
return entity;
100114
}
101115

102116
public long getCas(final Object entity) {
@@ -106,9 +120,9 @@ public long getCas(final Object entity) {
106120

107121
long cas = 0;
108122
if (versionProperty != null) {
109-
Object casObject = (Number)accessor.getProperty(versionProperty);
110-
if (casObject instanceof Number){
111-
cas = ((Number)casObject).longValue();
123+
Object casObject = (Number) accessor.getProperty(versionProperty);
124+
if (casObject instanceof Number) {
125+
cas = ((Number) casObject).longValue();
112126
}
113127
}
114128
return cas;

src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ public Mono<T> one(T object) {
7373
CouchbaseDocument converted = template.support().encodeEntity(o);
7474
return template.getCollection(collection).reactive()
7575
.insert(converted.getId(), converted.export(), buildInsertOptions()).map(result -> {
76-
template.support().applyUpdatedCas(object, result.cas());
77-
return o;
76+
Object updatedObject = template.support().applyUpdatedId(o, converted.getId());
77+
return (T) template.support().applyUpdatedCas(updatedObject, result.cas());
7878
});
7979
}).onErrorMap(throwable -> {
8080
if (throwable instanceof RuntimeException) {
@@ -97,7 +97,7 @@ private InsertOptions buildInsertOptions() {
9797
} else if (durabilityLevel != DurabilityLevel.NONE) {
9898
options.durability(durabilityLevel);
9999
}
100-
if (expiry != null && ! expiry.isZero()) {
100+
if (expiry != null && !expiry.isZero()) {
101101
options.expiry(expiry);
102102
} else if (domainType.isAnnotationPresent(Document.class)) {
103103
Document documentAnn = domainType.getAnnotation(Document.class);
@@ -110,26 +110,30 @@ private InsertOptions buildInsertOptions() {
110110
@Override
111111
public TerminatingInsertById<T> inCollection(final String collection) {
112112
Assert.hasText(collection, "Collection must not be null nor empty.");
113-
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, expiry);
113+
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
114+
expiry);
114115
}
115116

116117
@Override
117118
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
118119
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
119-
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, expiry);
120+
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
121+
expiry);
120122
}
121123

122124
@Override
123125
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
124126
Assert.notNull(persistTo, "PersistTo must not be null.");
125127
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
126-
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, expiry);
128+
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
129+
expiry);
127130
}
128131

129132
@Override
130133
public InsertByIdWithDurability<T> withExpiry(final Duration expiry) {
131134
Assert.notNull(expiry, "expiry must not be null.");
132-
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, expiry);
135+
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
136+
expiry);
133137
}
134138
}
135139

src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.couchbase.core;
1717

18+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
19+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
1820
import org.springframework.data.couchbase.core.mapping.Document;
1921
import reactor.core.publisher.Flux;
2022
import reactor.core.publisher.Mono;
@@ -73,8 +75,8 @@ public Mono<T> one(T object) {
7375
CouchbaseDocument converted = template.support().encodeEntity(o);
7476
return template.getCollection(collection).reactive()
7577
.upsert(converted.getId(), converted.export(), buildUpsertOptions()).map(result -> {
76-
template.support().applyUpdatedCas(object, result.cas());
77-
return o;
78+
Object updatedObject = template.support().applyUpdatedId(o, converted.getId());
79+
return (T) template.support().applyUpdatedCas(updatedObject, result.cas());
7880
});
7981
}).onErrorMap(throwable -> {
8082
if (throwable instanceof RuntimeException) {

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

+1
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
541541
generatedValueInfo = idProperty.findAnnotation(GeneratedValue.class);
542542
String generatedId = generateId(generatedValueInfo, prefixes, suffixes, idAttributes);
543543
target.setId(generatedId);
544+
// this is not effective if id is Immutable, and accessor.setProperty() returns a new object in getBean()
544545
accessor.setProperty(idProperty, generatedId);
545546
} else {
546547
target.setId(id);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
* Comparable entity base class for tests
23+
*
24+
* @author Michael Reiche
25+
*/
26+
public class ComparableEntity {
27+
28+
/**
29+
* equals() method that recursively calls equals on on fields
30+
*
31+
* @param that
32+
* @return
33+
* @throws RuntimeException
34+
*/
35+
@Override
36+
public boolean equals(Object that) throws RuntimeException {
37+
if (this == that) {
38+
return true;
39+
}
40+
if (that == null
41+
|| !(this.getClass().isAssignableFrom(that.getClass()) || that.getClass().isAssignableFrom(this.getClass()))) {
42+
return false;
43+
}
44+
// check that all the fields in this have an equal field in that
45+
for (Field f : this.getClass().getFields()) {
46+
if (!same(f, this, that)) {
47+
return false;
48+
}
49+
}
50+
// check that all the fields in that have an equal field in this
51+
for (Field f : that.getClass().getFields()) {
52+
if (!same(f, that, this)) {
53+
return false;
54+
}
55+
}
56+
// check that all the declared fields in this have an equal field in that
57+
for (Field f : this.getClass().getDeclaredFields()) {
58+
if (!same(f, this, that)) {
59+
return false;
60+
}
61+
}
62+
// check that all the declared fields in that have an equal field in this
63+
for (Field f : that.getClass().getDeclaredFields()) {
64+
if (!same(f, that, this)) {
65+
return false;
66+
}
67+
}
68+
return true;
69+
}
70+
71+
private static boolean same(Field f, Object a, Object b) {
72+
Object thisField = null;
73+
Object thatField = null;
74+
75+
try {
76+
thisField = f.get(a);
77+
thatField = f.get(b);
78+
} catch (IllegalAccessException e) {
79+
// assume that the important fields are in toString()
80+
thisField = a.toString();
81+
thatField = b.toString();
82+
}
83+
if (thisField == null && thatField == null) {
84+
return true;
85+
}
86+
if (thisField == null && thatField != null) {
87+
return false;
88+
}
89+
if (!thisField.equals(thatField)) {
90+
return false;
91+
}
92+
return true;
93+
}
94+
}

src/test/java/org/springframework/data/couchbase/domain/Course.java

+1-39
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @author Michael Reiche
2727
*/
28-
public class Course {
28+
public class Course extends ComparableEntity {
2929
@Id private final String id;
3030
private final String userId;
3131
private final String room;
@@ -52,42 +52,4 @@ public String toString() {
5252
return sb.toString();
5353
}
5454

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-
}
9355
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
package org.springframework.data.couchbase.domain;
17+
18+
import lombok.Value;
19+
import lombok.With;
20+
import org.springframework.data.annotation.Id;
21+
import org.springframework.data.annotation.Version;
22+
import org.springframework.data.couchbase.core.mapping.Document;
23+
import org.springframework.data.couchbase.core.mapping.Field;
24+
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
25+
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
26+
27+
/**
28+
* PersonValue entity for tests
29+
*
30+
* @author Michael Reiche
31+
*/
32+
33+
@Value
34+
@Document
35+
public class PersonValue {
36+
@Id @GeneratedValue(strategy = GenerationStrategy.UNIQUE)
37+
@With String id;
38+
// @Version @With
39+
long version;
40+
@Field String firstname;
41+
@Field String lastname;
42+
43+
public PersonValue(String id, long version, String firstname, String lastname) {
44+
this.id = id;
45+
this.version = version;
46+
this.firstname = firstname;
47+
this.lastname = lastname;
48+
}
49+
50+
public String toString() {
51+
StringBuilder sb = new StringBuilder();
52+
sb.append("PersonValue : {");
53+
sb.append(" id : " + getId());
54+
sb.append(", version : " + version);
55+
sb.append(", firstname : " + firstname);
56+
sb.append(", lastname : " + lastname);
57+
sb.append(" }");
58+
return sb.toString();
59+
}
60+
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
package org.springframework.data.couchbase.domain;
17+
18+
import org.springframework.data.repository.CrudRepository;
19+
20+
/**
21+
* PersonValue repository for tests
22+
*
23+
* @author Michael Reiche
24+
*/
25+
public interface PersonValueRepository extends CrudRepository<PersonValue, String> {
26+
27+
}

0 commit comments

Comments
 (0)