Skip to content

Commit fda07d9

Browse files
committed
Polishing.
Extract CreateCollectionOptions conversion to EntityOperations to unify collection creation. Adopt tests. See #3984 Original pull request: #3990.
1 parent 2bb8643 commit fda07d9

File tree

6 files changed

+188
-162
lines changed

6 files changed

+188
-162
lines changed

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

+92-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Optional;
2222

2323
import org.bson.Document;
24+
2425
import org.springframework.core.convert.ConversionService;
2526
import org.springframework.dao.InvalidDataAccessApiUsageException;
2627
import org.springframework.data.convert.CustomConversions;
@@ -32,7 +33,9 @@
3233
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
3334
import org.springframework.data.mongodb.core.CollectionOptions.TimeSeriesOptions;
3435
import org.springframework.data.mongodb.core.convert.MongoConverter;
36+
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
3537
import org.springframework.data.mongodb.core.convert.MongoWriter;
38+
import org.springframework.data.mongodb.core.convert.QueryMapper;
3639
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3740
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3841
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
@@ -41,9 +44,11 @@
4144
import org.springframework.data.mongodb.core.query.Criteria;
4245
import org.springframework.data.mongodb.core.query.Query;
4346
import org.springframework.data.mongodb.core.timeseries.Granularity;
47+
import org.springframework.data.mongodb.core.validation.Validator;
4448
import org.springframework.data.projection.EntityProjection;
4549
import org.springframework.data.projection.EntityProjectionIntrospector;
4650
import org.springframework.data.projection.ProjectionFactory;
51+
import org.springframework.data.util.Optionals;
4752
import org.springframework.lang.Nullable;
4853
import org.springframework.util.Assert;
4954
import org.springframework.util.ClassUtils;
@@ -52,6 +57,10 @@
5257
import org.springframework.util.ObjectUtils;
5358
import org.springframework.util.StringUtils;
5459

