From ee6048e2891e71dc86a0e9919a9427400419250f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 4 Sep 2019 11:23:53 +0200 Subject: [PATCH] DATAMONGO-2357 - Fix read/write for MongoDB client.model GeoJSON types. We now consider native GeoJSON types of the MongoDB client during conversion passing on the raw values to the driver when writing and using the configured MongoDB codecs on read. Original pull request: #786. --- .../AbstractMongoClientConfiguration.java | 1 + .../config/AbstractMongoConfiguration.java | 1 + .../AbstractReactiveMongoConfiguration.java | 1 + .../data/mongodb/core/MongoTemplate.java | 1 + .../mongodb/core/ReactiveMongoTemplate.java | 3 +- .../core/convert/MappingMongoConverter.java | 29 ++++++++++- .../core/mapping/MongoSimpleTypes.java | 17 ++++++ .../core/MongoTemplateMappingTests.java | 52 ++++++++++++++++++- .../core/SessionBoundMongoTemplateTests.java | 1 + 9 files changed, 103 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java index 48da93298..c5f10730f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java @@ -105,6 +105,7 @@ public abstract class AbstractMongoClientConfiguration extends MongoConfiguratio DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory()); MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext()); converter.setCustomConversions(customConversions()); + converter.setCodecRegistryProvider(mongoDbFactory()); return converter; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java index 13241b34d..99e5c786d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoConfiguration.java @@ -111,6 +111,7 @@ public abstract class AbstractMongoConfiguration extends MongoConfigurationSuppo DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory()); MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext()); converter.setCustomConversions(customConversions()); + converter.setCodecRegistryProvider(mongoDbFactory()); return converter; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java index c263df2f7..1b9225490 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java @@ -83,6 +83,7 @@ public abstract class AbstractReactiveMongoConfiguration extends MongoConfigurat MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mongoMappingContext()); converter.setCustomConversions(customConversions()); + converter.setCodecRegistryProvider(reactiveMongoDbFactory()); return converter; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 8aa9c46fd..55a12a434 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -2846,6 +2846,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); converter.setCustomConversions(conversions); + converter.setCodecRegistryProvider(factory); converter.afterPropertiesSet(); return converter; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 1e4760951..f445c1eae 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -2774,7 +2774,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati return type == null ? null : mappingContext.getPersistentEntity(type); } - private static MappingMongoConverter getDefaultMongoConverter() { + private MappingMongoConverter getDefaultMongoConverter() { MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList()); @@ -2784,6 +2784,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati MappingMongoConverter converter = new MappingMongoConverter(NO_OP_REF_RESOLVER, context); converter.setCustomConversions(conversions); + converter.setCodecRegistryProvider(this.mongoDatabaseFactory); converter.afterPropertiesSet(); return converter; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 0d0e7a685..1721bd0d8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -30,11 +30,13 @@ import java.util.Optional; import java.util.Set; import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; import org.bson.conversions.Bson; +import org.bson.json.JsonReader; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -57,6 +59,7 @@ import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.mapping.model.SpELExpressionEvaluator; import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; +import org.springframework.data.mongodb.CodecRegistryProvider; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -103,6 +106,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App protected @Nullable ApplicationContext applicationContext; protected MongoTypeMapper typeMapper; protected @Nullable String mapKeyDotReplacement = null; + protected @Nullable CodecRegistryProvider codecRegistryProvider; private SpELContext spELContext; @@ -141,6 +145,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App public MappingMongoConverter(MongoDbFactory mongoDbFactory, MappingContext, MongoPersistentProperty> mappingContext) { this(new DefaultDbRefResolver(mongoDbFactory), mappingContext); + setCodecRegistryProvider(mongoDbFactory); } /** @@ -178,6 +183,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App this.mapKeyDotReplacement = mapKeyDotReplacement; } + /** + * Configure a {@link CodecRegistryProvider} that provides native MongoDB {@link org.bson.codecs.Codec codecs} for + * reading values. + * + * @param codecRegistryProvider can be {@literal null}. + * @since 2.2 + */ + public void setCodecRegistryProvider(@Nullable CodecRegistryProvider codecRegistryProvider) { + this.codecRegistryProvider = codecRegistryProvider; + } + /* * (non-Javadoc) * @see org.springframework.data.convert.EntityConverter#getMappingContext() @@ -253,6 +269,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App MongoPersistentEntity entity = mappingContext.getPersistentEntity(typeToUse); if (entity == null) { + + if (codecRegistryProvider != null) { + + Optional> codec = codecRegistryProvider.getCodecFor(rawType); + if(codec.isPresent()) { + return codec.get().decode(new JsonReader(target.toJson()), + DecoderContext.builder().build()); + } + } + throw new MappingException(String.format(INVALID_TYPE_TO_READ, target, typeToUse.getType())); } @@ -1650,6 +1676,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App target.spELContext = spELContext; target.setInstantiators(instantiators); target.typeMapper = typeMapper; + target.setCodecRegistryProvider(dbFactory); target.afterPropertiesSet(); return target; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java index f0e24af5a..39163775d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java @@ -33,6 +33,14 @@ import org.bson.types.Symbol; import org.springframework.data.mapping.model.SimpleTypeHolder; import com.mongodb.DBRef; +import com.mongodb.client.model.geojson.Geometry; +import com.mongodb.client.model.geojson.GeometryCollection; +import com.mongodb.client.model.geojson.LineString; +import com.mongodb.client.model.geojson.MultiLineString; +import com.mongodb.client.model.geojson.MultiPoint; +import com.mongodb.client.model.geojson.MultiPolygon; +import com.mongodb.client.model.geojson.Point; +import com.mongodb.client.model.geojson.Polygon; /** * Simple constant holder for a {@link SimpleTypeHolder} enriched with Mongo specific simple types. @@ -80,6 +88,15 @@ public abstract class MongoSimpleTypes { simpleTypes.add(BsonString.class); simpleTypes.add(BsonTimestamp.class); + simpleTypes.add(Geometry.class); + simpleTypes.add(GeometryCollection.class); + simpleTypes.add(LineString.class); + simpleTypes.add(MultiLineString.class); + simpleTypes.add(MultiPoint.class); + simpleTypes.add(MultiPolygon.class); + simpleTypes.add(Point.class); + simpleTypes.add(Polygon.class); + MONGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingTests.java index e9941674f..3477a598a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateMappingTests.java @@ -16,20 +16,30 @@ package org.springframework.data.mongodb.core; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import lombok.Data; + +import java.util.Arrays; import org.bson.Document; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.dao.DataAccessException; +import org.springframework.data.annotation.Id; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.mongodb.MongoException; import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.geojson.Geometry; +import com.mongodb.client.model.geojson.MultiPolygon; +import com.mongodb.client.model.geojson.PolygonCoordinates; +import com.mongodb.client.model.geojson.Position; /** * Integration test for {@link MongoTemplate}. @@ -65,6 +75,46 @@ public class MongoTemplateMappingTests { checkPersonPersisted(template2); } + @Test // DATAMONGO-2357 + public void writesAndReadsEntityWithNativeMongoGeoJsonTypesCorrectly() { + + WithMongoGeoJson source = new WithMongoGeoJson(); + source.id = "id-2"; + source.multiPolygon = new MultiPolygon(Arrays.asList(new PolygonCoordinates(Arrays.asList(new Position(0, 0), + new Position(0, 1), new Position(1, 1), new Position(1, 0), new Position(0, 0))))); + + template1.save(source); + + assertThat(template1.findOne(query(where("id").is(source.id)), WithMongoGeoJson.class)).isEqualTo(source); + } + + @Test // DATAMONGO-2357 + public void writesAndReadsEntityWithOpenNativeMongoGeoJsonTypesCorrectly() { + + WithOpenMongoGeoJson source = new WithOpenMongoGeoJson(); + source.id = "id-2"; + source.geometry = new MultiPolygon(Arrays.asList(new PolygonCoordinates(Arrays.asList(new Position(0, 0), + new Position(0, 1), new Position(1, 1), new Position(1, 0), new Position(0, 0))))); + + template1.save(source); + + assertThat(template1.findOne(query(where("id").is(source.id)), WithOpenMongoGeoJson.class)).isEqualTo(source); + } + + @Data + static class WithMongoGeoJson { + + @Id String id; + MultiPolygon multiPolygon; + } + + @Data + static class WithOpenMongoGeoJson { + + @Id String id; + Geometry geometry; + } + private void addAndRetrievePerson(MongoTemplate template) { Person person = new Person("Oliver"); person.setAge(25); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java index 76c4b8a49..92978ce46 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java @@ -407,6 +407,7 @@ public class SessionBoundMongoTemplateTests { MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); converter.setCustomConversions(conversions); + converter.setCodecRegistryProvider(factory); converter.afterPropertiesSet(); return converter;