From 155663e66f2607699e87f1c8ae89f3be1b9ba0d5 Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Wed, 9 Jun 2021 21:54:24 -0400 Subject: [PATCH] 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 --- .../core/CouchbaseTemplateSupport.java | 14 +- .../core/NonReactiveSupportWrapper.java | 10 +- .../ReactiveCouchbaseTemplateSupport.java | 47 +-- .../ReactiveInsertByIdOperationSupport.java | 16 +- .../ReactiveRemoveByIdOperationSupport.java | 31 +- .../ReactiveReplaceByIdOperationSupport.java | 16 +- .../core/ReactiveTemplateSupport.java | 7 +- .../ReactiveUpsertByIdOperationSupport.java | 12 +- .../data/couchbase/core/TemplateSupport.java | 7 +- .../mapping/event/AfterConvertCallback.java | 18 +- .../mapping/event/AuditingEntityCallback.java | 23 +- .../mapping/event/AuditingEventListener.java | 37 ++- .../event/ReactiveAfterDeleteEvent.java | 28 ++ .../mapping/event/ReactiveAfterSaveEvent.java | 30 ++ .../event/ReactiveAuditingEntityCallback.java | 29 +- .../event/ReactiveAuditingEventListener.java | 87 +++++ .../event/ReactiveBeforeConvertEvent.java | 28 ++ .../event/ReactiveBeforeDeleteEvent.java | 28 ++ .../event/ReactiveBeforeSaveEvent.java | 30 ++ .../auditing/CouchbaseAuditingRegistrar.java | 17 +- .../ReactiveCouchbaseAuditingRegistrar.java | 29 +- ...hbaseTemplateKeyValueIntegrationTests.java | 3 - ...hbaseTemplateKeyValueIntegrationTests.java | 299 ++++++++++++++++++ .../data/couchbase/domain/Config.java | 7 +- .../couchbase/domain/NaiveAuditorAware.java | 3 +- .../domain/ReactiveNaiveAuditorAware.java | 3 +- ...chbaseRepositoryQueryIntegrationTests.java | 2 +- ...aseRepositoryKeyValueIntegrationTests.java | 2 +- 28 files changed, 751 insertions(+), 112 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterDeleteEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEventListener.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeDeleteEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveEvent.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateKeyValueIntegrationTests.java diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java index 71137777..140f4d9c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -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 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); diff --git a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java index adf0d570..6f890a7e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java @@ -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 applyUpdatedCas(Object entity, long cas) { - return Mono.fromSupplier(() -> support.applyUpdatedCas(entity, cas)); + public Mono 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); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java index d83b09ef..d1239370 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -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 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 applyUpdatedCas(final Object entity, final long cas) { + public Mono applyUpdatedCas(final Object entity, CouchbaseDocument converted, final long cas) { return Mono.fromSupplier(() -> { + Object returnValue; final ConvertingPropertyAccessor 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 applyUpdatedId(final Object entity, Object id) { return Mono.fromSupplier(() -> { final ConvertingPropertyAccessor 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 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}. *

* 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); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 225a593d..53a04794 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -78,16 +78,16 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe @Override public Mono one(T object) { - PseudoArgs 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 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) support.applyUpdatedCas(insertedObject, result.cas())))) + .flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas())))) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java index 8c7dcc51..a337c39e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -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 one(final String id) { - PseudoArgs 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 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 diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java index ccd04367..bca6ecd0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -78,14 +78,14 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO @Override public Mono one(T object) { - PseudoArgs 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) support.applyUpdatedCas(replacedObject, result.cas())))) + PseudoArgs 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); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java index d5585d1a..38704595 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java @@ -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 { Mono decodeEntity(String id, String source, long cas, Class entityClass); - Mono applyUpdatedCas(Object entity, long cas); + Mono applyUpdatedCas(T entity, CouchbaseDocument converted, long cas); - Mono applyUpdatedId(Object entity, Object id); + Mono applyUpdatedId(T entity, Object id); Long getCas(Object entity); String getJavaNameForEntity(Class clazz); + + void maybeEmitEvent(CouchbaseMappingEvent event); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java index 4ae12686..2c923b5d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -75,14 +75,14 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe @Override public Mono one(T object) { - PseudoArgs pArgs = new PseudoArgs<>(template, scope, collection, - options != null ? options : UpsertOptions.upsertOptions()); + PseudoArgs 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) support.applyUpdatedCas(updatedObject, result.cas())))) + .flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas())))) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); diff --git a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java index f9029a08..372543c3 100644 --- a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java @@ -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 decodeEntity(String id, String source, long cas, Class entityClass); - Object applyUpdatedCas(Object entity, long cas); + T applyUpdatedCas(T entity, CouchbaseDocument converted, long cas); - Object applyUpdatedId(Object entity, Object id); + T applyUpdatedId(T entity, Object id); long getCas(Object entity); String getJavaNameForEntity(Class clazz); + + void maybeEmitEvent(CouchbaseMappingEvent event); } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java index 8c8976ae..ec84d93f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertCallback.java @@ -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 extends EntityCallback { /** - * 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); } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java index 08c0489f..acde3514 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java @@ -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, Ordered { +public class AuditingEntityCallback implements BeforeConvertCallback, AfterConvertCallback, Ordered { private final ObjectFactory 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, Or * @param auditingHandlerFactory must not be {@literal null}. */ public AuditingEntityCallback(ObjectFactory auditingHandlerFactory) { - Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); this.auditingHandlerFactory = auditingHandlerFactory; } @@ -52,7 +54,19 @@ public class AuditingEntityCallback implements BeforeConvertCallback, 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, Or return 100; } - } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java index d5ecffa7..0ee5aaab 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java @@ -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> { +public class AuditingEventListener implements ApplicationListener> { private final ObjectFactory auditingHandlerFactory; @@ -41,9 +43,11 @@ public class AuditingEventListener implements ApplicationListener event) { - Optional.ofNullable(event.getSource())// - .ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it)); + public void onApplicationEvent(CouchbaseMappingEvent 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()); + } + } } + } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterDeleteEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterDeleteEvent.java new file mode 100644 index 00000000..52c79894 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterDeleteEvent.java @@ -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 extends CouchbaseMappingEvent { + + public ReactiveAfterDeleteEvent(E source) { + super(source, null); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveEvent.java new file mode 100644 index 00000000..3c0a97a3 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveEvent.java @@ -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 extends CouchbaseMappingEvent { + + public ReactiveAfterSaveEvent(E source, CouchbaseDocument document) { + super(source, document); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEntityCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEntityCallback.java index 1939a5d0..5f2348da 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEntityCallback.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEntityCallback.java @@ -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 ReactiveAfterConvertCallback + * Reactive {@link EntityCallback} to populate auditing related fields on an entity about to be saved. Based on ReactiveAfterConvertCallback * * @author Jorge Rodríguez Martín + * @authoer Michael Reiche * @since 4.2 */ -public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCallback, Ordered { +public class ReactiveAuditingEntityCallback + implements ReactiveBeforeConvertCallback, ReactiveAfterConvertCallback, Ordered { private final ObjectFactory 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 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 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 onAfterConvert(Object entity, CouchbaseDocument document, String collection) { + LOG.debug("onAfterConvert " + document.toString()); + return Mono.just(entity); + } /* * (non-Javadoc) * diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEventListener.java new file mode 100644 index 00000000..7d516dd5 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEventListener.java @@ -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> { + + private final ObjectFactory 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 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 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()); + } + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertEvent.java new file mode 100644 index 00000000..8f3ff6c3 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertEvent.java @@ -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 extends CouchbaseMappingEvent { + + public ReactiveBeforeConvertEvent(E source) { + super(source, null); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeDeleteEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeDeleteEvent.java new file mode 100644 index 00000000..161c2a45 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeDeleteEvent.java @@ -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 extends CouchbaseMappingEvent { + + public ReactiveBeforeDeleteEvent(E source) { + super(source, null); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveEvent.java new file mode 100644 index 00000000..b96e32b6 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveEvent.java @@ -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 extends CouchbaseMappingEvent { + + public ReactiveBeforeSaveEvent(E source, CouchbaseDocument document) { + super(source, document); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java index 902150d3..7120205c 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java +++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java @@ -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) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/ReactiveCouchbaseAuditingRegistrar.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/ReactiveCouchbaseAuditingRegistrar.java index 5f71197d..a38cbcbf 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/auditing/ReactiveCouchbaseAuditingRegistrar.java +++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/ReactiveCouchbaseAuditingRegistrar.java @@ -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); + } } diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java index f2d3fb81..c0980105 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -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 diff --git a/src/test/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateKeyValueIntegrationTests.java new file mode 100644 index 00000000..d4a47971 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateKeyValueIntegrationTests.java @@ -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 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) ((WithDurability) 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(), expiry=1, expiryExpression=${myExpiry} ) X ( insert, + // replace, upsert ) + Set 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 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) ((WithExpiry) operator).withExpiry(Duration.ofSeconds(1)); + } else if (clazz.equals(UserAnnotated3.class)) { // override the expiry from the annotation with no expiry + operator = (OneAndAllEntityReactive) ((WithExpiry) 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) {} + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/Config.java b/src/test/java/org/springframework/data/couchbase/domain/Config.java index 686628c7..4e2bc5c4 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Config.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Config.java @@ -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"; diff --git a/src/test/java/org/springframework/data/couchbase/domain/NaiveAuditorAware.java b/src/test/java/org/springframework/data/couchbase/domain/NaiveAuditorAware.java index b1c54f27..797a2dab 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/NaiveAuditorAware.java +++ b/src/test/java/org/springframework/data/couchbase/domain/NaiveAuditorAware.java @@ -32,7 +32,8 @@ import org.springframework.data.domain.AuditorAware; */ public class NaiveAuditorAware implements AuditorAware { - private Optional auditor = Optional.of("auditor"); + static public final String AUDITOR = "auditor"; + private Optional auditor = Optional.of(AUDITOR); @Override public Optional getCurrentAuditor() { diff --git a/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java b/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java index 447ac127..0f3b0814 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java +++ b/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java @@ -27,9 +27,10 @@ import reactor.core.publisher.Mono; */ public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware { + public static final String AUDITOR = "reactive_auditor"; @Override public Mono getCurrentAuditor() { - return Mono.just("auditor"); + return Mono.just(AUDITOR); } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java index 4a0918bf..49ddbcee 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -421,7 +421,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr Airport saved = airportRepository.save(vie); List 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); } diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryKeyValueIntegrationTests.java index 6888dd58..77e6fe2a 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryKeyValueIntegrationTests.java @@ -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(); }