60+
import com.mongodb.client.model.CreateCollectionOptions;
61+
import com.mongodb.client.model.TimeSeriesGranularity;
62+
import com.mongodb.client.model.ValidationOptions;
63+
5564
/**
5665
* Common operations performed on an entity in the context of it's mapping metadata.
5766
*
@@ -67,20 +76,31 @@ class EntityOperations {
6776
private static final String ID_FIELD = "_id";
6877

6978
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
79+
private final QueryMapper queryMapper;
7080

7181
private final EntityProjectionIntrospector introspector;
7282

83+
private final MongoJsonSchemaMapper schemaMapper;
84+
7385
EntityOperations(MongoConverter converter) {
74-
this(converter.getMappingContext(), converter.getCustomConversions(), converter.getProjectionFactory());
86+
this(converter, new QueryMapper(converter));
87+
}
88+
89+
EntityOperations(MongoConverter converter, QueryMapper queryMapper) {
90+
this(converter, converter.getMappingContext(), converter.getCustomConversions(), converter.getProjectionFactory(),
91+
queryMapper);
7592
}
7693

77-
EntityOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
78-
CustomConversions conversions, ProjectionFactory projectionFactory) {
94+
EntityOperations(MongoConverter converter,
95+
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
96+
CustomConversions conversions, ProjectionFactory projectionFactory, QueryMapper queryMapper) {
7997
this.context = context;
98+
this.queryMapper = queryMapper;
8099
this.introspector = EntityProjectionIntrospector.create(projectionFactory,
81100
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
82101
.and(((target, underlyingType) -> !conversions.isSimpleType(target))),
83102
context);
103+
this.schemaMapper = new MongoJsonSchemaMapper(converter);
84104
}
85105

86106
/**
@@ -259,6 +279,75 @@ public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType, C
259279
return introspector.introspect(resultType, entityType);
260280
}
261281

282+
/**
283+
* Convert given {@link CollectionOptions} to a document and take the domain type information into account when
284+
* creating a mapped schema for validation.
285+
*
286+
* @param collectionOptions can be {@literal null}.
287+
* @param entityType must not be {@literal null}. Use {@link Object} type instead.
288+
* @return the converted {@link CreateCollectionOptions}.
289+
* @since 3.4
290+
*/
291+
public CreateCollectionOptions convertToCreateCollectionOptions(@Nullable CollectionOptions collectionOptions,
292+
Class<?> entityType) {
293+
294+
Optional<Collation> collation = Optionals.firstNonEmpty(
295+
() -> Optional.ofNullable(collectionOptions).flatMap(CollectionOptions::getCollation),
296+
() -> forType(entityType).getCollation());//
297+
298+
CreateCollectionOptions result = new CreateCollectionOptions();
299+
collation.map(Collation::toMongoCollation).ifPresent(result::collation);
300+
301+
if (collectionOptions == null) {
302+
return result;
303+
}
304+
305+
collectionOptions.getCapped().ifPresent(result::capped);
306+
collectionOptions.getSize().ifPresent(result::sizeInBytes);
307+
collectionOptions.getMaxDocuments().ifPresent(result::maxDocuments);
308+
collectionOptions.getCollation().map(Collation::toMongoCollation).ifPresent(result::collation);
309+
310+
collectionOptions.getValidationOptions().ifPresent(it -> {
311+
312+
ValidationOptions validationOptions = new ValidationOptions();
313+
314+
it.getValidationAction().ifPresent(validationOptions::validationAction);
315+
it.getValidationLevel().ifPresent(validationOptions::validationLevel);
316+
317+
it.getValidator().ifPresent(val -> validationOptions.validator(getMappedValidator(val, entityType)));
318+
319+
result.validationOptions(validationOptions);
320+
});
321+
322+
collectionOptions.getTimeSeriesOptions().map(forType(entityType)::mapTimeSeriesOptions).ifPresent(it -> {
323+
324+
com.mongodb.client.model.TimeSeriesOptions options = new com.mongodb.client.model.TimeSeriesOptions(
325+
it.getTimeField());
326+
327+
if (StringUtils.hasText(it.getMetaField())) {
328+
options.metaField(it.getMetaField());
329+
}
330+
if (!Granularity.DEFAULT.equals(it.getGranularity())) {
331+
options.granularity(TimeSeriesGranularity.valueOf(it.getGranularity().name().toUpperCase()));
332+
}
333+
334+
result.timeSeriesOptions(options);
335+
});
336+
337+
return result;
338+
}
339+
340+
private Document getMappedValidator(Validator validator, Class<?> domainType) {
341+
342+
Document validationRules = validator.toDocument();
343+
344+
if (validationRules.containsKey("$jsonSchema")) {
345+
return schemaMapper.mapSchema(validationRules, domainType);
346+
}
347+
348+
return queryMapper.getMappedObject(validationRules, context.getPersistentEntity(domainType));
349+
}
350+
262351
/**
263352
* A representation of information about an entity.
264353
*

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

+62-51
Original file line numberDiff line numberDiff line change
@@ -648,13 +648,8 @@ public <T> MongoCollection<Document> createCollection(Class<T> entityClass,
648648

649649
Assert.notNull(entityClass, "EntityClass must not be null!");
650650

651-
CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
652-
options = Optionals
653-
.firstNonEmpty(() -> Optional.ofNullable(collectionOptions).flatMap(CollectionOptions::getCollation),
654-
() -> operations.forType(entityClass).getCollation()) //
655-
.map(options::collation).orElse(options);
656-
657-
return doCreateCollection(getCollectionName(entityClass), convertToDocument(options, entityClass));
651+
return doCreateCollection(getCollectionName(entityClass),
652+
operations.convertToCreateCollectionOptions(collectionOptions, entityClass));
658653
}
659654

660655
/*
@@ -676,7 +671,8 @@ public MongoCollection<Document> createCollection(String collectionName,
676671
@Nullable CollectionOptions collectionOptions) {
677672

678673
Assert.notNull(collectionName, "CollectionName must not be null!");
679-
return doCreateCollection(collectionName, convertToDocument(collectionOptions, Object.class));
674+
return doCreateCollection(collectionName,
675+
operations.convertToCreateCollectionOptions(collectionOptions, Object.class));
680676
}
681677

682678
/*
@@ -2475,66 +2471,81 @@ protected <T> T maybeCallAfterConvert(T object, Document document, String collec
24752471
* @param collectionOptions
24762472
* @return the collection that was created
24772473
*/
2478-
@SuppressWarnings("ConstantConditions")
24792474
protected MongoCollection<Document> doCreateCollection(String collectionName, Document collectionOptions) {
2475+
return doCreateCollection(collectionName, getCreateCollectionOptions(collectionOptions));
2476+
}
2477+
2478+
/**
2479+
* Create the specified collection using the provided options
2480+
*
2481+
* @param collectionName
2482+
* @param collectionOptions
2483+
* @return the collection that was created
2484+
* @since 3.3.3
2485+
*/
2486+
@SuppressWarnings("ConstantConditions")
2487+
protected MongoCollection<Document> doCreateCollection(String collectionName,
2488+
CreateCollectionOptions collectionOptions) {
24802489
return execute(db -> {
2490+
db.createCollection(collectionName, collectionOptions);
24812491

2482-
CreateCollectionOptions co = new CreateCollectionOptions();
2492+
MongoCollection<Document> coll = db.getCollection(collectionName, Document.class);
24832493

2484-
if (collectionOptions.containsKey("capped")) {
2485-
co.capped((Boolean) collectionOptions.get("capped"));
2486-
}
2487-
if (collectionOptions.containsKey("size")) {
2488-
co.sizeInBytes(((Number) collectionOptions.get("size")).longValue());
2489-
}
2490-
if (collectionOptions.containsKey("max")) {
2491-
co.maxDocuments(((Number) collectionOptions.get("max")).longValue());
2494+
// TODO: Emit a collection created event
2495+
if (LOGGER.isDebugEnabled()) {
2496+
LOGGER.debug(String.format("Created collection [%s]",
2497+
coll.getNamespace() != null ? coll.getNamespace().getCollectionName() : collectionName));
24922498
}
2499+
return coll;
2500+
});
2501+
}
24932502

2494-
if (collectionOptions.containsKey("collation")) {
2495-
co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class)));
2496-
}
2503+
private CreateCollectionOptions getCreateCollectionOptions(Document collectionOptions) {
24972504

2498-
if (collectionOptions.containsKey("validator")) {
2505+
CreateCollectionOptions co = new CreateCollectionOptions();
24992506

2500-
com.mongodb.client.model.ValidationOptions options = new com.mongodb.client.model.ValidationOptions();
2507+
if (collectionOptions.containsKey("capped")) {
2508+
co.capped((Boolean) collectionOptions.get("capped"));
2509+
}
2510+
if (collectionOptions.containsKey("size")) {
2511+
co.sizeInBytes(((Number) collectionOptions.get("size")).longValue());
2512+
}
2513+
if (collectionOptions.containsKey("max")) {
2514+
co.maxDocuments(((Number) collectionOptions.get("max")).longValue());
2515+
}
25012516

2502-
if (collectionOptions.containsKey("validationLevel")) {
2503-
options.validationLevel(ValidationLevel.fromString(collectionOptions.getString("validationLevel")));
2504-
}
2505-
if (collectionOptions.containsKey("validationAction")) {
2506-
options.validationAction(ValidationAction.fromString(collectionOptions.getString("validationAction")));
2507-
}
2517+
if (collectionOptions.containsKey("collation")) {
2518+
co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class)));
2519+
}
25082520

