Add support for reactive auditing and ReactiveEntityCallbacks.

Add support for reactive auditing and ReactiveEntityCallbacks.
Also, adapt CouchbaseAuditingRegistrar for support AuditingEntityCallback.

Closes #955.
Original pull request: #1102.

Co-authored-by: Carlos Espinado <carlosemart>
Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Jorge Rodríguez Martín
2021-04-06 17:28:28 +02:00
committed by mikereiche
parent 5a04711a6d
commit 3f1cea174f
34 changed files with 1041 additions and 106 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ package org.springframework.data.couchbase.config;
* @author Michael Nitschinger
* @author Simon Baslé
* @author Michael Reiche
* @author Jorge Rodríguez Martín
*/
public class BeanNames {
@@ -53,4 +54,9 @@ public class BeanNames {
* The name for the bean that will handle audit trail marking of entities.
*/
public static final String COUCHBASE_AUDITING_HANDLER = "couchbaseAuditingHandler";
/**
* The name for the bean that will handle reactive audit trail marking of entities.
*/
public static final String REACTIVE_COUCHBASE_AUDITING_HANDLER = "reactiveCouchbaseAuditingHandler";
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -180,4 +180,8 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
}
}
}
TemplateSupport support() {
return templateSupport;
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2012-2020 the original author or authors
/*
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,9 +46,10 @@ import org.slf4j.LoggerFactory;
* @author Michael Nitschinger
* @author Michael Reiche
* @author Jorge Rodriguez Martin
* @author Carlos Espinaco
* @since 3.0
*/
class CouchbaseTemplateSupport implements ApplicationContextAware {
class CouchbaseTemplateSupport implements ApplicationContextAware, TemplateSupport {
private static final Logger LOG = LoggerFactory.getLogger(CouchbaseTemplateSupport.class);
@@ -63,6 +65,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware {
this.translationService = translationService;
}
@Override
public CouchbaseDocument encodeEntity(final Object entityToEncode) {
maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode));
Object maybeNewEntity = maybeCallBeforeConvert(entityToEncode, "");
@@ -73,6 +76,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware {
return converted;
}
@Override
public <T> T decodeEntity(String id, String source, long cas, Class<T> entityClass) {
final CouchbaseDocument converted = new CouchbaseDocument(id);
converted.setId(id);
@@ -90,6 +94,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware {
return accessor.getBean();
}
@Override
public Object applyUpdatedCas(final Object entity, final long cas) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
@@ -102,6 +107,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware {
return entity;
}
@Override
public Object applyUpdatedId(final Object entity, Object id) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
@@ -114,6 +120,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware {
return entity;
}
@Override
public long getCas(final Object entity) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
@@ -129,6 +136,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware {
return cas;
}
@Override
public String getJavaNameForEntity(final Class<?> clazz) {
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,7 +53,7 @@ public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFind
this.domainType = domainType;
this.query = query;
this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, query,
scanConsistency);
scanConsistency, new NonReactiveSupportWrapper(template.support()));
this.scanConsistency = scanConsistency;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOpe
this.domainType = domainType;
this.collection = collection;
this.fields = fields;
this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, collection, fields);
this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, collection, fields, new NonReactiveSupportWrapper(template.support()));
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
this.returnType = returnType;
this.query = query;
this.reactiveSupport = new ReactiveFindByQuerySupport<T>(template.reactive(), domainType, returnType, query,
scanConsistency, collection, distinctFields);
scanConsistency, collection, distinctFields, new NonReactiveSupportWrapper(template.support()));
this.scanConsistency = scanConsistency;
this.collection = collection;
this.distinctFields = distinctFields;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
this.collection = collection;
this.returnType = returnType;
this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, returnType,
collection);
collection, new NonReactiveSupportWrapper(template.support()));
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, collection, persistTo,
replicateTo, durabilityLevel, expiry);
replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel, expiry);
domainType, collection, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel, expiry);
domainType, collection, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
}
@Override

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import reactor.core.publisher.Mono;
/**
* Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}.
*
* @author Carlos Espinaco
* @since 4.2
*/
public class NonReactiveSupportWrapper implements ReactiveTemplateSupport {
private final TemplateSupport support;
public NonReactiveSupportWrapper(TemplateSupport support) {
this.support = support;
}
@Override
public Mono<CouchbaseDocument> encodeEntity(Object entityToEncode) {
return Mono.fromSupplier(() -> support.encodeEntity(entityToEncode));
}
@Override
public <T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass) {
return Mono.fromSupplier(() -> support.decodeEntity(id, source, cas, entityClass));
}
@Override
public Mono<Object> applyUpdatedCas(Object entity, long cas) {
return Mono.fromSupplier(() -> support.applyUpdatedCas(entity, cas));
}
@Override
public Mono<Object> applyUpdatedId(Object entity, Object id) {
return Mono.fromSupplier(() -> support.applyUpdatedId(entity, id));
}
@Override
public Long getCas(Object entity) {
return support.getCas(entity);
}
@Override
public String getJavaNameForEntity(Class<?> clazz) {
return support.getJavaNameForEntity(clazz);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,13 +34,14 @@ import com.couchbase.client.java.Collection;
* @author Michael Nitschinger
* @author Michael Reiche
* @author Jorge Rodriguez Martin
* @author Carlos Espinaco
*/
public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, ApplicationContextAware {
private final CouchbaseClientFactory clientFactory;
private final CouchbaseConverter converter;
private final PersistenceExceptionTranslator exceptionTranslator;
private final CouchbaseTemplateSupport templateSupport;
private final ReactiveCouchbaseTemplateSupport templateSupport;
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this(clientFactory, converter, new JacksonTranslationService());
@@ -51,7 +52,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
this.clientFactory = clientFactory;
this.converter = converter;
this.exceptionTranslator = clientFactory.getExceptionTranslator();
this.templateSupport = new CouchbaseTemplateSupport(converter, translationService);
this.templateSupport = new ReactiveCouchbaseTemplateSupport(converter, translationService);
}
@Override
@@ -134,7 +135,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
return converter;
}
CouchbaseTemplateSupport support() {
ReactiveTemplateSupport support() {
return templateSupport;
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterConvertCallback;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
/**
* Internal encode/decode support for {@link ReactiveCouchbaseTemplate}.
*
* @author Carlos Espinaco
* @since 4.2
*/
class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, ReactiveTemplateSupport {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveCouchbaseTemplateSupport.class);
private final CouchbaseConverter converter;
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
private final TranslationService translationService;
private ReactiveEntityCallbacks reactiveEntityCallbacks;
private ApplicationContext applicationContext;
public ReactiveCouchbaseTemplateSupport(final CouchbaseConverter converter,
final TranslationService translationService) {
this.converter = converter;
this.mappingContext = converter.getMappingContext();
this.translationService = translationService;
}
@Override
public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
return Mono.just(entityToEncode)
.doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity)))
.flatMap(entity -> maybeCallBeforeConvert(entity, ""))
.map(maybeNewEntity -> {
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(maybeNewEntity, converted);
return converted;
})
.flatMap(converted -> maybeCallAfterConvert(entityToEncode, converted, "").thenReturn(converted))
.doOnNext(converted -> maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted)));
}
@Override
public <T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass) {
return Mono.fromSupplier(() -> {
final CouchbaseDocument converted = new CouchbaseDocument(id);
converted.setId(id);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass);
if (cas != 0 && persistentEntity.getVersionProperty() != null) {
converted.put(persistentEntity.getVersionProperty().getName(), cas);
}
T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted));
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(readEntity);
if (persistentEntity.getVersionProperty() != null) {
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
}
return accessor.getBean();
});
}
@Override
public Mono<Object> applyUpdatedCas(final Object entity, final long cas) {
return Mono.fromSupplier(() -> {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
accessor.setProperty(versionProperty, cas);
return accessor.getBean();
}
return entity;
});
}
@Override
public Mono<Object> applyUpdatedId(final Object entity, Object id) {
return Mono.fromSupplier(() -> {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty != null) {
accessor.setProperty(idProperty, id);
return accessor.getBean();
}
return entity;
});
}
@Override
public Long getCas(final Object entity) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
long cas = 0;
if (versionProperty != null) {
Object casObject = (Number) accessor.getProperty(versionProperty);
if (casObject instanceof Number) {
cas = ((Number) casObject).longValue();
}
}
return cas;
}
@Override
public String getJavaNameForEntity(final Class<?> clazz) {
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
return info.getJavaType().getName();
}
private <T> ConvertingPropertyAccessor<T> getPropertyAccessor(final T source) {
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(source.getClass());
PersistentPropertyAccessor<T> accessor = entity.getPropertyAccessor(source);
return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
if (reactiveEntityCallbacks == null) {
setReactiveEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
}
}
/**
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link
* org.springframework.data.mapping.callback.ReactiveEntityCallbacks callbacks} like the {@link
* ReactiveBeforeConvertCallback}.
* <p/>
* Overrides potentially existing {@link EntityCallbacks}.
*
* @param reactiveEntityCallbacks must not be {@literal null}.
* @throws IllegalArgumentException if the given instance is {@literal null}.
*/
public void setReactiveEntityCallbacks(ReactiveEntityCallbacks reactiveEntityCallbacks) {
Assert.notNull(reactiveEntityCallbacks, "EntityCallbacks must not be null!");
this.reactiveEntityCallbacks = reactiveEntityCallbacks;
}
void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
if (canPublishEvent()) {
try {
this.applicationContext.publishEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
} else {
LOG.info("maybeEmitEvent called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
}
}
private boolean canPublishEvent() {
return this.applicationContext != null;
}
protected <T> Mono<T> maybeCallBeforeConvert(T object, String collection) {
if (reactiveEntityCallbacks != null) {
try {
return reactiveEntityCallbacks.callback(ReactiveBeforeConvertCallback.class, object, collection);
} catch (Exception e) {
e.printStackTrace();
}
} else {
LOG.info("maybeCallBeforeConvert called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
}
return Mono.just(object);
}
protected <T> Mono<T> maybeCallAfterConvert(T object, CouchbaseDocument document, String collection) {
if (null != reactiveEntityCallbacks) {
return reactiveEntityCallbacks.callback(ReactiveAfterConvertCallback.class, object, document, collection);
} else {
LOG.info("maybeCallAfterConvert called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
}
return Mono.just(object);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.couchbase.core;
import com.couchbase.client.java.query.ReactiveQueryResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -38,7 +39,8 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
@Override
public <T> ReactiveFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY, AnalyticsScanConsistency.NOT_BOUNDED);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY, AnalyticsScanConsistency.NOT_BOUNDED,
template.support());
}
static class ReactiveFindByAnalyticsSupport<T> implements ReactiveFindByAnalytics<T> {
@@ -47,29 +49,31 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
private final Class<T> domainType;
private final AnalyticsQuery query;
private final AnalyticsScanConsistency scanConsistency;
private final ReactiveTemplateSupport support;
ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency) {
final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.scanConsistency = scanConsistency;
this.support = support;
}
@Override
public TerminatingFindByAnalytics<T> matching(AnalyticsQuery query) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support);
}
@Override
@Deprecated
public FindByAnalyticsWithQuery<T> consistentWith(AnalyticsScanConsistency scanConsistency) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support);
}
@Override
public FindByAnalyticsWithQuery<T> withConsistency(AnalyticsScanConsistency scanConsistency) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency);
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support);
}
@Override
@@ -93,19 +97,19 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
} else {
return throwable;
}
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> {
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).flatMap(row -> {
String id = "";
long cas = 0;
if (row.getString(TemplateUtils.SELECT_ID) == null) {
throw new CouchbaseException("analytics query did not project " + TemplateUtils.SELECT_ID
return Flux.error(new CouchbaseException("analytics query did not project " + TemplateUtils.SELECT_ID
+ ". Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_ID + " and "
+ TemplateUtils.SELECT_CAS + " : " + statement);
+ TemplateUtils.SELECT_CAS + " : " + statement));
}
id = row.getString(TemplateUtils.SELECT_ID);
if (row.getLong(TemplateUtils.SELECT_CAS) == null) {
throw new CouchbaseException("analytics query did not project " + TemplateUtils.SELECT_CAS
return Flux.error(new CouchbaseException("analytics query did not project " + TemplateUtils.SELECT_CAS
+ ". Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_ID + " and "
+ TemplateUtils.SELECT_CAS + " : " + statement);
+ TemplateUtils.SELECT_CAS + " : " + statement));
}
cas = row.getLong(TemplateUtils.SELECT_CAS);
row.removeKey(TemplateUtils.SELECT_ID);
@@ -145,7 +149,7 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*");
}
final String dataset = template.support().getJavaNameForEntity(domainType);
final String dataset = support.getJavaNameForEntity(domainType);
statement.append(" FROM ").append(dataset);
query.appendSort(statement);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
@Override
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
return new ReactiveFindByIdSupport<>(template, domainType, null, null);
return new ReactiveFindByIdSupport<>(template, domainType, null, null, template.support());
}
static class ReactiveFindByIdSupport<T> implements ReactiveFindById<T> {
@@ -49,13 +49,15 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
private final Class<T> domainType;
private final String collection;
private final List<String> fields;
private final ReactiveTemplateSupport support;
ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String collection,
List<String> fields) {
List<String> fields, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.fields = fields;
this.support = support;
}
@Override
@@ -66,7 +68,7 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
options.project(fields);
}
return template.getCollection(collection).reactive().get(docId, options);
}).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorResume(throwable -> {
if (throwable instanceof RuntimeException) {
if (throwable instanceof DocumentNotFoundException) {
@@ -91,13 +93,13 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, fields);
return new ReactiveFindByIdSupport<>(template, domainType, collection, fields, support);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields));
return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields), support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public <T> ReactiveFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY,
QueryScanConsistency.NOT_BOUNDED, null, null);
QueryScanConsistency.NOT_BOUNDED, null, null, template.support());
}
static class ReactiveFindByQuerySupport<T> implements ReactiveFindByQuery<T> {
@@ -57,10 +57,12 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
private final QueryScanConsistency scanConsistency;
private final String collection;
private final String[] distinctFields;
private final ReactiveTemplateSupport support;
ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType,
final Class<T> returnType, final Query query, final QueryScanConsistency scanConsistency,
final String collection, final String[] distinctFields) {
final String collection, final String[] distinctFields, final ReactiveTemplateSupport support) {
this.support = support;
Assert.notNull(domainType, "domainType must not be null!");
Assert.notNull(returnType, "returnType must not be null!");
@@ -82,41 +84,41 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
scanCons = scanConsistency;
}
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, collection,
distinctFields);
distinctFields, support);
}
@Override
public FindByQueryInCollection<T> inCollection(String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
distinctFields, support);
}
@Override
@Deprecated
public FindByQueryConsistentWith<T> consistentWith(QueryScanConsistency scanConsistency) {
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
distinctFields, support);
}
@Override
public FindByQueryWithConsistency<T> withConsistency(QueryScanConsistency scanConsistency) {
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
distinctFields, support);
}
@Override
public <R> FindByQueryWithConsistency<R> as(Class<R> returnType) {
Assert.notNull(returnType, "returnType must not be null!");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
distinctFields, support);
}
@Override
public FindByQueryWithDistinct<T> distinct(String[] distinctFields) {
Assert.notNull(distinctFields, "distinctFields must not be null!");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection,
distinctFields);
distinctFields, support);
}
@Override
@@ -144,26 +146,26 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> {
}).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> {
String id = "";
long cas = 0;
if (distinctFields == null) {
if (row.getString(TemplateUtils.SELECT_ID) == null) {
throw new CouchbaseException(
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_ID + ". Either use #{#n1ql.selectEntity} or project "
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement);
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
}
id = row.getString(TemplateUtils.SELECT_ID);
if (row.getLong(TemplateUtils.SELECT_CAS) == null) {
throw new CouchbaseException(
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_CAS + ". Either use #{#n1ql.selectEntity} or project "
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement);
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
}
cas = row.getLong(TemplateUtils.SELECT_CAS);
row.removeKey(TemplateUtils.SELECT_ID);
row.removeKey(TemplateUtils.SELECT_CAS);
}
return template.support().decodeEntity(id, row.toString(), cas, returnType);
return support.decodeEntity(id, row.toString(), cas, returnType);
});
});
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
@Override
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null);
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null, template.support());
}
static class ReactiveFindFromReplicasByIdSupport<T> implements ReactiveFindFromReplicasById<T> {
@@ -46,13 +46,15 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
private final Class<?> domainType;
private final Class<T> returnType;
private final String collection;
private final ReactiveTemplateSupport support;
ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class<?> domainType, Class<T> returnType,
String collection) {
String collection, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.returnType = returnType;
this.collection = collection;
this.support = support;
}
@Override
@@ -60,7 +62,7 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
return Mono.just(id).flatMap(docId -> {
GetAnyReplicaOptions options = getAnyReplicaOptions().transcoder(RawJsonTranscoder.INSTANCE);
return template.getCollection(collection).reactive().getAnyReplica(docId, options);
}).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), returnType))
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -78,7 +80,7 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, collection);
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, collection, support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,7 +42,7 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
public <T> ReactiveInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
DurabilityLevel.NONE, null, template.support());
}
static class ReactiveInsertByIdSupport<T> implements ReactiveInsertById<T> {
@@ -54,10 +54,11 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveTemplateSupport support;
ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, Duration expiry) {
final DurabilityLevel durabilityLevel, Duration expiry, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
@@ -65,18 +66,19 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.support = support;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.insert(converted.getId(), converted.export(), buildInsertOptions(converted)).map(result -> {
Object updatedObject = template.support().applyUpdatedId(o, converted.getId());
return (T) template.support().applyUpdatedCas(updatedObject, result.cas());
});
}).onErrorMap(throwable -> {
return (Mono<T>) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted ->
template.getCollection(collection).reactive()
.insert(converted.getId(), converted.export(), buildInsertOptions(converted))
.flatMap(result ->
support.applyUpdatedId(object, converted.getId())
.flatMap(
updatedObject -> support.applyUpdatedCas(updatedObject, result.cas())))
).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
@@ -109,14 +111,14 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
public TerminatingInsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
@@ -124,14 +126,14 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
public InsertByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
public <T> ReactiveReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
DurabilityLevel.NONE, null, template.support());
}
static class ReactiveReplaceByIdSupport<T> implements ReactiveReplaceById<T> {
@@ -53,10 +53,11 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveTemplateSupport support;
ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry) {
final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
@@ -64,15 +65,15 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.support = support;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return (Mono<T>) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> {
return template.getCollection(collection).reactive()
.replace(converted.getId(), converted.export(), buildReplaceOptions(o, converted))
.map(result -> (T) template.support().applyUpdatedCas(o, result.cas()));
.replace(converted.getId(), converted.export(), buildReplaceOptions(object, converted))
.flatMap(result -> support.applyUpdatedCas(object, result.cas()));
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -99,7 +100,7 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
long cas = template.support().getCas(object);
long cas = support.getCas(object);
options.cas(cas);
return options;
}
@@ -108,14 +109,14 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
public TerminatingReplaceById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
@@ -123,14 +124,14 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
public ReplaceByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import reactor.core.publisher.Mono;
public interface ReactiveTemplateSupport {
Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
<T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass);
Mono<Object> applyUpdatedCas(Object entity, long cas);
Mono<Object> applyUpdatedId(Object entity, Object id);
Long getCas(Object entity);
String getJavaNameForEntity(Class<?> clazz);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
public <T> ReactiveUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
DurabilityLevel.NONE, null, template.support());
}
static class ReactiveUpsertByIdSupport<T> implements ReactiveUpsertById<T> {
@@ -53,10 +53,11 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveTemplateSupport support;
ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, final Duration expiry) {
final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
@@ -64,18 +65,17 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.expiry = expiry;
this.support = support;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(converted)).map(result -> {
Object updatedObject = template.support().applyUpdatedId(o, converted.getId());
return (T) template.support().applyUpdatedCas(updatedObject, result.cas());
});
}).onErrorMap(throwable -> {
return (Mono<T>) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted ->
template.getCollection(collection).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(converted)).flatMap(result ->
support.applyUpdatedId(object, converted.getId())
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, result.cas())))
).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
@@ -108,14 +108,14 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
public TerminatingUpsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
@@ -123,14 +123,14 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
@Override
public UpsertByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
expiry, support);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
public interface TemplateSupport {
CouchbaseDocument encodeEntity(Object entityToEncode);
<T> T decodeEntity(String id, String source, long cas, Class<T> entityClass);
Object applyUpdatedCas(Object entity, long cas);
Object applyUpdatedId(Object entity, Object id);
long getCas(Object entity);
String getJavaNameForEntity(Class<?> clazz);
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.mapping.event;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.Ordered;
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
/**
* {@link EntityCallback} to populate auditing related fields on an entity about to be saved.
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Ordered {
private final ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory;
/**
* Creates a new {@link AuditingEntityCallback} using the given {@link MappingContext} and {@link AuditingHandler}
* provided by the given {@link ObjectFactory}.
*
* @param auditingHandlerFactory must not be {@literal null}.
*/
public AuditingEntityCallback(ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory) {
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
this.auditingHandlerFactory = auditingHandlerFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback#onBeforeConvert(java.lang.Object, java.lang.String)
*/
@Override
public Object onBeforeConvert(Object entity, String collection) {
return auditingHandlerFactory.getObject().markAudited(entity);
}
/*
* (non-Javadoc)
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
return 100;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.mapping.event;
import org.reactivestreams.Publisher;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.mapping.callback.EntityCallback;
/**
* Callback being invoked after a domain object is materialized from a {@link org.springframework.data.couchbase.core.mapping.CouchbaseDocument} when reading results.
*
* @author Jorge Rodríguez Martín
* @see org.springframework.data.mapping.callback.EntityCallbacks
* @since 4.2
*/
@FunctionalInterface
public interface ReactiveAfterConvertCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked after a domain object is converted to be persisted. Can return
* either the same of a modified instance of the domain object.
* @param entity the domain object to save.
* @param collection name of the collection.
* @return a {@link Publisher} emitting the domain object to be persisted.
*/
Publisher<T> onAfterConvert(T entity, CouchbaseDocument document, String collection);
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.mapping.event;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.Ordered;
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.mapping.callback.EntityCallback;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
/**
* Reactive {@link EntityCallback} to populate auditing related fields on an entity about to be
* saved. Based on <a href="https://github.com/spring-projects/spring-data-mongodb/blob/3.1.6/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/event/ReactiveAfterConvertCallback.java">ReactiveAfterConvertCallback</a>
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCallback<Object>, Ordered {
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
/**
* Creates a new {@link ReactiveAuditingEntityCallback} using the given {@link MappingContext} and
* {@link AuditingHandler} provided by the given {@link ObjectFactory}.
*
* @param auditingHandlerFactory must not be {@literal null}.
*/
public ReactiveAuditingEntityCallback(final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
this.auditingHandlerFactory = auditingHandlerFactory;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback#onBeforeConvert
* (java.lang.Object, java.lang.String)
*/
@Override
public Publisher<Object> onBeforeConvert(final Object entity, final String collection) {
return this.auditingHandlerFactory.getObject().markAudited(entity);
}
/*
* (non-Javadoc)
*
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
return 100;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core.mapping.event;
import org.reactivestreams.Publisher;
import org.springframework.data.mapping.callback.EntityCallback;
/**
* Callback being invoked before a domain object is converted to be persisted.
*
* @author Jorge Rodríguez Martín
* @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
* @since 4.2
*/
@FunctionalInterface
public interface ReactiveBeforeConvertCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before a domain object is converted to be persisted. Can return
* either the same of a modified instance of the domain object.
*
* @param entity the domain object to save.
* @param collection name of the collection.
* @return a {@link Publisher} emitting the domain object to be persisted.
*/
Publisher<T> onBeforeConvert(T entity, String collection);
}

View File

@@ -30,6 +30,7 @@ import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.event.AuditingEntityCallback;
import org.springframework.data.couchbase.core.mapping.event.AuditingEventListener;
import org.springframework.util.Assert;
@@ -41,6 +42,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Simon Baslé
* @author Michael Reiche
* @author Jorge Rodríguez Martín
*/
public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
@@ -84,7 +86,7 @@ public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarS
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
AuditingEventListener.class.getName(), registry);
AuditingEntityCallback.class.getName(), registry);
}
private void ensureMappingContext(BeanDefinitionRegistry registry, Object source) {

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.auditing;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.ReactiveAuditorAware;
/**
* Annotation to enable auditing in Couchbase using reactive infrastructure via annotation
* configuration.
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ReactiveCouchbaseAuditingRegistrar.class)
public @interface EnableReactiveCouchbaseAuditing {
/**
* Configures the {@link ReactiveAuditorAware} bean to be used to lookup the current principal.
*
* @return empty {@link String} by default.
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return {@literal true} by default.
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal
* true}.
*
* @return {@literal true} by default.
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the timestamp to be
* used for setting creation and modification dates.
*
* @return empty {@link String} by default.
*/
String dateTimeProviderRef() default "";
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.auditing;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.mapping.context.PersistentEntities;
/**
* Simple helper to be able to wire the {@link PersistentEntities} from a {@link
* MappingCouchbaseConverter} bean available in the application context.
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
private final MappingCouchbaseConverter converter;
/**
* Creates a new {@link PersistentEntitiesFactoryBean} for the given {@link
* MappingCouchbaseConverter}.
*
* @param converter must not be {@literal null}.
*/
public PersistentEntitiesFactoryBean(final MappingCouchbaseConverter converter) {
this.converter = converter;
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@Override
public PersistentEntities getObject() {
return PersistentEntities.of(this.converter.getMappingContext());
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@Override
public Class<?> getObjectType() {
return PersistentEntities.class;
}
}

View File

@@ -0,0 +1,81 @@
package org.springframework.data.couchbase.repository.auditing;
import static org.springframework.data.couchbase.config.BeanNames.*;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAuditingEntityCallback;
import org.springframework.util.Assert;
/**
* A support registrar that allows to set up reactive auditing for Couchbase (including {@link org.springframework.data.auditing.ReactiveAuditingHandler} and {
* IsNewStrategyFactory} set up). See {@link EnableReactiveCouchbaseAuditing} for the associated annotation.
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
class ReactiveCouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableReactiveCouchbaseAuditing.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
*/
@Override
protected String getAuditingHandlerBeanName() {
return REACTIVE_COUCHBASE_AUDITING_HANDLER;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
*/
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
registerInfrastructureBeanWithId(builder.getBeanDefinition(),
ReactiveAuditingEntityCallback.class.getName(), registry);
}
}

View File

@@ -16,8 +16,10 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Version;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.couchbase.core.mapping.Document;
@@ -36,6 +38,11 @@ public class Airport extends ComparableEntity {
String icao;
@CreatedBy private String createdBy;
@Version long version;
@PersistenceConstructor
public Airport(String id, String iata, String icao) {
this.id = id;
@@ -55,4 +62,7 @@ public class Airport extends ComparableEntity {
return icao;
}
public String getCreatedBy() {
return createdBy;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2012-2021 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
import org.springframework.data.domain.ReactiveAuditorAware;
import reactor.core.publisher.Mono;
/**
* This class returns a string that represents the current user
*
* @author Jorge Rodríguez Martín
* @since 4.2
*/
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
@Override
public Mono<String> getCurrentAuditor() {
return Mono.just("auditor");
}
}

View File

@@ -38,8 +38,10 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
@@ -50,10 +52,13 @@ import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.Iata;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
import org.springframework.data.couchbase.repository.query.CouchbaseRepositoryQuery;
@@ -378,6 +383,21 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
assertEquals(user, users.get(0));
}
@Test
void findBySimplePropertyAudited() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "low2");
Airport saved = airportRepository.save(vie);
List<Airport> airports1 = airportRepository.findAllByIata("vie");
assertEquals(saved, airports1.get(0));
assertEquals(saved.getCreatedBy(), "auditor"); // NaiveAuditorAware will provide this
} finally {
airportRepository.delete(vie);
}
}
private void sleep(int millis) {
try {
Thread.sleep(millis); // so they are executed out-of-order
@@ -386,6 +406,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
static class Config extends AbstractCouchbaseConfiguration {
@Override
@@ -408,6 +429,15 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
return bucketName();
}
@Bean(name = "auditorAwareRef")
public NaiveAuditorAware testAuditorAware() {
return new NaiveAuditorAware();
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
}
}

View File

@@ -18,15 +18,22 @@ package org.springframework.data.couchbase.repository;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
import org.springframework.data.couchbase.domain.ReactiveNaiveAuditorAware;
import org.springframework.data.couchbase.domain.ReactiveUserRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
@@ -39,23 +46,40 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
@Autowired ReactiveUserRepository userRepository;
@Autowired ReactiveAirportRepository airportRepository;
@Test
void saveAndFindById() {
User user = new User(UUID.randomUUID().toString(), "f", "l");
assertFalse(userRepository.existsById(user.getId()).block());
userRepository.save(user).block();
final User save = userRepository.save(user).block();
Optional<User> found = userRepository.findById(user.getId()).blockOptional();
assertTrue(found.isPresent());
found.ifPresent(u -> assertEquals(user, u));
found.ifPresent(u -> assertEquals(save, u));
assertTrue(userRepository.existsById(user.getId()).block());
}
@Test
void findBySimplePropertyAudited() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "low2");
Airport saved = airportRepository.save(vie).block();
List<Airport> airports1 = airportRepository.findAllByIata("vie").collectList().block();
assertEquals(saved, airports1.get(0));
assertEquals(saved.getCreatedBy(), "auditor"); // NaiveAuditorAware will provide this
} finally {
airportRepository.delete(vie).block();
}
}
@Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
@EnableReactiveCouchbaseAuditing
static class Config extends AbstractCouchbaseConfiguration {
@Override
@@ -78,6 +102,16 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
return bucketName();
}
@Bean(name = "auditorAwareRef")
public ReactiveNaiveAuditorAware testAuditorAware() {
return new ReactiveNaiveAuditorAware();
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
}
}