Skip to content

Commit c9097c9

Browse files
DATAMONGO-1849 - Add conversion target type hint to Field annotation.
By using Field#targetType it is now possible to pass down a type hint to the conversion subsystem. This allows specifying the desired target type for a property so that eg. a plain String can be stored as Code.
1 parent cd9d764 commit c9097c9

20 files changed

+545
-165
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingJsonSchemaCreator.java renamed to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.springframework.data.mapping.SimplePropertyHandler;
2727
import org.springframework.data.mapping.context.MappingContext;
2828
import org.springframework.data.mongodb.core.convert.MongoConverter;
29-
import org.springframework.data.mongodb.core.mapping.MongoId;
29+
import org.springframework.data.mongodb.core.mapping.Field;
3030
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3131
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
3232
import org.springframework.data.mongodb.core.schema.JsonSchemaObject;
@@ -42,24 +42,24 @@
4242
import org.springframework.util.ObjectUtils;
4343

4444
/**
45-
* {@link JsonSchemaCreator} implementation using both {@link MongoConverter} and {@link MappingContext} to obtain
45+
* {@link MongoJsonSchemaCreator} implementation using both {@link MongoConverter} and {@link MappingContext} to obtain
4646
* domain type meta information which considers {@link org.springframework.data.mongodb.core.mapping.Field field names}
4747
* and {@link org.springframework.data.mongodb.core.convert.MongoCustomConversions custom conversions}.
4848
*
4949
* @author Christoph Strobl
5050
* @since 2.2
5151
*/
52-
class MappingJsonSchemaCreator implements JsonSchemaCreator {
52+
class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
5353

5454
private MongoConverter converter;
5555
private MappingContext mappingContext;
5656

5757
/**
58-
* Create a new instance of {@link MappingJsonSchemaCreator}.
58+
* Create a new instance of {@link MappingMongoJsonSchemaCreator}.
5959
*
6060
* @param converter must not be {@literal null}.
6161
*/
62-
MappingJsonSchemaCreator(MongoConverter converter) {
62+
MappingMongoJsonSchemaCreator(MongoConverter converter) {
6363

6464
Assert.notNull(converter, "Converter must not be null!");
6565
this.converter = converter;
@@ -69,7 +69,7 @@ class MappingJsonSchemaCreator implements JsonSchemaCreator {
6969

7070
/*
7171
* (non-Javadoc)
72-
* org.springframework.data.mongodb.core.JsonSchemaCreator#createSchemaFor(java.lang.Class)
72+
* org.springframework.data.mongodb.core.MongoJsonSchemaCreator#createSchemaFor(java.lang.Class)
7373
*/
7474
@Override
7575
public MongoJsonSchema createSchemaFor(Class<?> type) {
@@ -195,8 +195,8 @@ private Class<?> computeTargetType(PersistentProperty<?> property) {
195195
return mongoProperty.getFieldType();
196196
}
197197

198-
if (mongoProperty.isAnnotationPresent(MongoId.class)) {
199-
return mongoProperty.findAnnotation(MongoId.class).value().getJavaClass();
198+
if (mongoProperty.hasExplicitWriteTarget()) {
199+
return mongoProperty.findAnnotation(Field.class).targetType().getJavaClass();
200200
}
201201

202202
return mongoProperty.getFieldType() != mongoProperty.getActualType() ? Object.class : mongoProperty.getFieldType();

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/JsonSchemaCreator.java renamed to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import org.springframework.util.Assert;
2121

2222
/**
23-
* {@link JsonSchemaCreator} extracts the {@link MongoJsonSchema} for a given {@link Class} by applying the following
23+
* {@link MongoJsonSchemaCreator} extracts the {@link MongoJsonSchema} for a given {@link Class} by applying the following
2424
* mapping rules.
2525
* <p>
2626
* <strong>Required Properties</strong><br />
@@ -49,7 +49,7 @@
4949
* @author Christoph Strobl
5050
* @since 2.2
5151
*/
52-
public interface JsonSchemaCreator {
52+
public interface MongoJsonSchemaCreator {
5353

5454
/**
5555
* Create the {@link MongoJsonSchema} for the given {@link Class type}.
@@ -60,14 +60,14 @@ public interface JsonSchemaCreator {
6060
MongoJsonSchema createSchemaFor(Class<?> type);
6161

6262
/**
63-
* Creates a new {@link JsonSchemaCreator} that is aware of conversions applied by the given {@link MongoConverter}.
63+
* Creates a new {@link MongoJsonSchemaCreator} that is aware of conversions applied by the given {@link MongoConverter}.
6464
*
6565
* @param mongoConverter must not be {@literal null}.
66-
* @return new instance of {@link JsonSchemaCreator}.
66+
* @return new instance of {@link MongoJsonSchemaCreator}.
6767
*/
68-
default JsonSchemaCreator jsonSchemaCreator(MongoConverter mongoConverter) {
68+
default MongoJsonSchemaCreator jsonSchemaCreator(MongoConverter mongoConverter) {
6969

7070
Assert.notNull(mongoConverter, "MongoConverter must not be null!");
71-
return new MappingJsonSchemaCreator(mongoConverter);
71+
return new MappingMongoJsonSchemaCreator(mongoConverter);
7272
}
7373
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,19 @@
1515
*/
1616
package org.springframework.data.mongodb.core.convert;
1717

18-
import java.util.*;
18+
import java.lang.reflect.Constructor;
19+
import java.lang.reflect.Method;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.HashSet;
25+
import java.util.LinkedHashMap;
26+
import java.util.List;
27+
import java.util.Map;
1928
import java.util.Map.Entry;
29+
import java.util.Optional;
30+
import java.util.Set;
2031

2132
import org.bson.Document;
2233
import org.bson.conversions.Bson;
@@ -29,9 +40,7 @@
2940
import org.springframework.core.CollectionFactory;
3041
import org.springframework.core.convert.ConversionService;
3142
import org.springframework.core.convert.support.DefaultConversionService;
32-
import org.springframework.data.convert.CustomConversions;
3343
import org.springframework.data.convert.EntityInstantiator;
34-
import org.springframework.data.convert.EntityInstantiators;
3544
import org.springframework.data.convert.TypeMapper;
3645
import org.springframework.data.mapping.Association;
3746
import org.springframework.data.mapping.MappingException;
@@ -660,6 +669,10 @@ private static Collection<?> asCollection(Object source) {
660669
protected List<Object> createCollection(Collection<?> collection, MongoPersistentProperty property) {
661670

662671
if (!property.isDbReference()) {
672+
673+
if (property.hasExplicitWriteTarget()) {
674+
return writeCollectionInternal(collection, new HijackedTypeInformation<>(property), new ArrayList<>());
675+
}
663676
return writeCollectionInternal(collection, property.getTypeInformation(), new BasicDBList());
664677
}
665678

@@ -739,7 +752,8 @@ private List<Object> writeCollectionInternal(Collection<?> source, @Nullable Typ
739752
Class<?> elementType = element == null ? null : element.getClass();
740753

741754
if (elementType == null || conversions.isSimpleType(elementType)) {
742-
collection.add(getPotentiallyConvertedSimpleWrite(element));
755+
collection.add(getPotentiallyConvertedSimpleWrite(element,
756+
componentType != null ? componentType.getType() : Object.class));
743757
} else if (element instanceof Collection || elementType.isArray()) {
744758
collection.add(writeCollectionInternal(asCollection(element), componentType, new BasicDBList()));
745759
} else {
@@ -842,7 +856,7 @@ private String potentiallyConvertMapKey(Object key) {
842856
}
843857

844858
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
845-
? (String) getPotentiallyConvertedSimpleWrite(key)
859+
? (String) getPotentiallyConvertedSimpleWrite(key, Object.class)
846860
: key.toString();
847861
}
848862

@@ -884,12 +898,13 @@ protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation<?> type, Ob
884898
* @param key must not be {@literal null}.
885899
*/
886900
private void writeSimpleInternal(Object value, Bson bson, String key) {
887-
addToMap(bson, key, getPotentiallyConvertedSimpleWrite(value));
901+
addToMap(bson, key, getPotentiallyConvertedSimpleWrite(value, Object.class));
888902
}
889903

890904
private void writeSimpleInternal(Object value, Bson bson, MongoPersistentProperty property) {
891905
DocumentAccessor accessor = new DocumentAccessor(bson);
892-
accessor.put(property, getPotentiallyConvertedSimpleWrite(value));
906+
accessor.put(property, getPotentiallyConvertedSimpleWrite(value,
907+
property.hasExplicitWriteTarget() ? property.getFieldType() : Object.class));
893908
}
894909

895910
/**
@@ -900,12 +915,19 @@ private void writeSimpleInternal(Object value, Bson bson, MongoPersistentPropert
900915
* @return
901916
*/
902917
@Nullable
903-
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
918+
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nullable Class<?> typeHint) {
904919

905920
if (value == null) {
906921
return null;
907922
}
908923

924+
if (typeHint != null && Object.class != typeHint) {
925+
926+
if (conversionService.canConvert(value.getClass(), typeHint)) {
927+
value = conversionService.convert(value, typeHint);
928+
}
929+
}
930+
909931
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
910932

911933
if (customTarget.isPresent()) {
@@ -1204,7 +1226,8 @@ public Object convertToMongoType(@Nullable Object obj, TypeInformation<?> typeIn
12041226

12051227
if (conversions.isSimpleType(obj.getClass())) {
12061228
// Doesn't need conversion
1207-
return getPotentiallyConvertedSimpleWrite(obj);
1229+
return getPotentiallyConvertedSimpleWrite(obj,
1230+
typeInformation != null ? typeInformation.getType() : Object.class);
12081231
}
12091232

12101233
if (obj instanceof List) {
@@ -1599,7 +1622,6 @@ List<Document> bulkReadRefs(List<DBRef> references) {
15991622
return dbRefResolver.bulkFetch(references);
16001623
}
16011624

1602-
16031625
/**
16041626
* Create a new {@link MappingMongoConverter} using the given {@link MongoDbFactory} when loading {@link DBRef}.
16051627
*
@@ -1667,4 +1689,91 @@ public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter)
16671689
return null;
16681690
}
16691691
}
1692+
1693+
private static class HijackedTypeInformation<S> implements TypeInformation<S> {
1694+
1695+
private MongoPersistentProperty persistentProperty;
1696+
private TypeInformation<?> delegate;
1697+
1698+
public HijackedTypeInformation(MongoPersistentProperty property) {
1699+
1700+
this.persistentProperty = property;
1701+
this.delegate = property.getTypeInformation();
1702+
}
1703+
1704+
@Override
1705+
public List<org.springframework.data.util.TypeInformation<?>> getParameterTypes(Constructor constructor) {
1706+
return persistentProperty.getTypeInformation().getParameterTypes(constructor);
1707+
}
1708+
1709+
@Override
1710+
public org.springframework.data.util.TypeInformation<?> getProperty(String property) {
1711+
return delegate.getProperty(property);
1712+
}
1713+
1714+
@Override
1715+
public boolean isCollectionLike() {
1716+
return delegate.isCollectionLike();
1717+
}
1718+
1719+
@Override
1720+
public org.springframework.data.util.TypeInformation<?> getComponentType() {
1721+
return ClassTypeInformation.from(persistentProperty.getFieldType());
1722+
}
1723+
1724+
@Override
1725+
public boolean isMap() {
1726+
return delegate.isMap();
1727+
}
1728+
1729+
@Override
1730+
public org.springframework.data.util.TypeInformation<?> getMapValueType() {
1731+
return ClassTypeInformation.from(persistentProperty.getFieldType());
1732+
}
1733+
1734+
@Override
1735+
public Class getType() {
1736+
return delegate.getType();
1737+
}
1738+
1739+
@Override
1740+
public ClassTypeInformation<?> getRawTypeInformation() {
1741+
return delegate.getRawTypeInformation();
1742+
}
1743+
1744+
@Override
1745+
public org.springframework.data.util.TypeInformation<?> getActualType() {
1746+
return delegate.getActualType();
1747+
}
1748+
1749+
@Override
1750+
public org.springframework.data.util.TypeInformation<?> getReturnType(Method method) {
1751+
return delegate.getReturnType(method);
1752+
}
1753+
1754+
@Override
1755+
public List<org.springframework.data.util.TypeInformation<?>> getParameterTypes(Method method) {
1756+
return delegate.getParameterTypes(method);
1757+
}
1758+
1759+
@Override
1760+
public org.springframework.data.util.TypeInformation<?> getSuperTypeInformation(Class superType) {
1761+
return delegate.getSuperTypeInformation(superType);
1762+
}
1763+
1764+
@Override
1765+
public boolean isAssignableFrom(org.springframework.data.util.TypeInformation target) {
1766+
return delegate.isAssignableFrom(target);
1767+
}
1768+
1769+
@Override
1770+
public List<org.springframework.data.util.TypeInformation<?>> getTypeArguments() {
1771+
return delegate.getTypeArguments();
1772+
}
1773+
1774+
@Override
1775+
public org.springframework.data.util.TypeInformation specialize(ClassTypeInformation type) {
1776+
return delegate.specialize(type);
1777+
}
1778+
}
16701779
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.bson.Document;
3535
import org.bson.types.Binary;
3636
import org.bson.types.Code;
37+
import org.bson.types.Decimal128;
3738
import org.bson.types.ObjectId;
3839
import org.springframework.core.convert.ConversionFailedException;
3940
import org.springframework.core.convert.TypeDescriptor;
@@ -75,7 +76,9 @@ static Collection<Object> getConvertersToRegister() {
7576
List<Object> converters = new ArrayList<>();
7677

7778
converters.add(BigDecimalToStringConverter.INSTANCE);
79+
converters.add(BigDecimalToDecimal128Converter.INSTANCE);
7880
converters.add(StringToBigDecimalConverter.INSTANCE);
81+
converters.add(Decimal128ToBigDecimalConverter.INSTANCE);
7982
converters.add(BigIntegerToStringConverter.INSTANCE);
8083
converters.add(StringToBigIntegerConverter.INSTANCE);
8184
converters.add(URLToStringConverter.INSTANCE);
@@ -158,6 +161,17 @@ public String convert(BigDecimal source) {
158161
}
159162
}
160163

164+
/**
165+
* @since 2.2
166+
*/
167+
enum BigDecimalToDecimal128Converter implements Converter<BigDecimal, Decimal128> {
168+
INSTANCE;
169+
170+
public Decimal128 convert(BigDecimal source) {
171+
return source == null ? null : new Decimal128(source);
172+
}
173+
}
174+
161175
enum StringToBigDecimalConverter implements Converter<String, BigDecimal> {
162176
INSTANCE;
163177

@@ -166,6 +180,17 @@ public BigDecimal convert(String source) {
166180
}
167181
}
168182

183+
/**
184+
* @since 2.2
185+
*/
186+
enum Decimal128ToBigDecimalConverter implements Converter<Decimal128, BigDecimal> {
187+
INSTANCE;
188+
189+
public BigDecimal convert(Decimal128 source) {
190+
return source.bigDecimalValue();
191+
}
192+
}
193+
169194
enum BigIntegerToStringConverter implements Converter<BigInteger, String> {
170195
INSTANCE;
171196

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ protected Document getMappedKeyword(Field property, Keyword keyword) {
334334
@SuppressWarnings("unchecked")
335335
protected Object getMappedValue(Field documentField, Object value) {
336336

337+
if(documentField.getProperty() != null && documentField.getProperty().hasExplicitWriteTarget()) {
338+
if(conversionService.canConvert(value.getClass(), documentField.getProperty().getFieldType())) {
339+
value = conversionService.convert(value, documentField.getProperty().getFieldType());
340+
}
341+
}
342+
337343
if (documentField.isIdField() && !documentField.isAssociation()) {
338344

339345
if (isDBObject(value)) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,22 @@ public String getFieldName() {
150150
@Override
151151
public Class<?> getFieldType() {
152152

153+
Field fieldAnnotation = findAnnotation(Field.class);
154+
153155
if (!isIdProperty()) {
154-
return getType();
155-
}
156156

157-
MongoId idAnnotation = findAnnotation(MongoId.class);
157+
if (fieldAnnotation == null || fieldAnnotation.targetType() == FieldType.IMPLICIT) {
158+
return getType();
159+
}
158160

159-
if (idAnnotation == null) {
160-
return FieldType.OBJECT_ID.getJavaClass();
161+
return fieldAnnotation.targetType().getJavaClass();
161162
}
162163

163-
FieldType fieldType = idAnnotation.targetType();
164+
if (fieldAnnotation == null) {
165+
return FieldType.OBJECT_ID.getJavaClass();
166+
}
164167

168+
FieldType fieldType = fieldAnnotation.targetType();
165169
if (fieldType == FieldType.IMPLICIT) {
166170
return getType();
167171
}

0 commit comments

Comments
 (0)