From 932901e709815ef6130894a51370496c8697e4bf Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Mon, 31 Oct 2022 06:34:02 -1000 Subject: [PATCH] Accept entity parameters to repository methods. (#1595) Closed #1586. --- .../convert/AbstractCouchbaseConverter.java | 32 +++++++++++++++---- .../convert/MappingCouchbaseConverter.java | 6 ++-- .../query/StringBasedN1qlQueryParser.java | 12 ++++++- .../domain/UserSubmissionRepository.java | 5 +++ ...chbaseRepositoryQueryIntegrationTests.java | 27 +++++++++++++--- 5 files changed, 66 insertions(+), 16 deletions(-) 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 0017c03e..df362f11 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 @@ -16,18 +16,21 @@ package org.springframework.data.couchbase.core.convert; +import java.util.Collection; import java.util.Collections; -import com.couchbase.client.java.query.QueryScanConsistency; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.CustomConversions; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; /** * An abstract {@link CouchbaseConverter} that provides the basics for the {@link MappingCouchbaseConverter}. @@ -141,19 +144,34 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter, * This convertForWriteIfNeed takes only the value to convert. It cannot access the annotations of the Field being * converted. * - * @param value the value to be converted to the class that would actually be stored. + * @param inValue the value to be converted to the class that would actually be stored. * @return */ @Override - public Object convertForWriteIfNeeded(Object value) { - if (value == null) { + public Object convertForWriteIfNeeded(Object inValue) { + if (inValue == null) { return null; } - return this.conversions.getCustomWriteTarget(value.getClass()) // - .map(it -> (Object) this.conversionService.convert(value, it)) // - .orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value); + Object value = this.conversions.getCustomWriteTarget(inValue.getClass()) // + .map(it -> (Object) this.conversionService.convert(inValue, it)) // + .orElse(inValue); + Class elementType = value.getClass(); + + if (elementType == null || conversions.isSimpleType(elementType)) { + value = Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; + } else if (value instanceof Collection || elementType.isArray()) { + TypeInformation type = ClassTypeInformation.from(value.getClass()); + value = ((MappingCouchbaseConverter) this).writeCollectionInternal(MappingCouchbaseConverter.asCollection(value), + new CouchbaseList(conversions.getSimpleTypeHolder()), type, null, null); + } else { + CouchbaseDocument embeddedDoc = new CouchbaseDocument(); + TypeInformation type = ClassTypeInformation.from(value.getClass()); + ((MappingCouchbaseConverter) this).writeInternalRoot(value, embeddedDoc, type, false, null); + value = embeddedDoc; + } + return 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 0733b8b5..42ec20b6 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 @@ -169,7 +169,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem * @param source the source object. * @return the target collection. */ - private static Collection asCollection(final Object source) { + protected static Collection asCollection(final Object source) { if (source instanceof Collection) { return (Collection) source; } @@ -459,7 +459,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem * @param property will be null for the root */ @SuppressWarnings("unchecked") - protected void writeInternalRoot(final Object source, CouchbaseDocument target, TypeInformation typeHint, + public void writeInternalRoot(final Object source, CouchbaseDocument target, TypeInformation typeHint, boolean withId, CouchbasePersistentProperty property) { if (source == null) { return; @@ -759,7 +759,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem * @param target the target document. * @return the created couchbase list. */ - private CouchbaseList writeCollectionInternal(final Collection source, final CouchbaseList target, + public CouchbaseList writeCollectionInternal(final Collection source, final CouchbaseList target, final TypeInformation type, CouchbasePersistentProperty prop, ConvertingPropertyAccessor accessor) { for (Object element : source) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java index d5f0765c..656c1d02 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java @@ -33,6 +33,8 @@ import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.mapping.Expiration; import org.springframework.data.couchbase.core.query.N1QLExpression; @@ -434,6 +436,12 @@ public class StringBasedN1qlQueryParser { for (Parameter parameter : this.queryMethod.getParameters().getBindableParameters()) { Object rawValue = accessor.getBindableValue(parameter.getIndex()); Object value = couchbaseConverter.convertForWriteIfNeeded(rawValue); + if (value instanceof CouchbaseDocument) { + value = ((CouchbaseDocument) value).export(); + } + if (value instanceof CouchbaseList) { + value = ((CouchbaseList) value).export(); + } putPositionalValue(posValues, value); } return posValues; @@ -452,7 +460,9 @@ public class StringBasedN1qlQueryParser { String placeholder = parameter.getPlaceholder(); Object rawValue = accessor.getBindableValue(parameter.getIndex()); Object value = couchbaseConverter.convertForWriteIfNeeded(rawValue); - + if (value instanceof CouchbaseDocument) { + value = ((CouchbaseDocument) value).export(); + } if (placeholder != null && placeholder.charAt(0) == ':') { placeholder = placeholder.replaceFirst(":", ""); putNamedValue(namedValues, placeholder, value); diff --git a/src/test/java/org/springframework/data/couchbase/domain/UserSubmissionRepository.java b/src/test/java/org/springframework/data/couchbase/domain/UserSubmissionRepository.java index 6eace6fe..ab80f82f 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/UserSubmissionRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/UserSubmissionRepository.java @@ -19,6 +19,7 @@ package org.springframework.data.couchbase.domain; import java.util.List; import org.springframework.data.couchbase.repository.CouchbaseRepository; +import org.springframework.data.couchbase.repository.Query; import org.springframework.data.couchbase.repository.ScanConsistency; import org.springframework.stereotype.Repository; @@ -35,4 +36,8 @@ public interface UserSubmissionRepository extends CouchbaseRepository findByUsername(String username); + @Query("UPDATE #{#n1ql.bucket} set address=$2 where meta().id=$1") + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + List setByIdAddress(String id, Address abc); + } 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 4c09a973..a051bed1 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -368,14 +368,14 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr AirportRepositoryScanConsistencyTest airportRepositoryRP = (AirportRepositoryScanConsistencyTest) ac .getBean("airportRepositoryScanConsistencyTest"); - List sizeBeforeTest = (List)airportRepositoryRP.findAll(); + List sizeBeforeTest = (List) airportRepositoryRP.findAll(); assertEquals(0, sizeBeforeTest.size()); boolean notFound = false; for (int i = 0; i < 100; i++) { Airport vie = new Airport("airports::vie", "vie", "low9"); Airport saved = airportRepositoryRP.save(vie); - List allSaved = (List)airportRepositoryRP.findAll(); + List allSaved = (List) airportRepositoryRP.findAll(); couchbaseTemplate.removeById(Airport.class).one(saved.getId()); if (allSaved.isEmpty()) { notFound = true; @@ -542,8 +542,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr // set version == 0 so save() will be an upsert, not a replace Airport saved = airportRepository.save(vie.clearVersion()); try { - airport2 = airportRepository.withOptions(queryOptions().scanConsistency(NOT_BOUNDED)) - .iata(saved.getIata()); + airport2 = airportRepository.withOptions(queryOptions().scanConsistency(NOT_BOUNDED)).iata(saved.getIata()); if (airport2 == null) { break; } @@ -679,7 +678,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr airportRepository.saveAll( Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT))) .collect(Collectors.toSet())); - couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).all(); + List aList = couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).all(); Long count = airportRepository.countFancyExpression(asList("JFK"), asList("jfk"), false); assertEquals(1, count); @@ -842,6 +841,24 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr } } + @Test + void updateObject() throws Exception { + UserSubmission userSubmission = new UserSubmission(); + userSubmission.setId("123"); + try { + userSubmission.setUsername("updateObject"); + userSubmissionRepository.save(userSubmission); + Address address = new Address(); // plaintext address with encrypted street + address.setStreet("Olcott Street"); + address.setCity("Santa Clara"); + userSubmissionRepository.setByIdAddress(userSubmission.getId(), address); + Optional fetched = userSubmissionRepository.findById(userSubmission.getId()); + assertEquals(address, fetched.get().getAddress()); + } finally { + airportRepository.deleteById(userSubmission.getId()); + } + } + @Test void stringDeleteTest() throws Exception { Airport airport = new Airport("airports::vie", "vie", "lowx");