Skip to content

Commit a00e997

Browse files
committed
DATACOUCH-625 - Save doesn't return updated entity if immutable.
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.
1 parent a85720c commit a00e997

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
@@ -71,10 +71,10 @@ static class ReactiveInsertByIdSupport<T> implements ReactiveInsertById<T> {
7171
public Mono<T> one(T object) {
7272
return Mono.just(object).flatMap(o -> {
7373
CouchbaseDocument converted = template.support().encodeEntity(o);
74+
Object updatedObject = template.support().applyUpdatedId(o, converted.getId());
7475
return template.getCollection(collection).reactive()
7576
.insert(converted.getId(), converted.export(), buildInsertOptions()).map(result -> {
76-
template.support().applyUpdatedCas(object, result.cas());
77-
return o;
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;
@@ -71,10 +73,10 @@ static class ReactiveUpsertByIdSupport<T> implements ReactiveUpsertById<T> {
7173
public Mono<T> one(T object) {
7274
return Mono.just(object).flatMap(o -> {
7375
CouchbaseDocument converted = template.support().encodeEntity(o);
76+
Object updatedObject = template.support().applyUpdatedId(o, converted.getId());
7477
return template.getCollection(collection).reactive()
7578
.upsert(converted.getId(), converted.export(), buildUpsertOptions()).map(result -> {
76-
template.support().applyUpdatedCas(object, result.cas());
77-
return o;
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)