Add missing hooks for callbacks. (#1142)

1) the previous change (#955) to CouchbaseAuditingRegister intended to register
 AuditingEntityCallback instead of AuditingEventListener but only changed the
name that AuditingEventListener was registered with. Since
AuditingEventListener does exactly the same thing as AuditingEntityCallback,
it behaved as those AuditingEntityCallback was being used.

2) It seems that the two mechanisms - EntityCallback and EventListener
both do more-or-less the same thing. I'm not sure why there are two
mechanisms to do the same thing.

3) Although the ReactiveAuditingEntityCallback mechanism is triggered
only for reactive operations and the non-reactive AuditingEntityCallback
is triggered only for non-reactive operations, the events are triggered
for both. Since there was a desire to have distinct callbacks for
reactive/non-reactive operations, I also made distinct events for
reactive/non-reactive operations.  However both the reactive and non-reactive
event mechanisms are triggered for either reactive and non-reactive operations,
so it is necessary for the mechanism to determine if it should act on the
event.

4) Although there is an AbstractCouchbaseEventListener that is extended by
LoggingEventListener and ValidatingEventListener, AuditingEventListener does
not extend it (maybe it should).

Closes #1074.

Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Michael Reiche
2021-06-09 21:54:24 -04:00
committed by GitHub
parent 67a7937cd2
commit 155663e66f
28 changed files with 751 additions and 112 deletions

View File

