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 711377770..140f4d9c4 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.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 @@ public T decodeEntity(String id, String source, long cas, Class entityCla } @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 @@ public void setEntityCallbacks(EntityCallbacks entityCallbacks) { 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 adf0d5709..6f890a7e7 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 @@ 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 Mono decodeEntity(String id, String source, long cas, Class ent } @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 Long getCas(Object entity) { 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 d83b09ef2..d12393707 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.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.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 @@ public ReactiveCouchbaseTemplateSupport(final CouchbaseConverter converter, @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 @@ public Mono decodeEntity(String id, String source, long cas, Class ent } @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 @@ public Mono applyUpdatedCas(final Object entity, final long cas) { 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 @@ public Mono applyUpdatedId(final Object entity, Object id) { @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 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } /** - * 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 @@ public void setReactiveEntityCallbacks(ReactiveEntityCallbacks reactiveEntityCal 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 225a593d1..53a047943 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 @@ static class ReactiveInsertByIdSupport implements ReactiveInsertById { @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 8c7dcc51f..a337c39e6 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 @@ static class ReactiveRemoveByIdSupport implements ReactiveRemoveById { @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 ccd043672..bca6ecd09 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 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { @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 d5585d1a7..387045954 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 @@ 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 4ae126860..2c923b5da 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 @@ static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { @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 f9029a08b..372543c3f 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 8c8976ae9..ec84d93f2 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 08c0489fa..acde35146 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 AuditingEntityCallback(ObjectFactory auditingH */ @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 int getOrder() { 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 d5ecffa7f..0ee5aaabf 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 @@ 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 @@ * @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 AuditingEventListener() { this.auditingHandlerFactory = null; } + private static final Logger LOG = LoggerFactory.getLogger(AuditingEventListener.class); + /** * Creates a new {@link AuditingEventListener} using the given {@link MappingContext} and {@link AuditingHandler} - * provided by the given {@link ObjectFactory}. + * provided by the given {@link ObjectFactory}. Registered in CouchbaseAuditingRegistrar * * @param auditingHandlerFactory must not be {@literal null}. */ @@ -57,8 +61,29 @@ public AuditingEventListener(ObjectFactory auditingHa * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) */ @Override - public void onApplicationEvent(BeforeConvertEvent 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 000000000..52c798940 --- /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 000000000..3c0a97a32 --- /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 1939a5d09..5f2348daf 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 ReactiveAuditingEntityCallback(final ObjectFactory 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 000000000..7d516dd5e --- /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 000000000..8f3ff6c30 --- /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 000000000..161c2a45d --- /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 000000000..b96e32b65 --- /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 902150d3e..7120205ce 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 @@ 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(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 5f71197d7..a38cbcbfa 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.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 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle 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 f2d3fb819..c09801059 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 @@ void upsertAndFindById() { assertEquals(user, found); couchbaseTemplate.removeById().one(user.getId()); - reactiveCouchbaseTemplate.replaceById(User.class).withDurability(PersistTo.ACTIVE, ReplicateTo.THREE).one(user); } @Test @@ -280,9 +279,7 @@ void existsById() { 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 000000000..d4a479719 --- /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 686628c71..4e2bc5c46 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 @@ */ @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 b1c54f27b..797a2dabb 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 @@ */ 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 447ac1276..0f3b08145 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 @@ */ 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 4a0918bf4..49ddbcee5 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 @@ void findBySimplePropertyAudited() { 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 6888dd581..77e6fe2a4 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 @@ void findByIdAudited() { 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(); }