From 3787fcc1723331df686991f33be781b7b39e792a Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Tue, 23 Mar 2021 02:25:38 +0800 Subject: [PATCH 1/7] Fix indent (#1094) --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 864533fff..dd04c0ddf 100644 --- a/pom.xml +++ b/pom.xml @@ -216,11 +216,11 @@ sonatype-snapshot https://oss.sonatype.org/content/repositories/snapshots - true - + true + - false - + false + jitpack.io From 24116aee35938f61477c17d2e79e64404429fea7 Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Wed, 24 Mar 2021 07:00:41 -0700 Subject: [PATCH 2/7] Update versions to correct values. (#1106) Closes #1105. Co-authored-by: mikereiche --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 52e76a285..9774fbccc 100644 --- a/README.adoc +++ b/README.adoc @@ -109,7 +109,7 @@ Add the Maven dependency: org.springframework.data spring-data-couchbase - ${version}.RELEASE + ${version} ---- @@ -120,7 +120,7 @@ If you'd rather like the latest snapshots of the upcoming major version, use our org.springframework.data spring-data-couchbase - ${version}.BUILD-SNAPSHOT + ${version}-SNAPSHOT From 214b25075adb3a74e149a383e118285fd86a251b Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Wed, 24 Mar 2021 07:57:11 -0700 Subject: [PATCH 3/7] Use queryScanConsistency on reactive deleteAll(). (#1108) It was present on non-Reactive, but missing from reactive. Closes #1096. Original pull request #1108. Co-authored-by: mikereiche --- .../SimpleReactiveCouchbaseRepository.java | 6 +++--- .../domain/ReactiveAirportRepository.java | 4 ++++ ...chbaseRepositoryQueryIntegrationTests.java | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java index d6c69ef71..edb8ab0f4 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java @@ -16,7 +16,7 @@ package org.springframework.data.couchbase.repository.support; -import static org.springframework.data.couchbase.repository.support.Util.*; +import static org.springframework.data.couchbase.repository.support.Util.hasNonZeroVersionProperty; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -26,7 +26,6 @@ import java.util.stream.Collectors; import org.reactivestreams.Publisher; - import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; import org.springframework.data.couchbase.core.query.Query; @@ -189,7 +188,8 @@ public Mono count() { @Override public Mono deleteAll() { - return operations.removeByQuery(entityInformation.getJavaType()).all().then(); + return operations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).all() + .then(); } /** diff --git a/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java index 63cb3388d..51c4d48cc 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/ReactiveAirportRepository.java @@ -45,6 +45,10 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository findAll(); + @Override + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Mono deleteAll(); + @Override Mono save(Airport a); diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java index d6b582f7a..1c2d0d9bd 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java @@ -195,6 +195,25 @@ void deleteAllById() { } } + @Test + void deleteAll() { + + Airport vienna = new Airport("airports::vie", "vie", "LOWW"); + Airport frankfurt = new Airport("airports::fra", "fra", "EDDF"); + Airport losAngeles = new Airport("airports::lax", "lax", "KLAX"); + + try { + airportRepository.saveAll(asList(vienna, frankfurt, losAngeles)).as(StepVerifier::create) + .expectNext(vienna, frankfurt, losAngeles).verifyComplete(); + + airportRepository.deleteAll().as(StepVerifier::create).verifyComplete(); + + airportRepository.findAll().as(StepVerifier::create).verifyComplete(); + } finally { + airportRepository.deleteAll().block(); + } + } + @Configuration @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") static class Config extends AbstractCouchbaseConfiguration { From 521006f0886cbc4e80b49657648c4e5f3cba36ef Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Wed, 24 Mar 2021 12:10:28 -0700 Subject: [PATCH 4/7] Add QueryCriteria arrayContaining. (#1110) Add QueryCriteria arrayContaining which maps to n1ql array_containing. Closes #1073. Original pull request #1109. Co-authored-by: mikereiche --- .../data/couchbase/core/query/QueryCriteria.java | 7 +++++++ .../data/couchbase/core/query/QueryCriteriaTests.java | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java index 7f1305812..4b7da2459 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java @@ -192,6 +192,13 @@ public QueryCriteria containing(@Nullable Object o) { return this; } + public QueryCriteria arrayContaining(@Nullable Object o) { + operator = "ARRAY_CONTAINING"; + value = new Object[] { o }; + format = "array_containing(%1$s, %3$s)"; + return this; + } + public QueryCriteria notContaining(@Nullable Object o) { value = new QueryCriteria[] { wrap(containing(o)) }; operator = "NOT"; diff --git a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java index ca1e06afd..cc563e015 100644 --- a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java @@ -123,14 +123,11 @@ void testStartingWith() { assertEquals("`name` like (\"Cou\"||\"%\")", c.export()); } - /* cannot do this properly yet because in arg to when() in - * startingWith() cannot be a QueryCriteria @Test void testStartingWithExpr() { QueryCriteria c = where(i("name")).startingWith(where(i("name")).plus("xxx")); - assertEquals("`name` like (((`name` || "xxx") || ""%""))", c.export()); + assertEquals("`name` like (((`name` || \"xxx\"))||\"%\")", c.export()); } - */ @Test void testEndingWith() { @@ -162,6 +159,12 @@ void testNotContaining() { assertEquals("not( (contains(`name`, \"Elvis\")) )", c.export()); } + @Test + void testArrayContaining() { + QueryCriteria c = where(i("name")).arrayContaining("Elvis"); + assertEquals("array_containing(`name`, \"Elvis\")", c.export()); + } + @Test void testLike() { QueryCriteria c = where(i("name")).like("%ouch%"); From fcfe778c4a73674ab10950a2fcfcd68ac4dfb774 Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Wed, 24 Mar 2021 14:25:28 -0700 Subject: [PATCH 5/7] Support enum arguments on repository queries. (#1112) Support enum in AbstractCouchbaseConverter.convertForWriteIfNeeded() and also call that from MappingCouchbaseConverter.getPotentiallyConvertedSimpleWrite() Closes #1069. Original pull request #1112. Co-authored-by: mikereiche --- .../core/convert/AbstractCouchbaseConverter.java | 4 +++- .../core/convert/MappingCouchbaseConverter.java | 11 ++--------- .../data/couchbase/domain/AirportRepository.java | 3 +++ .../springframework/data/couchbase/domain/Iata.java | 6 ++++++ .../CouchbaseRepositoryQueryIntegrationTests.java | 13 +++++++++++++ 5 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 src/test/java/org/springframework/data/couchbase/domain/Iata.java diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java index cc55d0503..d6cafffaa 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java @@ -17,6 +17,7 @@ package org.springframework.data.couchbase.core.convert; import java.util.Collections; +import java.util.Optional; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.ConversionService; @@ -100,7 +101,8 @@ public Object convertForWriteIfNeeded(Object value) { return this.conversions.getCustomWriteTarget(value.getClass()) // .map(it -> (Object) this.conversionService.convert(value, it)) // - .orElse(value); + .orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value); + } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java index e84055707..7793be558 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java @@ -774,15 +774,8 @@ private void writeSimpleInternal(final Object source, final CouchbaseDocument ta target.put(key, getPotentiallyConvertedSimpleWrite(source)); } - private Object getPotentiallyConvertedSimpleWrite(final Object value) { - if (value == null) { - return null; - } - - Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); - - return customTarget.map(it -> (Object) conversionService.convert(value, it)) - .orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value); + public Object getPotentiallyConvertedSimpleWrite(final Object value) { + return convertForWriteIfNeeded(value); } /** diff --git a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java index 911aebd70..1b04222f9 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -50,6 +50,9 @@ public interface AirportRepository extends PagingAndSortingRepository getAllByIata(String iata); diff --git a/src/test/java/org/springframework/data/couchbase/domain/Iata.java b/src/test/java/org/springframework/data/couchbase/domain/Iata.java new file mode 100644 index 000000000..5e603005c --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/Iata.java @@ -0,0 +1,6 @@ +package org.springframework.data.couchbase.domain; + +public enum Iata { + vie, + xxx +} 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 db9f53fc9..f0d17add4 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -47,6 +47,7 @@ import org.springframework.data.couchbase.domain.Address; 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.Person; import org.springframework.data.couchbase.domain.PersonRepository; import org.springframework.data.couchbase.domain.User; @@ -167,6 +168,18 @@ void findBySimpleProperty() { } } + @Test + void findByEnum() { + Airport vie = null; + try { + vie = new Airport("airports::vie", "vie", "loww"); + vie = airportRepository.save(vie); + Airport airport2 = airportRepository.findByIata(Iata.vie); + assertEquals(airport2.getId(), vie.getId()); + } finally { + airportRepository.delete(vie); + } + } @Test public void testCas() { User user = new User("1", "Dave", "Wilson"); From 4a900b8a66032b969cb7b8c4022c20b3b431b026 Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Thu, 25 Mar 2021 08:37:35 -0700 Subject: [PATCH 6/7] Support enum parameters to repository queries. (#1111) * Add QueryCriteria arrayContaining. Add QueryCriteria arrayContaining which maps to n1ql array_containing. Closes #1073. Original pull request #1109. * Support enum parameters to repository queries. Support enums in AbstractCouchbaseConverter.convertForWriteIfNeeded(). Closes #1069. Original pull request #1110. Co-authored-by: mikereiche From b0dd3d98ccc0814cebaec4f75dd45c1f9d62b22b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Rodr=C3=ADguez=20Mart=C3=ADn?= Date: Tue, 16 Mar 2021 18:12:08 +0100 Subject: [PATCH 7/7] DATACOUCH-955 - Add support for reactive auditing and ReactiveEntityCallbacks. Also, adapt CouchbaseAuditingRegistrar for support AuditingEntityCallback. Co-authored-by: Carlos Espinado --- .../data/couchbase/config/BeanNames.java | 8 +- .../couchbase/core/CouchbaseTemplate.java | 6 +- .../core/CouchbaseTemplateSupport.java | 12 +- ...utableFindByAnalyticsOperationSupport.java | 4 +- .../ExecutableFindByIdOperationSupport.java | 4 +- ...ExecutableFindByQueryOperationSupport.java | 4 +- ...eFindFromReplicasByIdOperationSupport.java | 4 +- .../ExecutableInsertByIdOperationSupport.java | 4 +- ...ExecutableReplaceByIdOperationSupport.java | 4 +- .../ExecutableUpsertByIdOperationSupport.java | 4 +- .../core/NonReactiveSupportWrapper.java | 65 +++++ .../core/ReactiveCouchbaseTemplate.java | 9 +- .../ReactiveCouchbaseTemplateSupport.java | 222 ++++++++++++++++++ ...activeFindByAnalyticsOperationSupport.java | 21 +- .../ReactiveFindByIdOperationSupport.java | 14 +- .../ReactiveFindByQueryOperationSupport.java | 24 +- ...eFindFromReplicasByIdOperationSupport.java | 12 +- .../ReactiveInsertByIdOperationSupport.java | 32 +-- .../ReactiveReplaceByIdOperationSupport.java | 25 +- .../core/ReactiveTemplateSupport.java | 35 +++ .../ReactiveUpsertByIdOperationSupport.java | 30 +-- .../data/couchbase/core/TemplateSupport.java | 33 +++ .../mapping/event/AuditingEntityCallback.java | 68 ++++++ .../event/ReactiveAfterConvertCallback.java | 40 ++++ .../event/ReactiveAuditingEntityCallback.java | 73 ++++++ .../event/ReactiveBeforeConvertCallback.java | 41 ++++ .../auditing/CouchbaseAuditingRegistrar.java | 4 +- .../EnableReactiveCouchbaseAuditing.java | 71 ++++++ .../PersistentEntitiesFactoryBean.java | 63 +++++ .../ReactiveCouchbaseAuditingRegistrar.java | 81 +++++++ .../data/couchbase/domain/Airport.java | 10 + .../domain/ReactiveNaiveAuditorAware.java | 35 +++ ...chbaseRepositoryQueryIntegrationTests.java | 30 +++ ...aseRepositoryKeyValueIntegrationTests.java | 40 +++- 34 files changed, 1033 insertions(+), 99 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAfterConvertCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveAuditingEntityCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/core/mapping/event/ReactiveBeforeConvertCallback.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/auditing/EnableReactiveCouchbaseAuditing.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/auditing/PersistentEntitiesFactoryBean.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/auditing/ReactiveCouchbaseAuditingRegistrar.java create mode 100644 src/test/java/org/springframework/data/couchbase/domain/ReactiveNaiveAuditorAware.java 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 d3205df94..1d315b0a0 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. @@ -36,7 +36,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 { @@ -45,29 +46,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 @@ -91,12 +94,12 @@ public Flux all() { } else { return throwable; } - }).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> { + }).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).flatMap(row -> { String id = row.getString("__id"); long cas = row.getLong("__cas"); row.removeKey("__id"); row.removeKey("__cas"); - return template.support().decodeEntity(id, row.toString(), cas, domainType); + return support.decodeEntity(id, row.toString(), cas, domainType); }); }); } @@ -131,7 +134,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 d9cd78af3..179b701a4 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. @@ -44,7 +44,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 { @@ -56,10 +56,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!"); @@ -81,41 +83,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 @@ -143,7 +145,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) { @@ -152,7 +154,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 d6208eeeb..b4856af2c 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.couchbase.core.mapping.Document; /** @@ -34,6 +36,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; @@ -53,4 +60,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 f0d17add4..fa88b72fb 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; @@ -48,10 +50,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; @@ -343,6 +348,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 @@ -351,6 +371,7 @@ private void sleep(int millis) { @Configuration @EnableCouchbaseRepositories("org.springframework.data.couchbase") + @EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef") static class Config extends AbstractCouchbaseConfiguration { @Override @@ -373,6 +394,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(); + } + } }