@@ -26,6 +26,7 @@ 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.AfterConvertCallback;
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback;
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
@@ -95,16 +96,21 @@ class CouchbaseTemplateSupport implements ApplicationContextAware, TemplateSuppo
}
@Override
public Object applyUpdatedCas(final Object entity, final long cas) {
public Object applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) {
Object returnValue;
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();
returnValue = accessor.getBean();
} else {
returnValue = entity;
}
return entity;
maybeEmitEvent(new AfterSaveEvent(returnValue, converted));
return returnValue;
}
@Override
@@ -172,7 +178,7 @@ class CouchbaseTemplateSupport implements ApplicationContextAware, TemplateSuppo
this.entityCallbacks = entityCallbacks;
}
void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
public void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
if (canPublishEvent()) {
try {
this.applicationContext.publishEvent(event);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import reactor.core.publisher.Mono;
/**
@@ -44,8 +45,8 @@ public class NonReactiveSupportWrapper implements ReactiveTemplateSupport {
}
@Override
public Mono<Object> applyUpdatedCas(Object entity, long cas) {
return Mono.fromSupplier(() -> support.applyUpdatedCas(entity, cas));
public Mono<Object> applyUpdatedCas(Object entity, CouchbaseDocument converted, long cas) {
return Mono.fromSupplier(() -> support.applyUpdatedCas(entity, converted, cas));
}
@Override
@@ -62,4 +63,9 @@ public class NonReactiveSupportWrapper implements ReactiveTemplateSupport {
public String getJavaNameForEntity(Class<?> clazz) {
return support.getJavaNameForEntity(clazz);
}
@Override
public void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
support.maybeEmitEvent(event);
}
}

View File

@@ -16,6 +16,10 @@
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterSaveEvent;
import reactor.core.publisher.Mono;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -26,11 +30,11 @@ import org.springframework.data.couchbase.core.convert.translation.TranslationSe
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.core.mapping.event.ReactiveBeforeConvertEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeSaveEvent;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks;
@@ -38,7 +42,6 @@ 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}.
@@ -65,16 +68,13 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
@Override
public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
return Mono.just(entityToEncode)
.doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity)))
.flatMap(entity -> maybeCallBeforeConvert(entity, ""))
.map(maybeNewEntity -> {
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new ReactiveBeforeConvertEvent<>(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)));
}).flatMap(converted -> maybeCallAfterConvert(entityToEncode, converted, "").thenReturn(converted))
.doOnNext(converted -> maybeEmitEvent(new ReactiveBeforeSaveEvent<>(entityToEncode, converted)));
}
@Override
@@ -98,17 +98,22 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
}
@Override
public Mono<Object> applyUpdatedCas(final Object entity, final long cas) {
public Mono<Object> applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) {
return Mono.fromSupplier(() -> {
Object returnValue;
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
accessor.setProperty(versionProperty, cas);
return accessor.getBean();
returnValue = accessor.getBean();
} else {
returnValue = entity;
}
return entity;
maybeEmitEvent(new ReactiveAfterSaveEvent(returnValue, converted));
return returnValue;
});
}
@@ -116,7 +121,8 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
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 CouchbasePersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty != null) {
@@ -130,8 +136,7 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
@Override
public Long getCas(final Object entity) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
long cas = 0;
@@ -166,9 +171,9 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
}
/**
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link
* org.springframework.data.mapping.callback.ReactiveEntityCallbacks callbacks} like the {@link
* ReactiveBeforeConvertCallback}.
* 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}.
*
@@ -180,7 +185,7 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
this.reactiveEntityCallbacks = reactiveEntityCallbacks;
}
void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
public void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
if (canPublishEvent()) {
try {
this.applicationContext.publishEvent(event);

View File

@@ -78,16 +78,16 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
@Override
public Mono<T> one(T object) {
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : InsertOptions.insertOptions());
LOG.trace("statement: {} scope: {} collection: {} options: {}", "insertById", pArgs.getScope(),
pArgs.getCollection(), pArgs.getOptions());
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : InsertOptions.insertOptions());
LOG.trace("statement: {} scope: {} collection: {} options: {}", "insertById", pArgs.getScope(),
pArgs.getCollection(), pArgs.getOptions());
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(insertedObject -> (Mono<T>) support.applyUpdatedCas(insertedObject, result.cas()))))
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas()))))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterDeleteEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeDeleteEvent;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -68,19 +70,22 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
@Override
public Mono<RemoveResult> one(final String id) {
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : RemoveOptions.removeOptions());
return Mono.just(id)
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions()))
.map(r -> RemoveResult.from(docId, r)))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : RemoveOptions.removeOptions());
return Mono.just(id).map(r -> {
template.support().maybeEmitEvent(new ReactiveBeforeDeleteEvent<>(r));
return r;
}).flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions())).map(r -> {
template.support().maybeEmitEvent(new ReactiveAfterDeleteEvent<>(r));
return RemoveResult.from(docId, r);
})).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override

View File

@@ -78,14 +78,14 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
@Override
public Mono<T> one(T object) {
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ReplaceOptions.replaceOptions());
LOG.trace("statement: {} pArgs: {}", "replaceById", pArgs);
return Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> template.getCouchbaseClientFactory()
.withScope(pArgs.getScope()).getCollection(pArgs.getCollection()).reactive()
.replace(converted.getId(), converted.export(), buildReplaceOptions(pArgs.getOptions(), object, converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(replacedObject -> (Mono<T>) support.applyUpdatedCas(replacedObject, result.cas()))))
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ReplaceOptions.replaceOptions());
LOG.trace("statement: {} pArgs: {}", "replaceById", pArgs);
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.replace(converted.getId(), converted.export(), buildReplaceOptions(pArgs.getOptions(), object, converted))
.flatMap(result -> support.applyUpdatedCas(object, converted, result.cas())))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import reactor.core.publisher.Mono;
public interface ReactiveTemplateSupport {
@@ -25,11 +26,13 @@ public interface ReactiveTemplateSupport {
<T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass);
Mono<Object> applyUpdatedCas(Object entity, long cas);
<T> Mono<T> applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
Mono<Object> applyUpdatedId(Object entity, Object id);
<T> Mono<T> applyUpdatedId(T entity, Object id);
Long getCas(Object entity);
String getJavaNameForEntity(Class<?> clazz);
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
}

View File

@@ -75,14 +75,14 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
@Override
public Mono<T> one(T object) {
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : UpsertOptions.upsertOptions());
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : UpsertOptions.upsertOptions());
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(updatedObject -> (Mono<T>) support.applyUpdatedCas(updatedObject, result.cas()))))
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas()))))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);

View File

@@ -16,6 +16,7 @@
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
public interface TemplateSupport {
@@ -23,11 +24,13 @@ public interface TemplateSupport {
<T> T decodeEntity(String id, String source, long cas, Class<T> entityClass);
Object applyUpdatedCas(Object entity, long cas);
<T> T applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
Object applyUpdatedId(Object entity, Object id);
<T> T applyUpdatedId(T entity, Object id);
long getCas(Object entity);
String getJavaNameForEntity(Class<?> clazz);
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-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,29 +15,27 @@
*/
package org.springframework.data.couchbase.core.mapping.event;
import org.springframework.data.couchbase.core.mapping.Document;
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 Document} when reading results.
* Callback being invoked after a domain object is materialized from a {@link CouchbaseDocument} when reading results.
*
* @author Roman Puchkovskiy
* @author Mark Paluch
* @author Michael Reiche
* @see org.springframework.data.mapping.callback.EntityCallbacks
* @since 3.0
* @since 4.2
*/
@FunctionalInterface
public interface AfterConvertCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked after a domain object is materialized from a {@link Document}. Can return either the
* same or a modified instance of the domain object.
* Entity callback method invoked after a domain object is materialized from a {@link CouchbaseDocument}. Can return
* either the same or a modified instance of the domain object.
*
* @param entity the domain object (the result of the conversion).
* @param document must not be {@literal null}.
* @param collection name of the collection.
* @return the domain object that is the result of reading it from the {@link Document}.
* @return the domain object that is the result of reading it from the {@link CouchbaseDocument}.
*/
T onAfterConvert(T entity, Document document, String collection);
T onAfterConvert(T entity, CouchbaseDocument document, String collection);
}