2509-
options.validator(collectionOptions.get("validator", Document.class));
2510-
co.validationOptions(options);
2511-
}
2521+
if (collectionOptions.containsKey("validator")) {
25122522

2513-
if (collectionOptions.containsKey("timeseries")) {
2523+
ValidationOptions options = new ValidationOptions();
25142524

2515-
Document timeSeries = collectionOptions.get("timeseries", Document.class);
2516-
com.mongodb.client.model.TimeSeriesOptions options = new com.mongodb.client.model.TimeSeriesOptions(
2517-
timeSeries.getString("timeField"));
2518-
if (timeSeries.containsKey("metaField")) {
2519-
options.metaField(timeSeries.getString("metaField"));
2520-
}
2521-
if (timeSeries.containsKey("granularity")) {
2522-
options.granularity(TimeSeriesGranularity.valueOf(timeSeries.getString("granularity").toUpperCase()));
2523-
}
2524-
co.timeSeriesOptions(options);
2525+
if (collectionOptions.containsKey("validationLevel")) {
2526+
options.validationLevel(ValidationLevel.fromString(collectionOptions.getString("validationLevel")));
2527+
}
2528+
if (collectionOptions.containsKey("validationAction")) {
2529+
options.validationAction(ValidationAction.fromString(collectionOptions.getString("validationAction")));
25252530
}
25262531

2527-
db.createCollection(collectionName, co);
2532+
options.validator(collectionOptions.get("validator", Document.class));
2533+
co.validationOptions(options);
2534+
}
25282535

2529-
MongoCollection<Document> coll = db.getCollection(collectionName, Document.class);
2536+
if (collectionOptions.containsKey("timeseries")) {
25302537

2531-
// TODO: Emit a collection created event
2532-
if (LOGGER.isDebugEnabled()) {
2533-
LOGGER.debug(String.format("Created collection [%s]",
2534-
coll.getNamespace() != null ? coll.getNamespace().getCollectionName() : collectionName));
2538+
Document timeSeries = collectionOptions.get("timeseries", Document.class);
2539+
TimeSeriesOptions options = new TimeSeriesOptions(timeSeries.getString("timeField"));
2540+
if (timeSeries.containsKey("metaField")) {
2541+
options.metaField(timeSeries.getString("metaField"));
25352542
}
2536-
return coll;
2537-
});
2543+
if (timeSeries.containsKey("granularity")) {
2544+
options.granularity(TimeSeriesGranularity.valueOf(timeSeries.getString("granularity").toUpperCase()));
2545+
}
2546+
co.timeSeriesOptions(options);
2547+
}
2548+
return co;
25382549
}
25392550

