From 5a4aec34df41df63ee20a8c3eba244348853490b Mon Sep 17 00:00:00 2001 From: Mico Piira Date: Sun, 22 Oct 2023 21:51:43 +0300 Subject: [PATCH 1/2] Callbacks refactoring Adds support for modifying entities after find operations through AfterConvertCallbacks and also unified the callbacks API with other Spring data projects such as R2DBC & MongoDB BREAKING: Before this change AfterConvertCallback was called before save operations. This is no longer the case as AfterConvertCallback is now only called after find operations. Applications currently using AfterConvertCallbacks should use BeforeSaveCallback instead. See: https://docs.spring.io/spring-data/r2dbc/docs/1.4.4/api/org/springframework/data/r2dbc/mapping/event/package-summary.html https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/mapping/event/package-summary.html --- .../core/AbstractTemplateSupport.java | 19 ++- .../core/CouchbaseTemplateSupport.java | 38 ++++-- .../data/couchbase/core/EncodedEntity.java | 24 ++++ .../core/NonReactiveSupportWrapper.java | 3 +- .../ReactiveCouchbaseTemplateSupport.java | 56 +++++++-- .../ReactiveInsertByIdOperationSupport.java | 10 +- .../ReactiveMutateInByIdOperationSupport.java | 10 +- .../ReactiveReplaceByIdOperationSupport.java | 15 ++- .../core/ReactiveTemplateSupport.java | 6 +- .../ReactiveUpsertByIdOperationSupport.java | 7 +- .../data/couchbase/core/TemplateSupport.java | 3 +- .../mapping/event/AfterConvertCallback.java | 13 +- .../core/mapping/event/AfterConvertEvent.java | 27 ++++ .../core/mapping/event/AfterSaveCallback.java | 37 ++++++ .../mapping/event/BeforeConvertCallback.java | 5 +- .../mapping/event/BeforeSaveCallback.java | 43 +++++++ .../event/ReactiveAfterConvertCallback.java | 14 ++- .../event/ReactiveAfterSaveCallback.java | 38 ++++++ .../event/ReactiveBeforeConvertCallback.java | 8 +- .../event/ReactiveBeforeSaveCallback.java | 44 +++++++ ...hbaseTemplateCallbackIntegrationTests.java | 119 ++++++++++++++++++ 21 files changed, 464 insertions(+), 75 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java create mode 100644 src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java diff --git a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java index 7152b7e91..fa31b9e04 100644 --- a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java @@ -15,11 +15,8 @@ */ package org.springframework.data.couchbase.core; -import java.lang.reflect.InaccessibleObjectException; -import java.util.Map; -import java.util.Set; - import com.couchbase.client.core.annotation.Stability; +import com.couchbase.client.core.error.CouchbaseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; @@ -29,7 +26,6 @@ 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.AfterSaveEvent; import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent; import org.springframework.data.couchbase.core.support.TemplateUtils; import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation; @@ -39,13 +35,16 @@ import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.util.ClassUtils; -import com.couchbase.client.core.error.CouchbaseException; +import java.lang.reflect.InaccessibleObjectException; +import java.util.Map; +import java.util.Set; /** * Base shared by Reactive and non-Reactive TemplateSupport * * @author Michael Reiche + * @author Mico Piira */ @Stability.Internal public abstract class AbstractTemplateSupport { @@ -68,7 +67,7 @@ public AbstractTemplateSupport(ReactiveCouchbaseTemplate template, CouchbaseConv abstract ReactiveCouchbaseTemplate getReactiveTemplate(); public T decodeEntityBase(Object id, String source, Long cas, Class entityClass, String scope, - String collection, Object txResultHolder, CouchbaseResourceHolder holder) { + String collection, Object txResultHolder, CouchbaseResourceHolder holder, CouchbaseDocument converted) { // this is the entity class defined for the repository. It may not be the class of the document that was read // we will reset it after reading the document @@ -88,7 +87,6 @@ public T decodeEntityBase(Object id, String source, Long cas, Class entit // to unwrap. This results in List being unwrapped past String[] to String, so this may also be a // Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told. // if this is a Collection or array, only the first element will be returned. - final CouchbaseDocument converted = new CouchbaseDocument(id); Set> set = ((CouchbaseDocument) translationService.decode(source, converted)) .getContent().entrySet(); return (T) set.iterator().next().getValue(); @@ -99,8 +97,6 @@ public T decodeEntityBase(Object id, String source, Long cas, Class entit + TemplateUtils.SELECT_ID); } - final CouchbaseDocument converted = new CouchbaseDocument(id); - // if possible, set the version property in the source so that if the constructor has a long version argument, // it will have a value and not fail (as null is not a valid argument for a long argument). This possible failure // can be avoid by defining the argument as Long instead of long. @@ -148,7 +144,7 @@ CouchbasePersistentEntity couldBePersistentEntity(Class entityClass) { return null; } - public T applyResultBase(T entity, CouchbaseDocument converted, Object id, long cas, + public T applyResultBase(T entity, Object id, long cas, Object txResultHolder, CouchbaseResourceHolder holder) { ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); @@ -168,7 +164,6 @@ public T applyResultBase(T entity, CouchbaseDocument converted, Object id, l if (holder != null) { holder.transactionResultHolder(txResultHolder, (T) accessor.getBean()); } - maybeEmitEvent(new AfterSaveEvent(accessor.getBean(), converted)); return (T) accessor.getBean(); } 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 8714296df..18290da4e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -22,10 +22,7 @@ import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; -import org.springframework.data.couchbase.core.mapping.event.AfterConvertCallback; -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; +import org.springframework.data.couchbase.core.mapping.event.*; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.util.Assert; @@ -37,6 +34,7 @@ * @author Michael Reiche * @author Jorge Rodriguez Martin * @author Carlos Espinaco + * @author Mico Piira * @since 3.0 */ class CouchbaseTemplateSupport extends AbstractTemplateSupport implements ApplicationContextAware, TemplateSupport { @@ -51,26 +49,30 @@ public CouchbaseTemplateSupport(final CouchbaseTemplate template, final Couchbas } @Override - public CouchbaseDocument encodeEntity(final Object entityToEncode) { + public EncodedEntity encodeEntity(final T entityToEncode) { maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode)); Object maybeNewEntity = maybeCallBeforeConvert(entityToEncode, ""); final CouchbaseDocument converted = new CouchbaseDocument(); converter.write(maybeNewEntity, converted); - maybeCallAfterConvert(entityToEncode, converted, ""); maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted)); - return converted; + return new EncodedEntity<>(maybeCallBeforeSave(entityToEncode, converted, ""), converted); } @Override public T decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, Object txHolder, CouchbaseResourceHolder holder) { - return decodeEntityBase(id, source, cas, entityClass, scope, collection, txHolder, holder); + CouchbaseDocument converted = new CouchbaseDocument(id); + T decoded = decodeEntityBase(id, source, cas, entityClass, scope, collection, txHolder, holder, converted); + maybeEmitEvent(new AfterConvertEvent<>(decoded, converted)); + return maybeCallAfterConvert(decoded, converted, ""); } @Override public T applyResult(T entity, CouchbaseDocument converted, Object id, long cas, Object txResultHolder, CouchbaseResourceHolder holder) { - return applyResultBase(entity, converted, id, cas, txResultHolder, holder); + T applied = applyResultBase(entity, id, cas, txResultHolder, holder); + maybeEmitEvent(new AfterSaveEvent<>(applied, converted)); + return maybeCallAfterSave(applied, converted, ""); } @Override @@ -119,6 +121,24 @@ protected T maybeCallAfterConvert(T object, CouchbaseDocument document, Stri return object; } + protected T maybeCallAfterSave(T object, CouchbaseDocument document, String collection) { + if (null != entityCallbacks) { + return entityCallbacks.callback(AfterSaveCallback.class, object, document, collection); + } else { + LOG.info("maybeCallAfterSave called, but CouchbaseTemplate not initialized with applicationContext"); + } + return object; + } + + protected T maybeCallBeforeSave(T object, CouchbaseDocument document, String collection) { + if (null != entityCallbacks) { + return entityCallbacks.callback(BeforeSaveCallback.class, object, document, collection); + } else { + LOG.info("maybeCallBeforeSave called, but CouchbaseTemplate not initialized with applicationContext"); + } + return object; + } + @Override ReactiveCouchbaseTemplate getReactiveTemplate() { return template.reactive(); diff --git a/src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java b/src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java new file mode 100644 index 000000000..09f888919 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/EncodedEntity.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-2023 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +/** + * @author Mico Piira + */ +public record EncodedEntity(T entity, CouchbaseDocument document) { +} 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 4d90f591a..b824d5a35 100644 --- a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java @@ -26,6 +26,7 @@ * * @author Carlos Espinaco * @author Michael Reiche + * @author Mico Piira * @since 4.2 */ public class NonReactiveSupportWrapper implements ReactiveTemplateSupport { @@ -37,7 +38,7 @@ public NonReactiveSupportWrapper(TemplateSupport support) { } @Override - public Mono encodeEntity(Object entityToEncode) { + public Mono> encodeEntity(T entityToEncode) { return Mono.fromSupplier(() -> support.encodeEntity(entityToEncode)); } 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 5b9843536..72b86aacc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -16,28 +16,25 @@ package org.springframework.data.couchbase.core; -import reactor.core.publisher.Mono; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; -import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent; -import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent; -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.*; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.util.Assert; +import reactor.core.publisher.Mono; /** * Internal encode/decode support for {@link ReactiveCouchbaseTemplate}. * * @author Carlos Espinaco * @author Michael Reiche + * @author Mico Piira * @since 4.2 */ class ReactiveCouchbaseTemplateSupport extends AbstractTemplateSupport @@ -53,14 +50,19 @@ public ReactiveCouchbaseTemplateSupport(final ReactiveCouchbaseTemplate template } @Override - public Mono encodeEntity(final Object entityToEncode) { - return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity))) - .flatMap(entity -> maybeCallBeforeConvert(entity, "")).map(maybeNewEntity -> { + public Mono> encodeEntity(final T entityToEncode) { + maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode)); + return maybeCallBeforeConvert(entityToEncode, "") + .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 -> { + maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted)); + return maybeCallBeforeSave(entityToEncode, converted, "") + .map(potentiallyModified -> new EncodedEntity<>(potentiallyModified, converted)); + }); } @Override @@ -71,14 +73,23 @@ ReactiveCouchbaseTemplate getReactiveTemplate() { @Override public Mono decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, Object txResultHolder, CouchbaseResourceHolder holder) { + CouchbaseDocument converted = new CouchbaseDocument(id); return Mono - .fromSupplier(() -> decodeEntityBase(id, source, cas, entityClass, scope, collection, txResultHolder, holder)); + .fromSupplier(() -> decodeEntityBase(id, source, cas, entityClass, scope, collection, txResultHolder, holder, converted)) + .flatMap(entity -> { + maybeEmitEvent(new AfterConvertEvent<>(entity, converted)); + return maybeCallAfterConvert(entity, converted, ""); + }); } @Override public Mono applyResult(T entity, CouchbaseDocument converted, Object id, Long cas, Object txResultHolder, CouchbaseResourceHolder holder) { - return Mono.fromSupplier(() -> applyResultBase(entity, converted, id, cas, txResultHolder, holder)); + return Mono.fromSupplier(() -> applyResultBase(entity, id, cas, txResultHolder, holder)) + .flatMap(saved -> { + maybeEmitEvent(new AfterSaveEvent<>(saved, converted)); + return maybeCallAfterSave(saved, converted, ""); + }); } @Override @@ -104,6 +115,25 @@ public void setReactiveEntityCallbacks(ReactiveEntityCallbacks reactiveEntityCal this.reactiveEntityCallbacks = reactiveEntityCallbacks; } + protected Mono maybeCallBeforeSave(T object, CouchbaseDocument document, String collection) { + if (reactiveEntityCallbacks != null) { + return reactiveEntityCallbacks.callback(ReactiveBeforeSaveCallback.class, object, document, collection); + } else { + LOG.info("maybeCallBeforeSave called, but ReactiveCouchbaseTemplate not initialized with applicationContext"); + } + return Mono.just(object); + } + + protected Mono maybeCallAfterSave(T object, CouchbaseDocument document, String collection) { + if (reactiveEntityCallbacks != null) { + return reactiveEntityCallbacks.callback(ReactiveAfterSaveCallback.class, object, document, collection); + } else { + LOG.info("maybeCallAfterSave called, but ReactiveCouchbaseTemplate not initialized with applicationContext"); + } + return Mono.just(object); + } + + protected Mono maybeCallBeforeConvert(T object, String collection) { if (reactiveEntityCallbacks != null) { return reactiveEntityCallbacks.callback(ReactiveBeforeConvertCallback.class, object, collection); 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 c6dfb5924..bd0f868d3 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -46,6 +46,7 @@ * * @author Michael Reiche * @author Tigran Babloyan + * @author Mico Piira */ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation { @@ -102,12 +103,15 @@ public Mono one(T object) { return Mono .just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())) .flatMap(collection -> support.encodeEntity(object) - .flatMap(converted -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> { + .flatMap(encodedEntity -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> { + T potentiallyModified = encodedEntity.entity(); + CouchbaseDocument converted = encodedEntity.document(); + if (!ctxOpt.isPresent()) { return collection.reactive() .insert(converted.getId().toString(), converted.export(), buildOptions(pArgs.getOptions(), converted)) - .flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(), + .flatMap(result -> this.support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null)); } else { rejectInvalidTransactionalOptions(); @@ -120,7 +124,7 @@ public Mono one(T object) { template.getCouchbaseClientFactory().getCluster().environment().transcoder() .encode(converted.export()).encoded(), new SpanWrapper(span)) - .flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(), + .flatMap(result -> this.support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null)); } })).onErrorMap(throwable -> { diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java index 4d09819d7..54de9a5f3 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java @@ -23,7 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; -import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; import org.springframework.util.Assert; @@ -37,6 +36,7 @@ * {@link ReactiveMutateInByIdOperation} implementations for Couchbase. * * @author Tigran Babloyan + * @author Mico Piira */ public class ReactiveMutateInByIdOperationSupport implements ReactiveMutateInByIdOperation { @@ -105,14 +105,16 @@ public Mono one(T object) { } Mono reactiveEntity = TransactionalSupport.verifyNotInTransaction("mutateInById") - .then(support.encodeEntity(object)).flatMap(converted -> { + .then(support.encodeEntity(object)).flatMap(encodedEntity -> { + T potentiallyModified = encodedEntity.entity(); + CouchbaseDocument converted = encodedEntity.document(); return Mono .just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()) .getCollection(pArgs.getCollection())) .flatMap(collection -> collection.reactive() - .mutateIn(converted.getId().toString(), getMutations(converted), buildMutateInOptions(pArgs.getOptions(), object, converted)) + .mutateIn(converted.getId().toString(), getMutations(converted), buildMutateInOptions(pArgs.getOptions(), potentiallyModified, converted)) .flatMap( - result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null))); + result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null))); }); return reactiveEntity.onErrorMap(throwable -> { 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 fa176074c..0c2f10195 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -49,6 +49,7 @@ * * @author Michael Reiche * @author Tigran Babloyan + * @author Mico Piira */ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation { @@ -105,20 +106,22 @@ public Mono one(T object) { return Mono .just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())) .flatMap(collection -> support.encodeEntity(object) - .flatMap(converted -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> { + .flatMap(encodedEntity -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> { + T potentiallyModified = encodedEntity.entity(); + CouchbaseDocument converted = encodedEntity.document(); if (!ctxOpt.isPresent()) { return collection.reactive() .replace(converted.getId().toString(), converted.export(), - buildReplaceOptions(pArgs.getOptions(), object, converted)) - .flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, + buildReplaceOptions(pArgs.getOptions(), potentiallyModified, converted)) + .flatMap(result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null)); } else { rejectInvalidTransactionalOptions(); - Long cas = support.getCas(object); + Long cas = support.getCas(potentiallyModified); if (cas == null || cas == 0) { throw new IllegalArgumentException( - "cas must be supplied in object for tx replace. object=" + object); + "cas must be supplied in object for tx replace. object=" + potentiallyModified); } CollectionIdentifier collId = makeCollectionIdentifier(collection.async()); @@ -138,7 +141,7 @@ public Mono one(T object) { return ctx.replace(getResult, template.getCouchbaseClientFactory().getCluster().environment() .transcoder().encode(converted.export()).encoded(), new SpanWrapper(span)); }).flatMap( - result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null)); + result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null)); } })).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { 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 a128ac09f..294aa3fb5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java @@ -15,20 +15,20 @@ */ package org.springframework.data.couchbase.core; -import reactor.core.publisher.Mono; - import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; +import reactor.core.publisher.Mono; /** * ReactiveTemplateSupport * * @author Michael Reiche + * @author Mico Piira */ public interface ReactiveTemplateSupport { - Mono encodeEntity(Object entityToEncode); + Mono> encodeEntity(T entityToEncode); Mono decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, Object txResultHolder, CouchbaseResourceHolder holder); 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 ea0c4e62e..61e6b2f64 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -38,6 +38,7 @@ * * @author Michael Reiche * @author Tigran Babloyan + * @author Mico Piira */ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation { @@ -92,14 +93,16 @@ public Mono one(T object) { LOG.debug("upsertById object={} {}", object, pArgs); } Mono reactiveEntity = TransactionalSupport.verifyNotInTransaction("upsertById") - .then(support.encodeEntity(object)).flatMap(converted -> { + .then(support.encodeEntity(object)).flatMap(encodedEntity -> { + T potentiallyModified = encodedEntity.entity(); + CouchbaseDocument converted = encodedEntity.document(); return Mono .just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()) .getCollection(pArgs.getCollection())) .flatMap(collection -> collection.reactive() .upsert(converted.getId().toString(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted)) .flatMap( - result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null))); + result -> support.applyResult(potentiallyModified, converted, converted.getId(), result.cas(), null, null))); }); return reactiveEntity.onErrorMap(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 8e176a815..b3b3a5c2f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java @@ -21,10 +21,11 @@ /** * @author Michael Reiche + * @author Mico Piira */ public interface TemplateSupport { - CouchbaseDocument encodeEntity(Object entityToEncode); + EncodedEntity encodeEntity(T entityToEncode); T decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, Object txResultHolder, CouchbaseResourceHolder holder); 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 a923790e4..7bc66fa0d 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 @@ -19,23 +19,22 @@ import org.springframework.data.mapping.callback.EntityCallback; /** - * Callback being invoked after a domain object is materialized from a {@link CouchbaseDocument} when reading results. + * Callback being invoked after a domain object is materialized from a document when reading results. * * @author Michael Reiche + * @author Mico Piira * @see org.springframework.data.mapping.callback.EntityCallbacks * @since 4.2 */ @FunctionalInterface public interface AfterConvertCallback extends EntityCallback { - /** - * 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. + * Entity callback method invoked after a domain object is materialized from a document. * * @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 CouchbaseDocument}. + * @param document must not be null. + * @param collection name of the document. + * @return the domain object that is the result of reading it from the document. */ T onAfterConvert(T entity, CouchbaseDocument document, String collection); } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java new file mode 100644 index 000000000..41fc9566c --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterConvertEvent.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020-2024 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 Mico Piira + */ +public class AfterConvertEvent extends CouchbaseMappingEvent { + public AfterConvertEvent(E source, CouchbaseDocument document) { + super(source, document); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java new file mode 100644 index 000000000..33a341e2e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveCallback.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-2024 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; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Entity callback triggered after save of a CouchbaseDocument. + * + * @author Mico Piira + */ +@FunctionalInterface +public interface AfterSaveCallback extends EntityCallback { + /** + * Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of the domain object. + * + * @param entity the domain object that was saved. + * @param document CouchbaseDocument representing the entity. + * @param collection name of the collection. + * @return the domain object that was persisted. + */ + T onAfterSave(T entity, CouchbaseDocument document, String collection); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java index f8cd1423a..3efa2a052 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertCallback.java @@ -22,15 +22,14 @@ * * @author Mark Paluch * @author Michael Reiche + * @author Mico Piira * @see org.springframework.data.mapping.callback.EntityCallbacks * @since 2.2 */ @FunctionalInterface public interface BeforeConvertCallback extends EntityCallback { - /** - * Entity callback method invoked before a domain object is converted to be persisted. Can return either the same or a - * modified instance of the domain object. + * Entity callback method invoked before a domain object is converted to be persisted. * * @param entity the domain object to save. * @param collection name of the collection. diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java new file mode 100644 index 000000000..162e023be --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveCallback.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020-2024 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; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Entity callback triggered before save of a CouchbaseDocument. + * + * @author Mico Piira + */ +@FunctionalInterface +public interface BeforeSaveCallback extends EntityCallback { + /** + * Entity callback method invoked before a domain object is saved. + * Can return either the same or a modified instance of the domain object and can modify CouchbaseDocument contents. + * This method is called after converting the entity to a CouchbaseDocument so effectively the document is used as + * outcome of invoking this callback. + * Changes to the domain object are not taken into account for saving, only changes to the document. + * Only transient fields of the entity should be changed in this callback. + * To change persistent the entity before being converted, use the {@link BeforeConvertCallback}. + * + * @param entity the domain object to save. + * @param document CouchbaseDocument representing the entity. + * @param collection name of the collection. + * @return the domain object to be persisted. + */ + T onBeforeSave(T entity, CouchbaseDocument document, String collection); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java index cacb59991..e90322e86 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java @@ -20,9 +20,10 @@ import org.springframework.data.mapping.callback.EntityCallback; /** - * Callback being invoked after a domain object is materialized from a {@link org.springframework.data.couchbase.core.mapping.CouchbaseDocument} when reading results. + * Callback being invoked after a domain object is materialized from a document when reading results. * * @author Jorge Rodríguez Martín + * @author Mico Piira * @see org.springframework.data.mapping.callback.EntityCallbacks * @since 4.2 */ @@ -30,11 +31,12 @@ public interface ReactiveAfterConvertCallback extends EntityCallback { /** - * Entity callback method invoked after a domain object is converted to be persisted. Can return - * either the same of a modified instance of the domain object. - * @param entity the domain object to save. - * @param collection name of the collection. - * @return a {@link Publisher} emitting the domain object to be persisted. + * Entity callback method invoked after a domain object is materialized from a document. + * + * @param entity the domain object (the result of the conversion). + * @param document must not be null. + * @param collection name of the document. + * @return the domain object that is the result of reading it from the document. */ Publisher onAfterConvert(T entity, CouchbaseDocument document, String collection); } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java new file mode 100644 index 000000000..68138170a --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterSaveCallback.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.mapping.event; + +import org.reactivestreams.Publisher; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Entity callback triggered after save of a CouchbaseDocument. + * + * @author Mico Piira + */ +@FunctionalInterface +public interface ReactiveAfterSaveCallback extends EntityCallback { + /** + * Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of the domain object. + * + * @param entity the domain object that was saved. + * @param document CouchbaseDocument representing the entity. + * @param collection name of the collection. + * @return the domain object that was persisted. + */ + Publisher onAfterSave(T entity, CouchbaseDocument document, String collection); +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java index ca6b61408..081ede271 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java @@ -22,20 +22,18 @@ * Callback being invoked before a domain object is converted to be persisted. * * @author Jorge Rodríguez Martín + * @author Mico Piira * @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks * @since 4.2 */ @FunctionalInterface public interface ReactiveBeforeConvertCallback extends EntityCallback { - /** - * Entity callback method invoked before a domain object is converted to be persisted. Can return - * either the same of a modified instance of the domain object. + * Entity callback method invoked before a domain object is converted to be persisted. * * @param entity the domain object to save. * @param collection name of the collection. - * @return a {@link Publisher} emitting the domain object to be persisted. + * @return the domain object to be persisted. */ Publisher onBeforeConvert(T entity, String collection); - } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java new file mode 100644 index 000000000..0ff1c73bc --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeSaveCallback.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.mapping.event; + +import org.reactivestreams.Publisher; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Entity callback triggered before save of a CouchbaseDocument. + * + * @author Mico Piira + */ +@FunctionalInterface +public interface ReactiveBeforeSaveCallback extends EntityCallback { + /** + * Entity callback method invoked before a domain object is saved. + * Can return either the same or a modified instance of the domain object and can modify CouchbaseDocument contents. + * This method is called after converting the entity to a CouchbaseDocument so effectively the document is used as + * outcome of invoking this callback. + * Changes to the domain object are not taken into account for saving, only changes to the document. + * Only transient fields of the entity should be changed in this callback. + * To change persistent the entity before being converted, use the {@link ReactiveBeforeConvertCallback}. + * + * @param entity the domain object to save. + * @param document CouchbaseDocument representing the entity. + * @param collection name of the collection. + * @return the domain object to be persisted. + */ + Publisher onBeforeSave(T entity, CouchbaseDocument document, String collection); +} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java new file mode 100644 index 000000000..4b42a240a --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateCallbackIntegrationTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2023 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.data.annotation.Id; +import org.springframework.data.couchbase.core.mapping.event.*; +import org.springframework.data.couchbase.core.mapping.id.GeneratedValue; +import org.springframework.data.couchbase.domain.Config; +import org.springframework.data.couchbase.util.ClusterType; +import org.springframework.data.couchbase.util.IgnoreWhen; +import org.springframework.data.couchbase.util.JavaIntegrationTests; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import reactor.core.publisher.Mono; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Mico Piira + */ +@IgnoreWhen(clusterTypes = ClusterType.MOCKED) +@SpringJUnitConfig({Config.class, CouchbaseTemplateCallbackIntegrationTests.Callbacks.class}) +@DirtiesContext +class CouchbaseTemplateCallbackIntegrationTests extends JavaIntegrationTests { + + @Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate; + @Autowired CouchbaseTemplate couchbaseTemplate; + + static class Callbacks { + + @Bean + ReactiveBeforeConvertCallback reactiveBeforeConvertCallback() { + return (entity, collection) -> Mono.just(new CallbacksTestEntity(entity.id(), entity.name() + "_beforeconvert")); + } + + @Bean + ReactiveAfterSaveCallback reactiveAfterSaveCallback() { + return (entity, document, collection) -> Mono.just(new CallbacksTestEntity(entity.id(), entity.name() + "_aftersave")); + } + + @Bean + ReactiveBeforeSaveCallback reactiveBeforeSaveCallback() { + return (entity, document, collection) -> + Mono.fromCallable(() -> document.put("name", document.get("name") + "_beforesave")) + .thenReturn(new CallbacksTestEntity(entity.id(), entity.name() + "_beforesave2")); + } + + @Bean + ReactiveAfterConvertCallback reactiveAfterConvertCallback() { + return (entity, document, collection) -> Mono.just(new CallbacksTestEntity(entity.id(), entity.name() + "_afterconvert")); + } + + @Bean + BeforeConvertCallback BeforeConvertCallback() { + return (entity, collection) -> new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_beforeconvert"); + } + + @Bean + AfterSaveCallback AfterSaveCallback() { + return (entity, document, collection) -> new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_aftersave"); + } + + @Bean + BeforeSaveCallback BeforeSaveCallback() { + return (entity, document, collection) -> { + document.put("name", document.get("name") + "_beforesave"); + return new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_beforesave2"); + }; + } + + @Bean + AfterConvertCallback AfterConvertCallback() { + return (entity, document, collection) -> new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(entity.id(), entity.name() + "_afterconvert"); + } + + } + + public record CallbacksTestEntity(@Id @GeneratedValue String id, String name) { } + + @Test + void testReactiveCallbacks() { + CallbacksTestEntity entity = new CallbacksTestEntity(UUID.randomUUID().toString(), "a"); + CallbacksTestEntity saved = reactiveCouchbaseTemplate.insertById(CallbacksTestEntity.class) + .one(entity) + .block(); + assertEquals("a_beforesave2_aftersave", saved.name()); + CallbacksTestEntity block = reactiveCouchbaseTemplate.findById(CallbacksTestEntity.class).one(entity.id()).block(); + assertEquals("a_beforeconvert_beforesave_afterconvert", block.name()); + } + + @Test + void testBlockingCallbacks() { + CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity entity = new CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity(UUID.randomUUID().toString(), "a"); + CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity saved = couchbaseTemplate.insertById(CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity.class) + .one(entity); + assertEquals("a_beforesave2_aftersave", saved.name()); + CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity block = couchbaseTemplate.findById(CouchbaseTemplateCallbackIntegrationTests.CallbacksTestEntity.class).one(entity.id()); + assertEquals("a_beforeconvert_beforesave_afterconvert", block.name()); + } + +} From 5a607ad121a0fd8a382d5791a5c56ee7ecd98013 Mon Sep 17 00:00:00 2001 From: Mico Piira Date: Wed, 3 Jan 2024 10:05:36 +0200 Subject: [PATCH 2/2] Delete no-op AuditingEntityCallback.java --- .../mapping/event/AuditingEntityCallback.java | 81 ------------------- .../auditing/CouchbaseAuditingRegistrar.java | 11 --- 2 files changed, 92 deletions(-) delete mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java 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 deleted file mode 100644 index 5c82c0e95..000000000 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2012-2024 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.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, 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} - * provided by the given {@link ObjectFactory}. - * - * @param auditingHandlerFactory must not be {@literal null}. - */ - public AuditingEntityCallback(ObjectFactory auditingHandlerFactory) { - Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); - this.auditingHandlerFactory = auditingHandlerFactory; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback#onBeforeConvert(java.lang.Object, java.lang.String) - */ - @Override - public Object onBeforeConvert(Object entity, String collection) { - // LOG.trace("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.trace("onAfterConvert " + document); - return entity; - } - - /* - * (non-Javadoc) - * @see org.springframework.core.Ordered#getOrder() - */ - @Override - public int getOrder() { - return 100; - } - -} 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 5e770f948..2a9877d8d 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 @@ -31,7 +31,6 @@ import org.springframework.data.config.ParsingUtils; import org.springframework.data.couchbase.config.BeanNames; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.core.mapping.event.AuditingEntityCallback; import org.springframework.data.couchbase.core.mapping.event.AuditingEventListener; import org.springframework.util.Assert; @@ -85,16 +84,6 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!"); Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); - // Register the AuditEntityCallback - - BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder - .rootBeanDefinition(AuditingEntityCallback.class); - listenerBeanDefinitionBuilder - .addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry)); - - registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(), - AuditingEntityCallback.class.getName(), registry); - // Register the AuditingEventListener BeanDefinitionBuilder listenerBeanDefinitionBuilder2 = BeanDefinitionBuilder