diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java index 2d985fe1d..cb9bf63ea 100644 --- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.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. @@ -24,6 +24,7 @@ * @author Michael Nitschinger * @author Simon Baslé * @author Michael Reiche + * @author Jorge Rodríguez Martín */ public class BeanNames { @@ -53,4 +54,9 @@ public class BeanNames { * The name for the bean that will handle audit trail marking of entities. */ public static final String COUCHBASE_AUDITING_HANDLER = "couchbaseAuditingHandler"; + + /** + * The name for the bean that will handle reactive audit trail marking of entities. + */ + public static final String REACTIVE_COUCHBASE_AUDITING_HANDLER = "reactiveCouchbaseAuditingHandler"; } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 9fc59eb3a..29829d9b3 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.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. @@ -180,4 +180,8 @@ private void prepareIndexCreator(final ApplicationContext context) { } } } + + TemplateSupport support() { + return templateSupport; + } } 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 ef0d5031d..711377770 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -1,5 +1,6 @@ /* - * Copyright 2012-2020 the original author or authors +/* + * Copyright 2012-2021 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,9 +46,10 @@ * @author Michael Nitschinger * @author Michael Reiche * @author Jorge Rodriguez Martin + * @author Carlos Espinaco * @since 3.0 */ -class CouchbaseTemplateSupport implements ApplicationContextAware { +class CouchbaseTemplateSupport implements ApplicationContextAware, TemplateSupport { private static final Logger LOG = LoggerFactory.getLogger(CouchbaseTemplateSupport.class); @@ -63,6 +65,7 @@ public CouchbaseTemplateSupport(final CouchbaseConverter converter, final Transl this.translationService = translationService; } + @Override public CouchbaseDocument encodeEntity(final Object entityToEncode) { maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode)); Object maybeNewEntity = maybeCallBeforeConvert(entityToEncode, ""); @@ -73,6 +76,7 @@ public CouchbaseDocument encodeEntity(final Object entityToEncode) { return converted; } + @Override public T decodeEntity(String id, String source, long cas, Class entityClass) { final CouchbaseDocument converted = new CouchbaseDocument(id); converted.setId(id); @@ -90,6 +94,7 @@ public T decodeEntity(String id, String source, long cas, Class entityCla return accessor.getBean(); } + @Override public Object applyUpdatedCas(final Object entity, final long cas) { final ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass()); @@ -102,6 +107,7 @@ public Object applyUpdatedCas(final Object entity, final long cas) { return entity; } + @Override public Object applyUpdatedId(final Object entity, Object id) { final ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass()); @@ -114,6 +120,7 @@ public Object applyUpdatedId(final Object entity, Object id) { return entity; } + @Override public long getCas(final Object entity) { final ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass()); @@ -129,6 +136,7 @@ public long getCas(final Object entity) { return cas; } + @Override public String getJavaNameForEntity(final Class clazz) { final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); MappingCouchbaseEntityInformation info = new MappingCouchbaseEntityInformation<>(persistentEntity); diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java index d0e517136..4c430fcaa 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.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. @@ -53,7 +53,7 @@ static class ExecutableFindByAnalyticsSupport implements ExecutableFindByAnal this.domainType = domainType; this.query = query; this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, query, - scanConsistency); + scanConsistency, new NonReactiveSupportWrapper(template.support())); this.scanConsistency = scanConsistency; } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java index caaa7c928..5948c8066 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.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. @@ -48,7 +48,7 @@ static class ExecutableFindByIdSupport implements ExecutableFindById { this.domainType = domainType; this.collection = collection; this.fields = fields; - this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, collection, fields); + this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, collection, fields, new NonReactiveSupportWrapper(template.support())); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java index ee4908984..7e95be43b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.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. @@ -65,7 +65,7 @@ static class ExecutableFindByQuerySupport implements ExecutableFindByQuery this.returnType = returnType; this.query = query; this.reactiveSupport = new ReactiveFindByQuerySupport(template.reactive(), domainType, returnType, query, - scanConsistency, collection, distinctFields); + scanConsistency, collection, distinctFields, new NonReactiveSupportWrapper(template.support())); this.scanConsistency = scanConsistency; this.collection = collection; this.distinctFields = distinctFields; diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java index eccebf35c..fa81cf631 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.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. @@ -48,7 +48,7 @@ static class ExecutableFindFromReplicasByIdSupport implements ExecutableFindF this.collection = collection; this.returnType = returnType; this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, returnType, - collection); + collection, new NonReactiveSupportWrapper(template.support())); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java index 7405fad64..ba2b35086 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.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. @@ -62,7 +62,7 @@ static class ExecutableInsertByIdSupport implements ExecutableInsertById { this.durabilityLevel = durabilityLevel; this.expiry = expiry; this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, collection, persistTo, - replicateTo, durabilityLevel, expiry); + replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support())); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java index 399fa6969..27a1c5b4b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.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. @@ -62,7 +62,7 @@ static class ExecutableReplaceByIdSupport implements ExecutableReplaceById this.durabilityLevel = durabilityLevel; this.expiry = expiry; this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(), - domainType, collection, persistTo, replicateTo, durabilityLevel, expiry); + domainType, collection, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support())); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java index 9ec260ce8..e31fee644 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.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. @@ -62,7 +62,7 @@ static class ExecutableUpsertByIdSupport implements ExecutableUpsertById { this.durabilityLevel = durabilityLevel; this.expiry = expiry; this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(), - domainType, collection, persistTo, replicateTo, durabilityLevel, expiry); + domainType, collection, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support())); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java new file mode 100644 index 000000000..adf0d5709 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +import reactor.core.publisher.Mono; + +/** + * Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}. + * + * @author Carlos Espinaco + * @since 4.2 + */ +public class NonReactiveSupportWrapper implements ReactiveTemplateSupport { + + private final TemplateSupport support; + + public NonReactiveSupportWrapper(TemplateSupport support) { + this.support = support; + } + + @Override + public Mono encodeEntity(Object entityToEncode) { + return Mono.fromSupplier(() -> support.encodeEntity(entityToEncode)); + } + + @Override + public Mono decodeEntity(String id, String source, long cas, Class entityClass) { + return Mono.fromSupplier(() -> support.decodeEntity(id, source, cas, entityClass)); + } + + @Override + public Mono applyUpdatedCas(Object entity, long cas) { + return Mono.fromSupplier(() -> support.applyUpdatedCas(entity, cas)); + } + + @Override + public Mono applyUpdatedId(Object entity, Object id) { + return Mono.fromSupplier(() -> support.applyUpdatedId(entity, id)); + } + + @Override + public Long getCas(Object entity) { + return support.getCas(entity); + } + + @Override + public String getJavaNameForEntity(Class clazz) { + return support.getJavaNameForEntity(clazz); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java index 4b01c9b9d..96866bc49 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.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. @@ -34,13 +34,14 @@ * @author Michael Nitschinger * @author Michael Reiche * @author Jorge Rodriguez Martin + * @author Carlos Espinaco */ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, ApplicationContextAware { private final CouchbaseClientFactory clientFactory; private final CouchbaseConverter converter; private final PersistenceExceptionTranslator exceptionTranslator; - private final CouchbaseTemplateSupport templateSupport; + private final ReactiveCouchbaseTemplateSupport templateSupport; public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) { this(clientFactory, converter, new JacksonTranslationService()); @@ -51,7 +52,7 @@ public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, fin this.clientFactory = clientFactory; this.converter = converter; this.exceptionTranslator = clientFactory.getExceptionTranslator(); - this.templateSupport = new CouchbaseTemplateSupport(converter, translationService); + this.templateSupport = new ReactiveCouchbaseTemplateSupport(converter, translationService); } @Override @@ -134,7 +135,7 @@ public CouchbaseConverter getConverter() { return converter; } - CouchbaseTemplateSupport support() { + ReactiveTemplateSupport support() { return templateSupport; } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java new file mode 100644 index 000000000..d83b09ef2 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -0,0 +1,222 @@ +/* + * Copyright 2012-2021 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.convert.translation.TranslationService; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent; +import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent; +import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent; +import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterConvertCallback; +import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback; +import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.callback.EntityCallbacks; +import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.util.Assert; +import reactor.core.publisher.Mono; + +/** + * Internal encode/decode support for {@link ReactiveCouchbaseTemplate}. + * + * @author Carlos Espinaco + * @since 4.2 + */ +class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, ReactiveTemplateSupport { + + private static final Logger LOG = LoggerFactory.getLogger(ReactiveCouchbaseTemplateSupport.class); + + private final CouchbaseConverter converter; + private final MappingContext, CouchbasePersistentProperty> mappingContext; + private final TranslationService translationService; + private ReactiveEntityCallbacks reactiveEntityCallbacks; + private ApplicationContext applicationContext; + + public ReactiveCouchbaseTemplateSupport(final CouchbaseConverter converter, + final TranslationService translationService) { + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + this.translationService = translationService; + } + + @Override + public Mono encodeEntity(final Object entityToEncode) { + return Mono.just(entityToEncode) + .doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity))) + .flatMap(entity -> maybeCallBeforeConvert(entity, "")) + .map(maybeNewEntity -> { + final CouchbaseDocument converted = new CouchbaseDocument(); + converter.write(maybeNewEntity, converted); + return converted; + }) + .flatMap(converted -> maybeCallAfterConvert(entityToEncode, converted, "").thenReturn(converted)) + .doOnNext(converted -> maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted))); + } + + @Override + public Mono decodeEntity(String id, String source, long cas, Class entityClass) { + return Mono.fromSupplier(() -> { + final CouchbaseDocument converted = new CouchbaseDocument(id); + converted.setId(id); + CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass); + if (cas != 0 && persistentEntity.getVersionProperty() != null) { + converted.put(persistentEntity.getVersionProperty().getName(), cas); + } + + T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted)); + final ConvertingPropertyAccessor accessor = getPropertyAccessor(readEntity); + + if (persistentEntity.getVersionProperty() != null) { + accessor.setProperty(persistentEntity.getVersionProperty(), cas); + } + return accessor.getBean(); + }); + } + + @Override + public Mono applyUpdatedCas(final Object entity, final long cas) { + return Mono.fromSupplier(() -> { + 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(); + } + return entity; + }); + } + + @Override + public Mono applyUpdatedId(final Object entity, Object id) { + return Mono.fromSupplier(() -> { + final ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); + final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass()); + final CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty(); + + if (idProperty != null) { + accessor.setProperty(idProperty, id); + return accessor.getBean(); + } + return entity; + }); + } + + @Override + public Long getCas(final Object entity) { + final ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); + final CouchbasePersistentEntity persistentEntity = mappingContext + .getRequiredPersistentEntity(entity.getClass()); + final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); + + long cas = 0; + if (versionProperty != null) { + Object casObject = (Number) accessor.getProperty(versionProperty); + if (casObject instanceof Number) { + cas = ((Number) casObject).longValue(); + } + } + return cas; + } + + @Override + public String getJavaNameForEntity(final Class clazz) { + final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); + MappingCouchbaseEntityInformation info = new MappingCouchbaseEntityInformation<>(persistentEntity); + return info.getJavaType().getName(); + } + + private ConvertingPropertyAccessor getPropertyAccessor(final T source) { + CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(source.getClass()); + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); + return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + if (reactiveEntityCallbacks == null) { + setReactiveEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext)); + } + } + + /** + * Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link + * org.springframework.data.mapping.callback.ReactiveEntityCallbacks callbacks} like the {@link + * ReactiveBeforeConvertCallback}. + *

+ * Overrides potentially existing {@link EntityCallbacks}. + * + * @param reactiveEntityCallbacks must not be {@literal null}. + * @throws IllegalArgumentException if the given instance is {@literal null}. + */ + public void setReactiveEntityCallbacks(ReactiveEntityCallbacks reactiveEntityCallbacks) { + Assert.notNull(reactiveEntityCallbacks, "EntityCallbacks must not be null!"); + this.reactiveEntityCallbacks = reactiveEntityCallbacks; + } + + void maybeEmitEvent(CouchbaseMappingEvent event) { + if (canPublishEvent()) { + try { + this.applicationContext.publishEvent(event); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + LOG.info("maybeEmitEvent called, but ReactiveCouchbaseTemplate not initialized with applicationContext"); + } + + } + + private boolean canPublishEvent() { + return this.applicationContext != null; + } + + protected Mono maybeCallBeforeConvert(T object, String collection) { + if (reactiveEntityCallbacks != null) { + try { + return reactiveEntityCallbacks.callback(ReactiveBeforeConvertCallback.class, object, collection); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + LOG.info("maybeCallBeforeConvert called, but ReactiveCouchbaseTemplate not initialized with applicationContext"); + } + return Mono.just(object); + } + + protected Mono maybeCallAfterConvert(T object, CouchbaseDocument document, String collection) { + if (null != reactiveEntityCallbacks) { + return reactiveEntityCallbacks.callback(ReactiveAfterConvertCallback.class, object, document, collection); + } else { + LOG.info("maybeCallAfterConvert called, but ReactiveCouchbaseTemplate not initialized with applicationContext"); + } + return Mono.just(object); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java index 83df95ad5..5c94ab770 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.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. @@ -38,7 +38,8 @@ public ReactiveFindByAnalyticsOperationSupport(final ReactiveCouchbaseTemplate t @Override public ReactiveFindByAnalytics findByAnalytics(final Class domainType) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY, AnalyticsScanConsistency.NOT_BOUNDED); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY, AnalyticsScanConsistency.NOT_BOUNDED, + template.support()); } static class ReactiveFindByAnalyticsSupport implements ReactiveFindByAnalytics { @@ -47,29 +48,31 @@ static class ReactiveFindByAnalyticsSupport implements ReactiveFindByAnalytic private final Class domainType; private final AnalyticsQuery query; private final AnalyticsScanConsistency scanConsistency; + private final ReactiveTemplateSupport support; ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class domainType, - final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency) { + final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency, ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.query = query; this.scanConsistency = scanConsistency; + this.support = support; } @Override public TerminatingFindByAnalytics matching(AnalyticsQuery query) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support); } @Override @Deprecated public FindByAnalyticsWithQuery consistentWith(AnalyticsScanConsistency scanConsistency) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support); } @Override public FindByAnalyticsWithQuery withConsistency(AnalyticsScanConsistency scanConsistency) { - return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency); + return new ReactiveFindByAnalyticsSupport<>(template, domainType, query, scanConsistency, support); } @Override @@ -145,7 +148,7 @@ private String assembleEntityQuery(final boolean count) { statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*"); } - final String dataset = template.support().getJavaNameForEntity(domainType); + final String dataset = support.getJavaNameForEntity(domainType); statement.append(" FROM ").append(dataset); query.appendSort(statement); diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index 0cff986be..0a34f7534 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.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. @@ -40,7 +40,7 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati @Override public ReactiveFindById findById(Class domainType) { - return new ReactiveFindByIdSupport<>(template, domainType, null, null); + return new ReactiveFindByIdSupport<>(template, domainType, null, null, template.support()); } static class ReactiveFindByIdSupport implements ReactiveFindById { @@ -49,13 +49,15 @@ static class ReactiveFindByIdSupport implements ReactiveFindById { private final Class domainType; private final String collection; private final List fields; + private final ReactiveTemplateSupport support; ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, String collection, - List fields) { + List fields, ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.collection = collection; this.fields = fields; + this.support = support; } @Override @@ -66,7 +68,7 @@ public Mono one(final String id) { options.project(fields); } return template.getCollection(collection).reactive().get(docId, options); - }).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType)) + }).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType)) .onErrorResume(throwable -> { if (throwable instanceof RuntimeException) { if (throwable instanceof DocumentNotFoundException) { @@ -91,13 +93,13 @@ public Flux all(final Collection ids) { @Override public TerminatingFindById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveFindByIdSupport<>(template, domainType, collection, fields); + return new ReactiveFindByIdSupport<>(template, domainType, collection, fields, support); } @Override public FindByIdWithCollection project(String... fields) { Assert.notEmpty(fields, "Fields must not be null nor empty."); - return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields)); + return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields), support); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index eea12845d..0894fa1d8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.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. @@ -45,7 +45,7 @@ public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate templ @Override public ReactiveFindByQuery findByQuery(final Class domainType) { return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY, - QueryScanConsistency.NOT_BOUNDED, null, null); + QueryScanConsistency.NOT_BOUNDED, null, null, template.support()); } static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { @@ -57,10 +57,12 @@ static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { private final QueryScanConsistency scanConsistency; private final String collection; private final String[] distinctFields; + private final ReactiveTemplateSupport support; ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, final Class returnType, final Query query, final QueryScanConsistency scanConsistency, - final String collection, final String[] distinctFields) { + final String collection, final String[] distinctFields, final ReactiveTemplateSupport support) { + this.support = support; Assert.notNull(domainType, "domainType must not be null!"); Assert.notNull(returnType, "returnType must not be null!"); @@ -82,41 +84,41 @@ public FindByQueryWithQuery matching(Query query) { scanCons = scanConsistency; } return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, collection, - distinctFields); + distinctFields, support); } @Override public FindByQueryInCollection inCollection(String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + distinctFields, support); } @Override @Deprecated public FindByQueryConsistentWith consistentWith(QueryScanConsistency scanConsistency) { return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + distinctFields, support); } @Override public FindByQueryWithConsistency withConsistency(QueryScanConsistency scanConsistency) { return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + distinctFields, support); } @Override public FindByQueryWithConsistency as(Class returnType) { Assert.notNull(returnType, "returnType must not be null!"); return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + distinctFields, support); } @Override public FindByQueryWithDistinct distinct(String[] distinctFields) { Assert.notNull(distinctFields, "distinctFields must not be null!"); return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, collection, - distinctFields); + distinctFields, support); } @Override @@ -144,7 +146,7 @@ public Flux all() { } else { return throwable; } - }).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> { + }).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> { String id = ""; long cas = 0; if (distinctFields == null) { @@ -163,7 +165,7 @@ public Flux all() { row.removeKey(TemplateUtils.SELECT_ID); row.removeKey(TemplateUtils.SELECT_CAS); } - return template.support().decodeEntity(id, row.toString(), cas, returnType); + return support.decodeEntity(id, row.toString(), cas, returnType); }); }); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java index bc9e9d7ef..098087bff 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.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. @@ -37,7 +37,7 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin @Override public ReactiveFindFromReplicasById findFromReplicasById(Class domainType) { - return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType, null, template.support()); } static class ReactiveFindFromReplicasByIdSupport implements ReactiveFindFromReplicasById { @@ -46,13 +46,15 @@ static class ReactiveFindFromReplicasByIdSupport implements ReactiveFindFromR private final Class domainType; private final Class returnType; private final String collection; + private final ReactiveTemplateSupport support; ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, Class returnType, - String collection) { + String collection, ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.returnType = returnType; this.collection = collection; + this.support = support; } @Override @@ -60,7 +62,7 @@ public Mono any(final String id) { return Mono.just(id).flatMap(docId -> { GetAnyReplicaOptions options = getAnyReplicaOptions().transcoder(RawJsonTranscoder.INSTANCE); return template.getCollection(collection).reactive().getAnyReplica(docId, options); - }).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), returnType)) + }).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType)) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); @@ -78,7 +80,7 @@ public Flux any(Collection ids) { @Override public TerminatingFindFromReplicasById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, collection); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, collection, support); } } 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 3898fcd82..c28894859 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.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. @@ -42,7 +42,7 @@ public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate templa public ReactiveInsertById insertById(final Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, - DurabilityLevel.NONE, null); + DurabilityLevel.NONE, null, template.support()); } static class ReactiveInsertByIdSupport implements ReactiveInsertById { @@ -54,10 +54,11 @@ static class ReactiveInsertByIdSupport implements ReactiveInsertById { private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; private final Duration expiry; + private final ReactiveTemplateSupport support; ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, - final DurabilityLevel durabilityLevel, Duration expiry) { + final DurabilityLevel durabilityLevel, Duration expiry, ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.collection = collection; @@ -65,18 +66,19 @@ static class ReactiveInsertByIdSupport implements ReactiveInsertById { this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; this.expiry = expiry; + this.support = support; } @Override public Mono one(T object) { - return Mono.just(object).flatMap(o -> { - CouchbaseDocument converted = template.support().encodeEntity(o); - return template.getCollection(collection).reactive() - .insert(converted.getId(), converted.export(), buildInsertOptions(converted)).map(result -> { - Object updatedObject = template.support().applyUpdatedId(o, converted.getId()); - return (T) template.support().applyUpdatedCas(updatedObject, result.cas()); - }); - }).onErrorMap(throwable -> { + return (Mono) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> + template.getCollection(collection).reactive() + .insert(converted.getId(), converted.export(), buildInsertOptions(converted)) + .flatMap(result -> + support.applyUpdatedId(object, converted.getId()) + .flatMap( + updatedObject -> support.applyUpdatedCas(updatedObject, result.cas()))) + ).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); } else { @@ -109,14 +111,14 @@ private InsertOptions buildInsertOptions(CouchbaseDocument doc) { // CouchbaseDo public TerminatingInsertById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override public InsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override @@ -124,14 +126,14 @@ public InsertByIdWithCollection withDurability(final PersistTo persistTo, fin Assert.notNull(persistTo, "PersistTo must not be null."); Assert.notNull(replicateTo, "ReplicateTo must not be null."); return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override public InsertByIdWithDurability withExpiry(final Duration expiry) { Assert.notNull(expiry, "expiry must not be null."); return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } } 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 86442c12a..173c2bb89 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.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. @@ -41,7 +41,7 @@ public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate templ public ReactiveReplaceById replaceById(final Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, - DurabilityLevel.NONE, null); + DurabilityLevel.NONE, null, template.support()); } static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { @@ -53,10 +53,11 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; private final Duration expiry; + private final ReactiveTemplateSupport support; ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, - final DurabilityLevel durabilityLevel, final Duration expiry) { + final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.collection = collection; @@ -64,15 +65,15 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; this.expiry = expiry; + this.support = support; } @Override public Mono one(T object) { - return Mono.just(object).flatMap(o -> { - CouchbaseDocument converted = template.support().encodeEntity(o); + return (Mono) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> { return template.getCollection(collection).reactive() - .replace(converted.getId(), converted.export(), buildReplaceOptions(o, converted)) - .map(result -> (T) template.support().applyUpdatedCas(o, result.cas())); + .replace(converted.getId(), converted.export(), buildReplaceOptions(object, converted)) + .flatMap(result -> support.applyUpdatedCas(object, result.cas())); }).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); @@ -99,7 +100,7 @@ private ReplaceOptions buildReplaceOptions(T object, CouchbaseDocument doc) { } else if (doc.getExpiration() != 0) { options.expiry(Duration.ofSeconds(doc.getExpiration())); } - long cas = template.support().getCas(object); + long cas = support.getCas(object); options.cas(cas); return options; } @@ -108,14 +109,14 @@ private ReplaceOptions buildReplaceOptions(T object, CouchbaseDocument doc) { public TerminatingReplaceById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override public ReplaceByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override @@ -123,14 +124,14 @@ public ReplaceByIdWithCollection withDurability(final PersistTo persistTo, fi Assert.notNull(persistTo, "PersistTo must not be null."); Assert.notNull(replicateTo, "ReplicateTo must not be null."); return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override public ReplaceByIdWithDurability withExpiry(final Duration expiry) { Assert.notNull(expiry, "expiry must not be null."); return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java new file mode 100644 index 000000000..d5585d1a7 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +import reactor.core.publisher.Mono; + +public interface ReactiveTemplateSupport { + + Mono encodeEntity(Object entityToEncode); + + Mono decodeEntity(String id, String source, long cas, Class entityClass); + + Mono applyUpdatedCas(Object entity, long cas); + + Mono applyUpdatedId(Object entity, Object id); + + Long getCas(Object entity); + + String getJavaNameForEntity(Class clazz); +} 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 8de91df0a..7976b9880 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.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. @@ -41,7 +41,7 @@ public ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate templa public ReactiveUpsertById upsertById(final Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, - DurabilityLevel.NONE, null); + DurabilityLevel.NONE, null, template.support()); } static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { @@ -53,10 +53,11 @@ static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; private final Duration expiry; + private final ReactiveTemplateSupport support; ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, - final DurabilityLevel durabilityLevel, final Duration expiry) { + final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) { this.template = template; this.domainType = domainType; this.collection = collection; @@ -64,18 +65,17 @@ static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; this.expiry = expiry; + this.support = support; } @Override public Mono one(T object) { - return Mono.just(object).flatMap(o -> { - CouchbaseDocument converted = template.support().encodeEntity(o); - return template.getCollection(collection).reactive() - .upsert(converted.getId(), converted.export(), buildUpsertOptions(converted)).map(result -> { - Object updatedObject = template.support().applyUpdatedId(o, converted.getId()); - return (T) template.support().applyUpdatedCas(updatedObject, result.cas()); - }); - }).onErrorMap(throwable -> { + return (Mono) Mono.just(object).flatMap(support::encodeEntity).flatMap(converted -> + template.getCollection(collection).reactive() + .upsert(converted.getId(), converted.export(), buildUpsertOptions(converted)).flatMap(result -> + support.applyUpdatedId(object, converted.getId()) + .flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, result.cas()))) + ).onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); } else { @@ -108,14 +108,14 @@ private UpsertOptions buildUpsertOptions(CouchbaseDocument doc) { public TerminatingUpsertById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override public UpsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override @@ -123,14 +123,14 @@ public UpsertByIdWithCollection withDurability(final PersistTo persistTo, fin Assert.notNull(persistTo, "PersistTo must not be null."); Assert.notNull(replicateTo, "ReplicateTo must not be null."); return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } @Override public UpsertByIdWithDurability withExpiry(final Duration expiry) { Assert.notNull(expiry, "expiry must not be null."); return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, - expiry); + expiry, support); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java new file mode 100644 index 000000000..f9029a08b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; + +public interface TemplateSupport { + + CouchbaseDocument encodeEntity(Object entityToEncode); + + T decodeEntity(String id, String source, long cas, Class entityClass); + + Object applyUpdatedCas(Object entity, long cas); + + Object applyUpdatedId(Object entity, Object id); + + long getCas(Object entity); + + String getJavaNameForEntity(Class clazz); +} 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 new file mode 100644 index 000000000..08c0489fa --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2021 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.mapping.event; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.core.Ordered; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.IsNewAwareAuditingHandler; +import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + + +/** + * {@link EntityCallback} to populate auditing related fields on an entity about to be saved. + * + * @author Jorge Rodríguez Martín + * @since 4.2 + */ +public class AuditingEntityCallback implements BeforeConvertCallback, Ordered { + + private final ObjectFactory auditingHandlerFactory; + + /** + * Creates a new {@link AuditingEntityCallback} using the given {@link MappingContext} and {@link AuditingHandler} + * provided by the given {@link ObjectFactory}. + * + * @param auditingHandlerFactory must not be {@literal null}. + */ + public AuditingEntityCallback(ObjectFactory auditingHandlerFactory) { + + Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); + this.auditingHandlerFactory = auditingHandlerFactory; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback#onBeforeConvert(java.lang.Object, java.lang.String) + */ + @Override + public Object onBeforeConvert(Object entity, String collection) { + return auditingHandlerFactory.getObject().markAudited(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.Ordered#getOrder() + */ + @Override + public int getOrder() { + return 100; + } + + +} 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 new file mode 100644 index 000000000..30ce4b740 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.mapping.event; + +import org.reactivestreams.Publisher; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Callback being invoked after a domain object is materialized from a {@link org.springframework.data.couchbase.core.mapping.CouchbaseDocument} when reading results. + * + * @author Jorge Rodríguez Martín + * @see org.springframework.data.mapping.callback.EntityCallbacks + * @since 4.2 + */ +@FunctionalInterface +public interface ReactiveAfterConvertCallback 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. + */ + Publisher onAfterConvert(T entity, CouchbaseDocument document, String collection); +} 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 new file mode 100644 index 000000000..1939a5d09 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEntityCallback.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.mapping.event; + +import org.reactivestreams.Publisher; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.core.Ordered; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.Document; +import org.springframework.data.mapping.callback.EntityCallback; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +/** + * Reactive {@link EntityCallback} to populate auditing related fields on an entity about to be + * saved. Based on ReactiveAfterConvertCallback + * + * @author Jorge Rodríguez Martín + * @since 4.2 + */ +public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCallback, Ordered { + + private final ObjectFactory auditingHandlerFactory; + + /** + * Creates a new {@link ReactiveAuditingEntityCallback} using the given {@link MappingContext} and + * {@link AuditingHandler} provided by the given {@link ObjectFactory}. + * + * @param auditingHandlerFactory must not be {@literal null}. + */ + public ReactiveAuditingEntityCallback(final ObjectFactory auditingHandlerFactory) { + Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); + this.auditingHandlerFactory = auditingHandlerFactory; + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback#onBeforeConvert + * (java.lang.Object, java.lang.String) + */ + @Override + public Publisher onBeforeConvert(final Object entity, final String collection) { + return this.auditingHandlerFactory.getObject().markAudited(entity); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.core.Ordered#getOrder() + */ + @Override + public int getOrder() { + return 100; + } + +} 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 new file mode 100644 index 000000000..cea87439f --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core.mapping.event; + +import org.reactivestreams.Publisher; +import org.springframework.data.mapping.callback.EntityCallback; + +/** + * Callback being invoked before a domain object is converted to be persisted. + * + * @author Jorge Rodríguez Martín + * @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks + * @since 4.2 + */ +@FunctionalInterface +public interface ReactiveBeforeConvertCallback 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. + * + * @param entity the domain object to save. + * @param collection name of the collection. + * @return a {@link Publisher} emitting the domain object to be persisted. + */ + Publisher onBeforeConvert(T entity, String collection); + +} 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 c16151e3b..902150d3e 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 @@ -30,6 +30,7 @@ import org.springframework.data.config.ParsingUtils; import org.springframework.data.couchbase.config.BeanNames; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; +import org.springframework.data.couchbase.core.mapping.event.AuditingEntityCallback; import org.springframework.data.couchbase.core.mapping.event.AuditingEventListener; import org.springframework.util.Assert; @@ -41,6 +42,7 @@ * @author Oliver Gierke * @author Simon Baslé * @author Michael Reiche + * @author Jorge Rodríguez Martín */ public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { @@ -84,7 +86,7 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle .addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry)); registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(), - AuditingEventListener.class.getName(), registry); + AuditingEntityCallback.class.getName(), registry); } private void ensureMappingContext(BeanDefinitionRegistry registry, Object source) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/EnableReactiveCouchbaseAuditing.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/EnableReactiveCouchbaseAuditing.java new file mode 100644 index 000000000..e558a4d9e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/EnableReactiveCouchbaseAuditing.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.repository.auditing; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.context.annotation.Import; +import org.springframework.data.auditing.DateTimeProvider; +import org.springframework.data.domain.ReactiveAuditorAware; + +/** + * Annotation to enable auditing in Couchbase using reactive infrastructure via annotation + * configuration. + * + * @author Jorge Rodríguez Martín + * @since 4.2 + */ +@Inherited +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(ReactiveCouchbaseAuditingRegistrar.class) +public @interface EnableReactiveCouchbaseAuditing { + + /** + * Configures the {@link ReactiveAuditorAware} bean to be used to lookup the current principal. + * + * @return empty {@link String} by default. + */ + String auditorAwareRef() default ""; + + /** + * Configures whether the creation and modification dates are set. Defaults to {@literal true}. + * + * @return {@literal true} by default. + */ + boolean setDates() default true; + + /** + * Configures whether the entity shall be marked as modified on creation. Defaults to {@literal + * true}. + * + * @return {@literal true} by default. + */ + boolean modifyOnCreate() default true; + + /** + * Configures a {@link DateTimeProvider} bean name that allows customizing the timestamp to be + * used for setting creation and modification dates. + * + * @return empty {@link String} by default. + */ + String dateTimeProviderRef() default ""; +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/PersistentEntitiesFactoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/PersistentEntitiesFactoryBean.java new file mode 100644 index 000000000..19182b7aa --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/PersistentEntitiesFactoryBean.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.repository.auditing; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.mapping.context.PersistentEntities; + +/** + * Simple helper to be able to wire the {@link PersistentEntities} from a {@link + * MappingCouchbaseConverter} bean available in the application context. + * + * @author Jorge Rodríguez Martín + * @since 4.2 + */ +class PersistentEntitiesFactoryBean implements FactoryBean { + + private final MappingCouchbaseConverter converter; + + /** + * Creates a new {@link PersistentEntitiesFactoryBean} for the given {@link + * MappingCouchbaseConverter}. + * + * @param converter must not be {@literal null}. + */ + public PersistentEntitiesFactoryBean(final MappingCouchbaseConverter converter) { + this.converter = converter; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.beans.factory.FactoryBean#getObject() + */ + @Override + public PersistentEntities getObject() { + return PersistentEntities.of(this.converter.getMappingContext()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + public Class getObjectType() { + return PersistentEntities.class; + } + +} 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 new file mode 100644 index 000000000..5f71197d7 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/auditing/ReactiveCouchbaseAuditingRegistrar.java @@ -0,0 +1,81 @@ +package org.springframework.data.couchbase.repository.auditing; + +import static org.springframework.data.couchbase.config.BeanNames.*; + +import java.lang.annotation.Annotation; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler; +import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport; +import org.springframework.data.auditing.config.AuditingConfiguration; +import org.springframework.data.config.ParsingUtils; +import org.springframework.data.couchbase.core.mapping.event.ReactiveAuditingEntityCallback; +import org.springframework.util.Assert; + + +/** + * A support registrar that allows to set up reactive auditing for Couchbase (including {@link org.springframework.data.auditing.ReactiveAuditingHandler} and { + * IsNewStrategyFactory} set up). See {@link EnableReactiveCouchbaseAuditing} for the associated annotation. + * + * @author Jorge Rodríguez Martín + * @since 4.2 + */ +class ReactiveCouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation() + */ + @Override + protected Class getAnnotation() { + return EnableReactiveCouchbaseAuditing.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName() + */ + @Override + protected String getAuditingHandlerBeanName() { + return REACTIVE_COUCHBASE_AUDITING_HANDLER; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration) + */ + @Override + protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) { + Assert.notNull(configuration, "AuditingConfiguration must not be null!"); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class); + + BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class); + definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); + + builder.addConstructorArgValue(definition.getBeanDefinition()); + return configureDefaultAuditHandlerAttributes(configuration, builder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry) + */ + @Override + protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition, + BeanDefinitionRegistry registry) { + Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!"); + Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class); + + builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry)); + builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource()); + + registerInfrastructureBeanWithId(builder.getBeanDefinition(), + ReactiveAuditingEntityCallback.class.getName(), registry); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/Airport.java b/src/test/java/org/springframework/data/couchbase/domain/Airport.java index eb0441894..d920c9b26 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/Airport.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Airport.java @@ -16,8 +16,10 @@ package org.springframework.data.couchbase.domain; +import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.annotation.Version; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.couchbase.core.mapping.Document; @@ -36,6 +38,11 @@ public class Airport extends ComparableEntity { String icao; + @CreatedBy private String createdBy; + + @Version long version; + + @PersistenceConstructor public Airport(String id, String iata, String icao) { this.id = id; @@ -55,4 +62,7 @@ public String getIcao() { return icao; } + public String getCreatedBy() { + return createdBy; + } } diff --git a/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java b/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java new file mode 100644 index 000000000..447ac1276 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2021 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.domain; + +import org.springframework.data.domain.ReactiveAuditorAware; +import reactor.core.publisher.Mono; + + +/** + * This class returns a string that represents the current user + * + * @author Jorge Rodríguez Martín + * @since 4.2 + */ +public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware { + + @Override + public Mono getCurrentAuditor() { + 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 abf1adfc2..4532a4e48 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -38,8 +38,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.auditing.DateTimeProvider; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.core.CouchbaseTemplate; @@ -50,10 +52,13 @@ import org.springframework.data.couchbase.domain.Airport; import org.springframework.data.couchbase.domain.AirportRepository; import org.springframework.data.couchbase.domain.Iata; +import org.springframework.data.couchbase.domain.NaiveAuditorAware; import org.springframework.data.couchbase.domain.Person; import org.springframework.data.couchbase.domain.PersonRepository; import org.springframework.data.couchbase.domain.User; import org.springframework.data.couchbase.domain.UserRepository; +import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider; +import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod; import org.springframework.data.couchbase.repository.query.CouchbaseRepositoryQuery; @@ -378,6 +383,21 @@ void couchbaseRepositoryQuery() throws Exception { assertEquals(user, users.get(0)); } + @Test + void findBySimplePropertyAudited() { + Airport vie = null; + try { + vie = new Airport("airports::vie", "vie", "low2"); + Airport saved = airportRepository.save(vie); + List airports1 = airportRepository.findAllByIata("vie"); + assertEquals(saved, airports1.get(0)); + assertEquals(saved.getCreatedBy(), "auditor"); // NaiveAuditorAware will provide this + } finally { + airportRepository.delete(vie); + } + } + + private void sleep(int millis) { try { Thread.sleep(millis); // so they are executed out-of-order @@ -386,6 +406,7 @@ private void sleep(int millis) { @Configuration @EnableCouchbaseRepositories("org.springframework.data.couchbase") + @EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef") static class Config extends AbstractCouchbaseConfiguration { @Override @@ -408,6 +429,15 @@ public String getBucketName() { return bucketName(); } + @Bean(name = "auditorAwareRef") + public NaiveAuditorAware testAuditorAware() { + return new NaiveAuditorAware(); + } + + @Bean(name = "dateTimeProviderRef") + public DateTimeProvider testDateTimeProvider() { + return new AuditingDateTimeProvider(); + } } } 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 55288ada3..9f10bc476 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryKeyValueIntegrationTests.java @@ -18,15 +18,22 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; import java.util.Optional; import java.util.UUID; - import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.auditing.DateTimeProvider; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.domain.Airport; +import org.springframework.data.couchbase.domain.ReactiveAirportRepository; +import org.springframework.data.couchbase.domain.ReactiveNaiveAuditorAware; import org.springframework.data.couchbase.domain.ReactiveUserRepository; import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider; +import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing; import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories; import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; import org.springframework.data.couchbase.util.ClusterType; @@ -39,23 +46,40 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster @Autowired ReactiveUserRepository userRepository; + @Autowired ReactiveAirportRepository airportRepository; + @Test void saveAndFindById() { User user = new User(UUID.randomUUID().toString(), "f", "l"); assertFalse(userRepository.existsById(user.getId()).block()); - userRepository.save(user).block(); + final User save = userRepository.save(user).block(); Optional found = userRepository.findById(user.getId()).blockOptional(); assertTrue(found.isPresent()); - found.ifPresent(u -> assertEquals(user, u)); + found.ifPresent(u -> assertEquals(save, u)); assertTrue(userRepository.existsById(user.getId()).block()); } + @Test + void findBySimplePropertyAudited() { + Airport vie = null; + try { + vie = new Airport("airports::vie", "vie", "low2"); + Airport saved = airportRepository.save(vie).block(); + List airports1 = airportRepository.findAllByIata("vie").collectList().block(); + assertEquals(saved, airports1.get(0)); + assertEquals(saved.getCreatedBy(), "auditor"); // NaiveAuditorAware will provide this + } finally { + airportRepository.delete(vie).block(); + } + } + @Configuration @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") + @EnableReactiveCouchbaseAuditing static class Config extends AbstractCouchbaseConfiguration { @Override @@ -78,6 +102,16 @@ public String getBucketName() { return bucketName(); } + @Bean(name = "auditorAwareRef") + public ReactiveNaiveAuditorAware testAuditorAware() { + return new ReactiveNaiveAuditorAware(); + } + + @Bean(name = "dateTimeProviderRef") + public DateTimeProvider testDateTimeProvider() { + return new AuditingDateTimeProvider(); + } + } }