From 5c80ee0087e2bac43cea42aa141487dfef033a12 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 17 Oct 2019 14:30:34 +0200 Subject: [PATCH] DATAMONGO-2388 - Fix CodecConfigurationException when reading index info that contains DbRef. Provide the default CodecRegistry when converting partial index data to its String representation used in IndexInfo. Original pull request: #797. --- .../data/mongodb/core/index/IndexInfo.java | 20 ++++- .../data/mongodb/util/BsonUtils.java | 85 +++++++++++++++++++ ...efaultIndexOperationsIntegrationTests.java | 17 ++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index f58435548..333b5cf82 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Optional; import org.bson.Document; +import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -107,8 +108,8 @@ public class IndexInfo { boolean sparse = sourceDocument.containsKey("sparse") ? (Boolean) sourceDocument.get("sparse") : false; String language = sourceDocument.containsKey("default_language") ? (String) sourceDocument.get("default_language") : ""; - String partialFilter = sourceDocument.containsKey("partialFilterExpression") - ? ((Document) sourceDocument.get("partialFilterExpression")).toJson() : null; + + String partialFilter = extractPartialFilterString(sourceDocument); IndexInfo info = new IndexInfo(indexFields, name, unique, sparse, language); info.partialFilterExpression = partialFilter; @@ -116,6 +117,21 @@ public class IndexInfo { return info; } + /** + * @param sourceDocument + * @return the {@link String} representation of the partial filter {@link Document}. + * @since 2.1.11 + */ + @Nullable + private static String extractPartialFilterString(Document sourceDocument) { + + if (!sourceDocument.containsKey("partialFilterExpression")) { + return null; + } + + return BsonUtils.toJson(sourceDocument.get("partialFilterExpression", Document.class)); + } + /** * Returns the individual index fields of the index. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java index 1aa904f8c..a1a2b9636 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java @@ -15,17 +15,27 @@ */ package org.springframework.data.mongodb.util; +import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.bson.BsonValue; import org.bson.Document; import org.bson.conversions.Bson; +import org.bson.json.JsonParseException; + +import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.DBRef; +import com.mongodb.MongoClientSettings; /** * @author Christoph Strobl @@ -104,4 +114,79 @@ public class BsonUtils { return value; } } + + /** + * Serialize the given {@link Document} as Json applying default codecs if necessary. + * + * @param source + * @return + * @since 2.1.1 + */ + @Nullable + public static String toJson(@Nullable Document source) { + + if (source == null) { + return null; + } + + try { + return source.toJson(); + } catch (Exception e) { + return toJson((Object) source); + } + } + + private static String toJson(@Nullable Object value) { + + if (value == null) { + return null; + } + + try { + return value instanceof Document + ? ((Document) value).toJson(MongoClientSettings.getDefaultCodecRegistry().get(Document.class)) + : serializeValue(value); + + } catch (Exception e) { + + if (value instanceof Collection) { + return toString((Collection) value); + } else if (value instanceof Map) { + return toString((Map) value); + } else if (ObjectUtils.isArray(value)) { + return toString(Arrays.asList(ObjectUtils.toObjectArray(value))); + } + + throw e instanceof JsonParseException ? (JsonParseException) e : new JsonParseException(e); + } + } + + private static String serializeValue(@Nullable Object value) { + + if (value == null) { + return "null"; + } + + String documentJson = new Document("toBeEncoded", value).toJson(); + return documentJson.substring(documentJson.indexOf(':') + 1, documentJson.length() - 1).trim(); + } + + private static String toString(Map source) { + + return iterableToDelimitedString(source.entrySet(), "{ ", " }", + entry -> String.format("\"%s\" : %s", entry.getKey(), toJson(entry.getValue()))); + } + + private static String toString(Collection source) { + return iterableToDelimitedString(source, "[ ", " ]", BsonUtils::toJson); + } + + private static String iterableToDelimitedString(Iterable source, String prefix, String postfix, + Converter transformer) { + + return prefix + + StringUtils.collectionToCommaDelimitedString( + StreamSupport.stream(source.spliterator(), false).map(transformer::convert).collect(Collectors.toList())) + + postfix; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java index 681d7225d..c922075e5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java @@ -21,6 +21,7 @@ import static org.junit.Assume.*; import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*; import static org.springframework.data.mongodb.core.query.Criteria.*; +import org.bson.BsonDocument; import org.bson.Document; import org.junit.Before; import org.junit.Test; @@ -40,6 +41,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.IndexOptions; /** * Integration tests for {@link DefaultIndexOperations}. @@ -150,6 +152,21 @@ public class DefaultIndexOperationsIntegrationTests { assertThat(info.getPartialFilterExpression()).isEqualTo("{ \"a_g_e\" : { \"$gte\" : 10 } }"); } + @Test // DATAMONGO-2388 + public void shouldReadIndexWithPartialFilterContainingDbRefCorrectly() { + + BsonDocument partialFilter = BsonDocument.parse( + "{ \"the-ref\" : { \"$ref\" : \"other-collection\", \"$id\" : { \"$oid\" : \"59ce08baf264b906810fe8c5\"} } }"); + IndexOptions indexOptions = new IndexOptions(); + indexOptions.name("partial-with-dbref"); + indexOptions.partialFilterExpression(partialFilter); + + collection.createIndex(BsonDocument.parse("{ \"key-1\" : 1, \"key-2\": 1}"), indexOptions); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-dbref"); + assertThat(BsonDocument.parse(info.getPartialFilterExpression())).isEqualTo(partialFilter); + } + @Test // DATAMONGO-1518 public void shouldCreateIndexWithCollationCorrectly() {