25402551
/**

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

+13-48
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@
112112
import org.springframework.data.mongodb.core.query.Query;
113113
import org.springframework.data.mongodb.core.query.UpdateDefinition;
114114
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
115-
import org.springframework.data.mongodb.core.timeseries.Granularity;
116115
import org.springframework.data.mongodb.core.validation.Validator;
117116
import org.springframework.data.mongodb.util.BsonUtils;
118117
import org.springframework.data.projection.EntityProjection;
@@ -131,7 +130,16 @@
131130
import com.mongodb.MongoException;
132131
import com.mongodb.ReadPreference;
133132
import com.mongodb.WriteConcern;
134-
import com.mongodb.client.model.*;
133+
import com.mongodb.client.model.CountOptions;
134+
import com.mongodb.client.model.CreateCollectionOptions;
135+
import com.mongodb.client.model.DeleteOptions;
136+
import com.mongodb.client.model.EstimatedDocumentCountOptions;
137+
import com.mongodb.client.model.FindOneAndDeleteOptions;
138+
import com.mongodb.client.model.FindOneAndReplaceOptions;
139+
import com.mongodb.client.model.FindOneAndUpdateOptions;
140+
import com.mongodb.client.model.ReplaceOptions;
141+
import com.mongodb.client.model.ReturnDocument;
142+
import com.mongodb.client.model.UpdateOptions;
135143
import com.mongodb.client.model.changestream.FullDocument;
136144
import com.mongodb.client.result.DeleteResult;
137145
import com.mongodb.client.result.InsertOneResult;
@@ -718,13 +726,8 @@ public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass
718726

719727
Assert.notNull(entityClass, "EntityClass must not be null!");
720728

721-
CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
722-
options = Optionals
723-
.firstNonEmpty(() -> Optional.ofNullable(collectionOptions).flatMap(CollectionOptions::getCollation),
724-
() -> operations.forType(entityClass).getCollation()) //
725-
.map(options::collation).orElse(options);
726-
727-
return doCreateCollection(getCollectionName(entityClass), convertToCreateCollectionOptions(options, entityClass));
729+
return doCreateCollection(getCollectionName(entityClass),
730+
operations.convertToCreateCollectionOptions(collectionOptions, entityClass));
728731
}
729732

730733
/*
@@ -2542,45 +2545,7 @@ protected CreateCollectionOptions convertToCreateCollectionOptions(@Nullable Col
25422545

25432546
protected CreateCollectionOptions convertToCreateCollectionOptions(@Nullable CollectionOptions collectionOptions,
25442547
Class<?> entityType) {
2545-
2546-
CreateCollectionOptions result = new CreateCollectionOptions();
2547-
2548-
if (collectionOptions == null) {
2549-
return result;
2550-
}
2551-
2552-
collectionOptions.getCapped().ifPresent(result::capped);
2553-
collectionOptions.getSize().ifPresent(result::sizeInBytes);
2554-
collectionOptions.getMaxDocuments().ifPresent(result::maxDocuments);
2555-
collectionOptions.getCollation().map(Collation::toMongoCollation).ifPresent(result::collation);
2556-
2557-
collectionOptions.getValidationOptions().ifPresent(it -> {
2558-
2559-
ValidationOptions validationOptions = new ValidationOptions();
2560-
2561-
it.getValidationAction().ifPresent(validationOptions::validationAction);
2562-
it.getValidationLevel().ifPresent(validationOptions::validationLevel);
2563-
2564-
it.getValidator().ifPresent(val -> validationOptions.validator(getMappedValidator(val, entityType)));
2565-
2566-
result.validationOptions(validationOptions);
2567-
});
2568-
2569-
collectionOptions.getTimeSeriesOptions().map(operations.forType(entityType)::mapTimeSeriesOptions).ifPresent(it -> {
2570-
2571-
TimeSeriesOptions options = new TimeSeriesOptions(it.getTimeField());
2572-
2573-
if (StringUtils.hasText(it.getMetaField())) {
2574-
options.metaField(it.getMetaField());
2575-
}
2576-
if (!Granularity.DEFAULT.equals(it.getGranularity())) {
2577-
options.granularity(TimeSeriesGranularity.valueOf(it.getGranularity().name().toUpperCase()));
2578-
}
2579-
2580-
result.timeSeriesOptions(options);
2581-
});
2582-
2583-
return result;
2548+
return operations.convertToCreateCollectionOptions(collectionOptions, entityType);
25842549
}
25852550

25862551
private Document getMappedValidator(Validator validator, Class<?> domainType) {

0 commit comments

Comments
 (0)