View File

@@ -15,24 +15,27 @@
*/
package org.springframework.data.couchbase.core.mapping.event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.couchbase.core.mapping.CouchbaseDocument;
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 {
public class AuditingEntityCallback implements BeforeConvertCallback<Object>, AfterConvertCallback<Object>, Ordered {
private final ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory;
private static final Logger LOG = LoggerFactory.getLogger(AuditingEntityCallback.class);
/**
* Creates a new {@link AuditingEntityCallback} using the given {@link MappingContext} and {@link AuditingHandler}
@@ -41,7 +44,6 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Or
* @param auditingHandlerFactory must not be {@literal null}.
*/
public AuditingEntityCallback(ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory) {
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
this.auditingHandlerFactory = auditingHandlerFactory;
}
@@ -52,7 +54,19 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Or
*/
@Override
public Object onBeforeConvert(Object entity, String collection) {
return auditingHandlerFactory.getObject().markAudited(entity);
//LOG.debug("onBeforeConvert " + entity);
return entity; // markAudited called in AuditingEventListener.onApplicationEvent()
// auditingHandlerFactory.getObject().markAudited(entity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.couchbase.core.mapping.event.AfterConvertCallback#onAfterConvert(java.lang.Object, CouchbaseDocument, java.lang.String)
*/
@Override
public Object onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
//LOG.debug("onAfterConvert " + document);
return entity;
}
/*
@@ -64,5 +78,4 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Or
return 100;
}
}

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.
@@ -18,6 +18,8 @@ package org.springframework.data.couchbase.core.mapping.event;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.data.auditing.AuditingHandler;
@@ -33,7 +35,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Michael Reiche
*/
public class AuditingEventListener implements ApplicationListener<BeforeConvertEvent<Object>> {
public class AuditingEventListener implements ApplicationListener<CouchbaseMappingEvent<Object>> {
private final ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory;
@@ -41,9 +43,11 @@ public class AuditingEventListener implements ApplicationListener<BeforeConvertE
this.auditingHandlerFactory = null;
}
private static final Logger LOG = LoggerFactory.getLogger(AuditingEventListener.class);
/**
* Creates a new {@link AuditingEventListener} using the given {@link MappingContext} and {@link AuditingHandler}
* provided by the given {@link ObjectFactory}.
* provided by the given {@link ObjectFactory}. Registered in CouchbaseAuditingRegistrar
*
* @param auditingHandlerFactory must not be {@literal null}.
*/
@@ -57,8 +61,29 @@ public class AuditingEventListener implements ApplicationListener<BeforeConvertE
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(BeforeConvertEvent<Object> event) {
Optional.ofNullable(event.getSource())//
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
public void onApplicationEvent(CouchbaseMappingEvent<Object> event) {
if (event instanceof BeforeConvertEvent) {
Optional.ofNullable(event.getSource())//
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof BeforeSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof AfterSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof BeforeDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof AfterDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (!event.getClass().getSimpleName().startsWith("Reactive")) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.getClass().getSimpleName() + " " + event.getSource());
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2020 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;
/**
* @author Michael Reiche
*/
public class ReactiveAfterDeleteEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveAfterDeleteEvent(E source) {
super(source, null);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012-2020 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.data.couchbase.core.mapping.CouchbaseDocument;
/**
* @author Michael Reiche
*/
public class ReactiveAfterSaveEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveAfterSaveEvent(E source, CouchbaseDocument document) {
super(source, document);
}
}

View File

@@ -15,27 +15,33 @@
*/
package org.springframework.data.couchbase.core.mapping.event;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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>
* 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
* @authoer Michael Reiche
* @since 4.2
*/
public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCallback<Object>, Ordered {
public class ReactiveAuditingEntityCallback
implements ReactiveBeforeConvertCallback<Object>, ReactiveAfterConvertCallback<Object>, Ordered {
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveAuditingEntityCallback.class);
/**
* Creates a new {@link ReactiveAuditingEntityCallback} using the given {@link MappingContext} and
@@ -44,7 +50,7 @@ public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCall
* @param auditingHandlerFactory must not be {@literal null}.
*/
public ReactiveAuditingEntityCallback(final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
Assert.notNull(auditingHandlerFactory, "ReactiveIsNewAwareAuditingHandler must not be null!");
this.auditingHandlerFactory = auditingHandlerFactory;
}
@@ -57,9 +63,22 @@ public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCall
*/
@Override
public Publisher<Object> onBeforeConvert(final Object entity, final String collection) {
LOG.debug("onBeforeConvert " + entity.toString());
return this.auditingHandlerFactory.getObject().markAudited(entity);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.couchbase.core.mapping.event.ReactiveAfterConvertCallback#onAfterConvert
* (java.lang.Object, CouchbaseDocument, java.lang.String)
*/
@Override
public Publisher<Object> onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
LOG.debug("onAfterConvert " + document.toString());
return Mono.just(entity);
}
/*
* (non-Javadoc)
*

View File

@@ -0,0 +1,87 @@
/*
* 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 java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
/**
* Reactive Event listener to populate auditing related fields on an entity about to be saved.
*
* @author Michael Reiche
*/
public class ReactiveAuditingEventListener implements ApplicationListener<CouchbaseMappingEvent<Object>> {
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
public ReactiveAuditingEventListener() {
this.auditingHandlerFactory = null;
}
private static final Logger LOG = LoggerFactory.getLogger(ReactiveAuditingEventListener.class);
/**
* Creates a new {@link ReactiveAuditingEventListener} using the given {@link MappingContext} and
* {@link AuditingHandler} provided by the given {@link ObjectFactory}. Registered in CouchbaseAuditingRegistrar
*
* @param auditingHandlerFactory must not be {@literal null}.
*/
public ReactiveAuditingEventListener(ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
this.auditingHandlerFactory = auditingHandlerFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(CouchbaseMappingEvent<Object> event) {
if (event instanceof ReactiveBeforeConvertEvent) {
Optional.ofNullable(event.getSource())//
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveBeforeSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveAfterSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveBeforeDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveAfterDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event.getClass().getSimpleName().startsWith("Reactive")) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.getClass().getSimpleName() + " " + event.getSource());
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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;
/**
* @author Michael Reiche
*/
public class ReactiveBeforeConvertEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveBeforeConvertEvent(E source) {
super(source, null);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2020 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;
/**
* @author Michael Reiche
*/
public class ReactiveBeforeDeleteEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveBeforeDeleteEvent(E source) {
super(source, null);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.data.couchbase.core.mapping.CouchbaseDocument;
/**
* @author Michael Reiche
*/
public class ReactiveBeforeSaveEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveBeforeSaveEvent(E source, CouchbaseDocument document) {
super(source, document);
}
}

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.
@@ -80,13 +80,26 @@ public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarS
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
// Register the AuditEntityCallback
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(AuditingEventListener.class);
.rootBeanDefinition(AuditingEntityCallback.class);
listenerBeanDefinitionBuilder
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
AuditingEntityCallback.class.getName(), registry);
// Register the AuditingEventListener
BeanDefinitionBuilder listenerBeanDefinitionBuilder2 = BeanDefinitionBuilder
.rootBeanDefinition(AuditingEventListener.class);
listenerBeanDefinitionBuilder2
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder2.getBeanDefinition(),
AuditingEventListener.class.getName(), registry);
}
private void ensureMappingContext(BeanDefinitionRegistry registry, Object source) {

View File

@@ -1,8 +1,9 @@
package org.springframework.data.couchbase.repository.auditing;
import static org.springframework.data.couchbase.config.BeanNames.*;
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_AUDITING_HANDLER;
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;
@@ -12,14 +13,16 @@ import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarS
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.data.couchbase.core.mapping.event.ReactiveAuditingEventListener;
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.
* 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
* @author Michael Reiche
* @since 4.2
*/
class ReactiveCouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
@@ -69,13 +72,27 @@ class ReactiveCouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrar
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
// Register the AuditingEntityCallback
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
registerInfrastructureBeanWithId(builder.getBeanDefinition(),
ReactiveAuditingEntityCallback.class.getName(), registry);
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
registry);
// Register the AuditingEventListener
BeanDefinitionBuilder builder2 = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEventListener.class);
builder2
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
registerInfrastructureBeanWithId(builder2.getBeanDefinition(), ReactiveAuditingEventListener.class.getName(),
registry);
}
}

View File

@@ -89,7 +89,6 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
assertEquals(user, found);
couchbaseTemplate.removeById().one(user.getId());
reactiveCouchbaseTemplate.replaceById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.THREE).one(user);
}
@Test
@@ -280,9 +279,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
User user = new User(id, "firstname", "lastname");
User inserted = couchbaseTemplate.insertById(User.class).one(user);
assertEquals(user, inserted);
assertTrue(couchbaseTemplate.existsById().one(id));
}
@Test

