From 3c8d48772793ed7dd28e738d821c96294cbc6dd4 Mon Sep 17 00:00:00 2001 From: mikereiche Date: Mon, 23 Jan 2023 17:12:16 -0800 Subject: [PATCH] Add Converters for JsonNode, JsonObject and JsonArray. Closes #1650. --- .../AbstractCouchbaseConfiguration.java | 11 ++- .../core/convert/OtherConverters.java | 98 +++++++++++++++++++ ...hbaseTemplateKeyValueIntegrationTests.java | 9 +- .../data/couchbase/domain/User.java | 29 ++++++ .../query/StringN1qlQueryCreatorTests.java | 12 +-- 5 files changed, 146 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 9eacc4c26..14da5ebb5 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -415,7 +415,12 @@ public CustomConversions customConversions() { * @return must not be {@literal null}. */ public CustomConversions customConversions(CryptoManager cryptoManager, ObjectMapper objectMapper) { - List newConverters = new ArrayList(); + List newConverters = new ArrayList(); + // The following + newConverters.add(new OtherConverters.EnumToObject(getObjectMapper())); + newConverters.add(new IntegerToEnumConverterFactory(getObjectMapper())); + newConverters.add(new StringToEnumConverterFactory(getObjectMapper())); + newConverters.add(new BooleanToEnumConverterFactory(getObjectMapper())); CustomConversions customConversions = CouchbaseCustomConversions.create(configurationAdapter -> { SimplePropertyValueConversions valueConversions = new SimplePropertyValueConversions(); valueConversions.setConverterFactory( @@ -424,10 +429,6 @@ public CustomConversions customConversions(CryptoManager cryptoManager, ObjectMa valueConversions.afterPropertiesSet(); // wraps the CouchbasePropertyValueConverterFactory with CachingPVCFactory configurationAdapter.setPropertyValueConversions(valueConversions); configurationAdapter.registerConverters(newConverters); - configurationAdapter.registerConverter(new OtherConverters.EnumToObject(getObjectMapper())); - configurationAdapter.registerConverterFactory(new IntegerToEnumConverterFactory(getObjectMapper())); - configurationAdapter.registerConverterFactory(new StringToEnumConverterFactory(getObjectMapper())); - configurationAdapter.registerConverterFactory(new BooleanToEnumConverterFactory(getObjectMapper())); }); return customConversions; } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java b/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java index 5914dd018..e89786bf6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/OtherConverters.java @@ -24,12 +24,23 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValueModule; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.util.Base64Utils; import com.couchbase.client.core.encryption.CryptoManager; @@ -66,6 +77,12 @@ private OtherConverters() {} converters.add(StringToCharArray.INSTANCE); converters.add(ClassToString.INSTANCE); converters.add(StringToClass.INSTANCE); + converters.add(MapToJsonNode.INSTANCE); + converters.add(JsonNodeToMap.INSTANCE); + converters.add(JsonObjectToMap.INSTANCE); + converters.add(MapToJsonObject.INSTANCE); + converters.add(JsonArrayToCouchbaseList.INSTANCE); + converters.add(CouchbaseListToJsonArray.INSTANCE); // EnumToObject, IntegerToEnumConverterFactory and StringToEnumConverterFactory are // registered in // {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions( @@ -231,4 +248,85 @@ public Object convert(Enum source) { } } + @WritingConverter + public enum JsonNodeToMap implements Converter { + INSTANCE; + static ObjectMapper mapper= new ObjectMapper().registerModule(new JsonValueModule()); + @Override + public CouchbaseDocument convert(JsonNode source) { + if( source == null ){ + return null; + } + return new CouchbaseDocument().setContent((Map)mapper.convertValue(source, new TypeReference>(){})); + } + } + + @ReadingConverter + public enum MapToJsonNode implements Converter { + INSTANCE; + static ObjectMapper mapper= new ObjectMapper().registerModule(new JsonValueModule()); + + @Override + public JsonNode convert(CouchbaseDocument source) { + if( source == null ){ + return null; + } + return mapper.valueToTree(source.export()); + } + } + + @WritingConverter + public enum JsonObjectToMap implements Converter { + INSTANCE; + + @Override + public CouchbaseDocument convert(JsonObject source) { + if( source == null ){ + return null; + } + return new CouchbaseDocument().setContent(source); + } + } + + @ReadingConverter + public enum MapToJsonObject implements Converter { + INSTANCE; + static ObjectMapper mapper= new ObjectMapper(); + + @Override + public JsonObject convert(CouchbaseDocument source) { + if( source == null ){ + return null; + } + return JsonObject.from(source.export()); + } + } + + @WritingConverter + public enum JsonArrayToCouchbaseList implements Converter { + INSTANCE; + + @Override + public CouchbaseList convert(JsonArray source) { + if( source == null ){ + return null; + } + return new CouchbaseList(source.toList()); + } + } + + @ReadingConverter + public enum CouchbaseListToJsonArray implements Converter { + INSTANCE; + static ObjectMapper mapper= new ObjectMapper(); + + @Override + public JsonArray convert(CouchbaseList source) { + if( source == null ){ + return null; + } + return JsonArray.from(source.export()); + } + } + } diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java index 59eb29017..1b747b4a8 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -114,7 +114,7 @@ void findByIdWithExpiry() { assertEquals(1, foundUsers.size(), "should have found exactly 1 user"); assertEquals(user2, foundUsers.iterator().next()); } finally { - couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all(); + //couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all(); } } @@ -445,6 +445,13 @@ void insertById() { User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); User inserted = couchbaseTemplate.insertById(User.class).one(user); assertEquals(user, inserted); + User found = couchbaseTemplate.findById(User.class).one(user.getId()); + assertEquals(inserted, found); + System.err.println("inserted: "+inserted); + System.err.println("found: "+found); + System.err.println("found:jsonNode "+found.jsonNode.toPrettyString()); + System.err.println("found:jsonObject "+found.jsonObject.toString()); + System.err.println("found:jsonArray "+found.jsonArray.toString()); assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(User.class).one(user)); couchbaseTemplate.removeById(User.class).one(user.getId()); } diff --git a/src/test/java/org/springframework/data/couchbase/domain/User.java b/src/test/java/org/springframework/data/couchbase/domain/User.java index b87bd0515..b53c50bde 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/User.java +++ b/src/test/java/org/springframework/data/couchbase/domain/User.java @@ -17,8 +17,13 @@ package org.springframework.data.couchbase.domain; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; @@ -29,6 +34,10 @@ import org.springframework.data.annotation.Version; import org.springframework.data.couchbase.core.mapping.Document; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + /** * User entity for tests * @@ -40,14 +49,34 @@ @TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER) public class User extends AbstractUser implements Serializable { + public JsonNode jsonNode; + public JsonObject jsonObject; + public JsonArray jsonArray; + @PersistenceConstructor public User(final String id, final String firstname, final String lastname) { this.id = id; this.firstname = firstname; this.lastname = lastname; this.subtype = AbstractingTypeMapper.Type.USER; + this.jsonNode = new ObjectNode(JsonNodeFactory.instance); + try { + jsonNode = (new ObjectNode(JsonNodeFactory.instance)).put("myNumber", uid()); + } catch (Exception e) { + e.printStackTrace(); + } + Map map = new HashMap(); + map.put("myNumber", uid()); + this.jsonObject = JsonObject.jo().put("yourNumber",Long.valueOf(uid())); + this.jsonArray = JsonArray.from(Long.valueOf(uid()), Long.valueOf(uid())); } + @Transient int uid=1000; + long uid(){ + return uid++; + } + + @Version protected long version; @Transient protected String transientInfo; @CreatedBy protected String createdBy; diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java index f7ca81176..34f70fa66 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorTests.java @@ -110,7 +110,7 @@ void createsQueryCorrectly() throws Exception { Query query = creator.createQuery(); assertEquals( - "SELECT `_class`, META(`" + bucketName() + "SELECT `_class`, `jsonNode`, `jsonObject`, `jsonArray`, META(`" + bucketName() + "`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`" + bucketName() + "`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `" + bucketName() + "` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2", @@ -131,7 +131,7 @@ void createsQueryCorrectly2() throws Exception { Query query = creator.createQuery(); assertEquals( - "SELECT `_class`, META(`" + bucketName() + "SELECT `_class`, `jsonNode`, `jsonObject`, `jsonArray`, META(`" + bucketName() + "`).`cas` AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`" + bucketName() + "`).`id` AS __id, `firstname`, `lastname`, `subtype` FROM `" + bucketName() + "` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)", @@ -151,11 +151,9 @@ void spelTests() throws Exception { Query query = creator.createQuery(); - assertEquals( - "SELECT `_class`, META(`myCollection`).`cas` AS __cas, `createdBy`, `createdDate`, " - + "`lastModifiedBy`, `lastModifiedDate`, META(`myCollection`).`id` AS __id, `firstname`, " - + "`lastname`, `subtype` FROM `myCollection`|`_class` = \"abstractuser\"" - + "|`myCollection`|`myScope`|`myCollection`", + assertEquals("SELECT `_class`, `jsonNode`, `jsonObject`, `jsonArray`, META(`myCollection`).`cas`" + + " AS __cas, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, META(`myCollection`).`id`" + + " AS __id, `firstname`, `lastname`, `subtype` FROM `myCollection`|`_class` = \"abstractuser\"|`myCollection`|`myScope`|`myCollection`", query.toN1qlSelectString(converter, bucketName(), "myScope", "myCollection", User.class, null, false, null, null)); }