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.
This commit is contained in:
Christoph Strobl
2019-09-04 11:23:53 +02:00
committed by Mark Paluch
parent 9a062d53f3
commit ee6048e289
9 changed files with 103 additions and 3 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<? extends MongoPersistentEntity<?>, 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<? extends Codec<? extends S>> 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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -407,6 +407,7 @@ public class SessionBoundMongoTemplateTests {
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
converter.setCustomConversions(conversions);
converter.setCodecRegistryProvider(factory);
converter.afterPropertiesSet();
return converter;