Skip to content

Commit 0f3c90b

Browse files
committed
DATAMONGO-1849 - Polishing.
Fix generics usage in MappingMongoJsonSchemaCreator. Make fields final. Rename MappingMongoConverter.computeWriteTarget to getWriteTarget and expose it publicly for reuse in custom DefaultTypeMapper setups without the need to subclass MappingMongoConverter. Remove Nullability functionality for required fields as nullability indicators should originate from PersistentProperty and PreferredConstructor. Update documentation. Related ticket: DATACMNS-1513 Original pull request: #733.
1 parent 7f88889 commit 0f3c90b

File tree

9 files changed

+147
-183
lines changed

9 files changed

+147
-183
lines changed

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

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@
2121
import java.util.EnumSet;
2222
import java.util.List;
2323

24-
import org.springframework.data.mapping.PersistentEntity;
2524
import org.springframework.data.mapping.PersistentProperty;
26-
import org.springframework.data.mapping.SimplePropertyHandler;
2725
import org.springframework.data.mapping.context.MappingContext;
2826
import org.springframework.data.mongodb.core.convert.MongoConverter;
2927
import org.springframework.data.mongodb.core.mapping.Field;
28+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3029
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3130
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
3231
import org.springframework.data.mongodb.core.schema.JsonSchemaObject;
@@ -35,7 +34,6 @@
3534
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
3635
import org.springframework.data.mongodb.core.schema.MongoJsonSchema.MongoJsonSchemaBuilder;
3736
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject;
38-
import org.springframework.lang.Nullable;
3937
import org.springframework.util.Assert;
4038
import org.springframework.util.ClassUtils;
4139
import org.springframework.util.CollectionUtils;
@@ -45,26 +43,28 @@
4543
* {@link MongoJsonSchemaCreator} implementation using both {@link MongoConverter} and {@link MappingContext} to obtain
4644
* domain type meta information which considers {@link org.springframework.data.mongodb.core.mapping.Field field names}
4745
* and {@link org.springframework.data.mongodb.core.convert.MongoCustomConversions custom conversions}.
48-
*
46+
*
4947
* @author Christoph Strobl
48+
* @author Mark Paluch
5049
* @since 2.2
5150
*/
5251
class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
5352

54-
private MongoConverter converter;
55-
private MappingContext mappingContext;
53+
private final MongoConverter converter;
54+
private final MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
5655

5756
/**
5857
* Create a new instance of {@link MappingMongoJsonSchemaCreator}.
5958
*
6059
* @param converter must not be {@literal null}.
6160
*/
61+
@SuppressWarnings("unchecked")
6262
MappingMongoJsonSchemaCreator(MongoConverter converter) {
6363

6464
Assert.notNull(converter, "Converter must not be null!");
6565
this.converter = converter;
66-
this.mappingContext = converter.getMappingContext();
67-
66+
this.mappingContext = (MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty>) converter
67+
.getMappingContext();
6868
}
6969

