DATACOUCH-287 - Add CAS operations to RxJavaCouchbaseOperations.

Original pull request: #135.
This commit is contained in:
Alexander Derkach
2017-03-28 18:38:02 +03:00
committed by Subhashni Balakrishnan
parent 49045b031c
commit 6429dca5e6
6 changed files with 488 additions and 89 deletions

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
@@ -33,6 +31,7 @@ import rx.Observable;
/**
* @author Subhashni Balakrishnan
* @author Alex Derkach
* @since 3.0
*/
public interface RxJavaCouchbaseOperations {
@@ -45,6 +44,22 @@ public interface RxJavaCouchbaseOperations {
<T>Observable<T> save(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> insert(T objectToSave);
<T>Observable<T> insert(Iterable<T> batchToSave);
<T>Observable<T> insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> insert(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> update(T objectToSave);
<T>Observable<T> update(Iterable<T> batchToSave);
<T>Observable<T> update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> update(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> remove(T objectToRemove);
<T>Observable<T> remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo);

View File

@@ -18,7 +18,6 @@ package org.springframework.data.couchbase.core;
import static org.springframework.data.couchbase.core.CouchbaseTemplate.ensureNotIterable;
import java.util.Collection;
import java.util.Optional;
import com.couchbase.client.java.AsyncBucket;
@@ -29,10 +28,13 @@ import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.document.Document;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.error.DocumentAlreadyExistsException;
import com.couchbase.client.java.query.*;
import com.couchbase.client.java.view.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
@@ -44,39 +46,43 @@ import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import rx.Observable;
import rx.functions.Action4;
import rx.functions.Func3;
import rx.functions.Func4;
/**
* RxJavaCouchbaseTemplate implements operations using rxjava1 observables
* @author Subhashni Balakrishnan
* @author Mark Paluch
* @author Alex Derkach
* @since 3.0
*/
public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(RxJavaCouchbaseTemplate.class);
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
protected final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
private Bucket syncClient;
private AsyncBucket client;
private final ClusterInfo clusterInfo;
private final CouchbaseConverter converter;
private final TranslationService translationService;
protected final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
private Consistency configuredConsistency = Consistency.DEFAULT_CONSISTENCY;
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
private WriteResultChecking writeResultChecking = DEFAULT_WRITE_RESULT_CHECKING;
public <T> Observable<T> save(T objectToSave) {
return doPersist(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
return save(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
public <T> Observable<T> save(Iterable<T> batchToSave) {
public <T> Observable<T> save(Iterable<T> batchToSave) {
return Observable.from(batchToSave)
.flatMap(object -> save(object));
.flatMap(this::save);
}
public <T> Observable<T> save(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return doPersist(objectToSave, persistTo, replicateTo);
return doPersist(objectToSave, PersistType.SAVE, persistTo, replicateTo);
}
public <T> Observable<T> save(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
@@ -84,13 +90,57 @@ public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
.flatMap(object -> save(object, persistTo, replicateTo));
}
@Override
public <T> Observable<T> insert(T objectToSave) {
return insert(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public <T> Observable<T> insert(Iterable<T> batchToSave) {
return Observable.from(batchToSave)
.flatMap(this::insert);
}
@Override
public <T> Observable<T> insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return doPersist(objectToSave, PersistType.INSERT, persistTo, replicateTo);
}
@Override
public <T> Observable<T> insert(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return Observable.from(batchToSave)
.flatMap(objectToSave -> insert(objectToSave, persistTo, replicateTo));
}
@Override
public <T> Observable<T> update(T objectToSave) {
return update(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public <T> Observable<T> update(Iterable<T> batchToSave) {
return Observable.from(batchToSave)
.flatMap(this::update);
}
@Override
public <T> Observable<T> update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return doPersist(objectToSave, PersistType.UPDATE, persistTo, replicateTo);
}
@Override
public <T> Observable<T> update(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return Observable.from(batchToSave)
.flatMap(objectToSave -> update(objectToSave, persistTo, replicateTo));
}
public <T> Observable<T> remove(T objectToRemove) {
return doRemove(objectToRemove, PersistTo.NONE, ReplicateTo.NONE);
}
public <T> Observable<T> remove(Iterable<T> batchToRemove) {
return Observable.from(batchToRemove)
.flatMap(object -> remove(object));
.flatMap(this::remove);
}
public <T> Observable<T> remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
@@ -102,7 +152,6 @@ public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
.flatMap(object -> remove(object, persistTo, replicateTo));
}
public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client) {
this(clusterInfo, client, null, null);
}
@@ -117,8 +166,8 @@ public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
}
public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client,
final CouchbaseConverter converter,
final TranslationService translationService) {
final CouchbaseConverter converter,
final TranslationService translationService) {
this.syncClient = client;
this.clusterInfo = clusterInfo;
this.client = client.async();
@@ -157,38 +206,82 @@ public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
return new ConvertingPropertyAccessor(accessor, converter.getConversionService());
}
private <T> Observable<T> doPersist(T objectToPersist, PersistType persistType, PersistTo persistTo, ReplicateTo replicateTo) {
// If version is not set - assumption that document is new, otherwise updating
Optional<Long> version = getVersion(objectToPersist);
Func3<RawJsonDocument, PersistTo, ReplicateTo, Observable<RawJsonDocument>> persistFunction;
switch (persistType) {
case SAVE:
if (!version.isPresent()) {
//No version field - no cas
persistFunction = client::upsert;
} else if (version.get() > 0) {
//Updating existing document with cas
persistFunction = client::replace;
} else {
//Creating new document
persistFunction = client::insert;
}
break;
case UPDATE:
persistFunction = client::replace;
break;
case INSERT:
default:
persistFunction = client::insert;
break;
}
return persistFunction.call(toJsonDocument(objectToPersist), persistTo, replicateTo)
.flatMap(storedDoc -> {
if (storedDoc != null && storedDoc.cas() != 0) {
setVersion(objectToPersist, storedDoc.cas());
}
return Observable.just(objectToPersist);
})
.onErrorResumeNext(e -> {
if (e instanceof DocumentAlreadyExistsException) {
throw new OptimisticLockingFailureException(persistType.springDataOperationName +
" document with version value failed: " + version.orElse(null), e);
}
if (e instanceof CASMismatchException) {
throw new OptimisticLockingFailureException(persistType.springDataOperationName +
" document with version value failed: " + version.orElse(null), e);
}
return TemplateUtils.translateError(e);
});
}
private <T> Observable<T> doPersist(T objectToPersist, final PersistTo persistTo, final ReplicateTo replicateTo) {
ensureNotIterable(objectToPersist);
final ConvertingPropertyAccessor accessor = getPropertyAccessor(objectToPersist);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(objectToPersist.getClass());
Optional<CouchbasePersistentProperty> versionProperty = persistentEntity.getVersionProperty();
final Long version = versionProperty.flatMap(p -> accessor.getProperty(p, Long.class)).orElse(null);
private <T> RawJsonDocument toJsonDocument(T object) {
ensureNotIterable(object);
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(objectToPersist, converted);
RawJsonDocument doc = encodeAndWrap(converted, version);
return client.upsert(doc, persistTo, replicateTo)
.flatMap(rawJsonDocument -> Observable.just(objectToPersist))
.doOnError(e -> TemplateUtils.translateError(e));
converter.write(object, converted);
return encodeAndWrap(converted, getVersion(object).orElse(null));
}
private <T> Optional<CouchbasePersistentProperty> versionProperty(T object) {
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(object.getClass());
return persistentEntity.getVersionProperty();
}
private <T> Optional<Long> getVersion(T object) {
final ConvertingPropertyAccessor accessor = getPropertyAccessor(object);
Optional<CouchbasePersistentProperty> versionProperty = versionProperty(object);
return versionProperty.flatMap(p -> accessor.getProperty(p, Long.class));
}
private <T> void setVersion(T object, long cas) {
final ConvertingPropertyAccessor accessor = getPropertyAccessor(object);
versionProperty(object).ifPresent(p -> accessor.setProperty(p, Optional.ofNullable(cas)));
}
private <T> Observable<T> doRemove(T objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) {
ensureNotIterable(objectToRemove);
if(objectToRemove instanceof String) {
return client.remove((String) objectToRemove, persistTo, replicateTo)
.flatMap(rawJsonDocument -> Observable.just(objectToRemove))
.doOnError(e -> TemplateUtils.translateError(e));
} else {
final ConvertingPropertyAccessor accessor = getPropertyAccessor(objectToRemove);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(objectToRemove.getClass());
final Optional<CouchbasePersistentProperty> versionProperty = persistentEntity.getVersionProperty();
final Long version = versionProperty.flatMap(p -> accessor.getProperty(p, Long.class)).orElse(null);
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(objectToRemove, converted);
RawJsonDocument doc = encodeAndWrap(converted, version);
RawJsonDocument doc = toJsonDocument(objectToRemove);
return client.remove(doc, persistTo, replicateTo)
.flatMap(rawJsonDocument -> Observable.just(objectToRemove))
.doOnError(e -> TemplateUtils.translateError(e));
@@ -363,4 +456,19 @@ public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
return this.clusterInfo;
}
private enum PersistType {
SAVE("Save", "Upsert"),
INSERT("Insert", "Insert"),
UPDATE("Update", "Replace");
private final String sdkOperationName;
private final String springDataOperationName;
PersistType(String sdkOperationName, String springDataOperationName) {
this.sdkOperationName = sdkOperationName;
this.springDataOperationName = springDataOperationName;
}
}
}