View File

@@ -0,0 +1,299 @@
/*
* Copyright 2012-2020 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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.couchbase.core.ExecutableRemoveByIdOperation.ExecutableRemoveById;
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
import org.springframework.data.couchbase.domain.PersonValue;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserAnnotated;
import org.springframework.data.couchbase.domain.UserAnnotated2;
import org.springframework.data.couchbase.domain.UserAnnotated3;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
/**
* KV tests Theses tests rely on a cb server running.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
@BeforeEach
@Override
public void beforeEach() {
super.beforeEach();
reactiveCouchbaseTemplate.removeByQuery(User.class).all();
reactiveCouchbaseTemplate.removeByQuery(UserAnnotated.class).all();
reactiveCouchbaseTemplate.removeByQuery(UserAnnotated2.class).all();
}
@Test
void upsertAndFindById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
modified = reactiveCouchbaseTemplate.replaceById(User.class).one(user).block();
assertEquals(user, modified);
user.setVersion(12345678);
assertThrows(DataIntegrityViolationException.class,
() -> reactiveCouchbaseTemplate.replaceById(User.class).one(user).block());
User found = reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block();
user.setVersion(found.getVersion());
assertEquals(user, found);
reactiveCouchbaseTemplate.removeById().one(user.getId()).block();
}
@Test
void withDurability()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = User.class; // for now, just User.class. There is no Durability annotation.
// insert, replace, upsert
for (OneAndAllEntityReactive<User> operator : new OneAndAllEntityReactive[] {
reactiveCouchbaseTemplate.insertById(clazz), reactiveCouchbaseTemplate.replaceById(clazz),
reactiveCouchbaseTemplate.upsertById(clazz) }) {
// create an entity of type clazz
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
operator = (OneAndAllEntityReactive<User>) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
ReplicateTo.NONE);
}
// if replace, we need to insert a document to replace
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById) {
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
}
// call to insert/replace/update
User returned = operator.one(user).block();
assertEquals(user, returned);
User found = reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block();
assertEquals(user, found);
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById) {
reactiveCouchbaseTemplate.removeById().withDurability(PersistTo.ACTIVE, ReplicateTo.NONE).one(user.getId())
.block();
User removed = (User) reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
assertNull(removed, "found should have been null as document should be removed");
}
}
}
@Test
void withExpiryAndExpiryAnnotation()
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// ( withExpiry()<User>, expiry=1<UserAnnotated>, expiryExpression=${myExpiry}<UserAnnotated2> ) X ( insert,
// replace, upsert )
Set<User> users = new HashSet<>(); // set of all documents we will insert
// Entity classes
for (Class<?> clazz : new Class[] { User.class, UserAnnotated.class, UserAnnotated2.class, UserAnnotated3.class }) {
// insert, replace, upsert
for (OneAndAllEntityReactive<User> operator : new OneAndAllEntityReactive[] {
reactiveCouchbaseTemplate.insertById(clazz), reactiveCouchbaseTemplate.replaceById(clazz),
reactiveCouchbaseTemplate.upsertById(clazz) }) {
// create an entity of type clazz
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
"firstname", "lastname");
if (clazz.equals(User.class)) { // User.java doesn't have an expiry annotation
operator = (OneAndAllEntityReactive<User>) ((WithExpiry<User>) operator).withExpiry(Duration.ofSeconds(1));
} else if (clazz.equals(UserAnnotated3.class)) { // override the expiry from the annotation with no expiry
operator = (OneAndAllEntityReactive<User>) ((WithExpiry<User>) operator).withExpiry(Duration.ofSeconds(0));
}
// if replace or remove, we need to insert a document to replace
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById || operator instanceof ExecutableRemoveById) {
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
}
// call to insert/replace/update
User returned = operator.one(user).block();
assertEquals(user, returned);
users.add(user);
}
}
// check that they are gone after a few seconds.
sleepSecs(4);
for (User user : users) {
User found = reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
if (found instanceof UserAnnotated3) {
assertNotNull(found, "found should be non null as it was set to have no expirty");
} else {
assertNull(found, "found should have been null as document should be expired");
}
}
}
@Test
void findDocWhichDoesNotExist() {
assertNull(reactiveCouchbaseTemplate.findById(User.class).one(UUID.randomUUID().toString()).block());
}
@Test
void upsertAndReplaceById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
User toReplace = new User(modified.getId(), "some other", "lastname");
reactiveCouchbaseTemplate.replaceById(User.class).one(toReplace).block();
User loaded = reactiveCouchbaseTemplate.findById(User.class).one(toReplace.getId()).block();
assertEquals("some other", loaded.getFirstname());
reactiveCouchbaseTemplate.removeById().one(toReplace.getId()).block();
}
@Test
void upsertAndRemoveById() {
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
RemoveResult removeResult = reactiveCouchbaseTemplate.removeById().one(user.getId()).block();
assertEquals(user.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
assertNull(reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block());
}
{
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User modified = reactiveCouchbaseTemplate.upsertById(User.class).one(user).block();
assertEquals(user, modified);
// careful now - user and modified are the same object. The object has the new cas (@Version version)
Long savedCas = modified.getVersion();
modified.setVersion(123);
assertThrows(DataIntegrityViolationException.class, () -> reactiveCouchbaseTemplate.removeById()
.withCas(reactiveCouchbaseTemplate.support().getCas(modified)).one(modified.getId()).block());
modified.setVersion(savedCas);
reactiveCouchbaseTemplate.removeById().withCas(reactiveCouchbaseTemplate.support().getCas(modified))
.one(modified.getId()).block();
}
}
@Test
void insertById() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
}
@Test
void insertByIdwithDurability() {
User user = new User(UUID.randomUUID().toString(), "firstname", "lastname");
User inserted = reactiveCouchbaseTemplate.insertById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.NONE)
.one(user).block();
assertEquals(user, inserted);
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
}
@Test
void existsById() {
String id = UUID.randomUUID().toString();
assertFalse(reactiveCouchbaseTemplate.existsById().one(id).block());
User user = new User(id, "firstname", "lastname");
User inserted = reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
assertEquals(user, inserted);
assertTrue(reactiveCouchbaseTemplate.existsById().one(id).block());
}
@Test
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
void saveAndFindImmutableById() {
PersonValue personValue = new PersonValue(UUID.randomUUID().toString(), 123, "f", "l");
PersonValue inserted;
PersonValue upserted;
PersonValue replaced;
inserted = reactiveCouchbaseTemplate.insertById(PersonValue.class).one(personValue).block();
assertNotEquals(0, inserted.getVersion());
PersonValue foundInserted = reactiveCouchbaseTemplate.findById(PersonValue.class).one(inserted.getId()).block();
assertNotNull(foundInserted, "inserted personValue not found");
assertEquals(inserted, foundInserted);
// upsert will insert
reactiveCouchbaseTemplate.removeById().one(inserted.getId());
upserted = reactiveCouchbaseTemplate.upsertById(PersonValue.class).one(inserted).block();
assertNotEquals(0, upserted.getVersion());
PersonValue foundUpserted = reactiveCouchbaseTemplate.findById(PersonValue.class).one(upserted.getId()).block();
assertNotNull(foundUpserted, "upserted personValue not found");
assertEquals(upserted, foundUpserted);
// upsert will replace
upserted = reactiveCouchbaseTemplate.upsertById(PersonValue.class).one(inserted).block();
assertNotEquals(0, upserted.getVersion());
PersonValue foundUpserted2 = reactiveCouchbaseTemplate.findById(PersonValue.class).one(upserted.getId()).block();
assertNotNull(foundUpserted2, "upserted personValue not found");
assertEquals(upserted, foundUpserted2);
replaced = reactiveCouchbaseTemplate.replaceById(PersonValue.class).one(upserted).block();
assertNotEquals(0, replaced.getVersion());
PersonValue foundReplaced = reactiveCouchbaseTemplate.findById(PersonValue.class).one(replaced.getId()).block();
assertNotNull(foundReplaced, "replaced personValue not found");
assertEquals(replaced, foundReplaced);
}
private void sleepSecs(int i) {
try {
Thread.sleep(i * 1000);
} catch (InterruptedException ie) {}
}
}

View File

@@ -52,11 +52,8 @@ import com.couchbase.client.java.json.JacksonTransformers;
*/
@Configuration
@EnableCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef") // this
// activates
// auditing
@EnableReactiveCouchbaseAuditing(auditorAwareRef = "reactiveAuditorAwareRef",
dateTimeProviderRef = "dateTimeProviderRef") // this activates auditing
@EnableCouchbaseAuditing(auditorAwareRef="auditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableReactiveCouchbaseAuditing(auditorAwareRef="reactiveAuditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
public class Config extends AbstractCouchbaseConfiguration {
String bucketname = "travel-sample";

View File

@@ -32,7 +32,8 @@ import org.springframework.data.domain.AuditorAware;
*/
public class NaiveAuditorAware implements AuditorAware<String> {
private Optional<String> auditor = Optional.of("auditor");
static public final String AUDITOR = "auditor";
private Optional<String> auditor = Optional.of(AUDITOR);
@Override
public Optional<String> getCurrentAuditor() {

View File

@@ -27,9 +27,10 @@ import reactor.core.publisher.Mono;
*/
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
public static final String AUDITOR = "reactive_auditor";
@Override
public Mono<String> getCurrentAuditor() {
return Mono.just("auditor");
return Mono.just(AUDITOR);
}
}

View File

@@ -421,7 +421,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
Airport saved = airportRepository.save(vie);
List<Airport> airports1 = airportRepository.findAllByIata("vie");
assertEquals(saved, airports1.get(0));
assertEquals(saved.getCreatedBy(), "auditor"); // NaiveAuditorAware will provide this
assertEquals(saved.getCreatedBy(), NaiveAuditorAware.AUDITOR); // NaiveAuditorAware will provide this
} finally {
airportRepository.delete(vie);
}

View File

@@ -73,7 +73,7 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
Airport saved = airportRepository.save(vie).block();
Airport airport1 = airportRepository.findById(saved.getId()).block();
assertEquals(airport1, saved);
assertEquals(saved.getCreatedBy(), "auditor"); // NaiveAuditorAware will provide this
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide this
} finally {
airportRepository.delete(vie).block();
}