7070
/*
@@ -74,7 +74,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
7474
@Override
7575
public MongoJsonSchema createSchemaFor(Class<?> type) {
7676

77-
PersistentEntity<?, ?> entity = mappingContext.getPersistentEntity(type);
77+
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
7878
MongoJsonSchemaBuilder schemaBuilder = MongoJsonSchema.builder();
7979

8080
List<JsonSchemaProperty> schemaProperties = computePropertiesForEntity(Collections.emptyList(), entity);
@@ -84,35 +84,33 @@ public MongoJsonSchema createSchemaFor(Class<?> type) {
8484

8585
}
8686

87-
private List<JsonSchemaProperty> computePropertiesForEntity(List<PersistentProperty> path,
88-
PersistentEntity<?, ?> entity) {
87+
private List<JsonSchemaProperty> computePropertiesForEntity(List<MongoPersistentProperty> path,
88+
MongoPersistentEntity<?> entity) {
8989

9090
List<JsonSchemaProperty> schemaProperties = new ArrayList<>();
91-
entity.doWithProperties((SimplePropertyHandler) nested -> {
9291

93-
ArrayList<PersistentProperty> currentPath = new ArrayList<>(path);
92+
for (MongoPersistentProperty nested : entity) {
93+
94+
List<MongoPersistentProperty> currentPath = new ArrayList<>(path);
9495

9596
if (path.contains(nested)) { // cycle guard
9697
schemaProperties.add(createSchemaProperty(computePropertyFieldName(CollectionUtils.lastElement(currentPath)),
9798
Object.class, false));
98-
return;
99+
break;
99100
}
100101

101102
currentPath.add(nested);
102-
JsonSchemaProperty jsonSchemaProperty = computeSchemaForProperty(currentPath, entity);
103-
if (jsonSchemaProperty != null) {
104-
schemaProperties.add(jsonSchemaProperty);
105-
}
106-
});
103+
schemaProperties.add(computeSchemaForProperty(currentPath));
104+
}
107105

108106
return schemaProperties;
109107
}
110108

111-
private JsonSchemaProperty computeSchemaForProperty(List<PersistentProperty> path, PersistentEntity<?, ?> parent) {
109+
private JsonSchemaProperty computeSchemaForProperty(List<MongoPersistentProperty> path) {
112110

113-
PersistentProperty property = CollectionUtils.lastElement(path);
111+
MongoPersistentProperty property = CollectionUtils.lastElement(path);
114112

115-
boolean required = isRequiredProperty(parent, property);
113+
boolean required = isRequiredProperty(property);
116114
Class<?> rawTargetType = computeTargetType(property); // target type before conversion
117115
Class<?> targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type
118116

@@ -133,12 +131,12 @@ private JsonSchemaProperty computeSchemaForProperty(List<PersistentProperty> pat
133131
return createSchemaProperty(fieldName, targetType, required);
134132
}
135133

136-
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<PersistentProperty> path,
137-
PersistentProperty property, boolean required) {
134+
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path,
135+
MongoPersistentProperty property, boolean required) {
138136

139137
ObjectJsonSchemaProperty target = JsonSchemaProperty.object(property.getName());
140138
List<JsonSchemaProperty> nestedProperties = computePropertiesForEntity(path,
141-
mappingContext.getPersistentEntity(property));
139+
mappingContext.getRequiredPersistentEntity(property));
142140

143141
return createPotentiallyRequiredSchemaProperty(
144142
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required);
@@ -147,6 +145,7 @@ private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<PersistentPr
147145
private JsonSchemaProperty createEnumSchemaProperty(String fieldName, Class<?> targetType, boolean required) {
148146

149147
List<Object> possibleValues = new ArrayList<>();
148+
150149
for (Object enumValue : EnumSet.allOf((Class) targetType)) {
151150
possibleValues.add(converter.convertToMongoType(enumValue));
152151
}
@@ -178,10 +177,8 @@ private String computePropertyFieldName(PersistentProperty property) {
178177
: property.getName();
179178
}
180179

181-
private boolean isRequiredProperty(PersistentEntity<?, ?> parent, PersistentProperty property) {
182-
183-
return (parent.isConstructorArgument(property) && !property.isAnnotationPresent(Nullable.class))
184-
|| property.getType().isPrimitive();
180+
private boolean isRequiredProperty(PersistentProperty property) {
181+
return property.getType().isPrimitive();
185182
}
186183

187184
private Class<?> computeTargetType(PersistentProperty<?> property) {
@@ -196,7 +193,7 @@ private Class<?> computeTargetType(PersistentProperty<?> property) {
196193
}
197194

198195
if (mongoProperty.hasExplicitWriteTarget()) {
199-
return mongoProperty.findAnnotation(Field.class).targetType().getJavaClass();
196+
return mongoProperty.getRequiredAnnotation(Field.class).targetType().getJavaClass();
200197
}
201198

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

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

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,30 @@
2323
* {@link MongoJsonSchemaCreator} extracts the {@link MongoJsonSchema} for a given {@link Class} by applying the
2424
* following mapping rules.
2525
* <p>
26-
* <strong>Required Properties</strong><br />
27-
* - All Constructor arguments annotated with {@link org.springframework.lang.Nullable}. <br />
28-
* - Properties of primitive type. <br />
29-
* </p>
30-
* <p>
31-
* <strong>Ignored Properties</strong><br />
32-
* - All properties annotated with {@link org.springframework.data.annotation.Transient}. <br />
33-
* </p>
34-
* <p>
35-
* <strong>Property Type Mapping</strong><br />
36-
* - {@link java.lang.Object} -> {@code type : 'object'} <br />
37-
* - {@link java.util.Arrays} -> {@code type : 'array'} <br />
38-
* - {@link java.util.Collection} -> {@code type : 'array'} <br />
39-
* - {@link java.util.Map} -> {@code type : 'object'} <br />
40-
* - {@link java.lang.Enum} -> {@code type : 'string', enum : [the enum values]} <br />
41-
* - Simple Types -> {@code type : 'the corresponding bson type' } <br />
42-
* - Domain Types -> {@code type : 'object', properties : &#123;the types properties&#125; } <br />
26+
* <strong>Required Properties</strong>
27+
* <ul>
28+
* <li>Properties of primitive type</li>
29+
* </ul>
30+
* <strong>Ignored Properties</strong>
31+
* <ul>
32+
* <li>All properties annotated with {@link org.springframework.data.annotation.Transient}</li>
33+
* </ul>
34+
* <strong>Property Type Mapping</strong>
35+
* <ul>
36+
* <li>{@link java.lang.Object} -> {@code type : 'object'}</li>
37+
* <li>{@link java.util.Arrays} -> {@code type : 'array'}</li>
38+
* <li>{@link java.util.Collection} -> {@code type : 'array'}</li>
39+
* <li>{@link java.util.Map} -> {@code type : 'object'}</li>
40+
* <li>{@link java.lang.Enum} -> {@code type : 'string', enum : [the enum values]}</li>
41+
* <li>Simple Types -> {@code type : 'the corresponding bson type' }</li>
42+
* <li>Domain Types -> {@code type : 'object', properties : &#123;the types properties&#125; }</li>
43+
* </ul>
4344
* <br />
4445
* {@link org.springframework.data.annotation.Id _id} properties using types that can be converted into
4546
* {@link org.bson.types.ObjectId} like {@link String} will be mapped to {@code type : 'object'} unless there is more
4647
* specific information available via the {@link org.springframework.data.mongodb.core.mapping.MongoId} annotation.
4748
* </p>
48-
*
49+
*
4950
* @author Christoph Strobl
5051
* @since 2.2
5152
*/
@@ -66,7 +67,7 @@ public interface MongoJsonSchemaCreator {
6667
* @param mongoConverter must not be {@literal null}.
6768
* @return new instance of {@link MongoJsonSchemaCreator}.
6869
*/
69-
static MongoJsonSchemaCreator jsonSchemaCreator(MongoConverter mongoConverter) {
70+
static MongoJsonSchemaCreator create(MongoConverter mongoConverter) {
7071

7172
Assert.notNull(mongoConverter, "MongoConverter must not be null!");
7273
return new MappingMongoJsonSchemaCreator(mongoConverter);

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

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
*/
1616
package org.springframework.data.mongodb.core.convert;
1717

18-
import java.util.Arrays;
18+
import java.util.Collections;
1919
import java.util.List;
2020
import java.util.Map;
2121
import java.util.Set;
22-
import java.util.function.Function;
22+
import java.util.function.UnaryOperator;
2323

2424
import org.bson.Document;
2525
import org.bson.conversions.Bson;
26+
27+
import org.springframework.data.convert.CustomConversions;
2628
import org.springframework.data.convert.DefaultTypeMapper;
2729
import org.springframework.data.convert.SimpleTypeInformationMapper;
2830
import org.springframework.data.convert.TypeAliasAccessor;
@@ -59,29 +61,58 @@ public class DefaultMongoTypeMapper extends DefaultTypeMapper<Bson> implements M
5961

6062
private final TypeAliasAccessor<Bson> accessor;
6163
private final @Nullable String typeKey;
62-
private Function<Class<?>, Class<?>> writeTarget = it -> it;
64+
private UnaryOperator<Class<?>> writeTarget = UnaryOperator.identity();
6365

66+
/**
67+
* Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code _class}.
68+
*/
6469
public DefaultMongoTypeMapper() {
6570
this(DEFAULT_TYPE_KEY);
6671
}
6772

73+
/**
74+
* Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}.
75+
*
76+
* @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
77+
*/
6878
public DefaultMongoTypeMapper(@Nullable String typeKey) {
69-
this(typeKey, Arrays.asList(new SimpleTypeInformationMapper()));
79+
this(typeKey, Collections.singletonList(new SimpleTypeInformationMapper()));
7080
}
7181

82+
/**
83+
* Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}.
84+
*
85+
* @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
86+
* @param mappingContext the mapping context.
87+
*/
7288
public DefaultMongoTypeMapper(@Nullable String typeKey,
7389
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext) {
7490
this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext,
75-
Arrays.asList(new SimpleTypeInformationMapper()));
91+
Collections.singletonList(new SimpleTypeInformationMapper()));
7692
}
7793

94+
/**
95+
* Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}. Uses
96+
* {@link UnaryOperator} to apply {@link CustomConversions}.
97+
*
98+
* @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
99+
* @param mappingContext the mapping context to look up types using type hints.
100+
* @see MappingMongoConverter#getWriteTarget(Class)
101+
*/
78102
public DefaultMongoTypeMapper(@Nullable String typeKey,
79-
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext, Function<Class<?>, Class<?>> writeTarget) {
103+
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext, UnaryOperator<Class<?>> writeTarget) {
80104
this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext,
81-
Arrays.asList(new SimpleTypeInformationMapper()));
105+
Collections.singletonList(new SimpleTypeInformationMapper()));
82106
this.writeTarget = writeTarget;
83107
}
84108

109+
/**
110+
* Create a new {@link MongoTypeMapper} with fully-qualified type hints using {@code typeKey}. Uses
111+
* {@link TypeInformationMapper} to map type hints.
112+
*
113+
* @param typeKey name of the field to read and write type hints. Can be {@literal null} to disable type hints.
114+
* @param mappers
115+
*/
85116
public DefaultMongoTypeMapper(@Nullable String typeKey, List<? extends TypeInformationMapper> mappers) {
86117
this(typeKey, new DocumentTypeAliasAccessor(typeKey), null, mappers);
87118
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.bson.types.ObjectId;
3535
import org.slf4j.Logger;
3636
import org.slf4j.LoggerFactory;
37+
3738
import org.springframework.beans.BeansException;
3839
import org.springframework.context.ApplicationContext;
3940
import org.springframework.context.ApplicationContextAware;
@@ -122,7 +123,7 @@ public MappingMongoConverter(DbRefResolver dbRefResolver,
122123
this.dbRefResolver = dbRefResolver;
123124
this.mappingContext = mappingContext;
124125
this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext,
125-
this::computeWriteTarget);
126+
this::getWriteTarget);
126127
this.idMapper = new QueryMapper(this);
127128

128129
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
@@ -672,7 +673,7 @@ protected List<Object> createCollection(Collection<?> collection, MongoPersisten
672673
if (!property.isDbReference()) {
673674

674675
if (property.hasExplicitWriteTarget()) {
675-
return writeCollectionInternal(collection, new HijackedTypeInformation<>(property), new ArrayList<>());
676+
return writeCollectionInternal(collection, new TypeInformationWrapper<>(property), new ArrayList<>());
676677
}
677678
return writeCollectionInternal(collection, property.getTypeInformation(), new BasicDBList());
678679
}
@@ -1608,6 +1609,7 @@ private boolean canPublishEvent() {
16081609
* @param ref
16091610
* @return
16101611
*/
1612+
@Nullable
16111613
Document readRef(DBRef ref) {
16121614
return dbRefResolver.fetch(ref);
16131615
}
@@ -1630,7 +1632,7 @@ List<Document> bulkReadRefs(List<DBRef> references) {
16301632
* @return
16311633
* @since 2.2
16321634
*/
1633-
protected Class<?> computeWriteTarget(Class<?> source) {
1635+
public Class<?> getWriteTarget(Class<?> source) {
16341636
return conversions.getCustomWriteTarget(source).orElse(source);
16351637
}
16361638

@@ -1702,12 +1704,12 @@ public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter)
17021704
}
17031705
}
17041706

1705-
private static class HijackedTypeInformation<S> implements TypeInformation<S> {
1707+
private static class TypeInformationWrapper<S> implements TypeInformation<S> {
17061708

17071709
private MongoPersistentProperty persistentProperty;
17081710
private TypeInformation<?> delegate;
17091711

1710-
public HijackedTypeInformation(MongoPersistentProperty property) {
1712+
public TypeInformationWrapper(MongoPersistentProperty property) {
17111713

17121714
this.persistentProperty = property;
17131715
this.delegate = property.getTypeInformation();

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
* <p/>
3232
* Bson types are identified by a {@code byte} {@link #getBsonType() value}. This enumeration typically returns the
3333
* according bson type value except for {@link #IMPLICIT} which is a marker to derive the field type from a property.
34-
*
34+
*
3535
* @author Mark Paluch
36+
* @author Christoph Strobl
3637
* @since 2.2
3738
* @see org.bson.BsonType
3839
*/
@@ -67,7 +68,7 @@ public enum FieldType {
6768

6869
/**
6970
* Returns the BSON type identifier. Can be {@code -1} if {@link FieldType} maps to a synthetic Bson type.
70-
*
71+
*
7172
* @return the BSON type identifier. Can be {@code -1} if {@link FieldType} maps to a synthetic Bson type.
7273
*/
7374
public int getBsonType() {
@@ -76,7 +77,7 @@ public int getBsonType() {
7677

7778
/**
7879
* Returns the Java class used to represent the type.
79-
*
80+
*
8081
* @return the Java class used to represent the type.
8182
*/
8283
public Class<?> getJavaClass() {

0 commit comments

Comments
 (0)